Ticket #393: 393status45.dpatch

File 393status45.dpatch, 893.8 KB (added by kevan, at 2011-06-18T02:59:30Z)

teach webapi, WUI, and CLI how to deal with MDMF caps

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
245New patches:
246
247[interfaces.py: Add #993 interfaces
248Kevan Carstensen <kevan@isnotajoke.com>**20100809233244
249 Ignore-this: b58621ac5cc86f1b4b4149f9e6c6a1ce
250] {
251hunk ./src/allmydata/interfaces.py 499
252 class MustNotBeUnknownRWError(CapConstraintError):
253     """Cannot add an unknown child cap specified in a rw_uri field."""
254 
255+
256+class IReadable(Interface):
257+    """I represent a readable object -- either an immutable file, or a
258+    specific version of a mutable file.
259+    """
260+
261+    def is_readonly():
262+        """Return True if this reference provides mutable access to the given
263+        file or directory (i.e. if you can modify it), or False if not. Note
264+        that even if this reference is read-only, someone else may hold a
265+        read-write reference to it.
266+
267+        For an IReadable returned by get_best_readable_version(), this will
268+        always return True, but for instances of subinterfaces such as
269+        IMutableFileVersion, it may return False."""
270+
271+    def is_mutable():
272+        """Return True if this file or directory is mutable (by *somebody*,
273+        not necessarily you), False if it is is immutable. Note that a file
274+        might be mutable overall, but your reference to it might be
275+        read-only. On the other hand, all references to an immutable file
276+        will be read-only; there are no read-write references to an immutable
277+        file."""
278+
279+    def get_storage_index():
280+        """Return the storage index of the file."""
281+
282+    def get_size():
283+        """Return the length (in bytes) of this readable object."""
284+
285+    def download_to_data():
286+        """Download all of the file contents. I return a Deferred that fires
287+        with the contents as a byte string."""
288+
289+    def read(consumer, offset=0, size=None):
290+        """Download a portion (possibly all) of the file's contents, making
291+        them available to the given IConsumer. Return a Deferred that fires
292+        (with the consumer) when the consumer is unregistered (either because
293+        the last byte has been given to it, or because the consumer threw an
294+        exception during write(), possibly because it no longer wants to
295+        receive data). The portion downloaded will start at 'offset' and
296+        contain 'size' bytes (or the remainder of the file if size==None).
297+
298+        The consumer will be used in non-streaming mode: an IPullProducer
299+        will be attached to it.
300+
301+        The consumer will not receive data right away: several network trips
302+        must occur first. The order of events will be::
303+
304+         consumer.registerProducer(p, streaming)
305+          (if streaming == False)::
306+           consumer does p.resumeProducing()
307+            consumer.write(data)
308+           consumer does p.resumeProducing()
309+            consumer.write(data).. (repeat until all data is written)
310+         consumer.unregisterProducer()
311+         deferred.callback(consumer)
312+
313+        If a download error occurs, or an exception is raised by
314+        consumer.registerProducer() or consumer.write(), I will call
315+        consumer.unregisterProducer() and then deliver the exception via
316+        deferred.errback(). To cancel the download, the consumer should call
317+        p.stopProducing(), which will result in an exception being delivered
318+        via deferred.errback().
319+
320+        See src/allmydata/util/consumer.py for an example of a simple
321+        download-to-memory consumer.
322+        """
323+
324+
325+class IWritable(Interface):
326+    """
327+    I define methods that callers can use to update SDMF and MDMF
328+    mutable files on a Tahoe-LAFS grid.
329+    """
330+    # XXX: For the moment, we have only this. It is possible that we
331+    #      want to move overwrite() and modify() in here too.
332+    def update(data, offset):
333+        """
334+        I write the data from my data argument to the MDMF file,
335+        starting at offset. I continue writing data until my data
336+        argument is exhausted, appending data to the file as necessary.
337+        """
338+        # assert IMutableUploadable.providedBy(data)
339+        # to append data: offset=node.get_size_of_best_version()
340+        # do we want to support compacting MDMF?
341+        # for an MDMF file, this can be done with O(data.get_size())
342+        # memory. For an SDMF file, any modification takes
343+        # O(node.get_size_of_best_version()).
344+
345+
346+class IMutableFileVersion(IReadable):
347+    """I provide access to a particular version of a mutable file. The
348+    access is read/write if I was obtained from a filenode derived from
349+    a write cap, or read-only if the filenode was derived from a read cap.
350+    """
351+
352+    def get_sequence_number():
353+        """Return the sequence number of this version."""
354+
355+    def get_servermap():
356+        """Return the IMutableFileServerMap instance that was used to create
357+        this object.
358+        """
359+
360+    def get_writekey():
361+        """Return this filenode's writekey, or None if the node does not have
362+        write-capability. This may be used to assist with data structures
363+        that need to make certain data available only to writers, such as the
364+        read-write child caps in dirnodes. The recommended process is to have
365+        reader-visible data be submitted to the filenode in the clear (where
366+        it will be encrypted by the filenode using the readkey), but encrypt
367+        writer-visible data using this writekey.
368+        """
369+
370+    # TODO: Can this be overwrite instead of replace?
371+    def replace(new_contents):
372+        """Replace the contents of the mutable file, provided that no other
373+        node has published (or is attempting to publish, concurrently) a
374+        newer version of the file than this one.
375+
376+        I will avoid modifying any share that is different than the version
377+        given by get_sequence_number(). However, if another node is writing
378+        to the file at the same time as me, I may manage to update some shares
379+        while they update others. If I see any evidence of this, I will signal
380+        UncoordinatedWriteError, and the file will be left in an inconsistent
381+        state (possibly the version you provided, possibly the old version,
382+        possibly somebody else's version, and possibly a mix of shares from
383+        all of these).
384+
385+        The recommended response to UncoordinatedWriteError is to either
386+        return it to the caller (since they failed to coordinate their
387+        writes), or to attempt some sort of recovery. It may be sufficient to
388+        wait a random interval (with exponential backoff) and repeat your
389+        operation. If I do not signal UncoordinatedWriteError, then I was
390+        able to write the new version without incident.
391+
392+        I return a Deferred that fires (with a PublishStatus object) when the
393+        update has completed.
394+        """
395+
396+    def modify(modifier_cb):
397+        """Modify the contents of the file, by downloading this version,
398+        applying the modifier function (or bound method), then uploading
399+        the new version. This will succeed as long as no other node
400+        publishes a version between the download and the upload.
401+        I return a Deferred that fires (with a PublishStatus object) when
402+        the update is complete.
403+
404+        The modifier callable will be given three arguments: a string (with
405+        the old contents), a 'first_time' boolean, and a servermap. As with
406+        download_to_data(), the old contents will be from this version,
407+        but the modifier can use the servermap to make other decisions
408+        (such as refusing to apply the delta if there are multiple parallel
409+        versions, or if there is evidence of a newer unrecoverable version).
410+        'first_time' will be True the first time the modifier is called,
411+        and False on any subsequent calls.
412+
413+        The callable should return a string with the new contents. The
414+        callable must be prepared to be called multiple times, and must
415+        examine the input string to see if the change that it wants to make
416+        is already present in the old version. If it does not need to make
417+        any changes, it can either return None, or return its input string.
418+
419+        If the modifier raises an exception, it will be returned in the
420+        errback.
421+        """
422+
423+
424 # The hierarchy looks like this:
425 #  IFilesystemNode
426 #   IFileNode
427hunk ./src/allmydata/interfaces.py 758
428     def raise_error():
429         """Raise any error associated with this node."""
430 
431+    # XXX: These may not be appropriate outside the context of an IReadable.
432     def get_size():
433         """Return the length (in bytes) of the data this node represents. For
434         directory nodes, I return the size of the backing store. I return
435hunk ./src/allmydata/interfaces.py 775
436 class IFileNode(IFilesystemNode):
437     """I am a node which represents a file: a sequence of bytes. I am not a
438     container, like IDirectoryNode."""
439+    def get_best_readable_version():
440+        """Return a Deferred that fires with an IReadable for the 'best'
441+        available version of the file. The IReadable provides only read
442+        access, even if this filenode was derived from a write cap.
443 
444hunk ./src/allmydata/interfaces.py 780
445-class IImmutableFileNode(IFileNode):
446-    def read(consumer, offset=0, size=None):
447-        """Download a portion (possibly all) of the file's contents, making
448-        them available to the given IConsumer. Return a Deferred that fires
449-        (with the consumer) when the consumer is unregistered (either because
450-        the last byte has been given to it, or because the consumer threw an
451-        exception during write(), possibly because it no longer wants to
452-        receive data). The portion downloaded will start at 'offset' and
453-        contain 'size' bytes (or the remainder of the file if size==None).
454-
455-        The consumer will be used in non-streaming mode: an IPullProducer
456-        will be attached to it.
457+        For an immutable file, there is only one version. For a mutable
458+        file, the 'best' version is the recoverable version with the
459+        highest sequence number. If no uncoordinated writes have occurred,
460+        and if enough shares are available, then this will be the most
461+        recent version that has been uploaded. If no version is recoverable,
462+        the Deferred will errback with an UnrecoverableFileError.
463+        """
464 
465hunk ./src/allmydata/interfaces.py 788
466-        The consumer will not receive data right away: several network trips
467-        must occur first. The order of events will be::
468+    def download_best_version():
469+        """Download the contents of the version that would be returned
470+        by get_best_readable_version(). This is equivalent to calling
471+        download_to_data() on the IReadable given by that method.
472 
473hunk ./src/allmydata/interfaces.py 793
474-         consumer.registerProducer(p, streaming)
475-          (if streaming == False)::
476-           consumer does p.resumeProducing()
477-            consumer.write(data)
478-           consumer does p.resumeProducing()
479-            consumer.write(data).. (repeat until all data is written)
480-         consumer.unregisterProducer()
481-         deferred.callback(consumer)
482+        I return a Deferred that fires with a byte string when the file
483+        has been fully downloaded. To support streaming download, use
484+        the 'read' method of IReadable. If no version is recoverable,
485+        the Deferred will errback with an UnrecoverableFileError.
486+        """
487 
488hunk ./src/allmydata/interfaces.py 799
489-        If a download error occurs, or an exception is raised by
490-        consumer.registerProducer() or consumer.write(), I will call
491-        consumer.unregisterProducer() and then deliver the exception via
492-        deferred.errback(). To cancel the download, the consumer should call
493-        p.stopProducing(), which will result in an exception being delivered
494-        via deferred.errback().
495+    def get_size_of_best_version():
496+        """Find the size of the version that would be returned by
497+        get_best_readable_version().
498 
499hunk ./src/allmydata/interfaces.py 803
500-        See src/allmydata/util/consumer.py for an example of a simple
501-        download-to-memory consumer.
502+        I return a Deferred that fires with an integer. If no version
503+        is recoverable, the Deferred will errback with an
504+        UnrecoverableFileError.
505         """
506 
507hunk ./src/allmydata/interfaces.py 808
508+
509+class IImmutableFileNode(IFileNode, IReadable):
510+    """I am a node representing an immutable file. Immutable files have
511+    only one version"""
512+
513+
514 class IMutableFileNode(IFileNode):
515     """I provide access to a 'mutable file', which retains its identity
516     regardless of what contents are put in it.
517hunk ./src/allmydata/interfaces.py 873
518     only be retrieved and updated all-at-once, as a single big string. Future
519     versions of our mutable files will remove this restriction.
520     """
521-
522-    def download_best_version():
523-        """Download the 'best' available version of the file, meaning one of
524-        the recoverable versions with the highest sequence number. If no
525+    def get_best_mutable_version():
526+        """Return a Deferred that fires with an IMutableFileVersion for
527+        the 'best' available version of the file. The best version is
528+        the recoverable version with the highest sequence number. If no
529         uncoordinated writes have occurred, and if enough shares are
530hunk ./src/allmydata/interfaces.py 878
531-        available, then this will be the most recent version that has been
532-        uploaded.
533+        available, then this will be the most recent version that has
534+        been uploaded.
535 
536hunk ./src/allmydata/interfaces.py 881
537-        I update an internal servermap with MODE_READ, determine which
538-        version of the file is indicated by
539-        servermap.best_recoverable_version(), and return a Deferred that
540-        fires with its contents. If no version is recoverable, the Deferred
541-        will errback with UnrecoverableFileError.
542-        """
543-
544-    def get_size_of_best_version():
545-        """Find the size of the version that would be downloaded with
546-        download_best_version(), without actually downloading the whole file.
547-
548-        I return a Deferred that fires with an integer.
549+        If no version is recoverable, the Deferred will errback with an
550+        UnrecoverableFileError.
551         """
552 
553     def overwrite(new_contents):
554hunk ./src/allmydata/interfaces.py 921
555         errback.
556         """
557 
558-
559     def get_servermap(mode):
560         """Return a Deferred that fires with an IMutableFileServerMap
561         instance, updated using the given mode.
562hunk ./src/allmydata/interfaces.py 974
563         writer-visible data using this writekey.
564         """
565 
566+    def set_version(version):
567+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
568+        we upload in SDMF for reasons of compatibility. If you want to
569+        change this, set_version will let you do that.
570+
571+        To say that this file should be uploaded in SDMF, pass in a 0. To
572+        say that the file should be uploaded as MDMF, pass in a 1.
573+        """
574+
575+    def get_version():
576+        """Returns the mutable file protocol version."""
577+
578 class NotEnoughSharesError(Exception):
579     """Download was unable to get enough shares"""
580 
581hunk ./src/allmydata/interfaces.py 1822
582         """The upload is finished, and whatever filehandle was in use may be
583         closed."""
584 
585+
586+class IMutableUploadable(Interface):
587+    """
588+    I represent content that is due to be uploaded to a mutable filecap.
589+    """
590+    # This is somewhat simpler than the IUploadable interface above
591+    # because mutable files do not need to be concerned with possibly
592+    # generating a CHK, nor with per-file keys. It is a subset of the
593+    # methods in IUploadable, though, so we could just as well implement
594+    # the mutable uploadables as IUploadables that don't happen to use
595+    # those methods (with the understanding that the unused methods will
596+    # never be called on such objects)
597+    def get_size():
598+        """
599+        Returns a Deferred that fires with the size of the content held
600+        by the uploadable.
601+        """
602+
603+    def read(length):
604+        """
605+        Returns a list of strings which, when concatenated, are the next
606+        length bytes of the file, or fewer if there are fewer bytes
607+        between the current location and the end of the file.
608+        """
609+
610+    def close():
611+        """
612+        The process that used the Uploadable is finished using it, so
613+        the uploadable may be closed.
614+        """
615+
616 class IUploadResults(Interface):
617     """I am returned by upload() methods. I contain a number of public
618     attributes which can be read to determine the results of the upload. Some
619}
620[frontends/sftpd.py: Modify the sftp frontend to work with the MDMF changes
621Kevan Carstensen <kevan@isnotajoke.com>**20100809233535
622 Ignore-this: 2d25e2cfcd0d7bbcbba660c7e1da12f
623] {
624hunk ./src/allmydata/frontends/sftpd.py 33
625 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
626      NoSuchChildError, ChildOfWrongTypeError
627 from allmydata.mutable.common import NotWriteableError
628+from allmydata.mutable.publish import MutableFileHandle
629 from allmydata.immutable.upload import FileHandle
630 from allmydata.dirnode import update_metadata
631 from allmydata.util.fileutil import EncryptedTemporaryFile
632hunk ./src/allmydata/frontends/sftpd.py 667
633         else:
634             assert IFileNode.providedBy(filenode), filenode
635 
636-            if filenode.is_mutable():
637-                self.async.addCallback(lambda ign: filenode.download_best_version())
638-                def _downloaded(data):
639-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
640-                    self.consumer.write(data)
641-                    self.consumer.finish()
642-                    return None
643-                self.async.addCallback(_downloaded)
644-            else:
645-                download_size = filenode.get_size()
646-                assert download_size is not None, "download_size is None"
647+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
648+
649+            def _read(version):
650+                if noisy: self.log("_read", level=NOISY)
651+                download_size = version.get_size()
652+                assert download_size is not None
653+
654                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
655hunk ./src/allmydata/frontends/sftpd.py 675
656-                def _read(ign):
657-                    if noisy: self.log("_read immutable", level=NOISY)
658-                    filenode.read(self.consumer, 0, None)
659-                self.async.addCallback(_read)
660+
661+                version.read(self.consumer, 0, None)
662+            self.async.addCallback(_read)
663 
664         eventually(self.async.callback, None)
665 
666hunk ./src/allmydata/frontends/sftpd.py 821
667                     assert parent and childname, (parent, childname, self.metadata)
668                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
669 
670-                d2.addCallback(lambda ign: self.consumer.get_current_size())
671-                d2.addCallback(lambda size: self.consumer.read(0, size))
672-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
673+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
674             else:
675                 def _add_file(ign):
676                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
677}
678[immutable/filenode.py: Make the immutable file node implement the same interfaces as the mutable one
679Kevan Carstensen <kevan@isnotajoke.com>**20100810000619
680 Ignore-this: 93e536c0f8efb705310f13ff64621527
681] {
682hunk ./src/allmydata/immutable/filenode.py 8
683 now = time.time
684 from zope.interface import implements, Interface
685 from twisted.internet import defer
686-from twisted.internet.interfaces import IConsumer
687 
688hunk ./src/allmydata/immutable/filenode.py 9
689-from allmydata.interfaces import IImmutableFileNode, IUploadResults
690 from allmydata import uri
691hunk ./src/allmydata/immutable/filenode.py 10
692+from twisted.internet.interfaces import IConsumer
693+from twisted.protocols import basic
694+from foolscap.api import eventually
695+from allmydata.interfaces import IImmutableFileNode, ICheckable, \
696+     IDownloadTarget, IUploadResults
697+from allmydata.util import dictutil, log, base32, consumer
698+from allmydata.immutable.checker import Checker
699 from allmydata.check_results import CheckResults, CheckAndRepairResults
700 from allmydata.util.dictutil import DictOfSets
701 from pycryptopp.cipher.aes import AES
702hunk ./src/allmydata/immutable/filenode.py 296
703         return self._cnode.check_and_repair(monitor, verify, add_lease)
704     def check(self, monitor, verify=False, add_lease=False):
705         return self._cnode.check(monitor, verify, add_lease)
706+
707+    def get_best_readable_version(self):
708+        """
709+        Return an IReadable of the best version of this file. Since
710+        immutable files can have only one version, we just return the
711+        current filenode.
712+        """
713+        return defer.succeed(self)
714+
715+
716+    def download_best_version(self):
717+        """
718+        Download the best version of this file, returning its contents
719+        as a bytestring. Since there is only one version of an immutable
720+        file, we download and return the contents of this file.
721+        """
722+        d = consumer.download_to_data(self)
723+        return d
724+
725+    # for an immutable file, download_to_data (specified in IReadable)
726+    # is the same as download_best_version (specified in IFileNode). For
727+    # mutable files, the difference is more meaningful, since they can
728+    # have multiple versions.
729+    download_to_data = download_best_version
730+
731+
732+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
733+    # get_size_of_best_version(IFileNode) are all the same for immutable
734+    # files.
735+    get_size_of_best_version = get_current_size
736}
737[immutable/literal.py: implement the same interfaces as other filenodes
738Kevan Carstensen <kevan@isnotajoke.com>**20100810000633
739 Ignore-this: b50dd5df2d34ecd6477b8499a27aef13
740] hunk ./src/allmydata/immutable/literal.py 106
741         d.addCallback(lambda lastSent: consumer)
742         return d
743 
744+    # IReadable, IFileNode, IFilesystemNode
745+    def get_best_readable_version(self):
746+        return defer.succeed(self)
747+
748+
749+    def download_best_version(self):
750+        return defer.succeed(self.u.data)
751+
752+
753+    download_to_data = download_best_version
754+    get_size_of_best_version = get_current_size
755+
756[scripts: tell 'tahoe put' about MDMF
757Kevan Carstensen <kevan@isnotajoke.com>**20100813234957
758 Ignore-this: c106b3384fc676bd3c0fb466d2a52b1b
759] {
760hunk ./src/allmydata/scripts/cli.py 160
761     optFlags = [
762         ("mutable", "m", "Create a mutable file instead of an immutable one."),
763         ]
764+    optParameters = [
765+        ("mutable-type", None, False, "Create a mutable file in the given format. Valid formats are 'sdmf' for SDMF and 'mdmf' for MDMF"),
766+        ]
767 
768     def parseArgs(self, arg1=None, arg2=None):
769         # see Examples below
770hunk ./src/allmydata/scripts/tahoe_put.py 21
771     from_file = options.from_file
772     to_file = options.to_file
773     mutable = options['mutable']
774+    mutable_type = False
775+
776+    if mutable:
777+        mutable_type = options['mutable-type']
778     if options['quiet']:
779         verbosity = 0
780     else:
781hunk ./src/allmydata/scripts/tahoe_put.py 33
782     stdout = options.stdout
783     stderr = options.stderr
784 
785+    if mutable_type and mutable_type not in ('sdmf', 'mdmf'):
786+        # Don't try to pass unsupported types to the webapi
787+        print >>stderr, "error: %s is an invalid format" % mutable_type
788+        return 1
789+
790     if nodeurl[-1] != "/":
791         nodeurl += "/"
792     if to_file:
793hunk ./src/allmydata/scripts/tahoe_put.py 76
794         url = nodeurl + "uri"
795     if mutable:
796         url += "?mutable=true"
797+    if mutable_type:
798+        assert mutable
799+        url += "&mutable-type=%s" % mutable_type
800+
801     if from_file:
802         infileobj = open(os.path.expanduser(from_file), "rb")
803     else:
804}
805[web: Alter the webapi to get along with and take advantage of the MDMF changes
806Kevan Carstensen <kevan@isnotajoke.com>**20100814081012
807 Ignore-this: 96c2ed4e4a9f450fb84db5d711d10bd6
808 
809 The main benefit that the webapi gets from MDMF, at least initially, is
810 the ability to do a streaming download of an MDMF mutable file. It also
811 exposes a way (through the PUT verb) to append to or otherwise modify
812 (in-place) an MDMF mutable file.
813] {
814hunk ./src/allmydata/web/common.py 12
815 from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
816      FileTooLargeError, NotEnoughSharesError, NoSharesError, \
817      EmptyPathnameComponentError, MustBeDeepImmutableError, \
818-     MustBeReadonlyError, MustNotBeUnknownRWError
819+     MustBeReadonlyError, MustNotBeUnknownRWError, SDMF_VERSION, MDMF_VERSION
820 from allmydata.mutable.common import UnrecoverableFileError
821 from allmydata.util import abbreviate
822 from allmydata.util.encodingutil import to_str, quote_output
823hunk ./src/allmydata/web/common.py 35
824     else:
825         return boolean_of_arg(replace)
826 
827+
828+def parse_mutable_type_arg(arg):
829+    if not arg:
830+        return None # interpreted by the caller as "let the nodemaker decide"
831+
832+    arg = arg.lower()
833+    assert arg in ("mdmf", "sdmf")
834+
835+    if arg == "mdmf":
836+        return MDMF_VERSION
837+
838+    return SDMF_VERSION
839+
840+
841+def parse_offset_arg(offset):
842+    # XXX: This will raise a ValueError when invoked on something that
843+    # is not an integer. Is that okay? Or do we want a better error
844+    # message? Since this call is going to be used by programmers and
845+    # their tools rather than users (through the wui), it is not
846+    # inconsistent to return that, I guess.
847+    offset = int(offset)
848+    return offset
849+
850+
851 def get_root(ctx_or_req):
852     req = IRequest(ctx_or_req)
853     # the addSlash=True gives us one extra (empty) segment
854hunk ./src/allmydata/web/directory.py 19
855 from allmydata.uri import from_string_dirnode
856 from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
857      IImmutableFileNode, IMutableFileNode, ExistingChildError, \
858-     NoSuchChildError, EmptyPathnameComponentError
859+     NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
860 from allmydata.monitor import Monitor, OperationCancelledError
861 from allmydata import dirnode
862 from allmydata.web.common import text_plain, WebError, \
863hunk ./src/allmydata/web/directory.py 153
864         if not t:
865             # render the directory as HTML, using the docFactory and Nevow's
866             # whole templating thing.
867-            return DirectoryAsHTML(self.node)
868+            return DirectoryAsHTML(self.node,
869+                                   self.client.mutable_file_default)
870 
871         if t == "json":
872             return DirectoryJSONMetadata(ctx, self.node)
873hunk ./src/allmydata/web/directory.py 556
874     docFactory = getxmlfile("directory.xhtml")
875     addSlash = True
876 
877-    def __init__(self, node):
878+    def __init__(self, node, default_mutable_format):
879         rend.Page.__init__(self)
880         self.node = node
881 
882hunk ./src/allmydata/web/directory.py 560
883+        assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
884+        self.default_mutable_format = default_mutable_format
885+
886     def beforeRender(self, ctx):
887         # attempt to get the dirnode's children, stashing them (or the
888         # failure that results) for later use
889hunk ./src/allmydata/web/directory.py 780
890             ]]
891         forms.append(T.div(class_="freeform-form")[mkdir])
892 
893+        # Build input elements for mutable file type. We do this outside
894+        # of the list so we can check the appropriate format, based on
895+        # the default configured in the client (which reflects the
896+        # default configured in tahoe.cfg)
897+        if self.default_mutable_format == MDMF_VERSION:
898+            mdmf_input = T.input(type='radio', name='mutable-type',
899+                                 id='mutable-type-mdmf', value='mdmf',
900+                                 checked='checked')
901+        else:
902+            mdmf_input = T.input(type='radio', name='mutable-type',
903+                                 id='mutable-type-mdmf', value='mdmf')
904+
905+        if self.default_mutable_format == SDMF_VERSION:
906+            sdmf_input = T.input(type='radio', name='mutable-type',
907+                                 id='mutable-type-sdmf', value='sdmf',
908+                                 checked="checked")
909+        else:
910+            sdmf_input = T.input(type='radio', name='mutable-type',
911+                                 id='mutable-type-sdmf', value='sdmf')
912+
913         upload = T.form(action=".", method="post",
914                         enctype="multipart/form-data")[
915             T.fieldset[
916hunk ./src/allmydata/web/directory.py 812
917             T.input(type="submit", value="Upload"),
918             " Mutable?:",
919             T.input(type="checkbox", name="mutable"),
920+            sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
921+            mdmf_input,
922+            T.label(for_="mutable-type-mdmf")["MDMF (experimental)"],
923             ]]
924         forms.append(T.div(class_="freeform-form")[upload])
925 
926hunk ./src/allmydata/web/directory.py 850
927                 kiddata = ("filenode", {'size': childnode.get_size(),
928                                         'mutable': childnode.is_mutable(),
929                                         })
930+                if childnode.is_mutable() and \
931+                    childnode.get_version() is not None:
932+                    mutable_type = childnode.get_version()
933+                    assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
934+
935+                    if mutable_type == MDMF_VERSION:
936+                        mutable_type = "mdmf"
937+                    else:
938+                        mutable_type = "sdmf"
939+                    kiddata[1]['mutable-type'] = mutable_type
940+
941             elif IDirectoryNode.providedBy(childnode):
942                 kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
943             else:
944hunk ./src/allmydata/web/filenode.py 9
945 from nevow import url, rend
946 from nevow.inevow import IRequest
947 
948-from allmydata.interfaces import ExistingChildError
949+from allmydata.interfaces import ExistingChildError, SDMF_VERSION, MDMF_VERSION
950 from allmydata.monitor import Monitor
951 from allmydata.immutable.upload import FileHandle
952hunk ./src/allmydata/web/filenode.py 12
953+from allmydata.mutable.publish import MutableFileHandle
954+from allmydata.mutable.common import MODE_READ
955 from allmydata.util import log, base32
956 
957 from allmydata.web.common import text_plain, WebError, RenderMixin, \
958hunk ./src/allmydata/web/filenode.py 18
959      boolean_of_arg, get_arg, should_create_intermediate_directories, \
960-     MyExceptionHandler, parse_replace_arg
961+     MyExceptionHandler, parse_replace_arg, parse_offset_arg, \
962+     parse_mutable_type_arg
963 from allmydata.web.check_results import CheckResults, \
964      CheckAndRepairResults, LiteralCheckResults
965 from allmydata.web.info import MoreInfo
966hunk ./src/allmydata/web/filenode.py 29
967         # a new file is being uploaded in our place.
968         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
969         if mutable:
970-            req.content.seek(0)
971-            data = req.content.read()
972-            d = client.create_mutable_file(data)
973+            mutable_type = parse_mutable_type_arg(get_arg(req,
974+                                                          "mutable-type",
975+                                                          None))
976+            data = MutableFileHandle(req.content)
977+            d = client.create_mutable_file(data, version=mutable_type)
978             def _uploaded(newnode):
979                 d2 = self.parentnode.set_node(self.name, newnode,
980                                               overwrite=replace)
981hunk ./src/allmydata/web/filenode.py 66
982         d.addCallback(lambda res: childnode.get_uri())
983         return d
984 
985-    def _read_data_from_formpost(self, req):
986-        # SDMF: files are small, and we can only upload data, so we read
987-        # the whole file into memory before uploading.
988-        contents = req.fields["file"]
989-        contents.file.seek(0)
990-        data = contents.file.read()
991-        return data
992 
993     def replace_me_with_a_formpost(self, req, client, replace):
994         # create a new file, maybe mutable, maybe immutable
995hunk ./src/allmydata/web/filenode.py 71
996         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
997 
998+        # create an immutable file
999+        contents = req.fields["file"]
1000         if mutable:
1001hunk ./src/allmydata/web/filenode.py 74
1002-            data = self._read_data_from_formpost(req)
1003-            d = client.create_mutable_file(data)
1004+            mutable_type = parse_mutable_type_arg(get_arg(req, "mutable-type",
1005+                                                          None))
1006+            uploadable = MutableFileHandle(contents.file)
1007+            d = client.create_mutable_file(uploadable, version=mutable_type)
1008             def _uploaded(newnode):
1009                 d2 = self.parentnode.set_node(self.name, newnode,
1010                                               overwrite=replace)
1011hunk ./src/allmydata/web/filenode.py 85
1012                 return d2
1013             d.addCallback(_uploaded)
1014             return d
1015-        # create an immutable file
1016-        contents = req.fields["file"]
1017+
1018         uploadable = FileHandle(contents.file, convergence=client.convergence)
1019         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
1020         d.addCallback(lambda newnode: newnode.get_uri())
1021hunk ./src/allmydata/web/filenode.py 91
1022         return d
1023 
1024+
1025 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
1026     def __init__(self, client, parentnode, name):
1027         rend.Page.__init__(self)
1028hunk ./src/allmydata/web/filenode.py 174
1029             # properly. So we assume that at least the browser will agree
1030             # with itself, and echo back the same bytes that we were given.
1031             filename = get_arg(req, "filename", self.name) or "unknown"
1032-            if self.node.is_mutable():
1033-                # some day: d = self.node.get_best_version()
1034-                d = makeMutableDownloadable(self.node)
1035-            else:
1036-                d = defer.succeed(self.node)
1037+            d = self.node.get_best_readable_version()
1038             d.addCallback(lambda dn: FileDownloader(dn, filename))
1039             return d
1040         if t == "json":
1041hunk ./src/allmydata/web/filenode.py 178
1042-            if self.parentnode and self.name:
1043-                d = self.parentnode.get_metadata_for(self.name)
1044+            # We do this to make sure that fields like size and
1045+            # mutable-type (which depend on the file on the grid and not
1046+            # just on the cap) are filled in. The latter gets used in
1047+            # tests, in particular.
1048+            #
1049+            # TODO: Make it so that the servermap knows how to update in
1050+            # a mode specifically designed to fill in these fields, and
1051+            # then update it in that mode.
1052+            if self.node.is_mutable():
1053+                d = self.node.get_servermap(MODE_READ)
1054             else:
1055                 d = defer.succeed(None)
1056hunk ./src/allmydata/web/filenode.py 190
1057+            if self.parentnode and self.name:
1058+                d.addCallback(lambda ignored:
1059+                    self.parentnode.get_metadata_for(self.name))
1060+            else:
1061+                d.addCallback(lambda ignored: None)
1062             d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md))
1063             return d
1064         if t == "info":
1065hunk ./src/allmydata/web/filenode.py 211
1066         if t:
1067             raise WebError("GET file: bad t=%s" % t)
1068         filename = get_arg(req, "filename", self.name) or "unknown"
1069-        if self.node.is_mutable():
1070-            # some day: d = self.node.get_best_version()
1071-            d = makeMutableDownloadable(self.node)
1072-        else:
1073-            d = defer.succeed(self.node)
1074+        d = self.node.get_best_readable_version()
1075         d.addCallback(lambda dn: FileDownloader(dn, filename))
1076         return d
1077 
1078hunk ./src/allmydata/web/filenode.py 219
1079         req = IRequest(ctx)
1080         t = get_arg(req, "t", "").strip()
1081         replace = parse_replace_arg(get_arg(req, "replace", "true"))
1082+        offset = parse_offset_arg(get_arg(req, "offset", -1))
1083 
1084         if not t:
1085hunk ./src/allmydata/web/filenode.py 222
1086-            if self.node.is_mutable():
1087+            if self.node.is_mutable() and offset >= 0:
1088+                return self.update_my_contents(req, offset)
1089+
1090+            elif self.node.is_mutable():
1091                 return self.replace_my_contents(req)
1092             if not replace:
1093                 # this is the early trap: if someone else modifies the
1094hunk ./src/allmydata/web/filenode.py 232
1095                 # directory while we're uploading, the add_file(overwrite=)
1096                 # call in replace_me_with_a_child will do the late trap.
1097                 raise ExistingChildError()
1098+            if offset >= 0:
1099+                raise WebError("PUT to a file: append operation invoked "
1100+                               "on an immutable cap")
1101+
1102+
1103             assert self.parentnode and self.name
1104             return self.replace_me_with_a_child(req, self.client, replace)
1105         if t == "uri":
1106hunk ./src/allmydata/web/filenode.py 299
1107 
1108     def replace_my_contents(self, req):
1109         req.content.seek(0)
1110-        new_contents = req.content.read()
1111+        new_contents = MutableFileHandle(req.content)
1112         d = self.node.overwrite(new_contents)
1113         d.addCallback(lambda res: self.node.get_uri())
1114         return d
1115hunk ./src/allmydata/web/filenode.py 304
1116 
1117+
1118+    def update_my_contents(self, req, offset):
1119+        req.content.seek(0)
1120+        added_contents = MutableFileHandle(req.content)
1121+
1122+        d = self.node.get_best_mutable_version()
1123+        d.addCallback(lambda mv:
1124+            mv.update(added_contents, offset))
1125+        d.addCallback(lambda ignored:
1126+            self.node.get_uri())
1127+        return d
1128+
1129+
1130     def replace_my_contents_with_a_formpost(self, req):
1131         # we have a mutable file. Get the data from the formpost, and replace
1132         # the mutable file's contents with it.
1133hunk ./src/allmydata/web/filenode.py 320
1134-        new_contents = self._read_data_from_formpost(req)
1135+        new_contents = req.fields['file']
1136+        new_contents = MutableFileHandle(new_contents.file)
1137+
1138         d = self.node.overwrite(new_contents)
1139         d.addCallback(lambda res: self.node.get_uri())
1140         return d
1141hunk ./src/allmydata/web/filenode.py 327
1142 
1143-class MutableDownloadable:
1144-    #implements(IDownloadable)
1145-    def __init__(self, size, node):
1146-        self.size = size
1147-        self.node = node
1148-    def get_size(self):
1149-        return self.size
1150-    def is_mutable(self):
1151-        return True
1152-    def read(self, consumer, offset=0, size=None):
1153-        d = self.node.download_best_version()
1154-        d.addCallback(self._got_data, consumer, offset, size)
1155-        return d
1156-    def _got_data(self, contents, consumer, offset, size):
1157-        start = offset
1158-        if size is not None:
1159-            end = offset+size
1160-        else:
1161-            end = self.size
1162-        # SDMF: we can write the whole file in one big chunk
1163-        consumer.write(contents[start:end])
1164-        return consumer
1165-
1166-def makeMutableDownloadable(n):
1167-    d = defer.maybeDeferred(n.get_size_of_best_version)
1168-    d.addCallback(MutableDownloadable, n)
1169-    return d
1170 
1171 class FileDownloader(rend.Page):
1172     # since we override the rendering process (to let the tahoe Downloader
1173hunk ./src/allmydata/web/filenode.py 509
1174     data[1]['mutable'] = filenode.is_mutable()
1175     if edge_metadata is not None:
1176         data[1]['metadata'] = edge_metadata
1177+
1178+    if filenode.is_mutable() and filenode.get_version() is not None:
1179+        mutable_type = filenode.get_version()
1180+        assert mutable_type in (MDMF_VERSION, SDMF_VERSION)
1181+        if mutable_type == MDMF_VERSION:
1182+            mutable_type = "mdmf"
1183+        else:
1184+            mutable_type = "sdmf"
1185+        data[1]['mutable-type'] = mutable_type
1186+
1187     return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
1188 
1189 def FileURI(ctx, filenode):
1190hunk ./src/allmydata/web/root.py 15
1191 from allmydata import get_package_versions_string
1192 from allmydata import provisioning
1193 from allmydata.util import idlib, log
1194-from allmydata.interfaces import IFileNode
1195+from allmydata.interfaces import IFileNode, MDMF_VERSION, SDMF_VERSION
1196 from allmydata.web import filenode, directory, unlinked, status, operations
1197 from allmydata.web import reliability, storage
1198 from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
1199hunk ./src/allmydata/web/root.py 19
1200-     get_arg, RenderMixin, boolean_of_arg
1201+     get_arg, RenderMixin, boolean_of_arg, parse_mutable_type_arg
1202 
1203 
1204 class URIHandler(RenderMixin, rend.Page):
1205hunk ./src/allmydata/web/root.py 50
1206         if t == "":
1207             mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
1208             if mutable:
1209-                return unlinked.PUTUnlinkedSSK(req, self.client)
1210+                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
1211+                                                 None))
1212+                return unlinked.PUTUnlinkedSSK(req, self.client, version)
1213             else:
1214                 return unlinked.PUTUnlinkedCHK(req, self.client)
1215         if t == "mkdir":
1216hunk ./src/allmydata/web/root.py 70
1217         if t in ("", "upload"):
1218             mutable = bool(get_arg(req, "mutable", "").strip())
1219             if mutable:
1220-                return unlinked.POSTUnlinkedSSK(req, self.client)
1221+                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
1222+                                                         None))
1223+                return unlinked.POSTUnlinkedSSK(req, self.client, version)
1224             else:
1225                 return unlinked.POSTUnlinkedCHK(req, self.client)
1226         if t == "mkdir":
1227hunk ./src/allmydata/web/root.py 324
1228 
1229     def render_upload_form(self, ctx, data):
1230         # this is a form where users can upload unlinked files
1231+        #
1232+        # for mutable files, users can choose the format by selecting
1233+        # MDMF or SDMF from a radio button. They can also configure a
1234+        # default format in tahoe.cfg, which they rightly expect us to
1235+        # obey. we convey to them that we are obeying their choice by
1236+        # ensuring that the one that they've chosen is selected in the
1237+        # interface.
1238+        if self.client.mutable_file_default == MDMF_VERSION:
1239+            mdmf_input = T.input(type='radio', name='mutable-type',
1240+                                 value='mdmf', id='mutable-type-mdmf',
1241+                                 checked='checked')
1242+        else:
1243+            mdmf_input = T.input(type='radio', name='mutable-type',
1244+                                 value='mdmf', id='mutable-type-mdmf')
1245+
1246+        if self.client.mutable_file_default == SDMF_VERSION:
1247+            sdmf_input = T.input(type='radio', name='mutable-type',
1248+                                 value='sdmf', id='mutable-type-sdmf',
1249+                                 checked='checked')
1250+        else:
1251+            sdmf_input = T.input(type='radio', name='mutable-type',
1252+                                 value='sdmf', id='mutable-type-sdmf')
1253+
1254+
1255         form = T.form(action="uri", method="post",
1256                       enctype="multipart/form-data")[
1257             T.fieldset[
1258hunk ./src/allmydata/web/root.py 356
1259                   T.input(type="file", name="file", class_="freeform-input-file")],
1260             T.input(type="hidden", name="t", value="upload"),
1261             T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
1262+                  sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
1263+                  mdmf_input,
1264+                  T.label(for_='mutable-type-mdmf')['MDMF (experimental)'],
1265                   " ", T.input(type="submit", value="Upload!")],
1266             ]]
1267         return T.div[form]
1268hunk ./src/allmydata/web/unlinked.py 7
1269 from twisted.internet import defer
1270 from nevow import rend, url, tags as T
1271 from allmydata.immutable.upload import FileHandle
1272+from allmydata.mutable.publish import MutableFileHandle
1273 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
1274      convert_children_json, WebError
1275 from allmydata.web import status
1276hunk ./src/allmydata/web/unlinked.py 20
1277     # that fires with the URI of the new file
1278     return d
1279 
1280-def PUTUnlinkedSSK(req, client):
1281+def PUTUnlinkedSSK(req, client, version):
1282     # SDMF: files are small, and we can only upload data
1283     req.content.seek(0)
1284hunk ./src/allmydata/web/unlinked.py 23
1285-    data = req.content.read()
1286-    d = client.create_mutable_file(data)
1287+    data = MutableFileHandle(req.content)
1288+    d = client.create_mutable_file(data, version=version)
1289     d.addCallback(lambda n: n.get_uri())
1290     return d
1291 
1292hunk ./src/allmydata/web/unlinked.py 83
1293                       ["/uri/" + res.uri])
1294         return d
1295 
1296-def POSTUnlinkedSSK(req, client):
1297+def POSTUnlinkedSSK(req, client, version):
1298     # "POST /uri", to create an unlinked file.
1299     # SDMF: files are small, and we can only upload data
1300hunk ./src/allmydata/web/unlinked.py 86
1301-    contents = req.fields["file"]
1302-    contents.file.seek(0)
1303-    data = contents.file.read()
1304-    d = client.create_mutable_file(data)
1305+    contents = req.fields["file"].file
1306+    data = MutableFileHandle(contents)
1307+    d = client.create_mutable_file(data, version=version)
1308     d.addCallback(lambda n: n.get_uri())
1309     return d
1310 
1311}
1312[client.py: learn how to create different kinds of mutable files
1313Kevan Carstensen <kevan@isnotajoke.com>**20100814225711
1314 Ignore-this: 61ff665bc050cba5f58bf2ed779d692b
1315] {
1316hunk ./src/allmydata/client.py 25
1317 from allmydata.util.time_format import parse_duration, parse_date
1318 from allmydata.stats import StatsProvider
1319 from allmydata.history import History
1320-from allmydata.interfaces import IStatsProducer, RIStubClient
1321+from allmydata.interfaces import IStatsProducer, RIStubClient, \
1322+                                 SDMF_VERSION, MDMF_VERSION
1323 from allmydata.nodemaker import NodeMaker
1324 
1325 
1326hunk ./src/allmydata/client.py 357
1327                                    self.terminator,
1328                                    self.get_encoding_parameters(),
1329                                    self._key_generator)
1330+        default = self.get_config("client", "mutable.format", default="sdmf")
1331+        if default == "mdmf":
1332+            self.mutable_file_default = MDMF_VERSION
1333+        else:
1334+            self.mutable_file_default = SDMF_VERSION
1335 
1336     def get_history(self):
1337         return self.history
1338hunk ./src/allmydata/client.py 500
1339     def create_immutable_dirnode(self, children, convergence=None):
1340         return self.nodemaker.create_immutable_directory(children, convergence)
1341 
1342-    def create_mutable_file(self, contents=None, keysize=None):
1343-        return self.nodemaker.create_mutable_file(contents, keysize)
1344+    def create_mutable_file(self, contents=None, keysize=None, version=None):
1345+        if not version:
1346+            version = self.mutable_file_default
1347+        return self.nodemaker.create_mutable_file(contents, keysize,
1348+                                                  version=version)
1349 
1350     def upload(self, uploadable):
1351         uploader = self.getServiceNamed("uploader")
1352}
1353[mutable/checker.py and mutable/repair.py: Modify checker and repairer to work with MDMF
1354Kevan Carstensen <kevan@isnotajoke.com>**20100819003216
1355 Ignore-this: d3bd3260742be8964877f0a53543b01b
1356 
1357 The checker and repairer required minimal changes to work with the MDMF
1358 modifications made elsewhere. The checker duplicated a lot of the code
1359 that was already in the downloader, so I modified the downloader
1360 slightly to expose this functionality to the checker and removed the
1361 duplicated code. The repairer only required a minor change to deal with
1362 data representation.
1363] {
1364hunk ./src/allmydata/mutable/checker.py 2
1365 
1366-from twisted.internet import defer
1367-from twisted.python import failure
1368-from allmydata import hashtree
1369 from allmydata.uri import from_string
1370hunk ./src/allmydata/mutable/checker.py 3
1371-from allmydata.util import hashutil, base32, idlib, log
1372+from allmydata.util import base32, idlib, log
1373 from allmydata.check_results import CheckAndRepairResults, CheckResults
1374 
1375 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
1376hunk ./src/allmydata/mutable/checker.py 8
1377 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
1378-from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
1379+from allmydata.mutable.retrieve import Retrieve # for verifying
1380 
1381 class MutableChecker:
1382 
1383hunk ./src/allmydata/mutable/checker.py 25
1384 
1385     def check(self, verify=False, add_lease=False):
1386         servermap = ServerMap()
1387+        # Updating the servermap in MODE_CHECK will stand a good chance
1388+        # of finding all of the shares, and getting a good idea of
1389+        # recoverability, etc, without verifying.
1390         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
1391                              servermap, MODE_CHECK, add_lease=add_lease)
1392         if self._history:
1393hunk ./src/allmydata/mutable/checker.py 51
1394         if num_recoverable:
1395             self.best_version = servermap.best_recoverable_version()
1396 
1397+        # The file is unhealthy and needs to be repaired if:
1398+        # - There are unrecoverable versions.
1399         if servermap.unrecoverable_versions():
1400             self.need_repair = True
1401hunk ./src/allmydata/mutable/checker.py 55
1402+        # - There isn't a recoverable version.
1403         if num_recoverable != 1:
1404             self.need_repair = True
1405hunk ./src/allmydata/mutable/checker.py 58
1406+        # - The best recoverable version is missing some shares.
1407         if self.best_version:
1408             available_shares = servermap.shares_available()
1409             (num_distinct_shares, k, N) = available_shares[self.best_version]
1410hunk ./src/allmydata/mutable/checker.py 69
1411 
1412     def _verify_all_shares(self, servermap):
1413         # read every byte of each share
1414+        #
1415+        # This logic is going to be very nearly the same as the
1416+        # downloader. I bet we could pass the downloader a flag that
1417+        # makes it do this, and piggyback onto that instead of
1418+        # duplicating a bunch of code.
1419+        #
1420+        # Like:
1421+        #  r = Retrieve(blah, blah, blah, verify=True)
1422+        #  d = r.download()
1423+        #  (wait, wait, wait, d.callback)
1424+        # 
1425+        #  Then, when it has finished, we can check the servermap (which
1426+        #  we provided to Retrieve) to figure out which shares are bad,
1427+        #  since the Retrieve process will have updated the servermap as
1428+        #  it went along.
1429+        #
1430+        #  By passing the verify=True flag to the constructor, we are
1431+        #  telling the downloader a few things.
1432+        #
1433+        #  1. It needs to download all N shares, not just K shares.
1434+        #  2. It doesn't need to decrypt or decode the shares, only
1435+        #     verify them.
1436         if not self.best_version:
1437             return
1438hunk ./src/allmydata/mutable/checker.py 93
1439-        versionmap = servermap.make_versionmap()
1440-        shares = versionmap[self.best_version]
1441-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1442-         offsets_tuple) = self.best_version
1443-        offsets = dict(offsets_tuple)
1444-        readv = [ (0, offsets["EOF"]) ]
1445-        dl = []
1446-        for (shnum, peerid, timestamp) in shares:
1447-            ss = servermap.connections[peerid]
1448-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1449-            d.addCallback(self._got_answer, peerid, servermap)
1450-            dl.append(d)
1451-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
1452 
1453hunk ./src/allmydata/mutable/checker.py 94
1454-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
1455-        # isolate the callRemote to a separate method, so tests can subclass
1456-        # Publish and override it
1457-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
1458+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
1459+        d = r.download()
1460+        d.addCallback(self._process_bad_shares)
1461         return d
1462 
1463hunk ./src/allmydata/mutable/checker.py 99
1464-    def _got_answer(self, datavs, peerid, servermap):
1465-        for shnum,datav in datavs.items():
1466-            data = datav[0]
1467-            try:
1468-                self._got_results_one_share(shnum, peerid, data)
1469-            except CorruptShareError:
1470-                f = failure.Failure()
1471-                self.need_repair = True
1472-                self.bad_shares.append( (peerid, shnum, f) )
1473-                prefix = data[:SIGNED_PREFIX_LENGTH]
1474-                servermap.mark_bad_share(peerid, shnum, prefix)
1475-                ss = servermap.connections[peerid]
1476-                self.notify_server_corruption(ss, shnum, str(f.value))
1477-
1478-    def check_prefix(self, peerid, shnum, data):
1479-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1480-         offsets_tuple) = self.best_version
1481-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
1482-        if got_prefix != prefix:
1483-            raise CorruptShareError(peerid, shnum,
1484-                                    "prefix mismatch: share changed while we were reading it")
1485-
1486-    def _got_results_one_share(self, shnum, peerid, data):
1487-        self.check_prefix(peerid, shnum, data)
1488-
1489-        # the [seqnum:signature] pieces are validated by _compare_prefix,
1490-        # which checks their signature against the pubkey known to be
1491-        # associated with this file.
1492 
1493hunk ./src/allmydata/mutable/checker.py 100
1494-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
1495-         share_hash_chain, block_hash_tree, share_data,
1496-         enc_privkey) = unpack_share(data)
1497-
1498-        # validate [share_hash_chain,block_hash_tree,share_data]
1499-
1500-        leaves = [hashutil.block_hash(share_data)]
1501-        t = hashtree.HashTree(leaves)
1502-        if list(t) != block_hash_tree:
1503-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
1504-        share_hash_leaf = t[0]
1505-        t2 = hashtree.IncompleteHashTree(N)
1506-        # root_hash was checked by the signature
1507-        t2.set_hashes({0: root_hash})
1508-        try:
1509-            t2.set_hashes(hashes=share_hash_chain,
1510-                          leaves={shnum: share_hash_leaf})
1511-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
1512-                IndexError), e:
1513-            msg = "corrupt hashes: %s" % (e,)
1514-            raise CorruptShareError(peerid, shnum, msg)
1515-
1516-        # validate enc_privkey: only possible if we have a write-cap
1517-        if not self._node.is_readonly():
1518-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1519-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1520-            if alleged_writekey != self._node.get_writekey():
1521-                raise CorruptShareError(peerid, shnum, "invalid privkey")
1522+    def _process_bad_shares(self, bad_shares):
1523+        if bad_shares:
1524+            self.need_repair = True
1525+        self.bad_shares = bad_shares
1526 
1527hunk ./src/allmydata/mutable/checker.py 105
1528-    def notify_server_corruption(self, ss, shnum, reason):
1529-        ss.callRemoteOnly("advise_corrupt_share",
1530-                          "mutable", self._storage_index, shnum, reason)
1531 
1532     def _count_shares(self, smap, version):
1533         available_shares = smap.shares_available()
1534hunk ./src/allmydata/mutable/repairer.py 5
1535 from zope.interface import implements
1536 from twisted.internet import defer
1537 from allmydata.interfaces import IRepairResults, ICheckResults
1538+from allmydata.mutable.publish import MutableData
1539 
1540 class RepairResults:
1541     implements(IRepairResults)
1542hunk ./src/allmydata/mutable/repairer.py 108
1543             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
1544 
1545         d = self.node.download_version(smap, best_version, fetch_privkey=True)
1546+        d.addCallback(lambda data:
1547+            MutableData(data))
1548         d.addCallback(self.node.upload, smap)
1549         d.addCallback(self.get_results, smap)
1550         return d
1551}
1552[mutable/filenode.py: add versions and partial-file updates to the mutable file node
1553Kevan Carstensen <kevan@isnotajoke.com>**20100819003231
1554 Ignore-this: b7b5434201fdb9b48f902d7ab25ef45c
1555 
1556 One of the goals of MDMF as a GSoC project is to lay the groundwork for
1557 LDMF, a format that will allow Tahoe-LAFS to deal with and encourage
1558 multiple versions of a single cap on the grid. In line with this, there
1559 is a now a distinction between an overriding mutable file (which can be
1560 thought to correspond to the cap/unique identifier for that mutable
1561 file) and versions of the mutable file (which we can download, update,
1562 and so on). All download, upload, and modification operations end up
1563 happening on a particular version of a mutable file, but there are
1564 shortcut methods on the object representing the overriding mutable file
1565 that perform these operations on the best version of the mutable file
1566 (which is what code should be doing until we have LDMF and better
1567 support for other paradigms).
1568 
1569 Another goal of MDMF was to take advantage of segmentation to give
1570 callers more efficient partial file updates or appends. This patch
1571 implements methods that do that, too.
1572 
1573] {
1574hunk ./src/allmydata/mutable/filenode.py 7
1575 from zope.interface import implements
1576 from twisted.internet import defer, reactor
1577 from foolscap.api import eventually
1578-from allmydata.interfaces import IMutableFileNode, \
1579-     ICheckable, ICheckResults, NotEnoughSharesError
1580-from allmydata.util import hashutil, log
1581+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
1582+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
1583+     IMutableFileVersion, IWritable
1584+from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
1585 from allmydata.util.assertutil import precondition
1586 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
1587 from allmydata.monitor import Monitor
1588hunk ./src/allmydata/mutable/filenode.py 16
1589 from pycryptopp.cipher.aes import AES
1590 
1591-from allmydata.mutable.publish import Publish
1592+from allmydata.mutable.publish import Publish, MutableData,\
1593+                                      DEFAULT_MAX_SEGMENT_SIZE, \
1594+                                      TransformingUploadable
1595 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
1596      ResponseCache, UncoordinatedWriteError
1597 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
1598hunk ./src/allmydata/mutable/filenode.py 70
1599         self._sharemap = {} # known shares, shnum-to-[nodeids]
1600         self._cache = ResponseCache()
1601         self._most_recent_size = None
1602+        # filled in after __init__ if we're being created for the first time;
1603+        # filled in by the servermap updater before publishing, otherwise.
1604+        # set to this default value in case neither of those things happen,
1605+        # or in case the servermap can't find any shares to tell us what
1606+        # to publish as.
1607+        # TODO: Set this back to None, and find out why the tests fail
1608+        #       with it set to None.
1609+        self._protocol_version = None
1610 
1611         # all users of this MutableFileNode go through the serializer. This
1612         # takes advantage of the fact that Deferreds discard the callbacks
1613hunk ./src/allmydata/mutable/filenode.py 134
1614         return self._upload(initial_contents, None)
1615 
1616     def _get_initial_contents(self, contents):
1617-        if isinstance(contents, str):
1618-            return contents
1619         if contents is None:
1620hunk ./src/allmydata/mutable/filenode.py 135
1621-            return ""
1622+            return MutableData("")
1623+
1624+        if IMutableUploadable.providedBy(contents):
1625+            return contents
1626+
1627         assert callable(contents), "%s should be callable, not %s" % \
1628                (contents, type(contents))
1629         return contents(self)
1630hunk ./src/allmydata/mutable/filenode.py 209
1631 
1632     def get_size(self):
1633         return self._most_recent_size
1634+
1635     def get_current_size(self):
1636         d = self.get_size_of_best_version()
1637         d.addCallback(self._stash_size)
1638hunk ./src/allmydata/mutable/filenode.py 214
1639         return d
1640+
1641     def _stash_size(self, size):
1642         self._most_recent_size = size
1643         return size
1644hunk ./src/allmydata/mutable/filenode.py 273
1645             return cmp(self.__class__, them.__class__)
1646         return cmp(self._uri, them._uri)
1647 
1648-    def _do_serialized(self, cb, *args, **kwargs):
1649-        # note: to avoid deadlock, this callable is *not* allowed to invoke
1650-        # other serialized methods within this (or any other)
1651-        # MutableFileNode. The callable should be a bound method of this same
1652-        # MFN instance.
1653-        d = defer.Deferred()
1654-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
1655-        # we need to put off d.callback until this Deferred is finished being
1656-        # processed. Otherwise the caller's subsequent activities (like,
1657-        # doing other things with this node) can cause reentrancy problems in
1658-        # the Deferred code itself
1659-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
1660-        # add a log.err just in case something really weird happens, because
1661-        # self._serializer stays around forever, therefore we won't see the
1662-        # usual Unhandled Error in Deferred that would give us a hint.
1663-        self._serializer.addErrback(log.err)
1664-        return d
1665 
1666     #################################
1667     # ICheckable
1668hunk ./src/allmydata/mutable/filenode.py 298
1669 
1670 
1671     #################################
1672-    # IMutableFileNode
1673+    # IFileNode
1674+
1675+    def get_best_readable_version(self):
1676+        """
1677+        I return a Deferred that fires with a MutableFileVersion
1678+        representing the best readable version of the file that I
1679+        represent
1680+        """
1681+        return self.get_readable_version()
1682+
1683+
1684+    def get_readable_version(self, servermap=None, version=None):
1685+        """
1686+        I return a Deferred that fires with an MutableFileVersion for my
1687+        version argument, if there is a recoverable file of that version
1688+        on the grid. If there is no recoverable version, I fire with an
1689+        UnrecoverableFileError.
1690+
1691+        If a servermap is provided, I look in there for the requested
1692+        version. If no servermap is provided, I create and update a new
1693+        one.
1694+
1695+        If no version is provided, then I return a MutableFileVersion
1696+        representing the best recoverable version of the file.
1697+        """
1698+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
1699+        def _build_version((servermap, their_version)):
1700+            assert their_version in servermap.recoverable_versions()
1701+            assert their_version in servermap.make_versionmap()
1702+
1703+            mfv = MutableFileVersion(self,
1704+                                     servermap,
1705+                                     their_version,
1706+                                     self._storage_index,
1707+                                     self._storage_broker,
1708+                                     self._readkey,
1709+                                     history=self._history)
1710+            assert mfv.is_readonly()
1711+            # our caller can use this to download the contents of the
1712+            # mutable file.
1713+            return mfv
1714+        return d.addCallback(_build_version)
1715+
1716+
1717+    def _get_version_from_servermap(self,
1718+                                    mode,
1719+                                    servermap=None,
1720+                                    version=None):
1721+        """
1722+        I return a Deferred that fires with (servermap, version).
1723+
1724+        This function performs validation and a servermap update. If it
1725+        returns (servermap, version), the caller can assume that:
1726+            - servermap was last updated in mode.
1727+            - version is recoverable, and corresponds to the servermap.
1728+
1729+        If version and servermap are provided to me, I will validate
1730+        that version exists in the servermap, and that the servermap was
1731+        updated correctly.
1732+
1733+        If version is not provided, but servermap is, I will validate
1734+        the servermap and return the best recoverable version that I can
1735+        find in the servermap.
1736+
1737+        If the version is provided but the servermap isn't, I will
1738+        obtain a servermap that has been updated in the correct mode and
1739+        validate that version is found and recoverable.
1740+
1741+        If neither servermap nor version are provided, I will obtain a
1742+        servermap updated in the correct mode, and return the best
1743+        recoverable version that I can find in there.
1744+        """
1745+        # XXX: wording ^^^^
1746+        if servermap and servermap.last_update_mode == mode:
1747+            d = defer.succeed(servermap)
1748+        else:
1749+            d = self._get_servermap(mode)
1750+
1751+        def _get_version(servermap, v):
1752+            if v and v not in servermap.recoverable_versions():
1753+                v = None
1754+            elif not v:
1755+                v = servermap.best_recoverable_version()
1756+            if not v:
1757+                raise UnrecoverableFileError("no recoverable versions")
1758+
1759+            return (servermap, v)
1760+        return d.addCallback(_get_version, version)
1761+
1762 
1763     def download_best_version(self):
1764hunk ./src/allmydata/mutable/filenode.py 389
1765+        """
1766+        I return a Deferred that fires with the contents of the best
1767+        version of this mutable file.
1768+        """
1769         return self._do_serialized(self._download_best_version)
1770hunk ./src/allmydata/mutable/filenode.py 394
1771+
1772+
1773     def _download_best_version(self):
1774hunk ./src/allmydata/mutable/filenode.py 397
1775-        servermap = ServerMap()
1776-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
1777-        def _maybe_retry(f):
1778-            f.trap(NotEnoughSharesError)
1779-            # the download is worth retrying once. Make sure to use the
1780-            # old servermap, since it is what remembers the bad shares,
1781-            # but use MODE_WRITE to make it look for even more shares.
1782-            # TODO: consider allowing this to retry multiple times.. this
1783-            # approach will let us tolerate about 8 bad shares, I think.
1784-            return self._try_once_to_download_best_version(servermap,
1785-                                                           MODE_WRITE)
1786+        """
1787+        I am the serialized sibling of download_best_version.
1788+        """
1789+        d = self.get_best_readable_version()
1790+        d.addCallback(self._record_size)
1791+        d.addCallback(lambda version: version.download_to_data())
1792+
1793+        # It is possible that the download will fail because there
1794+        # aren't enough shares to be had. If so, we will try again after
1795+        # updating the servermap in MODE_WRITE, which may find more
1796+        # shares than updating in MODE_READ, as we just did. We can do
1797+        # this by getting the best mutable version and downloading from
1798+        # that -- the best mutable version will be a MutableFileVersion
1799+        # with a servermap that was last updated in MODE_WRITE, as we
1800+        # want. If this fails, then we give up.
1801+        def _maybe_retry(failure):
1802+            failure.trap(NotEnoughSharesError)
1803+
1804+            d = self.get_best_mutable_version()
1805+            d.addCallback(self._record_size)
1806+            d.addCallback(lambda version: version.download_to_data())
1807+            return d
1808+
1809         d.addErrback(_maybe_retry)
1810         return d
1811hunk ./src/allmydata/mutable/filenode.py 422
1812-    def _try_once_to_download_best_version(self, servermap, mode):
1813-        d = self._update_servermap(servermap, mode)
1814-        d.addCallback(self._once_updated_download_best_version, servermap)
1815-        return d
1816-    def _once_updated_download_best_version(self, ignored, servermap):
1817-        goal = servermap.best_recoverable_version()
1818-        if not goal:
1819-            raise UnrecoverableFileError("no recoverable versions")
1820-        return self._try_once_to_download_version(servermap, goal)
1821+
1822+
1823+    def _record_size(self, mfv):
1824+        """
1825+        I record the size of a mutable file version.
1826+        """
1827+        self._most_recent_size = mfv.get_size()
1828+        return mfv
1829+
1830 
1831     def get_size_of_best_version(self):
1832hunk ./src/allmydata/mutable/filenode.py 433
1833-        d = self.get_servermap(MODE_READ)
1834-        def _got_servermap(smap):
1835-            ver = smap.best_recoverable_version()
1836-            if not ver:
1837-                raise UnrecoverableFileError("no recoverable version")
1838-            return smap.size_of_version(ver)
1839-        d.addCallback(_got_servermap)
1840-        return d
1841+        """
1842+        I return the size of the best version of this mutable file.
1843 
1844hunk ./src/allmydata/mutable/filenode.py 436
1845+        This is equivalent to calling get_size() on the result of
1846+        get_best_readable_version().
1847+        """
1848+        d = self.get_best_readable_version()
1849+        return d.addCallback(lambda mfv: mfv.get_size())
1850+
1851+
1852+    #################################
1853+    # IMutableFileNode
1854+
1855+    def get_best_mutable_version(self, servermap=None):
1856+        """
1857+        I return a Deferred that fires with a MutableFileVersion
1858+        representing the best readable version of the file that I
1859+        represent. I am like get_best_readable_version, except that I
1860+        will try to make a writable version if I can.
1861+        """
1862+        return self.get_mutable_version(servermap=servermap)
1863+
1864+
1865+    def get_mutable_version(self, servermap=None, version=None):
1866+        """
1867+        I return a version of this mutable file. I return a Deferred
1868+        that fires with a MutableFileVersion
1869+
1870+        If version is provided, the Deferred will fire with a
1871+        MutableFileVersion initailized with that version. Otherwise, it
1872+        will fire with the best version that I can recover.
1873+
1874+        If servermap is provided, I will use that to find versions
1875+        instead of performing my own servermap update.
1876+        """
1877+        if self.is_readonly():
1878+            return self.get_readable_version(servermap=servermap,
1879+                                             version=version)
1880+
1881+        # get_mutable_version => write intent, so we require that the
1882+        # servermap is updated in MODE_WRITE
1883+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
1884+        def _build_version((servermap, smap_version)):
1885+            # these should have been set by the servermap update.
1886+            assert self._secret_holder
1887+            assert self._writekey
1888+
1889+            mfv = MutableFileVersion(self,
1890+                                     servermap,
1891+                                     smap_version,
1892+                                     self._storage_index,
1893+                                     self._storage_broker,
1894+                                     self._readkey,
1895+                                     self._writekey,
1896+                                     self._secret_holder,
1897+                                     history=self._history)
1898+            assert not mfv.is_readonly()
1899+            return mfv
1900+
1901+        return d.addCallback(_build_version)
1902+
1903+
1904+    # XXX: I'm uncomfortable with the difference between upload and
1905+    #      overwrite, which, FWICT, is basically that you don't have to
1906+    #      do a servermap update before you overwrite. We split them up
1907+    #      that way anyway, so I guess there's no real difficulty in
1908+    #      offering both ways to callers, but it also makes the
1909+    #      public-facing API cluttery, and makes it hard to discern the
1910+    #      right way of doing things.
1911+
1912+    # In general, we leave it to callers to ensure that they aren't
1913+    # going to cause UncoordinatedWriteErrors when working with
1914+    # MutableFileVersions. We know that the next three operations
1915+    # (upload, overwrite, and modify) will all operate on the same
1916+    # version, so we say that only one of them can be going on at once,
1917+    # and serialize them to ensure that that actually happens, since as
1918+    # the caller in this situation it is our job to do that.
1919     def overwrite(self, new_contents):
1920hunk ./src/allmydata/mutable/filenode.py 511
1921+        """
1922+        I overwrite the contents of the best recoverable version of this
1923+        mutable file with new_contents. This is equivalent to calling
1924+        overwrite on the result of get_best_mutable_version with
1925+        new_contents as an argument. I return a Deferred that eventually
1926+        fires with the results of my replacement process.
1927+        """
1928         return self._do_serialized(self._overwrite, new_contents)
1929hunk ./src/allmydata/mutable/filenode.py 519
1930+
1931+
1932     def _overwrite(self, new_contents):
1933hunk ./src/allmydata/mutable/filenode.py 522
1934+        """
1935+        I am the serialized sibling of overwrite.
1936+        """
1937+        d = self.get_best_mutable_version()
1938+        d.addCallback(lambda mfv: mfv.overwrite(new_contents))
1939+        d.addCallback(self._did_upload, new_contents.get_size())
1940+        return d
1941+
1942+
1943+
1944+    def upload(self, new_contents, servermap):
1945+        """
1946+        I overwrite the contents of the best recoverable version of this
1947+        mutable file with new_contents, using servermap instead of
1948+        creating/updating our own servermap. I return a Deferred that
1949+        fires with the results of my upload.
1950+        """
1951+        return self._do_serialized(self._upload, new_contents, servermap)
1952+
1953+
1954+    def modify(self, modifier, backoffer=None):
1955+        """
1956+        I modify the contents of the best recoverable version of this
1957+        mutable file with the modifier. This is equivalent to calling
1958+        modify on the result of get_best_mutable_version. I return a
1959+        Deferred that eventually fires with an UploadResults instance
1960+        describing this process.
1961+        """
1962+        return self._do_serialized(self._modify, modifier, backoffer)
1963+
1964+
1965+    def _modify(self, modifier, backoffer):
1966+        """
1967+        I am the serialized sibling of modify.
1968+        """
1969+        d = self.get_best_mutable_version()
1970+        d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
1971+        return d
1972+
1973+
1974+    def download_version(self, servermap, version, fetch_privkey=False):
1975+        """
1976+        Download the specified version of this mutable file. I return a
1977+        Deferred that fires with the contents of the specified version
1978+        as a bytestring, or errbacks if the file is not recoverable.
1979+        """
1980+        d = self.get_readable_version(servermap, version)
1981+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
1982+
1983+
1984+    def get_servermap(self, mode):
1985+        """
1986+        I return a servermap that has been updated in mode.
1987+
1988+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
1989+        MODE_ANYTHING. See servermap.py for more on what these mean.
1990+        """
1991+        return self._do_serialized(self._get_servermap, mode)
1992+
1993+
1994+    def _get_servermap(self, mode):
1995+        """
1996+        I am a serialized twin to get_servermap.
1997+        """
1998         servermap = ServerMap()
1999hunk ./src/allmydata/mutable/filenode.py 587
2000-        d = self._update_servermap(servermap, mode=MODE_WRITE)
2001-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
2002+        d = self._update_servermap(servermap, mode)
2003+        # The servermap will tell us about the most recent size of the
2004+        # file, so we may as well set that so that callers might get
2005+        # more data about us.
2006+        if not self._most_recent_size:
2007+            d.addCallback(self._get_size_from_servermap)
2008+        return d
2009+
2010+
2011+    def _get_size_from_servermap(self, servermap):
2012+        """
2013+        I extract the size of the best version of this file and record
2014+        it in self._most_recent_size. I return the servermap that I was
2015+        given.
2016+        """
2017+        if servermap.recoverable_versions():
2018+            v = servermap.best_recoverable_version()
2019+            size = v[4] # verinfo[4] == size
2020+            self._most_recent_size = size
2021+        return servermap
2022+
2023+
2024+    def _update_servermap(self, servermap, mode):
2025+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
2026+                             mode)
2027+        if self._history:
2028+            self._history.notify_mapupdate(u.get_status())
2029+        return u.update()
2030+
2031+
2032+    def set_version(self, version):
2033+        # I can be set in two ways:
2034+        #  1. When the node is created.
2035+        #  2. (for an existing share) when the Servermap is updated
2036+        #     before I am read.
2037+        assert version in (MDMF_VERSION, SDMF_VERSION)
2038+        self._protocol_version = version
2039+
2040+
2041+    def get_version(self):
2042+        return self._protocol_version
2043+
2044+
2045+    def _do_serialized(self, cb, *args, **kwargs):
2046+        # note: to avoid deadlock, this callable is *not* allowed to invoke
2047+        # other serialized methods within this (or any other)
2048+        # MutableFileNode. The callable should be a bound method of this same
2049+        # MFN instance.
2050+        d = defer.Deferred()
2051+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
2052+        # we need to put off d.callback until this Deferred is finished being
2053+        # processed. Otherwise the caller's subsequent activities (like,
2054+        # doing other things with this node) can cause reentrancy problems in
2055+        # the Deferred code itself
2056+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
2057+        # add a log.err just in case something really weird happens, because
2058+        # self._serializer stays around forever, therefore we won't see the
2059+        # usual Unhandled Error in Deferred that would give us a hint.
2060+        self._serializer.addErrback(log.err)
2061         return d
2062 
2063 
2064hunk ./src/allmydata/mutable/filenode.py 649
2065+    def _upload(self, new_contents, servermap):
2066+        """
2067+        A MutableFileNode still has to have some way of getting
2068+        published initially, which is what I am here for. After that,
2069+        all publishing, updating, modifying and so on happens through
2070+        MutableFileVersions.
2071+        """
2072+        assert self._pubkey, "update_servermap must be called before publish"
2073+
2074+        p = Publish(self, self._storage_broker, servermap)
2075+        if self._history:
2076+            self._history.notify_publish(p.get_status(),
2077+                                         new_contents.get_size())
2078+        d = p.publish(new_contents)
2079+        d.addCallback(self._did_upload, new_contents.get_size())
2080+        return d
2081+
2082+
2083+    def _did_upload(self, res, size):
2084+        self._most_recent_size = size
2085+        return res
2086+
2087+
2088+class MutableFileVersion:
2089+    """
2090+    I represent a specific version (most likely the best version) of a
2091+    mutable file.
2092+
2093+    Since I implement IReadable, instances which hold a
2094+    reference to an instance of me are guaranteed the ability (absent
2095+    connection difficulties or unrecoverable versions) to read the file
2096+    that I represent. Depending on whether I was initialized with a
2097+    write capability or not, I may also provide callers the ability to
2098+    overwrite or modify the contents of the mutable file that I
2099+    reference.
2100+    """
2101+    implements(IMutableFileVersion, IWritable)
2102+
2103+    def __init__(self,
2104+                 node,
2105+                 servermap,
2106+                 version,
2107+                 storage_index,
2108+                 storage_broker,
2109+                 readcap,
2110+                 writekey=None,
2111+                 write_secrets=None,
2112+                 history=None):
2113+
2114+        self._node = node
2115+        self._servermap = servermap
2116+        self._version = version
2117+        self._storage_index = storage_index
2118+        self._write_secrets = write_secrets
2119+        self._history = history
2120+        self._storage_broker = storage_broker
2121+
2122+        #assert isinstance(readcap, IURI)
2123+        self._readcap = readcap
2124+
2125+        self._writekey = writekey
2126+        self._serializer = defer.succeed(None)
2127+
2128+
2129+    def get_sequence_number(self):
2130+        """
2131+        Get the sequence number of the mutable version that I represent.
2132+        """
2133+        return self._version[0] # verinfo[0] == the sequence number
2134+
2135+
2136+    # TODO: Terminology?
2137+    def get_writekey(self):
2138+        """
2139+        I return a writekey or None if I don't have a writekey.
2140+        """
2141+        return self._writekey
2142+
2143+
2144+    def overwrite(self, new_contents):
2145+        """
2146+        I overwrite the contents of this mutable file version with the
2147+        data in new_contents.
2148+        """
2149+        assert not self.is_readonly()
2150+
2151+        return self._do_serialized(self._overwrite, new_contents)
2152+
2153+
2154+    def _overwrite(self, new_contents):
2155+        assert IMutableUploadable.providedBy(new_contents)
2156+        assert self._servermap.last_update_mode == MODE_WRITE
2157+
2158+        return self._upload(new_contents)
2159+
2160+
2161     def modify(self, modifier, backoffer=None):
2162         """I use a modifier callback to apply a change to the mutable file.
2163         I implement the following pseudocode::
2164hunk ./src/allmydata/mutable/filenode.py 785
2165         backoffer should not invoke any methods on this MutableFileNode
2166         instance, and it needs to be highly conscious of deadlock issues.
2167         """
2168+        assert not self.is_readonly()
2169+
2170         return self._do_serialized(self._modify, modifier, backoffer)
2171hunk ./src/allmydata/mutable/filenode.py 788
2172+
2173+
2174     def _modify(self, modifier, backoffer):
2175hunk ./src/allmydata/mutable/filenode.py 791
2176-        servermap = ServerMap()
2177         if backoffer is None:
2178             backoffer = BackoffAgent().delay
2179hunk ./src/allmydata/mutable/filenode.py 793
2180-        return self._modify_and_retry(servermap, modifier, backoffer, True)
2181-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
2182-        d = self._modify_once(servermap, modifier, first_time)
2183+        return self._modify_and_retry(modifier, backoffer, True)
2184+
2185+
2186+    def _modify_and_retry(self, modifier, backoffer, first_time):
2187+        """
2188+        I try to apply modifier to the contents of this version of the
2189+        mutable file. If I succeed, I return an UploadResults instance
2190+        describing my success. If I fail, I try again after waiting for
2191+        a little bit.
2192+        """
2193+        log.msg("doing modify")
2194+        d = self._modify_once(modifier, first_time)
2195         def _retry(f):
2196             f.trap(UncoordinatedWriteError)
2197             d2 = defer.maybeDeferred(backoffer, self, f)
2198hunk ./src/allmydata/mutable/filenode.py 809
2199             d2.addCallback(lambda ignored:
2200-                           self._modify_and_retry(servermap, modifier,
2201+                           self._modify_and_retry(modifier,
2202                                                   backoffer, False))
2203             return d2
2204         d.addErrback(_retry)
2205hunk ./src/allmydata/mutable/filenode.py 814
2206         return d
2207-    def _modify_once(self, servermap, modifier, first_time):
2208-        d = self._update_servermap(servermap, MODE_WRITE)
2209-        d.addCallback(self._once_updated_download_best_version, servermap)
2210+
2211+
2212+    def _modify_once(self, modifier, first_time):
2213+        """
2214+        I attempt to apply a modifier to the contents of the mutable
2215+        file.
2216+        """
2217+        # XXX: This is wrong -- we could get more servers if we updated
2218+        # in MODE_ANYTHING and possibly MODE_CHECK. Probably we want to
2219+        # assert that the last update wasn't MODE_READ
2220+        assert self._servermap.last_update_mode == MODE_WRITE
2221+
2222+        # download_to_data is serialized, so we have to call this to
2223+        # avoid deadlock.
2224+        d = self._try_to_download_data()
2225         def _apply(old_contents):
2226hunk ./src/allmydata/mutable/filenode.py 830
2227-            new_contents = modifier(old_contents, servermap, first_time)
2228+            new_contents = modifier(old_contents, self._servermap, first_time)
2229+            precondition((isinstance(new_contents, str) or
2230+                          new_contents is None),
2231+                         "Modifier function must return a string "
2232+                         "or None")
2233+
2234             if new_contents is None or new_contents == old_contents:
2235hunk ./src/allmydata/mutable/filenode.py 837
2236+                log.msg("no changes")
2237                 # no changes need to be made
2238                 if first_time:
2239                     return
2240hunk ./src/allmydata/mutable/filenode.py 845
2241                 # recovery when it observes UCWE, we need to do a second
2242                 # publish. See #551 for details. We'll basically loop until
2243                 # we managed an uncontested publish.
2244-                new_contents = old_contents
2245-            precondition(isinstance(new_contents, str),
2246-                         "Modifier function must return a string or None")
2247-            return self._upload(new_contents, servermap)
2248+                old_uploadable = MutableData(old_contents)
2249+                new_contents = old_uploadable
2250+            else:
2251+                new_contents = MutableData(new_contents)
2252+
2253+            return self._upload(new_contents)
2254         d.addCallback(_apply)
2255         return d
2256 
2257hunk ./src/allmydata/mutable/filenode.py 854
2258-    def get_servermap(self, mode):
2259-        return self._do_serialized(self._get_servermap, mode)
2260-    def _get_servermap(self, mode):
2261-        servermap = ServerMap()
2262-        return self._update_servermap(servermap, mode)
2263-    def _update_servermap(self, servermap, mode):
2264-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
2265-                             mode)
2266-        if self._history:
2267-            self._history.notify_mapupdate(u.get_status())
2268-        return u.update()
2269 
2270hunk ./src/allmydata/mutable/filenode.py 855
2271-    def download_version(self, servermap, version, fetch_privkey=False):
2272-        return self._do_serialized(self._try_once_to_download_version,
2273-                                   servermap, version, fetch_privkey)
2274-    def _try_once_to_download_version(self, servermap, version,
2275-                                      fetch_privkey=False):
2276-        r = Retrieve(self, servermap, version, fetch_privkey)
2277+    def is_readonly(self):
2278+        """
2279+        I return True if this MutableFileVersion provides no write
2280+        access to the file that it encapsulates, and False if it
2281+        provides the ability to modify the file.
2282+        """
2283+        return self._writekey is None
2284+
2285+
2286+    def is_mutable(self):
2287+        """
2288+        I return True, since mutable files are always mutable by
2289+        somebody.
2290+        """
2291+        return True
2292+
2293+
2294+    def get_storage_index(self):
2295+        """
2296+        I return the storage index of the reference that I encapsulate.
2297+        """
2298+        return self._storage_index
2299+
2300+
2301+    def get_size(self):
2302+        """
2303+        I return the length, in bytes, of this readable object.
2304+        """
2305+        return self._servermap.size_of_version(self._version)
2306+
2307+
2308+    def download_to_data(self, fetch_privkey=False):
2309+        """
2310+        I return a Deferred that fires with the contents of this
2311+        readable object as a byte string.
2312+
2313+        """
2314+        c = consumer.MemoryConsumer()
2315+        d = self.read(c, fetch_privkey=fetch_privkey)
2316+        d.addCallback(lambda mc: "".join(mc.chunks))
2317+        return d
2318+
2319+
2320+    def _try_to_download_data(self):
2321+        """
2322+        I am an unserialized cousin of download_to_data; I am called
2323+        from the children of modify() to download the data associated
2324+        with this mutable version.
2325+        """
2326+        c = consumer.MemoryConsumer()
2327+        # modify will almost certainly write, so we need the privkey.
2328+        d = self._read(c, fetch_privkey=True)
2329+        d.addCallback(lambda mc: "".join(mc.chunks))
2330+        return d
2331+
2332+
2333+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
2334+        """
2335+        I read a portion (possibly all) of the mutable file that I
2336+        reference into consumer.
2337+        """
2338+        return self._do_serialized(self._read, consumer, offset, size,
2339+                                   fetch_privkey)
2340+
2341+
2342+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
2343+        """
2344+        I am the serialized companion of read.
2345+        """
2346+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
2347         if self._history:
2348             self._history.notify_retrieve(r.get_status())
2349hunk ./src/allmydata/mutable/filenode.py 927
2350-        d = r.download()
2351-        d.addCallback(self._downloaded_version)
2352+        d = r.download(consumer, offset, size)
2353         return d
2354hunk ./src/allmydata/mutable/filenode.py 929
2355-    def _downloaded_version(self, data):
2356-        self._most_recent_size = len(data)
2357-        return data
2358 
2359hunk ./src/allmydata/mutable/filenode.py 930
2360-    def upload(self, new_contents, servermap):
2361-        return self._do_serialized(self._upload, new_contents, servermap)
2362-    def _upload(self, new_contents, servermap):
2363-        assert self._pubkey, "update_servermap must be called before publish"
2364-        p = Publish(self, self._storage_broker, servermap)
2365+
2366+    def _do_serialized(self, cb, *args, **kwargs):
2367+        # note: to avoid deadlock, this callable is *not* allowed to invoke
2368+        # other serialized methods within this (or any other)
2369+        # MutableFileNode. The callable should be a bound method of this same
2370+        # MFN instance.
2371+        d = defer.Deferred()
2372+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
2373+        # we need to put off d.callback until this Deferred is finished being
2374+        # processed. Otherwise the caller's subsequent activities (like,
2375+        # doing other things with this node) can cause reentrancy problems in
2376+        # the Deferred code itself
2377+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
2378+        # add a log.err just in case something really weird happens, because
2379+        # self._serializer stays around forever, therefore we won't see the
2380+        # usual Unhandled Error in Deferred that would give us a hint.
2381+        self._serializer.addErrback(log.err)
2382+        return d
2383+
2384+
2385+    def _upload(self, new_contents):
2386+        #assert self._pubkey, "update_servermap must be called before publish"
2387+        p = Publish(self._node, self._storage_broker, self._servermap)
2388         if self._history:
2389hunk ./src/allmydata/mutable/filenode.py 954
2390-            self._history.notify_publish(p.get_status(), len(new_contents))
2391+            self._history.notify_publish(p.get_status(),
2392+                                         new_contents.get_size())
2393         d = p.publish(new_contents)
2394hunk ./src/allmydata/mutable/filenode.py 957
2395-        d.addCallback(self._did_upload, len(new_contents))
2396+        d.addCallback(self._did_upload, new_contents.get_size())
2397         return d
2398hunk ./src/allmydata/mutable/filenode.py 959
2399+
2400+
2401     def _did_upload(self, res, size):
2402         self._most_recent_size = size
2403         return res
2404hunk ./src/allmydata/mutable/filenode.py 964
2405+
2406+    def update(self, data, offset):
2407+        """
2408+        Do an update of this mutable file version by inserting data at
2409+        offset within the file. If offset is the EOF, this is an append
2410+        operation. I return a Deferred that fires with the results of
2411+        the update operation when it has completed.
2412+
2413+        In cases where update does not append any data, or where it does
2414+        not append so many blocks that the block count crosses a
2415+        power-of-two boundary, this operation will use roughly
2416+        O(data.get_size()) memory/bandwidth/CPU to perform the update.
2417+        Otherwise, it must download, re-encode, and upload the entire
2418+        file again, which will use O(filesize) resources.
2419+        """
2420+        return self._do_serialized(self._update, data, offset)
2421+
2422+
2423+    def _update(self, data, offset):
2424+        """
2425+        I update the mutable file version represented by this particular
2426+        IMutableVersion by inserting the data in data at the offset
2427+        offset. I return a Deferred that fires when this has been
2428+        completed.
2429+        """
2430+        # We have two cases here:
2431+        # 1. The new data will add few enough segments so that it does
2432+        #    not cross into the next power-of-two boundary.
2433+        # 2. It doesn't.
2434+        #
2435+        # In the former case, we can modify the file in place. In the
2436+        # latter case, we need to re-encode the file.
2437+        new_size = data.get_size() + offset
2438+        old_size = self.get_size()
2439+        segment_size = self._version[3]
2440+        num_old_segments = mathutil.div_ceil(old_size,
2441+                                             segment_size)
2442+        num_new_segments = mathutil.div_ceil(new_size,
2443+                                             segment_size)
2444+        log.msg("got %d old segments, %d new segments" % \
2445+                        (num_old_segments, num_new_segments))
2446+
2447+        # We also do a whole file re-encode if the file is an SDMF file.
2448+        if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
2449+            log.msg("doing re-encode instead of in-place update")
2450+            return self._do_modify_update(data, offset)
2451+
2452+        log.msg("updating in place")
2453+        d = self._do_update_update(data, offset)
2454+        d.addCallback(self._decode_and_decrypt_segments, data, offset)
2455+        d.addCallback(self._build_uploadable_and_finish, data, offset)
2456+        return d
2457+
2458+
2459+    def _do_modify_update(self, data, offset):
2460+        """
2461+        I perform a file update by modifying the contents of the file
2462+        after downloading it, then reuploading it. I am less efficient
2463+        than _do_update_update, but am necessary for certain updates.
2464+        """
2465+        def m(old, servermap, first_time):
2466+            start = offset
2467+            rest = offset + data.get_size()
2468+            new = old[:start]
2469+            new += "".join(data.read(data.get_size()))
2470+            new += old[rest:]
2471+            return new
2472+        return self._modify(m, None)
2473+
2474+
2475+    def _do_update_update(self, data, offset):
2476+        """
2477+        I start the Servermap update that gets us the data we need to
2478+        continue the update process. I return a Deferred that fires when
2479+        the servermap update is done.
2480+        """
2481+        assert IMutableUploadable.providedBy(data)
2482+        assert self.is_mutable()
2483+        # offset == self.get_size() is valid and means that we are
2484+        # appending data to the file.
2485+        assert offset <= self.get_size()
2486+
2487+        # We'll need the segment that the data starts in, regardless of
2488+        # what we'll do later.
2489+        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
2490+        start_segment -= 1
2491+
2492+        # We only need the end segment if the data we append does not go
2493+        # beyond the current end-of-file.
2494+        end_segment = start_segment
2495+        if offset + data.get_size() < self.get_size():
2496+            end_data = offset + data.get_size()
2497+            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
2498+            end_segment -= 1
2499+        self._start_segment = start_segment
2500+        self._end_segment = end_segment
2501+
2502+        # Now ask for the servermap to be updated in MODE_WRITE with
2503+        # this update range.
2504+        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
2505+                             self._servermap,
2506+                             mode=MODE_WRITE,
2507+                             update_range=(start_segment, end_segment))
2508+        return u.update()
2509+
2510+
2511+    def _decode_and_decrypt_segments(self, ignored, data, offset):
2512+        """
2513+        After the servermap update, I take the encrypted and encoded
2514+        data that the servermap fetched while doing its update and
2515+        transform it into decoded-and-decrypted plaintext that can be
2516+        used by the new uploadable. I return a Deferred that fires with
2517+        the segments.
2518+        """
2519+        r = Retrieve(self._node, self._servermap, self._version)
2520+        # decode: takes in our blocks and salts from the servermap,
2521+        # returns a Deferred that fires with the corresponding plaintext
2522+        # segments. Does not download -- simply takes advantage of
2523+        # existing infrastructure within the Retrieve class to avoid
2524+        # duplicating code.
2525+        sm = self._servermap
2526+        # XXX: If the methods in the servermap don't work as
2527+        # abstractions, you should rewrite them instead of going around
2528+        # them.
2529+        update_data = sm.update_data
2530+        start_segments = {} # shnum -> start segment
2531+        end_segments = {} # shnum -> end segment
2532+        blockhashes = {} # shnum -> blockhash tree
2533+        for (shnum, data) in update_data.iteritems():
2534+            data = [d[1] for d in data if d[0] == self._version]
2535+
2536+            # Every data entry in our list should now be share shnum for
2537+            # a particular version of the mutable file, so all of the
2538+            # entries should be identical.
2539+            datum = data[0]
2540+            assert filter(lambda x: x != datum, data) == []
2541+
2542+            blockhashes[shnum] = datum[0]
2543+            start_segments[shnum] = datum[1]
2544+            end_segments[shnum] = datum[2]
2545+
2546+        d1 = r.decode(start_segments, self._start_segment)
2547+        d2 = r.decode(end_segments, self._end_segment)
2548+        d3 = defer.succeed(blockhashes)
2549+        return deferredutil.gatherResults([d1, d2, d3])
2550+
2551+
2552+    def _build_uploadable_and_finish(self, segments_and_bht, data, offset):
2553+        """
2554+        After the process has the plaintext segments, I build the
2555+        TransformingUploadable that the publisher will eventually
2556+        re-upload to the grid. I then invoke the publisher with that
2557+        uploadable, and return a Deferred when the publish operation has
2558+        completed without issue.
2559+        """
2560+        u = TransformingUploadable(data, offset,
2561+                                   self._version[3],
2562+                                   segments_and_bht[0],
2563+                                   segments_and_bht[1])
2564+        p = Publish(self._node, self._storage_broker, self._servermap)
2565+        return p.update(u, offset, segments_and_bht[2], self._version)
2566}
2567[mutable/publish.py: Modify the publish process to support MDMF
2568Kevan Carstensen <kevan@isnotajoke.com>**20100819003342
2569 Ignore-this: 2bb379974927e2e20cff75bae8302d1d
2570 
2571 The inner workings of the publishing process needed to be reworked to a
2572 large extend to cope with segmented mutable files, and to cope with
2573 partial-file updates of mutable files. This patch does that. It also
2574 introduces wrappers for uploadable data, allowing the use of
2575 filehandle-like objects as data sources, in addition to strings. This
2576 reduces memory inefficiency when dealing with large files through the
2577 webapi, and clarifies update code there.
2578] {
2579hunk ./src/allmydata/mutable/publish.py 3
2580 
2581 
2582-import os, struct, time
2583+import os, time
2584+from StringIO import StringIO
2585 from itertools import count
2586 from zope.interface import implements
2587 from twisted.internet import defer
2588hunk ./src/allmydata/mutable/publish.py 9
2589 from twisted.python import failure
2590-from allmydata.interfaces import IPublishStatus
2591+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
2592+                                 IMutableUploadable
2593 from allmydata.util import base32, hashutil, mathutil, idlib, log
2594 from allmydata.util.dictutil import DictOfSets
2595 from allmydata import hashtree, codec
2596hunk ./src/allmydata/mutable/publish.py 21
2597 from allmydata.mutable.common import MODE_WRITE, MODE_CHECK, \
2598      UncoordinatedWriteError, NotEnoughServersError
2599 from allmydata.mutable.servermap import ServerMap
2600-from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
2601-     unpack_checkstring, SIGNED_PREFIX
2602+from allmydata.mutable.layout import unpack_checkstring, MDMFSlotWriteProxy, \
2603+                                     SDMFSlotWriteProxy
2604+
2605+KiB = 1024
2606+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
2607+PUSHING_BLOCKS_STATE = 0
2608+PUSHING_EVERYTHING_ELSE_STATE = 1
2609+DONE_STATE = 2
2610 
2611 class PublishStatus:
2612     implements(IPublishStatus)
2613hunk ./src/allmydata/mutable/publish.py 118
2614         self._status.set_helper(False)
2615         self._status.set_progress(0.0)
2616         self._status.set_active(True)
2617+        self._version = self._node.get_version()
2618+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
2619+
2620 
2621     def get_status(self):
2622         return self._status
2623hunk ./src/allmydata/mutable/publish.py 132
2624             kwargs["facility"] = "tahoe.mutable.publish"
2625         return log.msg(*args, **kwargs)
2626 
2627+
2628+    def update(self, data, offset, blockhashes, version):
2629+        """
2630+        I replace the contents of this file with the contents of data,
2631+        starting at offset. I return a Deferred that fires with None
2632+        when the replacement has been completed, or with an error if
2633+        something went wrong during the process.
2634+
2635+        Note that this process will not upload new shares. If the file
2636+        being updated is in need of repair, callers will have to repair
2637+        it on their own.
2638+        """
2639+        # How this works:
2640+        # 1: Make peer assignments. We'll assign each share that we know
2641+        # about on the grid to that peer that currently holds that
2642+        # share, and will not place any new shares.
2643+        # 2: Setup encoding parameters. Most of these will stay the same
2644+        # -- datalength will change, as will some of the offsets.
2645+        # 3. Upload the new segments.
2646+        # 4. Be done.
2647+        assert IMutableUploadable.providedBy(data)
2648+
2649+        self.data = data
2650+
2651+        # XXX: Use the MutableFileVersion instead.
2652+        self.datalength = self._node.get_size()
2653+        if data.get_size() > self.datalength:
2654+            self.datalength = data.get_size()
2655+
2656+        self.log("starting update")
2657+        self.log("adding new data of length %d at offset %d" % \
2658+                    (data.get_size(), offset))
2659+        self.log("new data length is %d" % self.datalength)
2660+        self._status.set_size(self.datalength)
2661+        self._status.set_status("Started")
2662+        self._started = time.time()
2663+
2664+        self.done_deferred = defer.Deferred()
2665+
2666+        self._writekey = self._node.get_writekey()
2667+        assert self._writekey, "need write capability to publish"
2668+
2669+        # first, which servers will we publish to? We require that the
2670+        # servermap was updated in MODE_WRITE, so we can depend upon the
2671+        # peerlist computed by that process instead of computing our own.
2672+        assert self._servermap
2673+        assert self._servermap.last_update_mode in (MODE_WRITE, MODE_CHECK)
2674+        # we will push a version that is one larger than anything present
2675+        # in the grid, according to the servermap.
2676+        self._new_seqnum = self._servermap.highest_seqnum() + 1
2677+        self._status.set_servermap(self._servermap)
2678+
2679+        self.log(format="new seqnum will be %(seqnum)d",
2680+                 seqnum=self._new_seqnum, level=log.NOISY)
2681+
2682+        # We're updating an existing file, so all of the following
2683+        # should be available.
2684+        self.readkey = self._node.get_readkey()
2685+        self.required_shares = self._node.get_required_shares()
2686+        assert self.required_shares is not None
2687+        self.total_shares = self._node.get_total_shares()
2688+        assert self.total_shares is not None
2689+        self._status.set_encoding(self.required_shares, self.total_shares)
2690+
2691+        self._pubkey = self._node.get_pubkey()
2692+        assert self._pubkey
2693+        self._privkey = self._node.get_privkey()
2694+        assert self._privkey
2695+        self._encprivkey = self._node.get_encprivkey()
2696+
2697+        sb = self._storage_broker
2698+        full_peerlist = sb.get_servers_for_index(self._storage_index)
2699+        self.full_peerlist = full_peerlist # for use later, immutable
2700+        self.bad_peers = set() # peerids who have errbacked/refused requests
2701+
2702+        # This will set self.segment_size, self.num_segments, and
2703+        # self.fec. TODO: Does it know how to do the offset? Probably
2704+        # not. So do that part next.
2705+        self.setup_encoding_parameters(offset=offset)
2706+
2707+        # if we experience any surprises (writes which were rejected because
2708+        # our test vector did not match, or shares which we didn't expect to
2709+        # see), we set this flag and report an UncoordinatedWriteError at the
2710+        # end of the publish process.
2711+        self.surprised = False
2712+
2713+        # we keep track of three tables. The first is our goal: which share
2714+        # we want to see on which servers. This is initially populated by the
2715+        # existing servermap.
2716+        self.goal = set() # pairs of (peerid, shnum) tuples
2717+
2718+        # the second table is our list of outstanding queries: those which
2719+        # are in flight and may or may not be delivered, accepted, or
2720+        # acknowledged. Items are added to this table when the request is
2721+        # sent, and removed when the response returns (or errbacks).
2722+        self.outstanding = set() # (peerid, shnum) tuples
2723+
2724+        # the third is a table of successes: share which have actually been
2725+        # placed. These are populated when responses come back with success.
2726+        # When self.placed == self.goal, we're done.
2727+        self.placed = set() # (peerid, shnum) tuples
2728+
2729+        # we also keep a mapping from peerid to RemoteReference. Each time we
2730+        # pull a connection out of the full peerlist, we add it to this for
2731+        # use later.
2732+        self.connections = {}
2733+
2734+        self.bad_share_checkstrings = {}
2735+
2736+        # This is set at the last step of the publishing process.
2737+        self.versioninfo = ""
2738+
2739+        # we use the servermap to populate the initial goal: this way we will
2740+        # try to update each existing share in place. Since we're
2741+        # updating, we ignore damaged and missing shares -- callers must
2742+        # do a repair to repair and recreate these.
2743+        for (peerid, shnum) in self._servermap.servermap:
2744+            self.goal.add( (peerid, shnum) )
2745+            self.connections[peerid] = self._servermap.connections[peerid]
2746+        self.writers = {}
2747+
2748+        # SDMF files are updated differently.
2749+        self._version = MDMF_VERSION
2750+        writer_class = MDMFSlotWriteProxy
2751+
2752+        # For each (peerid, shnum) in self.goal, we make a
2753+        # write proxy for that peer. We'll use this to write
2754+        # shares to the peer.
2755+        for key in self.goal:
2756+            peerid, shnum = key
2757+            write_enabler = self._node.get_write_enabler(peerid)
2758+            renew_secret = self._node.get_renewal_secret(peerid)
2759+            cancel_secret = self._node.get_cancel_secret(peerid)
2760+            secrets = (write_enabler, renew_secret, cancel_secret)
2761+
2762+            self.writers[shnum] =  writer_class(shnum,
2763+                                                self.connections[peerid],
2764+                                                self._storage_index,
2765+                                                secrets,
2766+                                                self._new_seqnum,
2767+                                                self.required_shares,
2768+                                                self.total_shares,
2769+                                                self.segment_size,
2770+                                                self.datalength)
2771+            self.writers[shnum].peerid = peerid
2772+            assert (peerid, shnum) in self._servermap.servermap
2773+            old_versionid, old_timestamp = self._servermap.servermap[key]
2774+            (old_seqnum, old_root_hash, old_salt, old_segsize,
2775+             old_datalength, old_k, old_N, old_prefix,
2776+             old_offsets_tuple) = old_versionid
2777+            self.writers[shnum].set_checkstring(old_seqnum,
2778+                                                old_root_hash,
2779+                                                old_salt)
2780+
2781+        # Our remote shares will not have a complete checkstring until
2782+        # after we are done writing share data and have started to write
2783+        # blocks. In the meantime, we need to know what to look for when
2784+        # writing, so that we can detect UncoordinatedWriteErrors.
2785+        self._checkstring = self.writers.values()[0].get_checkstring()
2786+
2787+        # Now, we start pushing shares.
2788+        self._status.timings["setup"] = time.time() - self._started
2789+        # First, we encrypt, encode, and publish the shares that we need
2790+        # to encrypt, encode, and publish.
2791+
2792+        # Our update process fetched these for us. We need to update
2793+        # them in place as publishing happens.
2794+        self.blockhashes = {} # (shnum, [blochashes])
2795+        for (i, bht) in blockhashes.iteritems():
2796+            # We need to extract the leaves from our old hash tree.
2797+            old_segcount = mathutil.div_ceil(version[4],
2798+                                             version[3])
2799+            h = hashtree.IncompleteHashTree(old_segcount)
2800+            bht = dict(enumerate(bht))
2801+            h.set_hashes(bht)
2802+            leaves = h[h.get_leaf_index(0):]
2803+            for j in xrange(self.num_segments - len(leaves)):
2804+                leaves.append(None)
2805+
2806+            assert len(leaves) >= self.num_segments
2807+            self.blockhashes[i] = leaves
2808+            # This list will now be the leaves that were set during the
2809+            # initial upload + enough empty hashes to make it a
2810+            # power-of-two. If we exceed a power of two boundary, we
2811+            # should be encoding the file over again, and should not be
2812+            # here. So, we have
2813+            #assert len(self.blockhashes[i]) == \
2814+            #    hashtree.roundup_pow2(self.num_segments), \
2815+            #        len(self.blockhashes[i])
2816+            # XXX: Except this doesn't work. Figure out why.
2817+
2818+        # These are filled in later, after we've modified the block hash
2819+        # tree suitably.
2820+        self.sharehash_leaves = None # eventually [sharehashes]
2821+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
2822+                              # validate the share]
2823+
2824+        self.log("Starting push")
2825+
2826+        self._state = PUSHING_BLOCKS_STATE
2827+        self._push()
2828+
2829+        return self.done_deferred
2830+
2831+
2832     def publish(self, newdata):
2833         """Publish the filenode's current contents.  Returns a Deferred that
2834         fires (with None) when the publish has done as much work as it's ever
2835hunk ./src/allmydata/mutable/publish.py 344
2836         simultaneous write.
2837         """
2838 
2839-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
2840-        # 2: perform peer selection, get candidate servers
2841-        #  2a: send queries to n+epsilon servers, to determine current shares
2842-        #  2b: based upon responses, create target map
2843-        # 3: send slot_testv_and_readv_and_writev messages
2844-        # 4: as responses return, update share-dispatch table
2845-        # 4a: may need to run recovery algorithm
2846-        # 5: when enough responses are back, we're done
2847+        # 0. Setup encoding parameters, encoder, and other such things.
2848+        # 1. Encrypt, encode, and publish segments.
2849+        assert IMutableUploadable.providedBy(newdata)
2850 
2851hunk ./src/allmydata/mutable/publish.py 348
2852-        self.log("starting publish, datalen is %s" % len(newdata))
2853-        self._status.set_size(len(newdata))
2854+        self.data = newdata
2855+        self.datalength = newdata.get_size()
2856+        #if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
2857+        #    self._version = MDMF_VERSION
2858+        #else:
2859+        #    self._version = SDMF_VERSION
2860+
2861+        self.log("starting publish, datalen is %s" % self.datalength)
2862+        self._status.set_size(self.datalength)
2863         self._status.set_status("Started")
2864         self._started = time.time()
2865 
2866hunk ./src/allmydata/mutable/publish.py 405
2867         self.full_peerlist = full_peerlist # for use later, immutable
2868         self.bad_peers = set() # peerids who have errbacked/refused requests
2869 
2870-        self.newdata = newdata
2871-        self.salt = os.urandom(16)
2872-
2873+        # This will set self.segment_size, self.num_segments, and
2874+        # self.fec.
2875         self.setup_encoding_parameters()
2876 
2877         # if we experience any surprises (writes which were rejected because
2878hunk ./src/allmydata/mutable/publish.py 415
2879         # end of the publish process.
2880         self.surprised = False
2881 
2882-        # as a failsafe, refuse to iterate through self.loop more than a
2883-        # thousand times.
2884-        self.looplimit = 1000
2885-
2886         # we keep track of three tables. The first is our goal: which share
2887         # we want to see on which servers. This is initially populated by the
2888         # existing servermap.
2889hunk ./src/allmydata/mutable/publish.py 438
2890 
2891         self.bad_share_checkstrings = {}
2892 
2893+        # This is set at the last step of the publishing process.
2894+        self.versioninfo = ""
2895+
2896         # we use the servermap to populate the initial goal: this way we will
2897         # try to update each existing share in place.
2898         for (peerid, shnum) in self._servermap.servermap:
2899hunk ./src/allmydata/mutable/publish.py 454
2900             self.bad_share_checkstrings[key] = old_checkstring
2901             self.connections[peerid] = self._servermap.connections[peerid]
2902 
2903-        # create the shares. We'll discard these as they are delivered. SDMF:
2904-        # we're allowed to hold everything in memory.
2905+        # TODO: Make this part do peer selection.
2906+        self.update_goal()
2907+        self.writers = {}
2908+        if self._version == MDMF_VERSION:
2909+            writer_class = MDMFSlotWriteProxy
2910+        else:
2911+            writer_class = SDMFSlotWriteProxy
2912 
2913hunk ./src/allmydata/mutable/publish.py 462
2914+        # For each (peerid, shnum) in self.goal, we make a
2915+        # write proxy for that peer. We'll use this to write
2916+        # shares to the peer.
2917+        for key in self.goal:
2918+            peerid, shnum = key
2919+            write_enabler = self._node.get_write_enabler(peerid)
2920+            renew_secret = self._node.get_renewal_secret(peerid)
2921+            cancel_secret = self._node.get_cancel_secret(peerid)
2922+            secrets = (write_enabler, renew_secret, cancel_secret)
2923+
2924+            self.writers[shnum] =  writer_class(shnum,
2925+                                                self.connections[peerid],
2926+                                                self._storage_index,
2927+                                                secrets,
2928+                                                self._new_seqnum,
2929+                                                self.required_shares,
2930+                                                self.total_shares,
2931+                                                self.segment_size,
2932+                                                self.datalength)
2933+            self.writers[shnum].peerid = peerid
2934+            if (peerid, shnum) in self._servermap.servermap:
2935+                old_versionid, old_timestamp = self._servermap.servermap[key]
2936+                (old_seqnum, old_root_hash, old_salt, old_segsize,
2937+                 old_datalength, old_k, old_N, old_prefix,
2938+                 old_offsets_tuple) = old_versionid
2939+                self.writers[shnum].set_checkstring(old_seqnum,
2940+                                                    old_root_hash,
2941+                                                    old_salt)
2942+            elif (peerid, shnum) in self.bad_share_checkstrings:
2943+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
2944+                self.writers[shnum].set_checkstring(old_checkstring)
2945+
2946+        # Our remote shares will not have a complete checkstring until
2947+        # after we are done writing share data and have started to write
2948+        # blocks. In the meantime, we need to know what to look for when
2949+        # writing, so that we can detect UncoordinatedWriteErrors.
2950+        self._checkstring = self.writers.values()[0].get_checkstring()
2951+
2952+        # Now, we start pushing shares.
2953         self._status.timings["setup"] = time.time() - self._started
2954hunk ./src/allmydata/mutable/publish.py 502
2955-        d = self._encrypt_and_encode()
2956-        d.addCallback(self._generate_shares)
2957-        def _start_pushing(res):
2958-            self._started_pushing = time.time()
2959-            return res
2960-        d.addCallback(_start_pushing)
2961-        d.addCallback(self.loop) # trigger delivery
2962-        d.addErrback(self._fatal_error)
2963+        # First, we encrypt, encode, and publish the shares that we need
2964+        # to encrypt, encode, and publish.
2965+
2966+        # This will eventually hold the block hash chain for each share
2967+        # that we publish. We define it this way so that empty publishes
2968+        # will still have something to write to the remote slot.
2969+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
2970+        for i in xrange(self.total_shares):
2971+            blocks = self.blockhashes[i]
2972+            for j in xrange(self.num_segments):
2973+                blocks.append(None)
2974+        self.sharehash_leaves = None # eventually [sharehashes]
2975+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
2976+                              # validate the share]
2977+
2978+        self.log("Starting push")
2979+
2980+        self._state = PUSHING_BLOCKS_STATE
2981+        self._push()
2982 
2983         return self.done_deferred
2984 
2985hunk ./src/allmydata/mutable/publish.py 524
2986-    def setup_encoding_parameters(self):
2987-        segment_size = len(self.newdata)
2988+
2989+    def _update_status(self):
2990+        self._status.set_status("Sending Shares: %d placed out of %d, "
2991+                                "%d messages outstanding" %
2992+                                (len(self.placed),
2993+                                 len(self.goal),
2994+                                 len(self.outstanding)))
2995+        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
2996+
2997+
2998+    def setup_encoding_parameters(self, offset=0):
2999+        if self._version == MDMF_VERSION:
3000+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
3001+        else:
3002+            segment_size = self.datalength # SDMF is only one segment
3003         # this must be a multiple of self.required_shares
3004         segment_size = mathutil.next_multiple(segment_size,
3005                                               self.required_shares)
3006hunk ./src/allmydata/mutable/publish.py 543
3007         self.segment_size = segment_size
3008+
3009+        # Calculate the starting segment for the upload.
3010         if segment_size:
3011hunk ./src/allmydata/mutable/publish.py 546
3012-            self.num_segments = mathutil.div_ceil(len(self.newdata),
3013+            self.num_segments = mathutil.div_ceil(self.datalength,
3014                                                   segment_size)
3015hunk ./src/allmydata/mutable/publish.py 548
3016+            self.starting_segment = mathutil.div_ceil(offset,
3017+                                                      segment_size)
3018+            self.starting_segment -= 1
3019+            if offset == 0:
3020+                self.starting_segment = 0
3021+
3022         else:
3023             self.num_segments = 0
3024hunk ./src/allmydata/mutable/publish.py 556
3025-        assert self.num_segments in [0, 1,] # SDMF restrictions
3026+            self.starting_segment = 0
3027+
3028+
3029+        self.log("building encoding parameters for file")
3030+        self.log("got segsize %d" % self.segment_size)
3031+        self.log("got %d segments" % self.num_segments)
3032+
3033+        if self._version == SDMF_VERSION:
3034+            assert self.num_segments in (0, 1) # SDMF
3035+        # calculate the tail segment size.
3036+
3037+        if segment_size and self.datalength:
3038+            self.tail_segment_size = self.datalength % segment_size
3039+            self.log("got tail segment size %d" % self.tail_segment_size)
3040+        else:
3041+            self.tail_segment_size = 0
3042+
3043+        if self.tail_segment_size == 0 and segment_size:
3044+            # The tail segment is the same size as the other segments.
3045+            self.tail_segment_size = segment_size
3046+
3047+        # Make FEC encoders
3048+        fec = codec.CRSEncoder()
3049+        fec.set_params(self.segment_size,
3050+                       self.required_shares, self.total_shares)
3051+        self.piece_size = fec.get_block_size()
3052+        self.fec = fec
3053+
3054+        if self.tail_segment_size == self.segment_size:
3055+            self.tail_fec = self.fec
3056+        else:
3057+            tail_fec = codec.CRSEncoder()
3058+            tail_fec.set_params(self.tail_segment_size,
3059+                                self.required_shares,
3060+                                self.total_shares)
3061+            self.tail_fec = tail_fec
3062+
3063+        self._current_segment = self.starting_segment
3064+        self.end_segment = self.num_segments - 1
3065+        # Now figure out where the last segment should be.
3066+        if self.data.get_size() != self.datalength:
3067+            end = self.data.get_size()
3068+            self.end_segment = mathutil.div_ceil(end,
3069+                                                 segment_size)
3070+            self.end_segment -= 1
3071+        self.log("got start segment %d" % self.starting_segment)
3072+        self.log("got end segment %d" % self.end_segment)
3073+
3074+
3075+    def _push(self, ignored=None):
3076+        """
3077+        I manage state transitions. In particular, I see that we still
3078+        have a good enough number of writers to complete the upload
3079+        successfully.
3080+        """
3081+        # Can we still successfully publish this file?
3082+        # TODO: Keep track of outstanding queries before aborting the
3083+        #       process.
3084+        if len(self.writers) <= self.required_shares or self.surprised:
3085+            return self._failure()
3086+
3087+        # Figure out what we need to do next. Each of these needs to
3088+        # return a deferred so that we don't block execution when this
3089+        # is first called in the upload method.
3090+        if self._state == PUSHING_BLOCKS_STATE:
3091+            return self.push_segment(self._current_segment)
3092+
3093+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
3094+            return self.push_everything_else()
3095+
3096+        # If we make it to this point, we were successful in placing the
3097+        # file.
3098+        return self._done(None)
3099+
3100+
3101+    def push_segment(self, segnum):
3102+        if self.num_segments == 0 and self._version == SDMF_VERSION:
3103+            self._add_dummy_salts()
3104 
3105hunk ./src/allmydata/mutable/publish.py 635
3106-    def _fatal_error(self, f):
3107-        self.log("error during loop", failure=f, level=log.UNUSUAL)
3108-        self._done(f)
3109+        if segnum > self.end_segment:
3110+            # We don't have any more segments to push.
3111+            self._state = PUSHING_EVERYTHING_ELSE_STATE
3112+            return self._push()
3113+
3114+        d = self._encode_segment(segnum)
3115+        d.addCallback(self._push_segment, segnum)
3116+        def _increment_segnum(ign):
3117+            self._current_segment += 1
3118+        # XXX: I don't think we need to do addBoth here -- any errBacks
3119+        # should be handled within push_segment.
3120+        d.addBoth(_increment_segnum)
3121+        d.addBoth(self._turn_barrier)
3122+        d.addBoth(self._push)
3123+
3124+
3125+    def _turn_barrier(self, result):
3126+        """
3127+        I help the publish process avoid the recursion limit issues
3128+        described in #237.
3129+        """
3130+        return fireEventually(result)
3131+
3132+
3133+    def _add_dummy_salts(self):
3134+        """
3135+        SDMF files need a salt even if they're empty, or the signature
3136+        won't make sense. This method adds a dummy salt to each of our
3137+        SDMF writers so that they can write the signature later.
3138+        """
3139+        salt = os.urandom(16)
3140+        assert self._version == SDMF_VERSION
3141+
3142+        for writer in self.writers.itervalues():
3143+            writer.put_salt(salt)
3144+
3145+
3146+    def _encode_segment(self, segnum):
3147+        """
3148+        I encrypt and encode the segment segnum.
3149+        """
3150+        started = time.time()
3151+
3152+        if segnum + 1 == self.num_segments:
3153+            segsize = self.tail_segment_size
3154+        else:
3155+            segsize = self.segment_size
3156+
3157+
3158+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
3159+        data = self.data.read(segsize)
3160+        # XXX: This is dumb. Why return a list?
3161+        data = "".join(data)
3162+
3163+        assert len(data) == segsize, len(data)
3164+
3165+        salt = os.urandom(16)
3166+
3167+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
3168+        self._status.set_status("Encrypting")
3169+        enc = AES(key)
3170+        crypttext = enc.process(data)
3171+        assert len(crypttext) == len(data)
3172+
3173+        now = time.time()
3174+        self._status.timings["encrypt"] = now - started
3175+        started = now
3176+
3177+        # now apply FEC
3178+        if segnum + 1 == self.num_segments:
3179+            fec = self.tail_fec
3180+        else:
3181+            fec = self.fec
3182+
3183+        self._status.set_status("Encoding")
3184+        crypttext_pieces = [None] * self.required_shares
3185+        piece_size = fec.get_block_size()
3186+        for i in range(len(crypttext_pieces)):
3187+            offset = i * piece_size
3188+            piece = crypttext[offset:offset+piece_size]
3189+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
3190+            crypttext_pieces[i] = piece
3191+            assert len(piece) == piece_size
3192+        d = fec.encode(crypttext_pieces)
3193+        def _done_encoding(res):
3194+            elapsed = time.time() - started
3195+            self._status.timings["encode"] = elapsed
3196+            return (res, salt)
3197+        d.addCallback(_done_encoding)
3198+        return d
3199+
3200+
3201+    def _push_segment(self, encoded_and_salt, segnum):
3202+        """
3203+        I push (data, salt) as segment number segnum.
3204+        """
3205+        results, salt = encoded_and_salt
3206+        shares, shareids = results
3207+        self._status.set_status("Pushing segment")
3208+        for i in xrange(len(shares)):
3209+            sharedata = shares[i]
3210+            shareid = shareids[i]
3211+            if self._version == MDMF_VERSION:
3212+                hashed = salt + sharedata
3213+            else:
3214+                hashed = sharedata
3215+            block_hash = hashutil.block_hash(hashed)
3216+            self.blockhashes[shareid][segnum] = block_hash
3217+            # find the writer for this share
3218+            writer = self.writers[shareid]
3219+            writer.put_block(sharedata, segnum, salt)
3220+
3221+
3222+    def push_everything_else(self):
3223+        """
3224+        I put everything else associated with a share.
3225+        """
3226+        self._pack_started = time.time()
3227+        self.push_encprivkey()
3228+        self.push_blockhashes()
3229+        self.push_sharehashes()
3230+        self.push_toplevel_hashes_and_signature()
3231+        d = self.finish_publishing()
3232+        def _change_state(ignored):
3233+            self._state = DONE_STATE
3234+        d.addCallback(_change_state)
3235+        d.addCallback(self._push)
3236+        return d
3237+
3238+
3239+    def push_encprivkey(self):
3240+        encprivkey = self._encprivkey
3241+        self._status.set_status("Pushing encrypted private key")
3242+        for writer in self.writers.itervalues():
3243+            writer.put_encprivkey(encprivkey)
3244+
3245+
3246+    def push_blockhashes(self):
3247+        self.sharehash_leaves = [None] * len(self.blockhashes)
3248+        self._status.set_status("Building and pushing block hash tree")
3249+        for shnum, blockhashes in self.blockhashes.iteritems():
3250+            t = hashtree.HashTree(blockhashes)
3251+            self.blockhashes[shnum] = list(t)
3252+            # set the leaf for future use.
3253+            self.sharehash_leaves[shnum] = t[0]
3254+
3255+            writer = self.writers[shnum]
3256+            writer.put_blockhashes(self.blockhashes[shnum])
3257+
3258+
3259+    def push_sharehashes(self):
3260+        self._status.set_status("Building and pushing share hash chain")
3261+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
3262+        for shnum in xrange(len(self.sharehash_leaves)):
3263+            needed_indices = share_hash_tree.needed_hashes(shnum)
3264+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
3265+                                             for i in needed_indices] )
3266+            writer = self.writers[shnum]
3267+            writer.put_sharehashes(self.sharehashes[shnum])
3268+        self.root_hash = share_hash_tree[0]
3269+
3270+
3271+    def push_toplevel_hashes_and_signature(self):
3272+        # We need to to three things here:
3273+        #   - Push the root hash and salt hash
3274+        #   - Get the checkstring of the resulting layout; sign that.
3275+        #   - Push the signature
3276+        self._status.set_status("Pushing root hashes and signature")
3277+        for shnum in xrange(self.total_shares):
3278+            writer = self.writers[shnum]
3279+            writer.put_root_hash(self.root_hash)
3280+        self._update_checkstring()
3281+        self._make_and_place_signature()
3282+
3283+
3284+    def _update_checkstring(self):
3285+        """
3286+        After putting the root hash, MDMF files will have the
3287+        checkstring written to the storage server. This means that we
3288+        can update our copy of the checkstring so we can detect
3289+        uncoordinated writes. SDMF files will have the same checkstring,
3290+        so we need not do anything.
3291+        """
3292+        self._checkstring = self.writers.values()[0].get_checkstring()
3293+
3294+
3295+    def _make_and_place_signature(self):
3296+        """
3297+        I create and place the signature.
3298+        """
3299+        started = time.time()
3300+        self._status.set_status("Signing prefix")
3301+        signable = self.writers[0].get_signable()
3302+        self.signature = self._privkey.sign(signable)
3303+
3304+        for (shnum, writer) in self.writers.iteritems():
3305+            writer.put_signature(self.signature)
3306+        self._status.timings['sign'] = time.time() - started
3307+
3308+
3309+    def finish_publishing(self):
3310+        # We're almost done -- we just need to put the verification key
3311+        # and the offsets
3312+        started = time.time()
3313+        self._status.set_status("Pushing shares")
3314+        self._started_pushing = started
3315+        ds = []
3316+        verification_key = self._pubkey.serialize()
3317+
3318+
3319+        # TODO: Bad, since we remove from this same dict. We need to
3320+        # make a copy, or just use a non-iterated value.
3321+        for (shnum, writer) in self.writers.iteritems():
3322+            writer.put_verification_key(verification_key)
3323+            d = writer.finish_publishing()
3324+            # Add the (peerid, shnum) tuple to our list of outstanding
3325+            # queries. This gets used by _loop if some of our queries
3326+            # fail to place shares.
3327+            self.outstanding.add((writer.peerid, writer.shnum))
3328+            d.addCallback(self._got_write_answer, writer, started)
3329+            d.addErrback(self._connection_problem, writer)
3330+            ds.append(d)
3331+        self._record_verinfo()
3332+        self._status.timings['pack'] = time.time() - started
3333+        return defer.DeferredList(ds)
3334+
3335+
3336+    def _record_verinfo(self):
3337+        self.versioninfo = self.writers.values()[0].get_verinfo()
3338+
3339+
3340+    def _connection_problem(self, f, writer):
3341+        """
3342+        We ran into a connection problem while working with writer, and
3343+        need to deal with that.
3344+        """
3345+        self.log("found problem: %s" % str(f))
3346+        self._last_failure = f
3347+        del(self.writers[writer.shnum])
3348 
3349hunk ./src/allmydata/mutable/publish.py 875
3350-    def _update_status(self):
3351-        self._status.set_status("Sending Shares: %d placed out of %d, "
3352-                                "%d messages outstanding" %
3353-                                (len(self.placed),
3354-                                 len(self.goal),
3355-                                 len(self.outstanding)))
3356-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
3357 
3358hunk ./src/allmydata/mutable/publish.py 876
3359-    def loop(self, ignored=None):
3360-        self.log("entering loop", level=log.NOISY)
3361-        if not self._running:
3362-            return
3363-
3364-        self.looplimit -= 1
3365-        if self.looplimit <= 0:
3366-            raise LoopLimitExceededError("loop limit exceeded")
3367-
3368-        if self.surprised:
3369-            # don't send out any new shares, just wait for the outstanding
3370-            # ones to be retired.
3371-            self.log("currently surprised, so don't send any new shares",
3372-                     level=log.NOISY)
3373-        else:
3374-            self.update_goal()
3375-            # how far are we from our goal?
3376-            needed = self.goal - self.placed - self.outstanding
3377-            self._update_status()
3378-
3379-            if needed:
3380-                # we need to send out new shares
3381-                self.log(format="need to send %(needed)d new shares",
3382-                         needed=len(needed), level=log.NOISY)
3383-                self._send_shares(needed)
3384-                return
3385-
3386-        if self.outstanding:
3387-            # queries are still pending, keep waiting
3388-            self.log(format="%(outstanding)d queries still outstanding",
3389-                     outstanding=len(self.outstanding),
3390-                     level=log.NOISY)
3391-            return
3392-
3393-        # no queries outstanding, no placements needed: we're done
3394-        self.log("no queries outstanding, no placements needed: done",
3395-                 level=log.OPERATIONAL)
3396-        now = time.time()
3397-        elapsed = now - self._started_pushing
3398-        self._status.timings["push"] = elapsed
3399-        return self._done(None)
3400-
3401     def log_goal(self, goal, message=""):
3402         logmsg = [message]
3403         for (shnum, peerid) in sorted([(s,p) for (p,s) in goal]):
3404hunk ./src/allmydata/mutable/publish.py 957
3405             self.log_goal(self.goal, "after update: ")
3406 
3407 
3408+    def _got_write_answer(self, answer, writer, started):
3409+        if not answer:
3410+            # SDMF writers only pretend to write when readers set their
3411+            # blocks, salts, and so on -- they actually just write once,
3412+            # at the end of the upload process. In fake writes, they
3413+            # return defer.succeed(None). If we see that, we shouldn't
3414+            # bother checking it.
3415+            return
3416 
3417hunk ./src/allmydata/mutable/publish.py 966
3418-    def _encrypt_and_encode(self):
3419-        # this returns a Deferred that fires with a list of (sharedata,
3420-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
3421-        # shares that we care about.
3422-        self.log("_encrypt_and_encode")
3423-
3424-        self._status.set_status("Encrypting")
3425-        started = time.time()
3426-
3427-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
3428-        enc = AES(key)
3429-        crypttext = enc.process(self.newdata)
3430-        assert len(crypttext) == len(self.newdata)
3431+        peerid = writer.peerid
3432+        lp = self.log("_got_write_answer from %s, share %d" %
3433+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
3434 
3435         now = time.time()
3436hunk ./src/allmydata/mutable/publish.py 971
3437-        self._status.timings["encrypt"] = now - started
3438-        started = now
3439-
3440-        # now apply FEC
3441-
3442-        self._status.set_status("Encoding")
3443-        fec = codec.CRSEncoder()
3444-        fec.set_params(self.segment_size,
3445-                       self.required_shares, self.total_shares)
3446-        piece_size = fec.get_block_size()
3447-        crypttext_pieces = [None] * self.required_shares
3448-        for i in range(len(crypttext_pieces)):
3449-            offset = i * piece_size
3450-            piece = crypttext[offset:offset+piece_size]
3451-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
3452-            crypttext_pieces[i] = piece
3453-            assert len(piece) == piece_size
3454-
3455-        d = fec.encode(crypttext_pieces)
3456-        def _done_encoding(res):
3457-            elapsed = time.time() - started
3458-            self._status.timings["encode"] = elapsed
3459-            return res
3460-        d.addCallback(_done_encoding)
3461-        return d
3462-
3463-    def _generate_shares(self, shares_and_shareids):
3464-        # this sets self.shares and self.root_hash
3465-        self.log("_generate_shares")
3466-        self._status.set_status("Generating Shares")
3467-        started = time.time()
3468-
3469-        # we should know these by now
3470-        privkey = self._privkey
3471-        encprivkey = self._encprivkey
3472-        pubkey = self._pubkey
3473-
3474-        (shares, share_ids) = shares_and_shareids
3475-
3476-        assert len(shares) == len(share_ids)
3477-        assert len(shares) == self.total_shares
3478-        all_shares = {}
3479-        block_hash_trees = {}
3480-        share_hash_leaves = [None] * len(shares)
3481-        for i in range(len(shares)):
3482-            share_data = shares[i]
3483-            shnum = share_ids[i]
3484-            all_shares[shnum] = share_data
3485-
3486-            # build the block hash tree. SDMF has only one leaf.
3487-            leaves = [hashutil.block_hash(share_data)]
3488-            t = hashtree.HashTree(leaves)
3489-            block_hash_trees[shnum] = list(t)
3490-            share_hash_leaves[shnum] = t[0]
3491-        for leaf in share_hash_leaves:
3492-            assert leaf is not None
3493-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
3494-        share_hash_chain = {}
3495-        for shnum in range(self.total_shares):
3496-            needed_hashes = share_hash_tree.needed_hashes(shnum)
3497-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
3498-                                              for i in needed_hashes ] )
3499-        root_hash = share_hash_tree[0]
3500-        assert len(root_hash) == 32
3501-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
3502-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
3503-
3504-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
3505-                             self.required_shares, self.total_shares,
3506-                             self.segment_size, len(self.newdata))
3507-
3508-        # now pack the beginning of the share. All shares are the same up
3509-        # to the signature, then they have divergent share hash chains,
3510-        # then completely different block hash trees + salt + share data,
3511-        # then they all share the same encprivkey at the end. The sizes
3512-        # of everything are the same for all shares.
3513-
3514-        sign_started = time.time()
3515-        signature = privkey.sign(prefix)
3516-        self._status.timings["sign"] = time.time() - sign_started
3517-
3518-        verification_key = pubkey.serialize()
3519-
3520-        final_shares = {}
3521-        for shnum in range(self.total_shares):
3522-            final_share = pack_share(prefix,
3523-                                     verification_key,
3524-                                     signature,
3525-                                     share_hash_chain[shnum],
3526-                                     block_hash_trees[shnum],
3527-                                     all_shares[shnum],
3528-                                     encprivkey)
3529-            final_shares[shnum] = final_share
3530-        elapsed = time.time() - started
3531-        self._status.timings["pack"] = elapsed
3532-        self.shares = final_shares
3533-        self.root_hash = root_hash
3534-
3535-        # we also need to build up the version identifier for what we're
3536-        # pushing. Extract the offsets from one of our shares.
3537-        assert final_shares
3538-        offsets = unpack_header(final_shares.values()[0])[-1]
3539-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
3540-        verinfo = (self._new_seqnum, root_hash, self.salt,
3541-                   self.segment_size, len(self.newdata),
3542-                   self.required_shares, self.total_shares,
3543-                   prefix, offsets_tuple)
3544-        self.versioninfo = verinfo
3545-
3546-
3547-
3548-    def _send_shares(self, needed):
3549-        self.log("_send_shares")
3550-
3551-        # we're finally ready to send out our shares. If we encounter any
3552-        # surprises here, it's because somebody else is writing at the same
3553-        # time. (Note: in the future, when we remove the _query_peers() step
3554-        # and instead speculate about [or remember] which shares are where,
3555-        # surprises here are *not* indications of UncoordinatedWriteError,
3556-        # and we'll need to respond to them more gracefully.)
3557-
3558-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
3559-        # organize it by peerid.
3560-
3561-        peermap = DictOfSets()
3562-        for (peerid, shnum) in needed:
3563-            peermap.add(peerid, shnum)
3564-
3565-        # the next thing is to build up a bunch of test vectors. The
3566-        # semantics of Publish are that we perform the operation if the world
3567-        # hasn't changed since the ServerMap was constructed (more or less).
3568-        # For every share we're trying to place, we create a test vector that
3569-        # tests to see if the server*share still corresponds to the
3570-        # map.
3571-
3572-        all_tw_vectors = {} # maps peerid to tw_vectors
3573-        sm = self._servermap.servermap
3574-
3575-        for key in needed:
3576-            (peerid, shnum) = key
3577-
3578-            if key in sm:
3579-                # an old version of that share already exists on the
3580-                # server, according to our servermap. We will create a
3581-                # request that attempts to replace it.
3582-                old_versionid, old_timestamp = sm[key]
3583-                (old_seqnum, old_root_hash, old_salt, old_segsize,
3584-                 old_datalength, old_k, old_N, old_prefix,
3585-                 old_offsets_tuple) = old_versionid
3586-                old_checkstring = pack_checkstring(old_seqnum,
3587-                                                   old_root_hash,
3588-                                                   old_salt)
3589-                testv = (0, len(old_checkstring), "eq", old_checkstring)
3590-
3591-            elif key in self.bad_share_checkstrings:
3592-                old_checkstring = self.bad_share_checkstrings[key]
3593-                testv = (0, len(old_checkstring), "eq", old_checkstring)
3594-
3595-            else:
3596-                # add a testv that requires the share not exist
3597-
3598-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
3599-                # constraints are handled. If the same object is referenced
3600-                # multiple times inside the arguments, foolscap emits a
3601-                # 'reference' token instead of a distinct copy of the
3602-                # argument. The bug is that these 'reference' tokens are not
3603-                # accepted by the inbound constraint code. To work around
3604-                # this, we need to prevent python from interning the
3605-                # (constant) tuple, by creating a new copy of this vector
3606-                # each time.
3607-
3608-                # This bug is fixed in foolscap-0.2.6, and even though this
3609-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
3610-                # supposed to be able to interoperate with older versions of
3611-                # Tahoe which are allowed to use older versions of foolscap,
3612-                # including foolscap-0.2.5 . In addition, I've seen other
3613-                # foolscap problems triggered by 'reference' tokens (see #541
3614-                # for details). So we must keep this workaround in place.
3615-
3616-                #testv = (0, 1, 'eq', "")
3617-                testv = tuple([0, 1, 'eq', ""])
3618-
3619-            testvs = [testv]
3620-            # the write vector is simply the share
3621-            writev = [(0, self.shares[shnum])]
3622-
3623-            if peerid not in all_tw_vectors:
3624-                all_tw_vectors[peerid] = {}
3625-                # maps shnum to (testvs, writevs, new_length)
3626-            assert shnum not in all_tw_vectors[peerid]
3627-
3628-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
3629-
3630-        # we read the checkstring back from each share, however we only use
3631-        # it to detect whether there was a new share that we didn't know
3632-        # about. The success or failure of the write will tell us whether
3633-        # there was a collision or not. If there is a collision, the first
3634-        # thing we'll do is update the servermap, which will find out what
3635-        # happened. We could conceivably reduce a roundtrip by using the
3636-        # readv checkstring to populate the servermap, but really we'd have
3637-        # to read enough data to validate the signatures too, so it wouldn't
3638-        # be an overall win.
3639-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
3640-
3641-        # ok, send the messages!
3642-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
3643-        started = time.time()
3644-        for (peerid, tw_vectors) in all_tw_vectors.items():
3645-
3646-            write_enabler = self._node.get_write_enabler(peerid)
3647-            renew_secret = self._node.get_renewal_secret(peerid)
3648-            cancel_secret = self._node.get_cancel_secret(peerid)
3649-            secrets = (write_enabler, renew_secret, cancel_secret)
3650-            shnums = tw_vectors.keys()
3651-
3652-            for shnum in shnums:
3653-                self.outstanding.add( (peerid, shnum) )
3654+        elapsed = now - started
3655 
3656hunk ./src/allmydata/mutable/publish.py 973
3657-            d = self._do_testreadwrite(peerid, secrets,
3658-                                       tw_vectors, read_vector)
3659-            d.addCallbacks(self._got_write_answer, self._got_write_error,
3660-                           callbackArgs=(peerid, shnums, started),
3661-                           errbackArgs=(peerid, shnums, started))
3662-            # tolerate immediate errback, like with DeadReferenceError
3663-            d.addBoth(fireEventually)
3664-            d.addCallback(self.loop)
3665-            d.addErrback(self._fatal_error)
3666+        self._status.add_per_server_time(peerid, elapsed)
3667 
3668hunk ./src/allmydata/mutable/publish.py 975
3669-        self._update_status()
3670-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
3671+        wrote, read_data = answer
3672 
3673hunk ./src/allmydata/mutable/publish.py 977
3674-    def _do_testreadwrite(self, peerid, secrets,
3675-                          tw_vectors, read_vector):
3676-        storage_index = self._storage_index
3677-        ss = self.connections[peerid]
3678+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
3679 
3680hunk ./src/allmydata/mutable/publish.py 979
3681-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
3682-        d = ss.callRemote("slot_testv_and_readv_and_writev",
3683-                          storage_index,
3684-                          secrets,
3685-                          tw_vectors,
3686-                          read_vector)
3687-        return d
3688+        # We need to remove from surprise_shares any shares that we are
3689+        # knowingly also writing to that peer from other writers.
3690 
3691hunk ./src/allmydata/mutable/publish.py 982
3692-    def _got_write_answer(self, answer, peerid, shnums, started):
3693-        lp = self.log("_got_write_answer from %s" %
3694-                      idlib.shortnodeid_b2a(peerid))
3695-        for shnum in shnums:
3696-            self.outstanding.discard( (peerid, shnum) )
3697+        # TODO: Precompute this.
3698+        known_shnums = [x.shnum for x in self.writers.values()
3699+                        if x.peerid == peerid]
3700+        surprise_shares -= set(known_shnums)
3701+        self.log("found the following surprise shares: %s" %
3702+                 str(surprise_shares))
3703 
3704hunk ./src/allmydata/mutable/publish.py 989
3705-        now = time.time()
3706-        elapsed = now - started
3707-        self._status.add_per_server_time(peerid, elapsed)
3708-
3709-        wrote, read_data = answer
3710-
3711-        surprise_shares = set(read_data.keys()) - set(shnums)
3712+        # Now surprise shares contains all of the shares that we did not
3713+        # expect to be there.
3714 
3715         surprised = False
3716         for shnum in surprise_shares:
3717hunk ./src/allmydata/mutable/publish.py 996
3718             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
3719             checkstring = read_data[shnum][0]
3720-            their_version_info = unpack_checkstring(checkstring)
3721-            if their_version_info == self._new_version_info:
3722+            # What we want to do here is to see if their (seqnum,
3723+            # roothash, salt) is the same as our (seqnum, roothash,
3724+            # salt), or the equivalent for MDMF. The best way to do this
3725+            # is to store a packed representation of our checkstring
3726+            # somewhere, then not bother unpacking the other
3727+            # checkstring.
3728+            if checkstring == self._checkstring:
3729                 # they have the right share, somehow
3730 
3731                 if (peerid,shnum) in self.goal:
3732hunk ./src/allmydata/mutable/publish.py 1081
3733             self.log("our testv failed, so the write did not happen",
3734                      parent=lp, level=log.WEIRD, umid="8sc26g")
3735             self.surprised = True
3736-            self.bad_peers.add(peerid) # don't ask them again
3737+            self.bad_peers.add(writer) # don't ask them again
3738             # use the checkstring to add information to the log message
3739             for (shnum,readv) in read_data.items():
3740                 checkstring = readv[0]
3741hunk ./src/allmydata/mutable/publish.py 1103
3742                 # if expected_version==None, then we didn't expect to see a
3743                 # share on that peer, and the 'surprise_shares' clause above
3744                 # will have logged it.
3745-            # self.loop() will take care of finding new homes
3746             return
3747 
3748hunk ./src/allmydata/mutable/publish.py 1105
3749-        for shnum in shnums:
3750-            self.placed.add( (peerid, shnum) )
3751-            # and update the servermap
3752-            self._servermap.add_new_share(peerid, shnum,
3753+        # and update the servermap
3754+        # self.versioninfo is set during the last phase of publishing.
3755+        # If we get there, we know that responses correspond to placed
3756+        # shares, and can safely execute these statements.
3757+        if self.versioninfo:
3758+            self.log("wrote successfully: adding new share to servermap")
3759+            self._servermap.add_new_share(peerid, writer.shnum,
3760                                           self.versioninfo, started)
3761hunk ./src/allmydata/mutable/publish.py 1113
3762-
3763-        # self.loop() will take care of checking to see if we're done
3764+            self.placed.add( (peerid, writer.shnum) )
3765+        self._update_status()
3766+        # the next method in the deferred chain will check to see if
3767+        # we're done and successful.
3768         return
3769 
3770hunk ./src/allmydata/mutable/publish.py 1119
3771-    def _got_write_error(self, f, peerid, shnums, started):
3772-        for shnum in shnums:
3773-            self.outstanding.discard( (peerid, shnum) )
3774-        self.bad_peers.add(peerid)
3775-        if self._first_write_error is None:
3776-            self._first_write_error = f
3777-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
3778-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
3779-                 failure=f,
3780-                 level=log.UNUSUAL)
3781-        # self.loop() will take care of checking to see if we're done
3782-        return
3783-
3784 
3785     def _done(self, res):
3786         if not self._running:
3787hunk ./src/allmydata/mutable/publish.py 1126
3788         self._running = False
3789         now = time.time()
3790         self._status.timings["total"] = now - self._started
3791+
3792+        elapsed = now - self._started_pushing
3793+        self._status.timings['push'] = elapsed
3794+
3795         self._status.set_active(False)
3796hunk ./src/allmydata/mutable/publish.py 1131
3797-        if isinstance(res, failure.Failure):
3798-            self.log("Publish done, with failure", failure=res,
3799-                     level=log.WEIRD, umid="nRsR9Q")
3800-            self._status.set_status("Failed")
3801-        elif self.surprised:
3802-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
3803-            self._status.set_status("UncoordinatedWriteError")
3804-            # deliver a failure
3805-            res = failure.Failure(UncoordinatedWriteError())
3806-            # TODO: recovery
3807-        else:
3808-            self.log("Publish done, success")
3809-            self._status.set_status("Finished")
3810-            self._status.set_progress(1.0)
3811+        self.log("Publish done, success")
3812+        self._status.set_status("Finished")
3813+        self._status.set_progress(1.0)
3814         eventually(self.done_deferred.callback, res)
3815 
3816hunk ./src/allmydata/mutable/publish.py 1136
3817+    def _failure(self):
3818+
3819+        if not self.surprised:
3820+            # We ran out of servers
3821+            self.log("Publish ran out of good servers, "
3822+                     "last failure was: %s" % str(self._last_failure))
3823+            e = NotEnoughServersError("Ran out of non-bad servers, "
3824+                                      "last failure was %s" %
3825+                                      str(self._last_failure))
3826+        else:
3827+            # We ran into shares that we didn't recognize, which means
3828+            # that we need to return an UncoordinatedWriteError.
3829+            self.log("Publish failed with UncoordinatedWriteError")
3830+            e = UncoordinatedWriteError()
3831+        f = failure.Failure(e)
3832+        eventually(self.done_deferred.callback, f)
3833+
3834+
3835+class MutableFileHandle:
3836+    """
3837+    I am a mutable uploadable built around a filehandle-like object,
3838+    usually either a StringIO instance or a handle to an actual file.
3839+    """
3840+    implements(IMutableUploadable)
3841+
3842+    def __init__(self, filehandle):
3843+        # The filehandle is defined as a generally file-like object that
3844+        # has these two methods. We don't care beyond that.
3845+        assert hasattr(filehandle, "read")
3846+        assert hasattr(filehandle, "close")
3847+
3848+        self._filehandle = filehandle
3849+        # We must start reading at the beginning of the file, or we risk
3850+        # encountering errors when the data read does not match the size
3851+        # reported to the uploader.
3852+        self._filehandle.seek(0)
3853+
3854+        # We have not yet read anything, so our position is 0.
3855+        self._marker = 0
3856+
3857+
3858+    def get_size(self):
3859+        """
3860+        I return the amount of data in my filehandle.
3861+        """
3862+        if not hasattr(self, "_size"):
3863+            old_position = self._filehandle.tell()
3864+            # Seek to the end of the file by seeking 0 bytes from the
3865+            # file's end
3866+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
3867+            self._size = self._filehandle.tell()
3868+            # Restore the previous position, in case this was called
3869+            # after a read.
3870+            self._filehandle.seek(old_position)
3871+            assert self._filehandle.tell() == old_position
3872+
3873+        assert hasattr(self, "_size")
3874+        return self._size
3875+
3876+
3877+    def pos(self):
3878+        """
3879+        I return the position of my read marker -- i.e., how much data I
3880+        have already read and returned to callers.
3881+        """
3882+        return self._marker
3883+
3884+
3885+    def read(self, length):
3886+        """
3887+        I return some data (up to length bytes) from my filehandle.
3888+
3889+        In most cases, I return length bytes, but sometimes I won't --
3890+        for example, if I am asked to read beyond the end of a file, or
3891+        an error occurs.
3892+        """
3893+        results = self._filehandle.read(length)
3894+        self._marker += len(results)
3895+        return [results]
3896+
3897+
3898+    def close(self):
3899+        """
3900+        I close the underlying filehandle. Any further operations on the
3901+        filehandle fail at this point.
3902+        """
3903+        self._filehandle.close()
3904+
3905+
3906+class MutableData(MutableFileHandle):
3907+    """
3908+    I am a mutable uploadable built around a string, which I then cast
3909+    into a StringIO and treat as a filehandle.
3910+    """
3911+
3912+    def __init__(self, s):
3913+        # Take a string and return a file-like uploadable.
3914+        assert isinstance(s, str)
3915+
3916+        MutableFileHandle.__init__(self, StringIO(s))
3917+
3918+
3919+class TransformingUploadable:
3920+    """
3921+    I am an IMutableUploadable that wraps another IMutableUploadable,
3922+    and some segments that are already on the grid. When I am called to
3923+    read, I handle merging of boundary segments.
3924+    """
3925+    implements(IMutableUploadable)
3926+
3927+
3928+    def __init__(self, data, offset, segment_size, start, end):
3929+        assert IMutableUploadable.providedBy(data)
3930+
3931+        self._newdata = data
3932+        self._offset = offset
3933+        self._segment_size = segment_size
3934+        self._start = start
3935+        self._end = end
3936+
3937+        self._read_marker = 0
3938+
3939+        self._first_segment_offset = offset % segment_size
3940+
3941+        num = self.log("TransformingUploadable: starting", parent=None)
3942+        self._log_number = num
3943+        self.log("got fso: %d" % self._first_segment_offset)
3944+        self.log("got offset: %d" % self._offset)
3945+
3946+
3947+    def log(self, *args, **kwargs):
3948+        if 'parent' not in kwargs:
3949+            kwargs['parent'] = self._log_number
3950+        if "facility" not in kwargs:
3951+            kwargs["facility"] = "tahoe.mutable.transforminguploadable"
3952+        return log.msg(*args, **kwargs)
3953+
3954+
3955+    def get_size(self):
3956+        return self._offset + self._newdata.get_size()
3957+
3958+
3959+    def read(self, length):
3960+        # We can get data from 3 sources here.
3961+        #   1. The first of the segments provided to us.
3962+        #   2. The data that we're replacing things with.
3963+        #   3. The last of the segments provided to us.
3964+
3965+        # are we in state 0?
3966+        self.log("reading %d bytes" % length)
3967+
3968+        old_start_data = ""
3969+        old_data_length = self._first_segment_offset - self._read_marker
3970+        if old_data_length > 0:
3971+            if old_data_length > length:
3972+                old_data_length = length
3973+            self.log("returning %d bytes of old start data" % old_data_length)
3974+
3975+            old_data_end = old_data_length + self._read_marker
3976+            old_start_data = self._start[self._read_marker:old_data_end]
3977+            length -= old_data_length
3978+        else:
3979+            # otherwise calculations later get screwed up.
3980+            old_data_length = 0
3981+
3982+        # Is there enough new data to satisfy this read? If not, we need
3983+        # to pad the end of the data with data from our last segment.
3984+        old_end_length = length - \
3985+            (self._newdata.get_size() - self._newdata.pos())
3986+        old_end_data = ""
3987+        if old_end_length > 0:
3988+            self.log("reading %d bytes of old end data" % old_end_length)
3989+
3990+            # TODO: We're not explicitly checking for tail segment size
3991+            # here. Is that a problem?
3992+            old_data_offset = (length - old_end_length + \
3993+                               old_data_length) % self._segment_size
3994+            self.log("reading at offset %d" % old_data_offset)
3995+            old_end = old_data_offset + old_end_length
3996+            old_end_data = self._end[old_data_offset:old_end]
3997+            length -= old_end_length
3998+            assert length == self._newdata.get_size() - self._newdata.pos()
3999+
4000+        self.log("reading %d bytes of new data" % length)
4001+        new_data = self._newdata.read(length)
4002+        new_data = "".join(new_data)
4003+
4004+        self._read_marker += len(old_start_data + new_data + old_end_data)
4005+
4006+        return old_start_data + new_data + old_end_data
4007 
4008hunk ./src/allmydata/mutable/publish.py 1327
4009+    def close(self):
4010+        pass
4011}
4012[nodemaker.py: Make nodemaker expose a way to create MDMF files
4013Kevan Carstensen <kevan@isnotajoke.com>**20100819003509
4014 Ignore-this: a6701746d6b992fc07bc0556a2b4a61d
4015] {
4016hunk ./src/allmydata/nodemaker.py 3
4017 import weakref
4018 from zope.interface import implements
4019-from allmydata.interfaces import INodeMaker
4020+from allmydata.util.assertutil import precondition
4021+from allmydata.interfaces import INodeMaker, SDMF_VERSION
4022 from allmydata.immutable.literal import LiteralFileNode
4023 from allmydata.immutable.filenode import ImmutableFileNode, CiphertextFileNode
4024 from allmydata.immutable.upload import Data
4025hunk ./src/allmydata/nodemaker.py 9
4026 from allmydata.mutable.filenode import MutableFileNode
4027+from allmydata.mutable.publish import MutableData
4028 from allmydata.dirnode import DirectoryNode, pack_children
4029 from allmydata.unknown import UnknownNode
4030 from allmydata import uri
4031hunk ./src/allmydata/nodemaker.py 92
4032             return self._create_dirnode(filenode)
4033         return None
4034 
4035-    def create_mutable_file(self, contents=None, keysize=None):
4036+    def create_mutable_file(self, contents=None, keysize=None,
4037+                            version=SDMF_VERSION):
4038         n = MutableFileNode(self.storage_broker, self.secret_holder,
4039                             self.default_encoding_parameters, self.history)
4040hunk ./src/allmydata/nodemaker.py 96
4041+        n.set_version(version)
4042         d = self.key_generator.generate(keysize)
4043         d.addCallback(n.create_with_keys, contents)
4044         d.addCallback(lambda res: n)
4045hunk ./src/allmydata/nodemaker.py 103
4046         return d
4047 
4048     def create_new_mutable_directory(self, initial_children={}):
4049+        # mutable directories will always be SDMF for now, to help
4050+        # compatibility with older clients.
4051+        version = SDMF_VERSION
4052+        # initial_children must have metadata (i.e. {} instead of None)
4053+        for (name, (node, metadata)) in initial_children.iteritems():
4054+            precondition(isinstance(metadata, dict),
4055+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
4056+            node.raise_error()
4057         d = self.create_mutable_file(lambda n:
4058hunk ./src/allmydata/nodemaker.py 112
4059-                                     pack_children(initial_children, n.get_writekey()))
4060+                                     MutableData(pack_children(initial_children,
4061+                                                    n.get_writekey())),
4062+                                     version=version)
4063         d.addCallback(self._create_dirnode)
4064         return d
4065 
4066}
4067[docs: update docs to mention MDMF
4068Kevan Carstensen <kevan@isnotajoke.com>**20100814225644
4069 Ignore-this: 1c3caa3cd44831007dcfbef297814308
4070] {
4071merger 0.0 (
4072hunk ./docs/configuration.rst 324
4073+Frontend Configuration
4074+======================
4075+
4076+The Tahoe client process can run a variety of frontend file-access protocols.
4077+You will use these to create and retrieve files from the virtual filesystem.
4078+Configuration details for each are documented in the following
4079+protocol-specific guides:
4080+
4081+HTTP
4082+
4083+    Tahoe runs a webserver by default on port 3456. This interface provides a
4084+    human-oriented "WUI", with pages to create, modify, and browse
4085+    directories and files, as well as a number of pages to check on the
4086+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
4087+    with a REST-ful HTTP interface that can be used by other programs
4088+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
4089+    details, and the ``web.port`` and ``web.static`` config variables above.
4090+    The `<frontends/download-status.rst>`_ document also describes a few WUI
4091+    status pages.
4092+
4093+CLI
4094+
4095+    The main "bin/tahoe" executable includes subcommands for manipulating the
4096+    filesystem, uploading/downloading files, and creating/running Tahoe
4097+    nodes. See `<frontends/CLI.rst>`_ for details.
4098+
4099+FTP, SFTP
4100+
4101+    Tahoe can also run both FTP and SFTP servers, and map a username/password
4102+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
4103+    for instructions on configuring these services, and the ``[ftpd]`` and
4104+    ``[sftpd]`` sections of ``tahoe.cfg``.
4105+
4106merger 0.0 (
4107replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
4108merger 0.0 (
4109hunk ./docs/configuration.rst 384
4110-shares.needed = (int, optional) aka "k", default 3
4111-shares.total = (int, optional) aka "N", N >= k, default 10
4112-shares.happy = (int, optional) 1 <= happy <= N, default 7
4113-
4114- These three values set the default encoding parameters. Each time a new file
4115- is uploaded, erasure-coding is used to break the ciphertext into separate
4116- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
4117- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
4118- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
4119- Setting k to 1 is equivalent to simple replication (uploading N copies of
4120- the file).
4121-
4122- These values control the tradeoff between storage overhead, performance, and
4123- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
4124- backend storage space (the actual value will be a bit more, because of other
4125- forms of overhead). Up to N-k shares can be lost before the file becomes
4126- unrecoverable, so assuming there are at least N servers, up to N-k servers
4127- can be offline without losing the file. So large N/k ratios are more
4128- reliable, and small N/k ratios use less disk space. Clearly, k must never be
4129- smaller than N.
4130-
4131- Large values of N will slow down upload operations slightly, since more
4132- servers must be involved, and will slightly increase storage overhead due to
4133- the hash trees that are created. Large values of k will cause downloads to
4134- be marginally slower, because more servers must be involved. N cannot be
4135- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
4136- uses.
4137-
4138- shares.happy allows you control over the distribution of your immutable file.
4139- For a successful upload, shares are guaranteed to be initially placed on
4140- at least 'shares.happy' distinct servers, the correct functioning of any
4141- k of which is sufficient to guarantee the availability of the uploaded file.
4142- This value should not be larger than the number of servers on your grid.
4143-
4144- A value of shares.happy <= k is allowed, but does not provide any redundancy
4145- if some servers fail or lose shares.
4146-
4147- (Mutable files use a different share placement algorithm that does not
4148-  consider this parameter.)
4149-
4150-
4151-== Storage Server Configuration ==
4152-
4153-[storage]
4154-enabled = (boolean, optional)
4155-
4156- If this is True, the node will run a storage server, offering space to other
4157- clients. If it is False, the node will not run a storage server, meaning
4158- that no shares will be stored on this node. Use False this for clients who
4159- do not wish to provide storage service. The default value is True.
4160-
4161-readonly = (boolean, optional)
4162-
4163- If True, the node will run a storage server but will not accept any shares,
4164- making it effectively read-only. Use this for storage servers which are
4165- being decommissioned: the storage/ directory could be mounted read-only,
4166- while shares are moved to other servers. Note that this currently only
4167- affects immutable shares. Mutable shares (used for directories) will be
4168- written and modified anyway. See ticket #390 for the current status of this
4169- bug. The default value is False.
4170-
4171-reserved_space = (str, optional)
4172-
4173- If provided, this value defines how much disk space is reserved: the storage
4174- server will not accept any share which causes the amount of free disk space
4175- to drop below this value. (The free space is measured by a call to statvfs(2)
4176- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
4177- user account under which the storage server runs.)
4178-
4179- This string contains a number, with an optional case-insensitive scale
4180- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
4181- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
4182- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
4183-
4184-expire.enabled =
4185-expire.mode =
4186-expire.override_lease_duration =
4187-expire.cutoff_date =
4188-expire.immutable =
4189-expire.mutable =
4190-
4191- These settings control garbage-collection, in which the server will delete
4192- shares that no longer have an up-to-date lease on them. Please see the
4193- neighboring "garbage-collection.txt" document for full details.
4194-
4195-
4196-== Running A Helper ==
4197+Running A Helper
4198+================
4199hunk ./docs/configuration.rst 424
4200+mutable.format = sdmf or mdmf
4201+
4202+ This value tells Tahoe-LAFS what the default mutable file format should
4203+ be. If mutable.format=sdmf, then newly created mutable files will be in
4204+ the old SDMF format. This is desirable for clients that operate on
4205+ grids where some peers run older versions of Tahoe-LAFS, as these older
4206+ versions cannot read the new MDMF mutable file format. If
4207+ mutable.format = mdmf, then newly created mutable files will use the
4208+ new MDMF format, which supports efficient in-place modification and
4209+ streaming downloads. You can overwrite this value using a special
4210+ mutable-type parameter in the webapi. If you do not specify a value
4211+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
4212+
4213+ Note that this parameter only applies to mutable files. Mutable
4214+ directories, which are stored as mutable files, are not controlled by
4215+ this parameter and will always use SDMF. We may revisit this decision
4216+ in future versions of Tahoe-LAFS.
4217)
4218)
4219)
4220hunk ./docs/frontends/webapi.rst 363
4221  writeable mutable file, that file's contents will be overwritten in-place. If
4222  it is a read-cap for a mutable file, an error will occur. If it is an
4223  immutable file, the old file will be discarded, and a new one will be put in
4224- its place.
4225+ its place. If the target file is a writable mutable file, you may also
4226+ specify an "offset" parameter -- a byte offset that determines where in
4227+ the mutable file the data from the HTTP request body is placed. This
4228+ operation is relatively efficient for MDMF mutable files, and is
4229+ relatively inefficient (but still supported) for SDMF mutable files.
4230 
4231  When creating a new file, if "mutable=true" is in the query arguments, the
4232  operation will create a mutable file instead of an immutable one.
4233hunk ./docs/frontends/webapi.rst 388
4234 
4235  If "mutable=true" is in the query arguments, the operation will create a
4236  mutable file, and return its write-cap in the HTTP respose. The default is
4237- to create an immutable file, returning the read-cap as a response.
4238+ to create an immutable file, returning the read-cap as a response. If
4239+ you create a mutable file, you can also use the "mutable-type" query
4240+ parameter. If "mutable-type=sdmf", then the mutable file will be created
4241+ in the old SDMF mutable file format. This is desirable for files that
4242+ need to be read by old clients. If "mutable-type=mdmf", then the file
4243+ will be created in the new MDMF mutable file format. MDMF mutable files
4244+ can be downloaded more efficiently, and modified in-place efficiently,
4245+ but are not compatible with older versions of Tahoe-LAFS. If no
4246+ "mutable-type" argument is given, the file is created in whatever
4247+ format was configured in tahoe.cfg.
4248 
4249 Creating A New Directory
4250 ------------------------
4251hunk ./docs/frontends/webapi.rst 1082
4252  If a "mutable=true" argument is provided, the operation will create a
4253  mutable file, and the response body will contain the write-cap instead of
4254  the upload results page. The default is to create an immutable file,
4255- returning the upload results page as a response.
4256+ returning the upload results page as a response. If you create a
4257+ mutable file, you may choose to specify the format of that mutable file
4258+ with the "mutable-type" parameter. If "mutable-type=mdmf", then the
4259+ file will be created as an MDMF mutable file. If "mutable-type=sdmf",
4260+ then the file will be created as an SDMF mutable file. If no value is
4261+ specified, the file will be created in whatever format is specified in
4262+ tahoe.cfg.
4263 
4264 
4265 ``POST /uri/$DIRCAP/[SUBDIRS../]?t=upload``
4266}
4267[mutable/layout.py and interfaces.py: add MDMF writer and reader
4268Kevan Carstensen <kevan@isnotajoke.com>**20100819003304
4269 Ignore-this: 44400fec923987b62830da2ed5075fb4
4270 
4271 The MDMF writer is responsible for keeping state as plaintext is
4272 gradually processed into share data by the upload process. When the
4273 upload finishes, it will write all of its share data to a remote server,
4274 reporting its status back to the publisher.
4275 
4276 The MDMF reader is responsible for abstracting an MDMF file as it sits
4277 on the grid from the downloader; specifically, by receiving and
4278 responding to requests for arbitrary data within the MDMF file.
4279 
4280 The interfaces.py file has also been modified to contain an interface
4281 for the writer.
4282] {
4283hunk ./src/allmydata/interfaces.py 7
4284      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
4285 
4286 HASH_SIZE=32
4287+SALT_SIZE=16
4288+
4289+SDMF_VERSION=0
4290+MDMF_VERSION=1
4291 
4292 Hash = StringConstraint(maxLength=HASH_SIZE,
4293                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
4294hunk ./src/allmydata/interfaces.py 424
4295         """
4296 
4297 
4298+class IMutableSlotWriter(Interface):
4299+    """
4300+    The interface for a writer around a mutable slot on a remote server.
4301+    """
4302+    def set_checkstring(checkstring, *args):
4303+        """
4304+        Set the checkstring that I will pass to the remote server when
4305+        writing.
4306+
4307+            @param checkstring A packed checkstring to use.
4308+
4309+        Note that implementations can differ in which semantics they
4310+        wish to support for set_checkstring -- they can, for example,
4311+        build the checkstring themselves from its constituents, or
4312+        some other thing.
4313+        """
4314+
4315+    def get_checkstring():
4316+        """
4317+        Get the checkstring that I think currently exists on the remote
4318+        server.
4319+        """
4320+
4321+    def put_block(data, segnum, salt):
4322+        """
4323+        Add a block and salt to the share.
4324+        """
4325+
4326+    def put_encprivey(encprivkey):
4327+        """
4328+        Add the encrypted private key to the share.
4329+        """
4330+
4331+    def put_blockhashes(blockhashes=list):
4332+        """
4333+        Add the block hash tree to the share.
4334+        """
4335+
4336+    def put_sharehashes(sharehashes=dict):
4337+        """
4338+        Add the share hash chain to the share.
4339+        """
4340+
4341+    def get_signable():
4342+        """
4343+        Return the part of the share that needs to be signed.
4344+        """
4345+
4346+    def put_signature(signature):
4347+        """
4348+        Add the signature to the share.
4349+        """
4350+
4351+    def put_verification_key(verification_key):
4352+        """
4353+        Add the verification key to the share.
4354+        """
4355+
4356+    def finish_publishing():
4357+        """
4358+        Do anything necessary to finish writing the share to a remote
4359+        server. I require that no further publishing needs to take place
4360+        after this method has been called.
4361+        """
4362+
4363+
4364 class IURI(Interface):
4365     def init_from_string(uri):
4366         """Accept a string (as created by my to_string() method) and populate
4367hunk ./src/allmydata/mutable/layout.py 4
4368 
4369 import struct
4370 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4371+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4372+                                 MDMF_VERSION, IMutableSlotWriter
4373+from allmydata.util import mathutil, observer
4374+from twisted.python import failure
4375+from twisted.internet import defer
4376+from zope.interface import implements
4377+
4378+
4379+# These strings describe the format of the packed structs they help process
4380+# Here's what they mean:
4381+#
4382+#  PREFIX:
4383+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4384+#    B: The version information; an 8 bit version identifier. Stored as
4385+#       an unsigned char. This is currently 00 00 00 00; our modifications
4386+#       will turn it into 00 00 00 01.
4387+#    Q: The sequence number; this is sort of like a revision history for
4388+#       mutable files; they start at 1 and increase as they are changed after
4389+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4390+#       length.
4391+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4392+#       characters = 32 bytes to store the value.
4393+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4394+#       16 characters.
4395+#
4396+#  SIGNED_PREFIX additions, things that are covered by the signature:
4397+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4398+#       which is convenient because our erasure coding scheme cannot
4399+#       encode if you ask for more than 255 pieces.
4400+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4401+#       same reasons as above.
4402+#    Q: The segment size of the uploaded file. This will essentially be the
4403+#       length of the file in SDMF. An unsigned long long, so we can store
4404+#       files of quite large size.
4405+#    Q: The data length of the uploaded file. Modulo padding, this will be
4406+#       the same of the data length field. Like the data length field, it is
4407+#       an unsigned long long and can be quite large.
4408+#
4409+#   HEADER additions:
4410+#     L: The offset of the signature of this. An unsigned long.
4411+#     L: The offset of the share hash chain. An unsigned long.
4412+#     L: The offset of the block hash tree. An unsigned long.
4413+#     L: The offset of the share data. An unsigned long.
4414+#     Q: The offset of the encrypted private key. An unsigned long long, to
4415+#        account for the possibility of a lot of share data.
4416+#     Q: The offset of the EOF. An unsigned long long, to account for the
4417+#        possibility of a lot of share data.
4418+#
4419+#  After all of these, we have the following:
4420+#    - The verification key: Occupies the space between the end of the header
4421+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4422+#    - The signature, which goes from the signature offset to the share hash
4423+#      chain offset.
4424+#    - The share hash chain, which goes from the share hash chain offset to
4425+#      the block hash tree offset.
4426+#    - The share data, which goes from the share data offset to the encrypted
4427+#      private key offset.
4428+#    - The encrypted private key offset, which goes until the end of the file.
4429+#
4430+#  The block hash tree in this encoding has only one share, so the offset of
4431+#  the share data will be 32 bits more than the offset of the block hash tree.
4432+#  Given this, we may need to check to see how many bytes a reasonably sized
4433+#  block hash tree will take up.
4434 
4435 PREFIX = ">BQ32s16s" # each version has a different prefix
4436 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4437hunk ./src/allmydata/mutable/layout.py 73
4438 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4439 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4440 HEADER_LENGTH = struct.calcsize(HEADER)
4441+OFFSETS = ">LLLLQQ"
4442+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4443 
4444hunk ./src/allmydata/mutable/layout.py 76
4445+# These are still used for some tests.
4446 def unpack_header(data):
4447     o = {}
4448     (version,
4449hunk ./src/allmydata/mutable/layout.py 92
4450      o['EOF']) = struct.unpack(HEADER, data[:HEADER_LENGTH])
4451     return (version, seqnum, root_hash, IV, k, N, segsize, datalen, o)
4452 
4453-def unpack_prefix_and_signature(data):
4454-    assert len(data) >= HEADER_LENGTH, len(data)
4455-    prefix = data[:SIGNED_PREFIX_LENGTH]
4456-
4457-    (version,
4458-     seqnum,
4459-     root_hash,
4460-     IV,
4461-     k, N, segsize, datalen,
4462-     o) = unpack_header(data)
4463-
4464-    if version != 0:
4465-        raise UnknownVersionError("got mutable share version %d, but I only understand version 0" % version)
4466-
4467-    if len(data) < o['share_hash_chain']:
4468-        raise NeedMoreDataError(o['share_hash_chain'],
4469-                                o['enc_privkey'], o['EOF']-o['enc_privkey'])
4470-
4471-    pubkey_s = data[HEADER_LENGTH:o['signature']]
4472-    signature = data[o['signature']:o['share_hash_chain']]
4473-
4474-    return (seqnum, root_hash, IV, k, N, segsize, datalen,
4475-            pubkey_s, signature, prefix)
4476-
4477 def unpack_share(data):
4478     assert len(data) >= HEADER_LENGTH
4479     o = {}
4480hunk ./src/allmydata/mutable/layout.py 139
4481             pubkey, signature, share_hash_chain, block_hash_tree,
4482             share_data, enc_privkey)
4483 
4484-def unpack_share_data(verinfo, hash_and_data):
4485-    (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, o_t) = verinfo
4486-
4487-    # hash_and_data starts with the share_hash_chain, so figure out what the
4488-    # offsets really are
4489-    o = dict(o_t)
4490-    o_share_hash_chain = 0
4491-    o_block_hash_tree = o['block_hash_tree'] - o['share_hash_chain']
4492-    o_share_data = o['share_data'] - o['share_hash_chain']
4493-    o_enc_privkey = o['enc_privkey'] - o['share_hash_chain']
4494-
4495-    share_hash_chain_s = hash_and_data[o_share_hash_chain:o_block_hash_tree]
4496-    share_hash_format = ">H32s"
4497-    hsize = struct.calcsize(share_hash_format)
4498-    assert len(share_hash_chain_s) % hsize == 0, len(share_hash_chain_s)
4499-    share_hash_chain = []
4500-    for i in range(0, len(share_hash_chain_s), hsize):
4501-        chunk = share_hash_chain_s[i:i+hsize]
4502-        (hid, h) = struct.unpack(share_hash_format, chunk)
4503-        share_hash_chain.append( (hid, h) )
4504-    share_hash_chain = dict(share_hash_chain)
4505-    block_hash_tree_s = hash_and_data[o_block_hash_tree:o_share_data]
4506-    assert len(block_hash_tree_s) % 32 == 0, len(block_hash_tree_s)
4507-    block_hash_tree = []
4508-    for i in range(0, len(block_hash_tree_s), 32):
4509-        block_hash_tree.append(block_hash_tree_s[i:i+32])
4510-
4511-    share_data = hash_and_data[o_share_data:o_enc_privkey]
4512-
4513-    return (share_hash_chain, block_hash_tree, share_data)
4514-
4515-
4516-def pack_checkstring(seqnum, root_hash, IV):
4517-    return struct.pack(PREFIX,
4518-                       0, # version,
4519-                       seqnum,
4520-                       root_hash,
4521-                       IV)
4522-
4523 def unpack_checkstring(checkstring):
4524     cs_len = struct.calcsize(PREFIX)
4525     version, seqnum, root_hash, IV = struct.unpack(PREFIX, checkstring[:cs_len])
4526hunk ./src/allmydata/mutable/layout.py 146
4527         raise UnknownVersionError("got mutable share version %d, but I only understand version 0" % version)
4528     return (seqnum, root_hash, IV)
4529 
4530-def pack_prefix(seqnum, root_hash, IV,
4531-                required_shares, total_shares,
4532-                segment_size, data_length):
4533-    prefix = struct.pack(SIGNED_PREFIX,
4534-                         0, # version,
4535-                         seqnum,
4536-                         root_hash,
4537-                         IV,
4538-
4539-                         required_shares,
4540-                         total_shares,
4541-                         segment_size,
4542-                         data_length,
4543-                         )
4544-    return prefix
4545 
4546 def pack_offsets(verification_key_length, signature_length,
4547                  share_hash_chain_length, block_hash_tree_length,
4548hunk ./src/allmydata/mutable/layout.py 192
4549                            encprivkey])
4550     return final_share
4551 
4552+def pack_prefix(seqnum, root_hash, IV,
4553+                required_shares, total_shares,
4554+                segment_size, data_length):
4555+    prefix = struct.pack(SIGNED_PREFIX,
4556+                         0, # version,
4557+                         seqnum,
4558+                         root_hash,
4559+                         IV,
4560+                         required_shares,
4561+                         total_shares,
4562+                         segment_size,
4563+                         data_length,
4564+                         )
4565+    return prefix
4566+
4567+
4568+class SDMFSlotWriteProxy:
4569+    implements(IMutableSlotWriter)
4570+    """
4571+    I represent a remote write slot for an SDMF mutable file. I build a
4572+    share in memory, and then write it in one piece to the remote
4573+    server. This mimics how SDMF shares were built before MDMF (and the
4574+    new MDMF uploader), but provides that functionality in a way that
4575+    allows the MDMF uploader to be built without much special-casing for
4576+    file format, which makes the uploader code more readable.
4577+    """
4578+    def __init__(self,
4579+                 shnum,
4580+                 rref, # a remote reference to a storage server
4581+                 storage_index,
4582+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4583+                 seqnum, # the sequence number of the mutable file
4584+                 required_shares,
4585+                 total_shares,
4586+                 segment_size,
4587+                 data_length): # the length of the original file
4588+        self.shnum = shnum
4589+        self._rref = rref
4590+        self._storage_index = storage_index
4591+        self._secrets = secrets
4592+        self._seqnum = seqnum
4593+        self._required_shares = required_shares
4594+        self._total_shares = total_shares
4595+        self._segment_size = segment_size
4596+        self._data_length = data_length
4597+
4598+        # This is an SDMF file, so it should have only one segment, so,
4599+        # modulo padding of the data length, the segment size and the
4600+        # data length should be the same.
4601+        expected_segment_size = mathutil.next_multiple(data_length,
4602+                                                       self._required_shares)
4603+        assert expected_segment_size == segment_size
4604+
4605+        self._block_size = self._segment_size / self._required_shares
4606+
4607+        # This is meant to mimic how SDMF files were built before MDMF
4608+        # entered the picture: we generate each share in its entirety,
4609+        # then push it off to the storage server in one write. When
4610+        # callers call set_*, they are just populating this dict.
4611+        # finish_publishing will stitch these pieces together into a
4612+        # coherent share, and then write the coherent share to the
4613+        # storage server.
4614+        self._share_pieces = {}
4615+
4616+        # This tells the write logic what checkstring to use when
4617+        # writing remote shares.
4618+        self._testvs = []
4619+
4620+        self._readvs = [(0, struct.calcsize(PREFIX))]
4621+
4622+
4623+    def set_checkstring(self, checkstring_or_seqnum,
4624+                              root_hash=None,
4625+                              salt=None):
4626+        """
4627+        Set the checkstring that I will pass to the remote server when
4628+        writing.
4629+
4630+            @param checkstring_or_seqnum: A packed checkstring to use,
4631+                   or a sequence number. I will treat this as a checkstr
4632+
4633+        Note that implementations can differ in which semantics they
4634+        wish to support for set_checkstring -- they can, for example,
4635+        build the checkstring themselves from its constituents, or
4636+        some other thing.
4637+        """
4638+        if root_hash and salt:
4639+            checkstring = struct.pack(PREFIX,
4640+                                      0,
4641+                                      checkstring_or_seqnum,
4642+                                      root_hash,
4643+                                      salt)
4644+        else:
4645+            checkstring = checkstring_or_seqnum
4646+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4647+
4648+
4649+    def get_checkstring(self):
4650+        """
4651+        Get the checkstring that I think currently exists on the remote
4652+        server.
4653+        """
4654+        if self._testvs:
4655+            return self._testvs[0][3]
4656+        return ""
4657+
4658+
4659+    def put_block(self, data, segnum, salt):
4660+        """
4661+        Add a block and salt to the share.
4662+        """
4663+        # SDMF files have only one segment
4664+        assert segnum == 0
4665+        assert len(data) == self._block_size
4666+        assert len(salt) == SALT_SIZE
4667+
4668+        self._share_pieces['sharedata'] = data
4669+        self._share_pieces['salt'] = salt
4670+
4671+        # TODO: Figure out something intelligent to return.
4672+        return defer.succeed(None)
4673+
4674+
4675+    def put_encprivkey(self, encprivkey):
4676+        """
4677+        Add the encrypted private key to the share.
4678+        """
4679+        self._share_pieces['encprivkey'] = encprivkey
4680+
4681+        return defer.succeed(None)
4682+
4683+
4684+    def put_blockhashes(self, blockhashes):
4685+        """
4686+        Add the block hash tree to the share.
4687+        """
4688+        assert isinstance(blockhashes, list)
4689+        for h in blockhashes:
4690+            assert len(h) == HASH_SIZE
4691+
4692+        # serialize the blockhashes, then set them.
4693+        blockhashes_s = "".join(blockhashes)
4694+        self._share_pieces['block_hash_tree'] = blockhashes_s
4695+
4696+        return defer.succeed(None)
4697+
4698+
4699+    def put_sharehashes(self, sharehashes):
4700+        """
4701+        Add the share hash chain to the share.
4702+        """
4703+        assert isinstance(sharehashes, dict)
4704+        for h in sharehashes.itervalues():
4705+            assert len(h) == HASH_SIZE
4706+
4707+        # serialize the sharehashes, then set them.
4708+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4709+                                 for i in sorted(sharehashes.keys())])
4710+        self._share_pieces['share_hash_chain'] = sharehashes_s
4711+
4712+        return defer.succeed(None)
4713+
4714+
4715+    def put_root_hash(self, root_hash):
4716+        """
4717+        Add the root hash to the share.
4718+        """
4719+        assert len(root_hash) == HASH_SIZE
4720+
4721+        self._share_pieces['root_hash'] = root_hash
4722+
4723+        return defer.succeed(None)
4724+
4725+
4726+    def put_salt(self, salt):
4727+        """
4728+        Add a salt to an empty SDMF file.
4729+        """
4730+        assert len(salt) == SALT_SIZE
4731+
4732+        self._share_pieces['salt'] = salt
4733+        self._share_pieces['sharedata'] = ""
4734+
4735+
4736+    def get_signable(self):
4737+        """
4738+        Return the part of the share that needs to be signed.
4739+
4740+        SDMF writers need to sign the packed representation of the
4741+        first eight fields of the remote share, that is:
4742+            - version number (0)
4743+            - sequence number
4744+            - root of the share hash tree
4745+            - salt
4746+            - k
4747+            - n
4748+            - segsize
4749+            - datalen
4750+
4751+        This method is responsible for returning that to callers.
4752+        """
4753+        return struct.pack(SIGNED_PREFIX,
4754+                           0,
4755+                           self._seqnum,
4756+                           self._share_pieces['root_hash'],
4757+                           self._share_pieces['salt'],
4758+                           self._required_shares,
4759+                           self._total_shares,
4760+                           self._segment_size,
4761+                           self._data_length)
4762+
4763+
4764+    def put_signature(self, signature):
4765+        """
4766+        Add the signature to the share.
4767+        """
4768+        self._share_pieces['signature'] = signature
4769+
4770+        return defer.succeed(None)
4771+
4772+
4773+    def put_verification_key(self, verification_key):
4774+        """
4775+        Add the verification key to the share.
4776+        """
4777+        self._share_pieces['verification_key'] = verification_key
4778+
4779+        return defer.succeed(None)
4780+
4781+
4782+    def get_verinfo(self):
4783+        """
4784+        I return my verinfo tuple. This is used by the ServermapUpdater
4785+        to keep track of versions of mutable files.
4786+
4787+        The verinfo tuple for MDMF files contains:
4788+            - seqnum
4789+            - root hash
4790+            - a blank (nothing)
4791+            - segsize
4792+            - datalen
4793+            - k
4794+            - n
4795+            - prefix (the thing that you sign)
4796+            - a tuple of offsets
4797+
4798+        We include the nonce in MDMF to simplify processing of version
4799+        information tuples.
4800+
4801+        The verinfo tuple for SDMF files is the same, but contains a
4802+        16-byte IV instead of a hash of salts.
4803+        """
4804+        return (self._seqnum,
4805+                self._share_pieces['root_hash'],
4806+                self._share_pieces['salt'],
4807+                self._segment_size,
4808+                self._data_length,
4809+                self._required_shares,
4810+                self._total_shares,
4811+                self.get_signable(),
4812+                self._get_offsets_tuple())
4813+
4814+    def _get_offsets_dict(self):
4815+        post_offset = HEADER_LENGTH
4816+        offsets = {}
4817+
4818+        verification_key_length = len(self._share_pieces['verification_key'])
4819+        o1 = offsets['signature'] = post_offset + verification_key_length
4820+
4821+        signature_length = len(self._share_pieces['signature'])
4822+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4823+
4824+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4825+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4826+
4827+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4828+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4829+
4830+        share_data_length = len(self._share_pieces['sharedata'])
4831+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4832+
4833+        encprivkey_length = len(self._share_pieces['encprivkey'])
4834+        offsets['EOF'] = o5 + encprivkey_length
4835+        return offsets
4836+
4837+
4838+    def _get_offsets_tuple(self):
4839+        offsets = self._get_offsets_dict()
4840+        return tuple([(key, value) for key, value in offsets.items()])
4841+
4842+
4843+    def _pack_offsets(self):
4844+        offsets = self._get_offsets_dict()
4845+        return struct.pack(">LLLLQQ",
4846+                           offsets['signature'],
4847+                           offsets['share_hash_chain'],
4848+                           offsets['block_hash_tree'],
4849+                           offsets['share_data'],
4850+                           offsets['enc_privkey'],
4851+                           offsets['EOF'])
4852+
4853+
4854+    def finish_publishing(self):
4855+        """
4856+        Do anything necessary to finish writing the share to a remote
4857+        server. I require that no further publishing needs to take place
4858+        after this method has been called.
4859+        """
4860+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4861+                  "share_hash_chain", "block_hash_tree"]:
4862+            assert k in self._share_pieces
4863+        # This is the only method that actually writes something to the
4864+        # remote server.
4865+        # First, we need to pack the share into data that we can write
4866+        # to the remote server in one write.
4867+        offsets = self._pack_offsets()
4868+        prefix = self.get_signable()
4869+        final_share = "".join([prefix,
4870+                               offsets,
4871+                               self._share_pieces['verification_key'],
4872+                               self._share_pieces['signature'],
4873+                               self._share_pieces['share_hash_chain'],
4874+                               self._share_pieces['block_hash_tree'],
4875+                               self._share_pieces['sharedata'],
4876+                               self._share_pieces['encprivkey']])
4877+
4878+        # Our only data vector is going to be writing the final share,
4879+        # in its entirely.
4880+        datavs = [(0, final_share)]
4881+
4882+        if not self._testvs:
4883+            # Our caller has not provided us with another checkstring
4884+            # yet, so we assume that we are writing a new share, and set
4885+            # a test vector that will allow a new share to be written.
4886+            self._testvs = []
4887+            self._testvs.append(tuple([0, 1, "eq", ""]))
4888+
4889+        tw_vectors = {}
4890+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4891+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4892+                                     self._storage_index,
4893+                                     self._secrets,
4894+                                     tw_vectors,
4895+                                     # TODO is it useful to read something?
4896+                                     self._readvs)
4897+
4898+
4899+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4900+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4901+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4902+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4903+MDMFCHECKSTRING = ">BQ32s"
4904+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4905+MDMFOFFSETS = ">QQQQQQ"
4906+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4907+
4908+class MDMFSlotWriteProxy:
4909+    implements(IMutableSlotWriter)
4910+
4911+    """
4912+    I represent a remote write slot for an MDMF mutable file.
4913+
4914+    I abstract away from my caller the details of block and salt
4915+    management, and the implementation of the on-disk format for MDMF
4916+    shares.
4917+    """
4918+    # Expected layout, MDMF:
4919+    # offset:     size:       name:
4920+    #-- signed part --
4921+    # 0           1           version number (01)
4922+    # 1           8           sequence number
4923+    # 9           32          share tree root hash
4924+    # 41          1           The "k" encoding parameter
4925+    # 42          1           The "N" encoding parameter
4926+    # 43          8           The segment size of the uploaded file
4927+    # 51          8           The data length of the original plaintext
4928+    #-- end signed part --
4929+    # 59          8           The offset of the encrypted private key
4930+    # 83          8           The offset of the signature
4931+    # 91          8           The offset of the verification key
4932+    # 67          8           The offset of the block hash tree
4933+    # 75          8           The offset of the share hash chain
4934+    # 99          8           The offset of the EOF
4935+    #
4936+    # followed by salts and share data, the encrypted private key, the
4937+    # block hash tree, the salt hash tree, the share hash chain, a
4938+    # signature over the first eight fields, and a verification key.
4939+    #
4940+    # The checkstring is the first three fields -- the version number,
4941+    # sequence number, root hash and root salt hash. This is consistent
4942+    # in meaning to what we have with SDMF files, except now instead of
4943+    # using the literal salt, we use a value derived from all of the
4944+    # salts -- the share hash root.
4945+    #
4946+    # The salt is stored before the block for each segment. The block
4947+    # hash tree is computed over the combination of block and salt for
4948+    # each segment. In this way, we get integrity checking for both
4949+    # block and salt with the current block hash tree arrangement.
4950+    #
4951+    # The ordering of the offsets is different to reflect the dependencies
4952+    # that we'll run into with an MDMF file. The expected write flow is
4953+    # something like this:
4954+    #
4955+    #   0: Initialize with the sequence number, encoding parameters and
4956+    #      data length. From this, we can deduce the number of segments,
4957+    #      and where they should go.. We can also figure out where the
4958+    #      encrypted private key should go, because we can figure out how
4959+    #      big the share data will be.
4960+    #
4961+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4962+    #      like
4963+    #
4964+    #       put_block(data, segnum, salt)
4965+    #
4966+    #      to write a block and a salt to the disk. We can do both of
4967+    #      these operations now because we have enough of the offsets to
4968+    #      know where to put them.
4969+    #
4970+    #   2: Put the encrypted private key. Use:
4971+    #
4972+    #        put_encprivkey(encprivkey)
4973+    #
4974+    #      Now that we know the length of the private key, we can fill
4975+    #      in the offset for the block hash tree.
4976+    #
4977+    #   3: We're now in a position to upload the block hash tree for
4978+    #      a share. Put that using something like:
4979+    #       
4980+    #        put_blockhashes(block_hash_tree)
4981+    #
4982+    #      Note that block_hash_tree is a list of hashes -- we'll take
4983+    #      care of the details of serializing that appropriately. When
4984+    #      we get the block hash tree, we are also in a position to
4985+    #      calculate the offset for the share hash chain, and fill that
4986+    #      into the offsets table.
4987+    #
4988+    #   4: At the same time, we're in a position to upload the salt hash
4989+    #      tree. This is a Merkle tree over all of the salts. We use a
4990+    #      Merkle tree so that we can validate each block,salt pair as
4991+    #      we download them later. We do this using
4992+    #
4993+    #        put_salthashes(salt_hash_tree)
4994+    #
4995+    #      When you do this, I automatically put the root of the tree
4996+    #      (the hash at index 0 of the list) in its appropriate slot in
4997+    #      the signed prefix of the share.
4998+    #
4999+    #   5: We're now in a position to upload the share hash chain for
5000+    #      a share. Do that with something like:
5001+    #     
5002+    #        put_sharehashes(share_hash_chain)
5003+    #
5004+    #      share_hash_chain should be a dictionary mapping shnums to
5005+    #      32-byte hashes -- the wrapper handles serialization.
5006+    #      We'll know where to put the signature at this point, also.
5007+    #      The root of this tree will be put explicitly in the next
5008+    #      step.
5009+    #
5010+    #      TODO: Why? Why not just include it in the tree here?
5011+    #
5012+    #   6: Before putting the signature, we must first put the
5013+    #      root_hash. Do this with:
5014+    #
5015+    #        put_root_hash(root_hash).
5016+    #     
5017+    #      In terms of knowing where to put this value, it was always
5018+    #      possible to place it, but it makes sense semantically to
5019+    #      place it after the share hash tree, so that's why you do it
5020+    #      in this order.
5021+    #
5022+    #   6: With the root hash put, we can now sign the header. Use:
5023+    #
5024+    #        get_signable()
5025+    #
5026+    #      to get the part of the header that you want to sign, and use:
5027+    #       
5028+    #        put_signature(signature)
5029+    #
5030+    #      to write your signature to the remote server.
5031+    #
5032+    #   6: Add the verification key, and finish. Do:
5033+    #
5034+    #        put_verification_key(key)
5035+    #
5036+    #      and
5037+    #
5038+    #        finish_publish()
5039+    #
5040+    # Checkstring management:
5041+    #
5042+    # To write to a mutable slot, we have to provide test vectors to ensure
5043+    # that we are writing to the same data that we think we are. These
5044+    # vectors allow us to detect uncoordinated writes; that is, writes
5045+    # where both we and some other shareholder are writing to the
5046+    # mutable slot, and to report those back to the parts of the program
5047+    # doing the writing.
5048+    #
5049+    # With SDMF, this was easy -- all of the share data was written in
5050+    # one go, so it was easy to detect uncoordinated writes, and we only
5051+    # had to do it once. With MDMF, not all of the file is written at
5052+    # once.
5053+    #
5054+    # If a share is new, we write out as much of the header as we can
5055+    # before writing out anything else. This gives other writers a
5056+    # canary that they can use to detect uncoordinated writes, and, if
5057+    # they do the same thing, gives us the same canary. We them update
5058+    # the share. We won't be able to write out two fields of the header
5059+    # -- the share tree hash and the salt hash -- until we finish
5060+    # writing out the share. We only require the writer to provide the
5061+    # initial checkstring, and keep track of what it should be after
5062+    # updates ourselves.
5063+    #
5064+    # If we haven't written anything yet, then on the first write (which
5065+    # will probably be a block + salt of a share), we'll also write out
5066+    # the header. On subsequent passes, we'll expect to see the header.
5067+    # This changes in two places:
5068+    #
5069+    #   - When we write out the salt hash
5070+    #   - When we write out the root of the share hash tree
5071+    #
5072+    # since these values will change the header. It is possible that we
5073+    # can just make those be written in one operation to minimize
5074+    # disruption.
5075+    def __init__(self,
5076+                 shnum,
5077+                 rref, # a remote reference to a storage server
5078+                 storage_index,
5079+                 secrets, # (write_enabler, renew_secret, cancel_secret)
5080+                 seqnum, # the sequence number of the mutable file
5081+                 required_shares,
5082+                 total_shares,
5083+                 segment_size,
5084+                 data_length): # the length of the original file
5085+        self.shnum = shnum
5086+        self._rref = rref
5087+        self._storage_index = storage_index
5088+        self._seqnum = seqnum
5089+        self._required_shares = required_shares
5090+        assert self.shnum >= 0 and self.shnum < total_shares
5091+        self._total_shares = total_shares
5092+        # We build up the offset table as we write things. It is the
5093+        # last thing we write to the remote server.
5094+        self._offsets = {}
5095+        self._testvs = []
5096+        # This is a list of write vectors that will be sent to our
5097+        # remote server once we are directed to write things there.
5098+        self._writevs = []
5099+        self._secrets = secrets
5100+        # The segment size needs to be a multiple of the k parameter --
5101+        # any padding should have been carried out by the publisher
5102+        # already.
5103+        assert segment_size % required_shares == 0
5104+        self._segment_size = segment_size
5105+        self._data_length = data_length
5106+
5107+        # These are set later -- we define them here so that we can
5108+        # check for their existence easily
5109+
5110+        # This is the root of the share hash tree -- the Merkle tree
5111+        # over the roots of the block hash trees computed for shares in
5112+        # this upload.
5113+        self._root_hash = None
5114+
5115+        # We haven't yet written anything to the remote bucket. By
5116+        # setting this, we tell the _write method as much. The write
5117+        # method will then know that it also needs to add a write vector
5118+        # for the checkstring (or what we have of it) to the first write
5119+        # request. We'll then record that value for future use.  If
5120+        # we're expecting something to be there already, we need to call
5121+        # set_checkstring before we write anything to tell the first
5122+        # write about that.
5123+        self._written = False
5124+
5125+        # When writing data to the storage servers, we get a read vector
5126+        # for free. We'll read the checkstring, which will help us
5127+        # figure out what's gone wrong if a write fails.
5128+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
5129+
5130+        # We calculate the number of segments because it tells us
5131+        # where the salt part of the file ends/share segment begins,
5132+        # and also because it provides a useful amount of bounds checking.
5133+        self._num_segments = mathutil.div_ceil(self._data_length,
5134+                                               self._segment_size)
5135+        self._block_size = self._segment_size / self._required_shares
5136+        # We also calculate the share size, to help us with block
5137+        # constraints later.
5138+        tail_size = self._data_length % self._segment_size
5139+        if not tail_size:
5140+            self._tail_block_size = self._block_size
5141+        else:
5142+            self._tail_block_size = mathutil.next_multiple(tail_size,
5143+                                                           self._required_shares)
5144+            self._tail_block_size /= self._required_shares
5145+
5146+        # We already know where the sharedata starts; right after the end
5147+        # of the header (which is defined as the signable part + the offsets)
5148+        # We can also calculate where the encrypted private key begins
5149+        # from what we know know.
5150+        self._actual_block_size = self._block_size + SALT_SIZE
5151+        data_size = self._actual_block_size * (self._num_segments - 1)
5152+        data_size += self._tail_block_size
5153+        data_size += SALT_SIZE
5154+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
5155+        self._offsets['enc_privkey'] += data_size
5156+        # We'll wait for the rest. Callers can now call my "put_block" and
5157+        # "set_checkstring" methods.
5158+
5159+
5160+    def set_checkstring(self,
5161+                        seqnum_or_checkstring,
5162+                        root_hash=None,
5163+                        salt=None):
5164+        """
5165+        Set checkstring checkstring for the given shnum.
5166+
5167+        This can be invoked in one of two ways.
5168+
5169+        With one argument, I assume that you are giving me a literal
5170+        checkstring -- e.g., the output of get_checkstring. I will then
5171+        set that checkstring as it is. This form is used by unit tests.
5172+
5173+        With two arguments, I assume that you are giving me a sequence
5174+        number and root hash to make a checkstring from. In that case, I
5175+        will build a checkstring and set it for you. This form is used
5176+        by the publisher.
5177+
5178+        By default, I assume that I am writing new shares to the grid.
5179+        If you don't explcitly set your own checkstring, I will use
5180+        one that requires that the remote share not exist. You will want
5181+        to use this method if you are updating a share in-place;
5182+        otherwise, writes will fail.
5183+        """
5184+        # You're allowed to overwrite checkstrings with this method;
5185+        # I assume that users know what they are doing when they call
5186+        # it.
5187+        if root_hash:
5188+            checkstring = struct.pack(MDMFCHECKSTRING,
5189+                                      1,
5190+                                      seqnum_or_checkstring,
5191+                                      root_hash)
5192+        else:
5193+            checkstring = seqnum_or_checkstring
5194+
5195+        if checkstring == "":
5196+            # We special-case this, since len("") = 0, but we need
5197+            # length of 1 for the case of an empty share to work on the
5198+            # storage server, which is what a checkstring that is the
5199+            # empty string means.
5200+            self._testvs = []
5201+        else:
5202+            self._testvs = []
5203+            self._testvs.append((0, len(checkstring), "eq", checkstring))
5204+
5205+
5206+    def __repr__(self):
5207+        return "MDMFSlotWriteProxy for share %d" % self.shnum
5208+
5209+
5210+    def get_checkstring(self):
5211+        """
5212+        Given a share number, I return a representation of what the
5213+        checkstring for that share on the server will look like.
5214+
5215+        I am mostly used for tests.
5216+        """
5217+        if self._root_hash:
5218+            roothash = self._root_hash
5219+        else:
5220+            roothash = "\x00" * 32
5221+        return struct.pack(MDMFCHECKSTRING,
5222+                           1,
5223+                           self._seqnum,
5224+                           roothash)
5225+
5226+
5227+    def put_block(self, data, segnum, salt):
5228+        """
5229+        I queue a write vector for the data, salt, and segment number
5230+        provided to me. I return None, as I do not actually cause
5231+        anything to be written yet.
5232+        """
5233+        if segnum >= self._num_segments:
5234+            raise LayoutInvalid("I won't overwrite the private key")
5235+        if len(salt) != SALT_SIZE:
5236+            raise LayoutInvalid("I was given a salt of size %d, but "
5237+                                "I wanted a salt of size %d")
5238+        if segnum + 1 == self._num_segments:
5239+            if len(data) != self._tail_block_size:
5240+                raise LayoutInvalid("I was given the wrong size block to write")
5241+        elif len(data) != self._block_size:
5242+            raise LayoutInvalid("I was given the wrong size block to write")
5243+
5244+        # We want to write at len(MDMFHEADER) + segnum * block_size.
5245+
5246+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
5247+        data = salt + data
5248+
5249+        self._writevs.append(tuple([offset, data]))
5250+
5251+
5252+    def put_encprivkey(self, encprivkey):
5253+        """
5254+        I queue a write vector for the encrypted private key provided to
5255+        me.
5256+        """
5257+        assert self._offsets
5258+        assert self._offsets['enc_privkey']
5259+        # You shouldn't re-write the encprivkey after the block hash
5260+        # tree is written, since that could cause the private key to run
5261+        # into the block hash tree. Before it writes the block hash
5262+        # tree, the block hash tree writing method writes the offset of
5263+        # the salt hash tree. So that's a good indicator of whether or
5264+        # not the block hash tree has been written.
5265+        if "share_hash_chain" in self._offsets:
5266+            raise LayoutInvalid("You must write this before the block hash tree")
5267+
5268+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
5269+            len(encprivkey)
5270+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
5271+
5272+
5273+    def put_blockhashes(self, blockhashes):
5274+        """
5275+        I queue a write vector to put the block hash tree in blockhashes
5276+        onto the remote server.
5277+
5278+        The encrypted private key must be queued before the block hash
5279+        tree, since we need to know how large it is to know where the
5280+        block hash tree should go. The block hash tree must be put
5281+        before the salt hash tree, since its size determines the
5282+        offset of the share hash chain.
5283+        """
5284+        assert self._offsets
5285+        assert isinstance(blockhashes, list)
5286+        if "block_hash_tree" not in self._offsets:
5287+            raise LayoutInvalid("You must put the encrypted private key "
5288+                                "before you put the block hash tree")
5289+        # If written, the share hash chain causes the signature offset
5290+        # to be defined.
5291+        if "signature" in self._offsets:
5292+            raise LayoutInvalid("You must put the block hash tree before "
5293+                                "you put the share hash chain")
5294+        blockhashes_s = "".join(blockhashes)
5295+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
5296+
5297+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
5298+                                  blockhashes_s]))
5299+
5300+
5301+    def put_sharehashes(self, sharehashes):
5302+        """
5303+        I queue a write vector to put the share hash chain in my
5304+        argument onto the remote server.
5305+
5306+        The salt hash tree must be queued before the share hash chain,
5307+        since we need to know where the salt hash tree ends before we
5308+        can know where the share hash chain starts. The share hash chain
5309+        must be put before the signature, since the length of the packed
5310+        share hash chain determines the offset of the signature. Also,
5311+        semantically, you must know what the root of the salt hash tree
5312+        is before you can generate a valid signature.
5313+        """
5314+        assert isinstance(sharehashes, dict)
5315+        if "share_hash_chain" not in self._offsets:
5316+            raise LayoutInvalid("You need to put the salt hash tree before "
5317+                                "you can put the share hash chain")
5318+        # The signature comes after the share hash chain. If the
5319+        # signature has already been written, we must not write another
5320+        # share hash chain. The signature writes the verification key
5321+        # offset when it gets sent to the remote server, so we look for
5322+        # that.
5323+        if "verification_key" in self._offsets:
5324+            raise LayoutInvalid("You must write the share hash chain "
5325+                                "before you write the signature")
5326+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
5327+                                  for i in sorted(sharehashes.keys())])
5328+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
5329+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
5330+                            sharehashes_s]))
5331+
5332+
5333+    def put_root_hash(self, roothash):
5334+        """
5335+        Put the root hash (the root of the share hash tree) in the
5336+        remote slot.
5337+        """
5338+        # It does not make sense to be able to put the root
5339+        # hash without first putting the share hashes, since you need
5340+        # the share hashes to generate the root hash.
5341+        #
5342+        # Signature is defined by the routine that places the share hash
5343+        # chain, so it's a good thing to look for in finding out whether
5344+        # or not the share hash chain exists on the remote server.
5345+        if "signature" not in self._offsets:
5346+            raise LayoutInvalid("You need to put the share hash chain "
5347+                                "before you can put the root share hash")
5348+        if len(roothash) != HASH_SIZE:
5349+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
5350+                                 % HASH_SIZE)
5351+        self._root_hash = roothash
5352+        # To write both of these values, we update the checkstring on
5353+        # the remote server, which includes them
5354+        checkstring = self.get_checkstring()
5355+        self._writevs.append(tuple([0, checkstring]))
5356+        # This write, if successful, changes the checkstring, so we need
5357+        # to update our internal checkstring to be consistent with the
5358+        # one on the server.
5359+
5360+
5361+    def get_signable(self):
5362+        """
5363+        Get the first seven fields of the mutable file; the parts that
5364+        are signed.
5365+        """
5366+        if not self._root_hash:
5367+            raise LayoutInvalid("You need to set the root hash "
5368+                                "before getting something to "
5369+                                "sign")
5370+        return struct.pack(MDMFSIGNABLEHEADER,
5371+                           1,
5372+                           self._seqnum,
5373+                           self._root_hash,
5374+                           self._required_shares,
5375+                           self._total_shares,
5376+                           self._segment_size,
5377+                           self._data_length)
5378+
5379+
5380+    def put_signature(self, signature):
5381+        """
5382+        I queue a write vector for the signature of the MDMF share.
5383+
5384+        I require that the root hash and share hash chain have been put
5385+        to the grid before I will write the signature to the grid.
5386+        """
5387+        if "signature" not in self._offsets:
5388+            raise LayoutInvalid("You must put the share hash chain "
5389+        # It does not make sense to put a signature without first
5390+        # putting the root hash and the salt hash (since otherwise
5391+        # the signature would be incomplete), so we don't allow that.
5392+                       "before putting the signature")
5393+        if not self._root_hash:
5394+            raise LayoutInvalid("You must complete the signed prefix "
5395+                                "before computing a signature")
5396+        # If we put the signature after we put the verification key, we
5397+        # could end up running into the verification key, and will
5398+        # probably screw up the offsets as well. So we don't allow that.
5399+        # The method that writes the verification key defines the EOF
5400+        # offset before writing the verification key, so look for that.
5401+        if "EOF" in self._offsets:
5402+            raise LayoutInvalid("You must write the signature before the verification key")
5403+
5404+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5405+        self._writevs.append(tuple([self._offsets['signature'], signature]))
5406+
5407+
5408+    def put_verification_key(self, verification_key):
5409+        """
5410+        I queue a write vector for the verification key.
5411+
5412+        I require that the signature have been written to the storage
5413+        server before I allow the verification key to be written to the
5414+        remote server.
5415+        """
5416+        if "verification_key" not in self._offsets:
5417+            raise LayoutInvalid("You must put the signature before you "
5418+                                "can put the verification key")
5419+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5420+        self._writevs.append(tuple([self._offsets['verification_key'],
5421+                            verification_key]))
5422+
5423+
5424+    def _get_offsets_tuple(self):
5425+        return tuple([(key, value) for key, value in self._offsets.items()])
5426+
5427+
5428+    def get_verinfo(self):
5429+        return (self._seqnum,
5430+                self._root_hash,
5431+                self._required_shares,
5432+                self._total_shares,
5433+                self._segment_size,
5434+                self._data_length,
5435+                self.get_signable(),
5436+                self._get_offsets_tuple())
5437+
5438+
5439+    def finish_publishing(self):
5440+        """
5441+        I add a write vector for the offsets table, and then cause all
5442+        of the write vectors that I've dealt with so far to be published
5443+        to the remote server, ending the write process.
5444+        """
5445+        if "EOF" not in self._offsets:
5446+            raise LayoutInvalid("You must put the verification key before "
5447+                                "you can publish the offsets")
5448+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5449+        offsets = struct.pack(MDMFOFFSETS,
5450+                              self._offsets['enc_privkey'],
5451+                              self._offsets['block_hash_tree'],
5452+                              self._offsets['share_hash_chain'],
5453+                              self._offsets['signature'],
5454+                              self._offsets['verification_key'],
5455+                              self._offsets['EOF'])
5456+        self._writevs.append(tuple([offsets_offset, offsets]))
5457+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5458+        params = struct.pack(">BBQQ",
5459+                             self._required_shares,
5460+                             self._total_shares,
5461+                             self._segment_size,
5462+                             self._data_length)
5463+        self._writevs.append(tuple([encoding_parameters_offset, params]))
5464+        return self._write(self._writevs)
5465+
5466+
5467+    def _write(self, datavs, on_failure=None, on_success=None):
5468+        """I write the data vectors in datavs to the remote slot."""
5469+        tw_vectors = {}
5470+        if not self._testvs:
5471+            self._testvs = []
5472+            self._testvs.append(tuple([0, 1, "eq", ""]))
5473+        if not self._written:
5474+            # Write a new checkstring to the share when we write it, so
5475+            # that we have something to check later.
5476+            new_checkstring = self.get_checkstring()
5477+            datavs.append((0, new_checkstring))
5478+            def _first_write():
5479+                self._written = True
5480+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5481+            on_success = _first_write
5482+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5483+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5484+                                  self._storage_index,
5485+                                  self._secrets,
5486+                                  tw_vectors,
5487+                                  self._readv)
5488+        def _result(results):
5489+            if isinstance(results, failure.Failure) or not results[0]:
5490+                # Do nothing; the write was unsuccessful.
5491+                if on_failure: on_failure()
5492+            else:
5493+                if on_success: on_success()
5494+            return results
5495+        d.addCallback(_result)
5496+        return d
5497+
5498+
5499+class MDMFSlotReadProxy:
5500+    """
5501+    I read from a mutable slot filled with data written in the MDMF data
5502+    format (which is described above).
5503+
5504+    I can be initialized with some amount of data, which I will use (if
5505+    it is valid) to eliminate some of the need to fetch it from servers.
5506+    """
5507+    def __init__(self,
5508+                 rref,
5509+                 storage_index,
5510+                 shnum,
5511+                 data=""):
5512+        # Start the initialization process.
5513+        self._rref = rref
5514+        self._storage_index = storage_index
5515+        self.shnum = shnum
5516+
5517+        # Before doing anything, the reader is probably going to want to
5518+        # verify that the signature is correct. To do that, they'll need
5519+        # the verification key, and the signature. To get those, we'll
5520+        # need the offset table. So fetch the offset table on the
5521+        # assumption that that will be the first thing that a reader is
5522+        # going to do.
5523+
5524+        # The fact that these encoding parameters are None tells us
5525+        # that we haven't yet fetched them from the remote share, so we
5526+        # should. We could just not set them, but the checks will be
5527+        # easier to read if we don't have to use hasattr.
5528+        self._version_number = None
5529+        self._sequence_number = None
5530+        self._root_hash = None
5531+        # Filled in if we're dealing with an SDMF file. Unused
5532+        # otherwise.
5533+        self._salt = None
5534+        self._required_shares = None
5535+        self._total_shares = None
5536+        self._segment_size = None
5537+        self._data_length = None
5538+        self._offsets = None
5539+
5540+        # If the user has chosen to initialize us with some data, we'll
5541+        # try to satisfy subsequent data requests with that data before
5542+        # asking the storage server for it. If
5543+        self._data = data
5544+        # The way callers interact with cache in the filenode returns
5545+        # None if there isn't any cached data, but the way we index the
5546+        # cached data requires a string, so convert None to "".
5547+        if self._data == None:
5548+            self._data = ""
5549+
5550+        self._queue_observers = observer.ObserverList()
5551+        self._queue_errbacks = observer.ObserverList()
5552+        self._readvs = []
5553+
5554+
5555+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5556+        """
5557+        I fetch the offset table and the header from the remote slot if
5558+        I don't already have them. If I do have them, I do nothing and
5559+        return an empty Deferred.
5560+        """
5561+        if self._offsets:
5562+            return defer.succeed(None)
5563+        # At this point, we may be either SDMF or MDMF. Fetching 107
5564+        # bytes will be enough to get header and offsets for both SDMF and
5565+        # MDMF, though we'll be left with 4 more bytes than we
5566+        # need if this ends up being MDMF. This is probably less
5567+        # expensive than the cost of a second roundtrip.
5568+        readvs = [(0, 107)]
5569+        d = self._read(readvs, force_remote)
5570+        d.addCallback(self._process_encoding_parameters)
5571+        d.addCallback(self._process_offsets)
5572+        return d
5573+
5574+
5575+    def _process_encoding_parameters(self, encoding_parameters):
5576+        assert self.shnum in encoding_parameters
5577+        encoding_parameters = encoding_parameters[self.shnum][0]
5578+        # The first byte is the version number. It will tell us what
5579+        # to do next.
5580+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5581+        if verno == MDMF_VERSION:
5582+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5583+            (verno,
5584+             seqnum,
5585+             root_hash,
5586+             k,
5587+             n,
5588+             segsize,
5589+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5590+                                      encoding_parameters[:read_size])
5591+            if segsize == 0 and datalen == 0:
5592+                # Empty file, no segments.
5593+                self._num_segments = 0
5594+            else:
5595+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5596+
5597+        elif verno == SDMF_VERSION:
5598+            read_size = SIGNED_PREFIX_LENGTH
5599+            (verno,
5600+             seqnum,
5601+             root_hash,
5602+             salt,
5603+             k,
5604+             n,
5605+             segsize,
5606+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5607+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5608+            self._salt = salt
5609+            if segsize == 0 and datalen == 0:
5610+                # empty file
5611+                self._num_segments = 0
5612+            else:
5613+                # non-empty SDMF files have one segment.
5614+                self._num_segments = 1
5615+        else:
5616+            raise UnknownVersionError("You asked me to read mutable file "
5617+                                      "version %d, but I only understand "
5618+                                      "%d and %d" % (verno, SDMF_VERSION,
5619+                                                     MDMF_VERSION))
5620+
5621+        self._version_number = verno
5622+        self._sequence_number = seqnum
5623+        self._root_hash = root_hash
5624+        self._required_shares = k
5625+        self._total_shares = n
5626+        self._segment_size = segsize
5627+        self._data_length = datalen
5628+
5629+        self._block_size = self._segment_size / self._required_shares
5630+        # We can upload empty files, and need to account for this fact
5631+        # so as to avoid zero-division and zero-modulo errors.
5632+        if datalen > 0:
5633+            tail_size = self._data_length % self._segment_size
5634+        else:
5635+            tail_size = 0
5636+        if not tail_size:
5637+            self._tail_block_size = self._block_size
5638+        else:
5639+            self._tail_block_size = mathutil.next_multiple(tail_size,
5640+                                                    self._required_shares)
5641+            self._tail_block_size /= self._required_shares
5642+
5643+        return encoding_parameters
5644+
5645+
5646+    def _process_offsets(self, offsets):
5647+        if self._version_number == 0:
5648+            read_size = OFFSETS_LENGTH
5649+            read_offset = SIGNED_PREFIX_LENGTH
5650+            end = read_size + read_offset
5651+            (signature,
5652+             share_hash_chain,
5653+             block_hash_tree,
5654+             share_data,
5655+             enc_privkey,
5656+             EOF) = struct.unpack(">LLLLQQ",
5657+                                  offsets[read_offset:end])
5658+            self._offsets = {}
5659+            self._offsets['signature'] = signature
5660+            self._offsets['share_data'] = share_data
5661+            self._offsets['block_hash_tree'] = block_hash_tree
5662+            self._offsets['share_hash_chain'] = share_hash_chain
5663+            self._offsets['enc_privkey'] = enc_privkey
5664+            self._offsets['EOF'] = EOF
5665+
5666+        elif self._version_number == 1:
5667+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5668+            read_length = MDMFOFFSETS_LENGTH
5669+            end = read_offset + read_length
5670+            (encprivkey,
5671+             blockhashes,
5672+             sharehashes,
5673+             signature,
5674+             verification_key,
5675+             eof) = struct.unpack(MDMFOFFSETS,
5676+                                  offsets[read_offset:end])
5677+            self._offsets = {}
5678+            self._offsets['enc_privkey'] = encprivkey
5679+            self._offsets['block_hash_tree'] = blockhashes
5680+            self._offsets['share_hash_chain'] = sharehashes
5681+            self._offsets['signature'] = signature
5682+            self._offsets['verification_key'] = verification_key
5683+            self._offsets['EOF'] = eof
5684+
5685+
5686+    def get_block_and_salt(self, segnum, queue=False):
5687+        """
5688+        I return (block, salt), where block is the block data and
5689+        salt is the salt used to encrypt that segment.
5690+        """
5691+        d = self._maybe_fetch_offsets_and_header()
5692+        def _then(ignored):
5693+            if self._version_number == 1:
5694+                base_share_offset = MDMFHEADERSIZE
5695+            else:
5696+                base_share_offset = self._offsets['share_data']
5697+
5698+            if segnum + 1 > self._num_segments:
5699+                raise LayoutInvalid("Not a valid segment number")
5700+
5701+            if self._version_number == 0:
5702+                share_offset = base_share_offset + self._block_size * segnum
5703+            else:
5704+                share_offset = base_share_offset + (self._block_size + \
5705+                                                    SALT_SIZE) * segnum
5706+            if segnum + 1 == self._num_segments:
5707+                data = self._tail_block_size
5708+            else:
5709+                data = self._block_size
5710+
5711+            if self._version_number == 1:
5712+                data += SALT_SIZE
5713+
5714+            readvs = [(share_offset, data)]
5715+            return readvs
5716+        d.addCallback(_then)
5717+        d.addCallback(lambda readvs:
5718+            self._read(readvs, queue=queue))
5719+        def _process_results(results):
5720+            assert self.shnum in results
5721+            if self._version_number == 0:
5722+                # We only read the share data, but we know the salt from
5723+                # when we fetched the header
5724+                data = results[self.shnum]
5725+                if not data:
5726+                    data = ""
5727+                else:
5728+                    assert len(data) == 1
5729+                    data = data[0]
5730+                salt = self._salt
5731+            else:
5732+                data = results[self.shnum]
5733+                if not data:
5734+                    salt = data = ""
5735+                else:
5736+                    salt_and_data = results[self.shnum][0]
5737+                    salt = salt_and_data[:SALT_SIZE]
5738+                    data = salt_and_data[SALT_SIZE:]
5739+            return data, salt
5740+        d.addCallback(_process_results)
5741+        return d
5742+
5743+
5744+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5745+        """
5746+        I return the block hash tree
5747+
5748+        I take an optional argument, needed, which is a set of indices
5749+        correspond to hashes that I should fetch. If this argument is
5750+        missing, I will fetch the entire block hash tree; otherwise, I
5751+        may attempt to fetch fewer hashes, based on what needed says
5752+        that I should do. Note that I may fetch as many hashes as I
5753+        want, so long as the set of hashes that I do fetch is a superset
5754+        of the ones that I am asked for, so callers should be prepared
5755+        to tolerate additional hashes.
5756+        """
5757+        # TODO: Return only the parts of the block hash tree necessary
5758+        # to validate the blocknum provided?
5759+        # This is a good idea, but it is hard to implement correctly. It
5760+        # is bad to fetch any one block hash more than once, so we
5761+        # probably just want to fetch the whole thing at once and then
5762+        # serve it.
5763+        if needed == set([]):
5764+            return defer.succeed([])
5765+        d = self._maybe_fetch_offsets_and_header()
5766+        def _then(ignored):
5767+            blockhashes_offset = self._offsets['block_hash_tree']
5768+            if self._version_number == 1:
5769+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5770+            else:
5771+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5772+            readvs = [(blockhashes_offset, blockhashes_length)]
5773+            return readvs
5774+        d.addCallback(_then)
5775+        d.addCallback(lambda readvs:
5776+            self._read(readvs, queue=queue, force_remote=force_remote))
5777+        def _build_block_hash_tree(results):
5778+            assert self.shnum in results
5779+
5780+            rawhashes = results[self.shnum][0]
5781+            results = [rawhashes[i:i+HASH_SIZE]
5782+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5783+            return results
5784+        d.addCallback(_build_block_hash_tree)
5785+        return d
5786+
5787+
5788+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5789+        """
5790+        I return the part of the share hash chain placed to validate
5791+        this share.
5792+
5793+        I take an optional argument, needed. Needed is a set of indices
5794+        that correspond to the hashes that I should fetch. If needed is
5795+        not present, I will fetch and return the entire share hash
5796+        chain. Otherwise, I may fetch and return any part of the share
5797+        hash chain that is a superset of the part that I am asked to
5798+        fetch. Callers should be prepared to deal with more hashes than
5799+        they've asked for.
5800+        """
5801+        if needed == set([]):
5802+            return defer.succeed([])
5803+        d = self._maybe_fetch_offsets_and_header()
5804+
5805+        def _make_readvs(ignored):
5806+            sharehashes_offset = self._offsets['share_hash_chain']
5807+            if self._version_number == 0:
5808+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5809+            else:
5810+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5811+            readvs = [(sharehashes_offset, sharehashes_length)]
5812+            return readvs
5813+        d.addCallback(_make_readvs)
5814+        d.addCallback(lambda readvs:
5815+            self._read(readvs, queue=queue, force_remote=force_remote))
5816+        def _build_share_hash_chain(results):
5817+            assert self.shnum in results
5818+
5819+            sharehashes = results[self.shnum][0]
5820+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5821+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5822+            results = dict([struct.unpack(">H32s", data)
5823+                            for data in results])
5824+            return results
5825+        d.addCallback(_build_share_hash_chain)
5826+        return d
5827+
5828+
5829+    def get_encprivkey(self, queue=False):
5830+        """
5831+        I return the encrypted private key.
5832+        """
5833+        d = self._maybe_fetch_offsets_and_header()
5834+
5835+        def _make_readvs(ignored):
5836+            privkey_offset = self._offsets['enc_privkey']
5837+            if self._version_number == 0:
5838+                privkey_length = self._offsets['EOF'] - privkey_offset
5839+            else:
5840+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5841+            readvs = [(privkey_offset, privkey_length)]
5842+            return readvs
5843+        d.addCallback(_make_readvs)
5844+        d.addCallback(lambda readvs:
5845+            self._read(readvs, queue=queue))
5846+        def _process_results(results):
5847+            assert self.shnum in results
5848+            privkey = results[self.shnum][0]
5849+            return privkey
5850+        d.addCallback(_process_results)
5851+        return d
5852+
5853+
5854+    def get_signature(self, queue=False):
5855+        """
5856+        I return the signature of my share.
5857+        """
5858+        d = self._maybe_fetch_offsets_and_header()
5859+
5860+        def _make_readvs(ignored):
5861+            signature_offset = self._offsets['signature']
5862+            if self._version_number == 1:
5863+                signature_length = self._offsets['verification_key'] - signature_offset
5864+            else:
5865+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5866+            readvs = [(signature_offset, signature_length)]
5867+            return readvs
5868+        d.addCallback(_make_readvs)
5869+        d.addCallback(lambda readvs:
5870+            self._read(readvs, queue=queue))
5871+        def _process_results(results):
5872+            assert self.shnum in results
5873+            signature = results[self.shnum][0]
5874+            return signature
5875+        d.addCallback(_process_results)
5876+        return d
5877+
5878+
5879+    def get_verification_key(self, queue=False):
5880+        """
5881+        I return the verification key.
5882+        """
5883+        d = self._maybe_fetch_offsets_and_header()
5884+
5885+        def _make_readvs(ignored):
5886+            if self._version_number == 1:
5887+                vk_offset = self._offsets['verification_key']
5888+                vk_length = self._offsets['EOF'] - vk_offset
5889+            else:
5890+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5891+                vk_length = self._offsets['signature'] - vk_offset
5892+            readvs = [(vk_offset, vk_length)]
5893+            return readvs
5894+        d.addCallback(_make_readvs)
5895+        d.addCallback(lambda readvs:
5896+            self._read(readvs, queue=queue))
5897+        def _process_results(results):
5898+            assert self.shnum in results
5899+            verification_key = results[self.shnum][0]
5900+            return verification_key
5901+        d.addCallback(_process_results)
5902+        return d
5903+
5904+
5905+    def get_encoding_parameters(self):
5906+        """
5907+        I return (k, n, segsize, datalen)
5908+        """
5909+        d = self._maybe_fetch_offsets_and_header()
5910+        d.addCallback(lambda ignored:
5911+            (self._required_shares,
5912+             self._total_shares,
5913+             self._segment_size,
5914+             self._data_length))
5915+        return d
5916+
5917+
5918+    def get_seqnum(self):
5919+        """
5920+        I return the sequence number for this share.
5921+        """
5922+        d = self._maybe_fetch_offsets_and_header()
5923+        d.addCallback(lambda ignored:
5924+            self._sequence_number)
5925+        return d
5926+
5927+
5928+    def get_root_hash(self):
5929+        """
5930+        I return the root of the block hash tree
5931+        """
5932+        d = self._maybe_fetch_offsets_and_header()
5933+        d.addCallback(lambda ignored: self._root_hash)
5934+        return d
5935+
5936+
5937+    def get_checkstring(self):
5938+        """
5939+        I return the packed representation of the following:
5940+
5941+            - version number
5942+            - sequence number
5943+            - root hash
5944+            - salt hash
5945+
5946+        which my users use as a checkstring to detect other writers.
5947+        """
5948+        d = self._maybe_fetch_offsets_and_header()
5949+        def _build_checkstring(ignored):
5950+            if self._salt:
5951+                checkstring = struct.pack(PREFIX,
5952+                                          self._version_number,
5953+                                          self._sequence_number,
5954+                                          self._root_hash,
5955+                                          self._salt)
5956+            else:
5957+                checkstring = struct.pack(MDMFCHECKSTRING,
5958+                                          self._version_number,
5959+                                          self._sequence_number,
5960+                                          self._root_hash)
5961+
5962+            return checkstring
5963+        d.addCallback(_build_checkstring)
5964+        return d
5965+
5966+
5967+    def get_prefix(self, force_remote):
5968+        d = self._maybe_fetch_offsets_and_header(force_remote)
5969+        d.addCallback(lambda ignored:
5970+            self._build_prefix())
5971+        return d
5972+
5973+
5974+    def _build_prefix(self):
5975+        # The prefix is another name for the part of the remote share
5976+        # that gets signed. It consists of everything up to and
5977+        # including the datalength, packed by struct.
5978+        if self._version_number == SDMF_VERSION:
5979+            return struct.pack(SIGNED_PREFIX,
5980+                           self._version_number,
5981+                           self._sequence_number,
5982+                           self._root_hash,
5983+                           self._salt,
5984+                           self._required_shares,
5985+                           self._total_shares,
5986+                           self._segment_size,
5987+                           self._data_length)
5988+
5989+        else:
5990+            return struct.pack(MDMFSIGNABLEHEADER,
5991+                           self._version_number,
5992+                           self._sequence_number,
5993+                           self._root_hash,
5994+                           self._required_shares,
5995+                           self._total_shares,
5996+                           self._segment_size,
5997+                           self._data_length)
5998+
5999+
6000+    def _get_offsets_tuple(self):
6001+        # The offsets tuple is another component of the version
6002+        # information tuple. It is basically our offsets dictionary,
6003+        # itemized and in a tuple.
6004+        return self._offsets.copy()
6005+
6006+
6007+    def get_verinfo(self):
6008+        """
6009+        I return my verinfo tuple. This is used by the ServermapUpdater
6010+        to keep track of versions of mutable files.
6011+
6012+        The verinfo tuple for MDMF files contains:
6013+            - seqnum
6014+            - root hash
6015+            - a blank (nothing)
6016+            - segsize
6017+            - datalen
6018+            - k
6019+            - n
6020+            - prefix (the thing that you sign)
6021+            - a tuple of offsets
6022+
6023+        We include the nonce in MDMF to simplify processing of version
6024+        information tuples.
6025+
6026+        The verinfo tuple for SDMF files is the same, but contains a
6027+        16-byte IV instead of a hash of salts.
6028+        """
6029+        d = self._maybe_fetch_offsets_and_header()
6030+        def _build_verinfo(ignored):
6031+            if self._version_number == SDMF_VERSION:
6032+                salt_to_use = self._salt
6033+            else:
6034+                salt_to_use = None
6035+            return (self._sequence_number,
6036+                    self._root_hash,
6037+                    salt_to_use,
6038+                    self._segment_size,
6039+                    self._data_length,
6040+                    self._required_shares,
6041+                    self._total_shares,
6042+                    self._build_prefix(),
6043+                    self._get_offsets_tuple())
6044+        d.addCallback(_build_verinfo)
6045+        return d
6046+
6047+
6048+    def flush(self):
6049+        """
6050+        I flush my queue of read vectors.
6051+        """
6052+        d = self._read(self._readvs)
6053+        def _then(results):
6054+            self._readvs = []
6055+            if isinstance(results, failure.Failure):
6056+                self._queue_errbacks.notify(results)
6057+            else:
6058+                self._queue_observers.notify(results)
6059+            self._queue_observers = observer.ObserverList()
6060+            self._queue_errbacks = observer.ObserverList()
6061+        d.addBoth(_then)
6062+
6063+
6064+    def _read(self, readvs, force_remote=False, queue=False):
6065+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
6066+        # TODO: It's entirely possible to tweak this so that it just
6067+        # fulfills the requests that it can, and not demand that all
6068+        # requests are satisfiable before running it.
6069+        if not unsatisfiable and not force_remote:
6070+            results = [self._data[offset:offset+length]
6071+                       for (offset, length) in readvs]
6072+            results = {self.shnum: results}
6073+            return defer.succeed(results)
6074+        else:
6075+            if queue:
6076+                start = len(self._readvs)
6077+                self._readvs += readvs
6078+                end = len(self._readvs)
6079+                def _get_results(results, start, end):
6080+                    if not self.shnum in results:
6081+                        return {self._shnum: [""]}
6082+                    return {self.shnum: results[self.shnum][start:end]}
6083+                d = defer.Deferred()
6084+                d.addCallback(_get_results, start, end)
6085+                self._queue_observers.subscribe(d.callback)
6086+                self._queue_errbacks.subscribe(d.errback)
6087+                return d
6088+            return self._rref.callRemote("slot_readv",
6089+                                         self._storage_index,
6090+                                         [self.shnum],
6091+                                         readvs)
6092+
6093+
6094+    def is_sdmf(self):
6095+        """I tell my caller whether or not my remote file is SDMF or MDMF
6096+        """
6097+        d = self._maybe_fetch_offsets_and_header()
6098+        d.addCallback(lambda ignored:
6099+            self._version_number == 0)
6100+        return d
6101+
6102+
6103+class LayoutInvalid(Exception):
6104+    """
6105+    This isn't a valid MDMF mutable file
6106+    """
6107merger 0.0 (
6108hunk ./src/allmydata/test/test_storage.py 3
6109-from allmydata.util import log
6110-
6111merger 0.0 (
6112hunk ./src/allmydata/test/test_storage.py 3
6113-import time, os.path, stat, re, simplejson, struct
6114+from allmydata.util import log
6115+
6116+import mock
6117hunk ./src/allmydata/test/test_storage.py 3
6118-import time, os.path, stat, re, simplejson, struct
6119+import time, os.path, stat, re, simplejson, struct, shutil
6120)
6121)
6122hunk ./src/allmydata/test/test_storage.py 23
6123 from allmydata.storage.expirer import LeaseCheckingCrawler
6124 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
6125      ReadBucketProxy
6126-from allmydata.interfaces import BadWriteEnablerError
6127-from allmydata.test.common import LoggingServiceParent
6128+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
6129+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
6130+                                     SIGNED_PREFIX, MDMFHEADER, \
6131+                                     MDMFOFFSETS, SDMFSlotWriteProxy
6132+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
6133+                                 SDMF_VERSION
6134+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
6135 from allmydata.test.common_web import WebRenderingMixin
6136 from allmydata.web.storage import StorageStatus, remove_prefix
6137 
6138hunk ./src/allmydata/test/test_storage.py 107
6139 
6140 class RemoteBucket:
6141 
6142+    def __init__(self):
6143+        self.read_count = 0
6144+        self.write_count = 0
6145+
6146     def callRemote(self, methname, *args, **kwargs):
6147         def _call():
6148             meth = getattr(self.target, "remote_" + methname)
6149hunk ./src/allmydata/test/test_storage.py 115
6150             return meth(*args, **kwargs)
6151+
6152+        if methname == "slot_readv":
6153+            self.read_count += 1
6154+        if "writev" in methname:
6155+            self.write_count += 1
6156+
6157         return defer.maybeDeferred(_call)
6158 
6159hunk ./src/allmydata/test/test_storage.py 123
6160+
6161 class BucketProxy(unittest.TestCase):
6162     def make_bucket(self, name, size):
6163         basedir = os.path.join("storage", "BucketProxy", name)
6164hunk ./src/allmydata/test/test_storage.py 1306
6165         self.failUnless(os.path.exists(prefixdir), prefixdir)
6166         self.failIf(os.path.exists(bucketdir), bucketdir)
6167 
6168+
6169+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
6170+    def setUp(self):
6171+        self.sparent = LoggingServiceParent()
6172+        self._lease_secret = itertools.count()
6173+        self.ss = self.create("MDMFProxies storage test server")
6174+        self.rref = RemoteBucket()
6175+        self.rref.target = self.ss
6176+        self.secrets = (self.write_enabler("we_secret"),
6177+                        self.renew_secret("renew_secret"),
6178+                        self.cancel_secret("cancel_secret"))
6179+        self.segment = "aaaaaa"
6180+        self.block = "aa"
6181+        self.salt = "a" * 16
6182+        self.block_hash = "a" * 32
6183+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
6184+        self.share_hash = self.block_hash
6185+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
6186+        self.signature = "foobarbaz"
6187+        self.verification_key = "vvvvvv"
6188+        self.encprivkey = "private"
6189+        self.root_hash = self.block_hash
6190+        self.salt_hash = self.root_hash
6191+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
6192+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
6193+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
6194+        # blockhashes and salt hashes are serialized in the same way,
6195+        # only we lop off the first element and store that in the
6196+        # header.
6197+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
6198+
6199+
6200+    def tearDown(self):
6201+        self.sparent.stopService()
6202+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
6203+
6204+
6205+    def write_enabler(self, we_tag):
6206+        return hashutil.tagged_hash("we_blah", we_tag)
6207+
6208+
6209+    def renew_secret(self, tag):
6210+        return hashutil.tagged_hash("renew_blah", str(tag))
6211+
6212+
6213+    def cancel_secret(self, tag):
6214+        return hashutil.tagged_hash("cancel_blah", str(tag))
6215+
6216+
6217+    def workdir(self, name):
6218+        basedir = os.path.join("storage", "MutableServer", name)
6219+        return basedir
6220+
6221+
6222+    def create(self, name):
6223+        workdir = self.workdir(name)
6224+        ss = StorageServer(workdir, "\x00" * 20)
6225+        ss.setServiceParent(self.sparent)
6226+        return ss
6227+
6228+
6229+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
6230+        # Start with the checkstring
6231+        data = struct.pack(">BQ32s",
6232+                           1,
6233+                           0,
6234+                           self.root_hash)
6235+        self.checkstring = data
6236+        # Next, the encoding parameters
6237+        if tail_segment:
6238+            data += struct.pack(">BBQQ",
6239+                                3,
6240+                                10,
6241+                                6,
6242+                                33)
6243+        elif empty:
6244+            data += struct.pack(">BBQQ",
6245+                                3,
6246+                                10,
6247+                                0,
6248+                                0)
6249+        else:
6250+            data += struct.pack(">BBQQ",
6251+                                3,
6252+                                10,
6253+                                6,
6254+                                36)
6255+        # Now we'll build the offsets.
6256+        sharedata = ""
6257+        if not tail_segment and not empty:
6258+            for i in xrange(6):
6259+                sharedata += self.salt + self.block
6260+        elif tail_segment:
6261+            for i in xrange(5):
6262+                sharedata += self.salt + self.block
6263+            sharedata += self.salt + "a"
6264+
6265+        # The encrypted private key comes after the shares + salts
6266+        offset_size = struct.calcsize(MDMFOFFSETS)
6267+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
6268+        # The blockhashes come after the private key
6269+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
6270+        # The sharehashes come after the salt hashes
6271+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
6272+        # The signature comes after the share hash chain
6273+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
6274+        # The verification key comes after the signature
6275+        verification_offset = signature_offset + len(self.signature)
6276+        # The EOF comes after the verification key
6277+        eof_offset = verification_offset + len(self.verification_key)
6278+        data += struct.pack(MDMFOFFSETS,
6279+                            encrypted_private_key_offset,
6280+                            blockhashes_offset,
6281+                            sharehashes_offset,
6282+                            signature_offset,
6283+                            verification_offset,
6284+                            eof_offset)
6285+        self.offsets = {}
6286+        self.offsets['enc_privkey'] = encrypted_private_key_offset
6287+        self.offsets['block_hash_tree'] = blockhashes_offset
6288+        self.offsets['share_hash_chain'] = sharehashes_offset
6289+        self.offsets['signature'] = signature_offset
6290+        self.offsets['verification_key'] = verification_offset
6291+        self.offsets['EOF'] = eof_offset
6292+        # Next, we'll add in the salts and share data,
6293+        data += sharedata
6294+        # the private key,
6295+        data += self.encprivkey
6296+        # the block hash tree,
6297+        data += self.block_hash_tree_s
6298+        # the share hash chain,
6299+        data += self.share_hash_chain_s
6300+        # the signature,
6301+        data += self.signature
6302+        # and the verification key
6303+        data += self.verification_key
6304+        return data
6305+
6306+
6307+    def write_test_share_to_server(self,
6308+                                   storage_index,
6309+                                   tail_segment=False,
6310+                                   empty=False):
6311+        """
6312+        I write some data for the read tests to read to self.ss
6313+
6314+        If tail_segment=True, then I will write a share that has a
6315+        smaller tail segment than other segments.
6316+        """
6317+        write = self.ss.remote_slot_testv_and_readv_and_writev
6318+        data = self.build_test_mdmf_share(tail_segment, empty)
6319+        # Finally, we write the whole thing to the storage server in one
6320+        # pass.
6321+        testvs = [(0, 1, "eq", "")]
6322+        tws = {}
6323+        tws[0] = (testvs, [(0, data)], None)
6324+        readv = [(0, 1)]
6325+        results = write(storage_index, self.secrets, tws, readv)
6326+        self.failUnless(results[0])
6327+
6328+
6329+    def build_test_sdmf_share(self, empty=False):
6330+        if empty:
6331+            sharedata = ""
6332+        else:
6333+            sharedata = self.segment * 6
6334+        self.sharedata = sharedata
6335+        blocksize = len(sharedata) / 3
6336+        block = sharedata[:blocksize]
6337+        self.blockdata = block
6338+        prefix = struct.pack(">BQ32s16s BBQQ",
6339+                             0, # version,
6340+                             0,
6341+                             self.root_hash,
6342+                             self.salt,
6343+                             3,
6344+                             10,
6345+                             len(sharedata),
6346+                             len(sharedata),
6347+                            )
6348+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
6349+        signature_offset = post_offset + len(self.verification_key)
6350+        sharehashes_offset = signature_offset + len(self.signature)
6351+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
6352+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
6353+        encprivkey_offset = sharedata_offset + len(block)
6354+        eof_offset = encprivkey_offset + len(self.encprivkey)
6355+        offsets = struct.pack(">LLLLQQ",
6356+                              signature_offset,
6357+                              sharehashes_offset,
6358+                              blockhashes_offset,
6359+                              sharedata_offset,
6360+                              encprivkey_offset,
6361+                              eof_offset)
6362+        final_share = "".join([prefix,
6363+                           offsets,
6364+                           self.verification_key,
6365+                           self.signature,
6366+                           self.share_hash_chain_s,
6367+                           self.block_hash_tree_s,
6368+                           block,
6369+                           self.encprivkey])
6370+        self.offsets = {}
6371+        self.offsets['signature'] = signature_offset
6372+        self.offsets['share_hash_chain'] = sharehashes_offset
6373+        self.offsets['block_hash_tree'] = blockhashes_offset
6374+        self.offsets['share_data'] = sharedata_offset
6375+        self.offsets['enc_privkey'] = encprivkey_offset
6376+        self.offsets['EOF'] = eof_offset
6377+        return final_share
6378+
6379+
6380+    def write_sdmf_share_to_server(self,
6381+                                   storage_index,
6382+                                   empty=False):
6383+        # Some tests need SDMF shares to verify that we can still
6384+        # read them. This method writes one, which resembles but is not
6385+        assert self.rref
6386+        write = self.ss.remote_slot_testv_and_readv_and_writev
6387+        share = self.build_test_sdmf_share(empty)
6388+        testvs = [(0, 1, "eq", "")]
6389+        tws = {}
6390+        tws[0] = (testvs, [(0, share)], None)
6391+        readv = []
6392+        results = write(storage_index, self.secrets, tws, readv)
6393+        self.failUnless(results[0])
6394+
6395+
6396+    def test_read(self):
6397+        self.write_test_share_to_server("si1")
6398+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6399+        # Check that every method equals what we expect it to.
6400+        d = defer.succeed(None)
6401+        def _check_block_and_salt((block, salt)):
6402+            self.failUnlessEqual(block, self.block)
6403+            self.failUnlessEqual(salt, self.salt)
6404+
6405+        for i in xrange(6):
6406+            d.addCallback(lambda ignored, i=i:
6407+                mr.get_block_and_salt(i))
6408+            d.addCallback(_check_block_and_salt)
6409+
6410+        d.addCallback(lambda ignored:
6411+            mr.get_encprivkey())
6412+        d.addCallback(lambda encprivkey:
6413+            self.failUnlessEqual(self.encprivkey, encprivkey))
6414+
6415+        d.addCallback(lambda ignored:
6416+            mr.get_blockhashes())
6417+        d.addCallback(lambda blockhashes:
6418+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6419+
6420+        d.addCallback(lambda ignored:
6421+            mr.get_sharehashes())
6422+        d.addCallback(lambda sharehashes:
6423+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6424+
6425+        d.addCallback(lambda ignored:
6426+            mr.get_signature())
6427+        d.addCallback(lambda signature:
6428+            self.failUnlessEqual(signature, self.signature))
6429+
6430+        d.addCallback(lambda ignored:
6431+            mr.get_verification_key())
6432+        d.addCallback(lambda verification_key:
6433+            self.failUnlessEqual(verification_key, self.verification_key))
6434+
6435+        d.addCallback(lambda ignored:
6436+            mr.get_seqnum())
6437+        d.addCallback(lambda seqnum:
6438+            self.failUnlessEqual(seqnum, 0))
6439+
6440+        d.addCallback(lambda ignored:
6441+            mr.get_root_hash())
6442+        d.addCallback(lambda root_hash:
6443+            self.failUnlessEqual(self.root_hash, root_hash))
6444+
6445+        d.addCallback(lambda ignored:
6446+            mr.get_seqnum())
6447+        d.addCallback(lambda seqnum:
6448+            self.failUnlessEqual(0, seqnum))
6449+
6450+        d.addCallback(lambda ignored:
6451+            mr.get_encoding_parameters())
6452+        def _check_encoding_parameters((k, n, segsize, datalen)):
6453+            self.failUnlessEqual(k, 3)
6454+            self.failUnlessEqual(n, 10)
6455+            self.failUnlessEqual(segsize, 6)
6456+            self.failUnlessEqual(datalen, 36)
6457+        d.addCallback(_check_encoding_parameters)
6458+
6459+        d.addCallback(lambda ignored:
6460+            mr.get_checkstring())
6461+        d.addCallback(lambda checkstring:
6462+            self.failUnlessEqual(checkstring, checkstring))
6463+        return d
6464+
6465+
6466+    def test_read_with_different_tail_segment_size(self):
6467+        self.write_test_share_to_server("si1", tail_segment=True)
6468+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6469+        d = mr.get_block_and_salt(5)
6470+        def _check_tail_segment(results):
6471+            block, salt = results
6472+            self.failUnlessEqual(len(block), 1)
6473+            self.failUnlessEqual(block, "a")
6474+        d.addCallback(_check_tail_segment)
6475+        return d
6476+
6477+
6478+    def test_get_block_with_invalid_segnum(self):
6479+        self.write_test_share_to_server("si1")
6480+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6481+        d = defer.succeed(None)
6482+        d.addCallback(lambda ignored:
6483+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6484+                            None,
6485+                            mr.get_block_and_salt, 7))
6486+        return d
6487+
6488+
6489+    def test_get_encoding_parameters_first(self):
6490+        self.write_test_share_to_server("si1")
6491+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6492+        d = mr.get_encoding_parameters()
6493+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6494+            self.failUnlessEqual(k, 3)
6495+            self.failUnlessEqual(n, 10)
6496+            self.failUnlessEqual(segment_size, 6)
6497+            self.failUnlessEqual(datalen, 36)
6498+        d.addCallback(_check_encoding_parameters)
6499+        return d
6500+
6501+
6502+    def test_get_seqnum_first(self):
6503+        self.write_test_share_to_server("si1")
6504+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6505+        d = mr.get_seqnum()
6506+        d.addCallback(lambda seqnum:
6507+            self.failUnlessEqual(seqnum, 0))
6508+        return d
6509+
6510+
6511+    def test_get_root_hash_first(self):
6512+        self.write_test_share_to_server("si1")
6513+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6514+        d = mr.get_root_hash()
6515+        d.addCallback(lambda root_hash:
6516+            self.failUnlessEqual(root_hash, self.root_hash))
6517+        return d
6518+
6519+
6520+    def test_get_checkstring_first(self):
6521+        self.write_test_share_to_server("si1")
6522+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6523+        d = mr.get_checkstring()
6524+        d.addCallback(lambda checkstring:
6525+            self.failUnlessEqual(checkstring, self.checkstring))
6526+        return d
6527+
6528+
6529+    def test_write_read_vectors(self):
6530+        # When writing for us, the storage server will return to us a
6531+        # read vector, along with its result. If a write fails because
6532+        # the test vectors failed, this read vector can help us to
6533+        # diagnose the problem. This test ensures that the read vector
6534+        # is working appropriately.
6535+        mw = self._make_new_mw("si1", 0)
6536+
6537+        for i in xrange(6):
6538+            mw.put_block(self.block, i, self.salt)
6539+        mw.put_encprivkey(self.encprivkey)
6540+        mw.put_blockhashes(self.block_hash_tree)
6541+        mw.put_sharehashes(self.share_hash_chain)
6542+        mw.put_root_hash(self.root_hash)
6543+        mw.put_signature(self.signature)
6544+        mw.put_verification_key(self.verification_key)
6545+        d = mw.finish_publishing()
6546+        def _then(results):
6547+            self.failUnless(len(results), 2)
6548+            result, readv = results
6549+            self.failUnless(result)
6550+            self.failIf(readv)
6551+            self.old_checkstring = mw.get_checkstring()
6552+            mw.set_checkstring("")
6553+        d.addCallback(_then)
6554+        d.addCallback(lambda ignored:
6555+            mw.finish_publishing())
6556+        def _then_again(results):
6557+            self.failUnlessEqual(len(results), 2)
6558+            result, readvs = results
6559+            self.failIf(result)
6560+            self.failUnlessIn(0, readvs)
6561+            readv = readvs[0][0]
6562+            self.failUnlessEqual(readv, self.old_checkstring)
6563+        d.addCallback(_then_again)
6564+        # The checkstring remains the same for the rest of the process.
6565+        return d
6566+
6567+
6568+    def test_blockhashes_after_share_hash_chain(self):
6569+        mw = self._make_new_mw("si1", 0)
6570+        d = defer.succeed(None)
6571+        # Put everything up to and including the share hash chain
6572+        for i in xrange(6):
6573+            d.addCallback(lambda ignored, i=i:
6574+                mw.put_block(self.block, i, self.salt))
6575+        d.addCallback(lambda ignored:
6576+            mw.put_encprivkey(self.encprivkey))
6577+        d.addCallback(lambda ignored:
6578+            mw.put_blockhashes(self.block_hash_tree))
6579+        d.addCallback(lambda ignored:
6580+            mw.put_sharehashes(self.share_hash_chain))
6581+
6582+        # Now try to put the block hash tree again.
6583+        d.addCallback(lambda ignored:
6584+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6585+                            None,
6586+                            mw.put_blockhashes, self.block_hash_tree))
6587+        return d
6588+
6589+
6590+    def test_encprivkey_after_blockhashes(self):
6591+        mw = self._make_new_mw("si1", 0)
6592+        d = defer.succeed(None)
6593+        # Put everything up to and including the block hash tree
6594+        for i in xrange(6):
6595+            d.addCallback(lambda ignored, i=i:
6596+                mw.put_block(self.block, i, self.salt))
6597+        d.addCallback(lambda ignored:
6598+            mw.put_encprivkey(self.encprivkey))
6599+        d.addCallback(lambda ignored:
6600+            mw.put_blockhashes(self.block_hash_tree))
6601+        d.addCallback(lambda ignored:
6602+            self.shouldFail(LayoutInvalid, "out of order private key",
6603+                            None,
6604+                            mw.put_encprivkey, self.encprivkey))
6605+        return d
6606+
6607+
6608+    def test_share_hash_chain_after_signature(self):
6609+        mw = self._make_new_mw("si1", 0)
6610+        d = defer.succeed(None)
6611+        # Put everything up to and including the signature
6612+        for i in xrange(6):
6613+            d.addCallback(lambda ignored, i=i:
6614+                mw.put_block(self.block, i, self.salt))
6615+        d.addCallback(lambda ignored:
6616+            mw.put_encprivkey(self.encprivkey))
6617+        d.addCallback(lambda ignored:
6618+            mw.put_blockhashes(self.block_hash_tree))
6619+        d.addCallback(lambda ignored:
6620+            mw.put_sharehashes(self.share_hash_chain))
6621+        d.addCallback(lambda ignored:
6622+            mw.put_root_hash(self.root_hash))
6623+        d.addCallback(lambda ignored:
6624+            mw.put_signature(self.signature))
6625+        # Now try to put the share hash chain again. This should fail
6626+        d.addCallback(lambda ignored:
6627+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6628+                            None,
6629+                            mw.put_sharehashes, self.share_hash_chain))
6630+        return d
6631+
6632+
6633+    def test_signature_after_verification_key(self):
6634+        mw = self._make_new_mw("si1", 0)
6635+        d = defer.succeed(None)
6636+        # Put everything up to and including the verification key.
6637+        for i in xrange(6):
6638+            d.addCallback(lambda ignored, i=i:
6639+                mw.put_block(self.block, i, self.salt))
6640+        d.addCallback(lambda ignored:
6641+            mw.put_encprivkey(self.encprivkey))
6642+        d.addCallback(lambda ignored:
6643+            mw.put_blockhashes(self.block_hash_tree))
6644+        d.addCallback(lambda ignored:
6645+            mw.put_sharehashes(self.share_hash_chain))
6646+        d.addCallback(lambda ignored:
6647+            mw.put_root_hash(self.root_hash))
6648+        d.addCallback(lambda ignored:
6649+            mw.put_signature(self.signature))
6650+        d.addCallback(lambda ignored:
6651+            mw.put_verification_key(self.verification_key))
6652+        # Now try to put the signature again. This should fail
6653+        d.addCallback(lambda ignored:
6654+            self.shouldFail(LayoutInvalid, "signature after verification",
6655+                            None,
6656+                            mw.put_signature, self.signature))
6657+        return d
6658+
6659+
6660+    def test_uncoordinated_write(self):
6661+        # Make two mutable writers, both pointing to the same storage
6662+        # server, both at the same storage index, and try writing to the
6663+        # same share.
6664+        mw1 = self._make_new_mw("si1", 0)
6665+        mw2 = self._make_new_mw("si1", 0)
6666+
6667+        def _check_success(results):
6668+            result, readvs = results
6669+            self.failUnless(result)
6670+
6671+        def _check_failure(results):
6672+            result, readvs = results
6673+            self.failIf(result)
6674+
6675+        def _write_share(mw):
6676+            for i in xrange(6):
6677+                mw.put_block(self.block, i, self.salt)
6678+            mw.put_encprivkey(self.encprivkey)
6679+            mw.put_blockhashes(self.block_hash_tree)
6680+            mw.put_sharehashes(self.share_hash_chain)
6681+            mw.put_root_hash(self.root_hash)
6682+            mw.put_signature(self.signature)
6683+            mw.put_verification_key(self.verification_key)
6684+            return mw.finish_publishing()
6685+        d = _write_share(mw1)
6686+        d.addCallback(_check_success)
6687+        d.addCallback(lambda ignored:
6688+            _write_share(mw2))
6689+        d.addCallback(_check_failure)
6690+        return d
6691+
6692+
6693+    def test_invalid_salt_size(self):
6694+        # Salts need to be 16 bytes in size. Writes that attempt to
6695+        # write more or less than this should be rejected.
6696+        mw = self._make_new_mw("si1", 0)
6697+        invalid_salt = "a" * 17 # 17 bytes
6698+        another_invalid_salt = "b" * 15 # 15 bytes
6699+        d = defer.succeed(None)
6700+        d.addCallback(lambda ignored:
6701+            self.shouldFail(LayoutInvalid, "salt too big",
6702+                            None,
6703+                            mw.put_block, self.block, 0, invalid_salt))
6704+        d.addCallback(lambda ignored:
6705+            self.shouldFail(LayoutInvalid, "salt too small",
6706+                            None,
6707+                            mw.put_block, self.block, 0,
6708+                            another_invalid_salt))
6709+        return d
6710+
6711+
6712+    def test_write_test_vectors(self):
6713+        # If we give the write proxy a bogus test vector at
6714+        # any point during the process, it should fail to write when we
6715+        # tell it to write.
6716+        def _check_failure(results):
6717+            self.failUnlessEqual(len(results), 2)
6718+            res, d = results
6719+            self.failIf(res)
6720+
6721+        def _check_success(results):
6722+            self.failUnlessEqual(len(results), 2)
6723+            res, d = results
6724+            self.failUnless(results)
6725+
6726+        mw = self._make_new_mw("si1", 0)
6727+        mw.set_checkstring("this is a lie")
6728+        for i in xrange(6):
6729+            mw.put_block(self.block, i, self.salt)
6730+        mw.put_encprivkey(self.encprivkey)
6731+        mw.put_blockhashes(self.block_hash_tree)
6732+        mw.put_sharehashes(self.share_hash_chain)
6733+        mw.put_root_hash(self.root_hash)
6734+        mw.put_signature(self.signature)
6735+        mw.put_verification_key(self.verification_key)
6736+        d = mw.finish_publishing()
6737+        d.addCallback(_check_failure)
6738+        d.addCallback(lambda ignored:
6739+            mw.set_checkstring(""))
6740+        d.addCallback(lambda ignored:
6741+            mw.finish_publishing())
6742+        d.addCallback(_check_success)
6743+        return d
6744+
6745+
6746+    def serialize_blockhashes(self, blockhashes):
6747+        return "".join(blockhashes)
6748+
6749+
6750+    def serialize_sharehashes(self, sharehashes):
6751+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6752+                        for i in sorted(sharehashes.keys())])
6753+        return ret
6754+
6755+
6756+    def test_write(self):
6757+        # This translates to a file with 6 6-byte segments, and with 2-byte
6758+        # blocks.
6759+        mw = self._make_new_mw("si1", 0)
6760+        # Test writing some blocks.
6761+        read = self.ss.remote_slot_readv
6762+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6763+        written_block_size = 2 + len(self.salt)
6764+        written_block = self.block + self.salt
6765+        for i in xrange(6):
6766+            mw.put_block(self.block, i, self.salt)
6767+
6768+        mw.put_encprivkey(self.encprivkey)
6769+        mw.put_blockhashes(self.block_hash_tree)
6770+        mw.put_sharehashes(self.share_hash_chain)
6771+        mw.put_root_hash(self.root_hash)
6772+        mw.put_signature(self.signature)
6773+        mw.put_verification_key(self.verification_key)
6774+        d = mw.finish_publishing()
6775+        def _check_publish(results):
6776+            self.failUnlessEqual(len(results), 2)
6777+            result, ign = results
6778+            self.failUnless(result, "publish failed")
6779+            for i in xrange(6):
6780+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6781+                                {0: [written_block]})
6782+
6783+            expected_private_key_offset = expected_sharedata_offset + \
6784+                                      len(written_block) * 6
6785+            self.failUnlessEqual(len(self.encprivkey), 7)
6786+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6787+                                 {0: [self.encprivkey]})
6788+
6789+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6790+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6791+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6792+                                 {0: [self.block_hash_tree_s]})
6793+
6794+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6795+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6796+                                 {0: [self.share_hash_chain_s]})
6797+
6798+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6799+                                 {0: [self.root_hash]})
6800+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6801+            self.failUnlessEqual(len(self.signature), 9)
6802+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6803+                                 {0: [self.signature]})
6804+
6805+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
6806+            self.failUnlessEqual(len(self.verification_key), 6)
6807+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6808+                                 {0: [self.verification_key]})
6809+
6810+            signable = mw.get_signable()
6811+            verno, seq, roothash, k, n, segsize, datalen = \
6812+                                            struct.unpack(">BQ32sBBQQ",
6813+                                                          signable)
6814+            self.failUnlessEqual(verno, 1)
6815+            self.failUnlessEqual(seq, 0)
6816+            self.failUnlessEqual(roothash, self.root_hash)
6817+            self.failUnlessEqual(k, 3)
6818+            self.failUnlessEqual(n, 10)
6819+            self.failUnlessEqual(segsize, 6)
6820+            self.failUnlessEqual(datalen, 36)
6821+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6822+
6823+            # Check the version number to make sure that it is correct.
6824+            expected_version_number = struct.pack(">B", 1)
6825+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6826+                                 {0: [expected_version_number]})
6827+            # Check the sequence number to make sure that it is correct
6828+            expected_sequence_number = struct.pack(">Q", 0)
6829+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6830+                                 {0: [expected_sequence_number]})
6831+            # Check that the encoding parameters (k, N, segement size, data
6832+            # length) are what they should be. These are  3, 10, 6, 36
6833+            expected_k = struct.pack(">B", 3)
6834+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6835+                                 {0: [expected_k]})
6836+            expected_n = struct.pack(">B", 10)
6837+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6838+                                 {0: [expected_n]})
6839+            expected_segment_size = struct.pack(">Q", 6)
6840+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6841+                                 {0: [expected_segment_size]})
6842+            expected_data_length = struct.pack(">Q", 36)
6843+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6844+                                 {0: [expected_data_length]})
6845+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6846+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6847+                                 {0: [expected_offset]})
6848+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6849+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6850+                                 {0: [expected_offset]})
6851+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6852+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6853+                                 {0: [expected_offset]})
6854+            expected_offset = struct.pack(">Q", expected_signature_offset)
6855+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6856+                                 {0: [expected_offset]})
6857+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6858+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6859+                                 {0: [expected_offset]})
6860+            expected_offset = struct.pack(">Q", expected_eof_offset)
6861+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6862+                                 {0: [expected_offset]})
6863+        d.addCallback(_check_publish)
6864+        return d
6865+
6866+    def _make_new_mw(self, si, share, datalength=36):
6867+        # This is a file of size 36 bytes. Since it has a segment
6868+        # size of 6, we know that it has 6 byte segments, which will
6869+        # be split into blocks of 2 bytes because our FEC k
6870+        # parameter is 3.
6871+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6872+                                6, datalength)
6873+        return mw
6874+
6875+
6876+    def test_write_rejected_with_too_many_blocks(self):
6877+        mw = self._make_new_mw("si0", 0)
6878+
6879+        # Try writing too many blocks. We should not be able to write
6880+        # more than 6
6881+        # blocks into each share.
6882+        d = defer.succeed(None)
6883+        for i in xrange(6):
6884+            d.addCallback(lambda ignored, i=i:
6885+                mw.put_block(self.block, i, self.salt))
6886+        d.addCallback(lambda ignored:
6887+            self.shouldFail(LayoutInvalid, "too many blocks",
6888+                            None,
6889+                            mw.put_block, self.block, 7, self.salt))
6890+        return d
6891+
6892+
6893+    def test_write_rejected_with_invalid_salt(self):
6894+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6895+        # less should cause an error.
6896+        mw = self._make_new_mw("si1", 0)
6897+        bad_salt = "a" * 17 # 17 bytes
6898+        d = defer.succeed(None)
6899+        d.addCallback(lambda ignored:
6900+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6901+                            None, mw.put_block, self.block, 7, bad_salt))
6902+        return d
6903+
6904+
6905+    def test_write_rejected_with_invalid_root_hash(self):
6906+        # Try writing an invalid root hash. This should be SHA256d, and
6907+        # 32 bytes long as a result.
6908+        mw = self._make_new_mw("si2", 0)
6909+        # 17 bytes != 32 bytes
6910+        invalid_root_hash = "a" * 17
6911+        d = defer.succeed(None)
6912+        # Before this test can work, we need to put some blocks + salts,
6913+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6914+        # failures that match what we are looking for, but are caused by
6915+        # the constraints imposed on operation ordering.
6916+        for i in xrange(6):
6917+            d.addCallback(lambda ignored, i=i:
6918+                mw.put_block(self.block, i, self.salt))
6919+        d.addCallback(lambda ignored:
6920+            mw.put_encprivkey(self.encprivkey))
6921+        d.addCallback(lambda ignored:
6922+            mw.put_blockhashes(self.block_hash_tree))
6923+        d.addCallback(lambda ignored:
6924+            mw.put_sharehashes(self.share_hash_chain))
6925+        d.addCallback(lambda ignored:
6926+            self.shouldFail(LayoutInvalid, "invalid root hash",
6927+                            None, mw.put_root_hash, invalid_root_hash))
6928+        return d
6929+
6930+
6931+    def test_write_rejected_with_invalid_blocksize(self):
6932+        # The blocksize implied by the writer that we get from
6933+        # _make_new_mw is 2bytes -- any more or any less than this
6934+        # should be cause for failure, unless it is the tail segment, in
6935+        # which case it may not be failure.
6936+        invalid_block = "a"
6937+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6938+                                             # one byte blocks
6939+        # 1 bytes != 2 bytes
6940+        d = defer.succeed(None)
6941+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6942+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6943+                            None, mw.put_block, invalid_block, 0,
6944+                            self.salt))
6945+        invalid_block = invalid_block * 3
6946+        # 3 bytes != 2 bytes
6947+        d.addCallback(lambda ignored:
6948+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6949+                            None,
6950+                            mw.put_block, invalid_block, 0, self.salt))
6951+        for i in xrange(5):
6952+            d.addCallback(lambda ignored, i=i:
6953+                mw.put_block(self.block, i, self.salt))
6954+        # Try to put an invalid tail segment
6955+        d.addCallback(lambda ignored:
6956+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6957+                            None,
6958+                            mw.put_block, self.block, 5, self.salt))
6959+        valid_block = "a"
6960+        d.addCallback(lambda ignored:
6961+            mw.put_block(valid_block, 5, self.salt))
6962+        return d
6963+
6964+
6965+    def test_write_enforces_order_constraints(self):
6966+        # We require that the MDMFSlotWriteProxy be interacted with in a
6967+        # specific way.
6968+        # That way is:
6969+        # 0: __init__
6970+        # 1: write blocks and salts
6971+        # 2: Write the encrypted private key
6972+        # 3: Write the block hashes
6973+        # 4: Write the share hashes
6974+        # 5: Write the root hash and salt hash
6975+        # 6: Write the signature and verification key
6976+        # 7: Write the file.
6977+        #
6978+        # Some of these can be performed out-of-order, and some can't.
6979+        # The dependencies that I want to test here are:
6980+        #  - Private key before block hashes
6981+        #  - share hashes and block hashes before root hash
6982+        #  - root hash before signature
6983+        #  - signature before verification key
6984+        mw0 = self._make_new_mw("si0", 0)
6985+        # Write some shares
6986+        d = defer.succeed(None)
6987+        for i in xrange(6):
6988+            d.addCallback(lambda ignored, i=i:
6989+                mw0.put_block(self.block, i, self.salt))
6990+        # Try to write the block hashes before writing the encrypted
6991+        # private key
6992+        d.addCallback(lambda ignored:
6993+            self.shouldFail(LayoutInvalid, "block hashes before key",
6994+                            None, mw0.put_blockhashes,
6995+                            self.block_hash_tree))
6996+
6997+        # Write the private key.
6998+        d.addCallback(lambda ignored:
6999+            mw0.put_encprivkey(self.encprivkey))
7000+
7001+
7002+        # Try to write the share hash chain without writing the block
7003+        # hash tree
7004+        d.addCallback(lambda ignored:
7005+            self.shouldFail(LayoutInvalid, "share hash chain before "
7006+                                           "salt hash tree",
7007+                            None,
7008+                            mw0.put_sharehashes, self.share_hash_chain))
7009+
7010+        # Try to write the root hash and without writing either the
7011+        # block hashes or the or the share hashes
7012+        d.addCallback(lambda ignored:
7013+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
7014+                            None,
7015+                            mw0.put_root_hash, self.root_hash))
7016+
7017+        # Now write the block hashes and try again
7018+        d.addCallback(lambda ignored:
7019+            mw0.put_blockhashes(self.block_hash_tree))
7020+
7021+        d.addCallback(lambda ignored:
7022+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
7023+                            None, mw0.put_root_hash, self.root_hash))
7024+
7025+        # We haven't yet put the root hash on the share, so we shouldn't
7026+        # be able to sign it.
7027+        d.addCallback(lambda ignored:
7028+            self.shouldFail(LayoutInvalid, "signature before root hash",
7029+                            None, mw0.put_signature, self.signature))
7030+
7031+        d.addCallback(lambda ignored:
7032+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
7033+
7034+        # ..and, since that fails, we also shouldn't be able to put the
7035+        # verification key.
7036+        d.addCallback(lambda ignored:
7037+            self.shouldFail(LayoutInvalid, "key before signature",
7038+                            None, mw0.put_verification_key,
7039+                            self.verification_key))
7040+
7041+        # Now write the share hashes.
7042+        d.addCallback(lambda ignored:
7043+            mw0.put_sharehashes(self.share_hash_chain))
7044+        # We should be able to write the root hash now too
7045+        d.addCallback(lambda ignored:
7046+            mw0.put_root_hash(self.root_hash))
7047+
7048+        # We should still be unable to put the verification key
7049+        d.addCallback(lambda ignored:
7050+            self.shouldFail(LayoutInvalid, "key before signature",
7051+                            None, mw0.put_verification_key,
7052+                            self.verification_key))
7053+
7054+        d.addCallback(lambda ignored:
7055+            mw0.put_signature(self.signature))
7056+
7057+        # We shouldn't be able to write the offsets to the remote server
7058+        # until the offset table is finished; IOW, until we have written
7059+        # the verification key.
7060+        d.addCallback(lambda ignored:
7061+            self.shouldFail(LayoutInvalid, "offsets before verification key",
7062+                            None,
7063+                            mw0.finish_publishing))
7064+
7065+        d.addCallback(lambda ignored:
7066+            mw0.put_verification_key(self.verification_key))
7067+        return d
7068+
7069+
7070+    def test_end_to_end(self):
7071+        mw = self._make_new_mw("si1", 0)
7072+        # Write a share using the mutable writer, and make sure that the
7073+        # reader knows how to read everything back to us.
7074+        d = defer.succeed(None)
7075+        for i in xrange(6):
7076+            d.addCallback(lambda ignored, i=i:
7077+                mw.put_block(self.block, i, self.salt))
7078+        d.addCallback(lambda ignored:
7079+            mw.put_encprivkey(self.encprivkey))
7080+        d.addCallback(lambda ignored:
7081+            mw.put_blockhashes(self.block_hash_tree))
7082+        d.addCallback(lambda ignored:
7083+            mw.put_sharehashes(self.share_hash_chain))
7084+        d.addCallback(lambda ignored:
7085+            mw.put_root_hash(self.root_hash))
7086+        d.addCallback(lambda ignored:
7087+            mw.put_signature(self.signature))
7088+        d.addCallback(lambda ignored:
7089+            mw.put_verification_key(self.verification_key))
7090+        d.addCallback(lambda ignored:
7091+            mw.finish_publishing())
7092+
7093+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7094+        def _check_block_and_salt((block, salt)):
7095+            self.failUnlessEqual(block, self.block)
7096+            self.failUnlessEqual(salt, self.salt)
7097+
7098+        for i in xrange(6):
7099+            d.addCallback(lambda ignored, i=i:
7100+                mr.get_block_and_salt(i))
7101+            d.addCallback(_check_block_and_salt)
7102+
7103+        d.addCallback(lambda ignored:
7104+            mr.get_encprivkey())
7105+        d.addCallback(lambda encprivkey:
7106+            self.failUnlessEqual(self.encprivkey, encprivkey))
7107+
7108+        d.addCallback(lambda ignored:
7109+            mr.get_blockhashes())
7110+        d.addCallback(lambda blockhashes:
7111+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
7112+
7113+        d.addCallback(lambda ignored:
7114+            mr.get_sharehashes())
7115+        d.addCallback(lambda sharehashes:
7116+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
7117+
7118+        d.addCallback(lambda ignored:
7119+            mr.get_signature())
7120+        d.addCallback(lambda signature:
7121+            self.failUnlessEqual(signature, self.signature))
7122+
7123+        d.addCallback(lambda ignored:
7124+            mr.get_verification_key())
7125+        d.addCallback(lambda verification_key:
7126+            self.failUnlessEqual(verification_key, self.verification_key))
7127+
7128+        d.addCallback(lambda ignored:
7129+            mr.get_seqnum())
7130+        d.addCallback(lambda seqnum:
7131+            self.failUnlessEqual(seqnum, 0))
7132+
7133+        d.addCallback(lambda ignored:
7134+            mr.get_root_hash())
7135+        d.addCallback(lambda root_hash:
7136+            self.failUnlessEqual(self.root_hash, root_hash))
7137+
7138+        d.addCallback(lambda ignored:
7139+            mr.get_encoding_parameters())
7140+        def _check_encoding_parameters((k, n, segsize, datalen)):
7141+            self.failUnlessEqual(k, 3)
7142+            self.failUnlessEqual(n, 10)
7143+            self.failUnlessEqual(segsize, 6)
7144+            self.failUnlessEqual(datalen, 36)
7145+        d.addCallback(_check_encoding_parameters)
7146+
7147+        d.addCallback(lambda ignored:
7148+            mr.get_checkstring())
7149+        d.addCallback(lambda checkstring:
7150+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
7151+        return d
7152+
7153+
7154+    def test_is_sdmf(self):
7155+        # The MDMFSlotReadProxy should also know how to read SDMF files,
7156+        # since it will encounter them on the grid. Callers use the
7157+        # is_sdmf method to test this.
7158+        self.write_sdmf_share_to_server("si1")
7159+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7160+        d = mr.is_sdmf()
7161+        d.addCallback(lambda issdmf:
7162+            self.failUnless(issdmf))
7163+        return d
7164+
7165+
7166+    def test_reads_sdmf(self):
7167+        # The slot read proxy should, naturally, know how to tell us
7168+        # about data in the SDMF format
7169+        self.write_sdmf_share_to_server("si1")
7170+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7171+        d = defer.succeed(None)
7172+        d.addCallback(lambda ignored:
7173+            mr.is_sdmf())
7174+        d.addCallback(lambda issdmf:
7175+            self.failUnless(issdmf))
7176+
7177+        # What do we need to read?
7178+        #  - The sharedata
7179+        #  - The salt
7180+        d.addCallback(lambda ignored:
7181+            mr.get_block_and_salt(0))
7182+        def _check_block_and_salt(results):
7183+            block, salt = results
7184+            # Our original file is 36 bytes long. Then each share is 12
7185+            # bytes in size. The share is composed entirely of the
7186+            # letter a. self.block contains 2 as, so 6 * self.block is
7187+            # what we are looking for.
7188+            self.failUnlessEqual(block, self.block * 6)
7189+            self.failUnlessEqual(salt, self.salt)
7190+        d.addCallback(_check_block_and_salt)
7191+
7192+        #  - The blockhashes
7193+        d.addCallback(lambda ignored:
7194+            mr.get_blockhashes())
7195+        d.addCallback(lambda blockhashes:
7196+            self.failUnlessEqual(self.block_hash_tree,
7197+                                 blockhashes,
7198+                                 blockhashes))
7199+        #  - The sharehashes
7200+        d.addCallback(lambda ignored:
7201+            mr.get_sharehashes())
7202+        d.addCallback(lambda sharehashes:
7203+            self.failUnlessEqual(self.share_hash_chain,
7204+                                 sharehashes))
7205+        #  - The keys
7206+        d.addCallback(lambda ignored:
7207+            mr.get_encprivkey())
7208+        d.addCallback(lambda encprivkey:
7209+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
7210+        d.addCallback(lambda ignored:
7211+            mr.get_verification_key())
7212+        d.addCallback(lambda verification_key:
7213+            self.failUnlessEqual(verification_key,
7214+                                 self.verification_key,
7215+                                 verification_key))
7216+        #  - The signature
7217+        d.addCallback(lambda ignored:
7218+            mr.get_signature())
7219+        d.addCallback(lambda signature:
7220+            self.failUnlessEqual(signature, self.signature, signature))
7221+
7222+        #  - The sequence number
7223+        d.addCallback(lambda ignored:
7224+            mr.get_seqnum())
7225+        d.addCallback(lambda seqnum:
7226+            self.failUnlessEqual(seqnum, 0, seqnum))
7227+
7228+        #  - The root hash
7229+        d.addCallback(lambda ignored:
7230+            mr.get_root_hash())
7231+        d.addCallback(lambda root_hash:
7232+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7233+        return d
7234+
7235+
7236+    def test_only_reads_one_segment_sdmf(self):
7237+        # SDMF shares have only one segment, so it doesn't make sense to
7238+        # read more segments than that. The reader should know this and
7239+        # complain if we try to do that.
7240+        self.write_sdmf_share_to_server("si1")
7241+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7242+        d = defer.succeed(None)
7243+        d.addCallback(lambda ignored:
7244+            mr.is_sdmf())
7245+        d.addCallback(lambda issdmf:
7246+            self.failUnless(issdmf))
7247+        d.addCallback(lambda ignored:
7248+            self.shouldFail(LayoutInvalid, "test bad segment",
7249+                            None,
7250+                            mr.get_block_and_salt, 1))
7251+        return d
7252+
7253+
7254+    def test_read_with_prefetched_mdmf_data(self):
7255+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7256+        # it data that you have already fetched. This is useful for
7257+        # cases like the Servermap, which prefetches ~2kb of data while
7258+        # finding out which shares are on the remote peer so that it
7259+        # doesn't waste round trips.
7260+        mdmf_data = self.build_test_mdmf_share()
7261+        self.write_test_share_to_server("si1")
7262+        def _make_mr(ignored, length):
7263+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7264+            return mr
7265+
7266+        d = defer.succeed(None)
7267+        # This should be enough to fill in both the encoding parameters
7268+        # and the table of offsets, which will complete the version
7269+        # information tuple.
7270+        d.addCallback(_make_mr, 107)
7271+        d.addCallback(lambda mr:
7272+            mr.get_verinfo())
7273+        def _check_verinfo(verinfo):
7274+            self.failUnless(verinfo)
7275+            self.failUnlessEqual(len(verinfo), 9)
7276+            (seqnum,
7277+             root_hash,
7278+             salt_hash,
7279+             segsize,
7280+             datalen,
7281+             k,
7282+             n,
7283+             prefix,
7284+             offsets) = verinfo
7285+            self.failUnlessEqual(seqnum, 0)
7286+            self.failUnlessEqual(root_hash, self.root_hash)
7287+            self.failUnlessEqual(segsize, 6)
7288+            self.failUnlessEqual(datalen, 36)
7289+            self.failUnlessEqual(k, 3)
7290+            self.failUnlessEqual(n, 10)
7291+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7292+                                          1,
7293+                                          seqnum,
7294+                                          root_hash,
7295+                                          k,
7296+                                          n,
7297+                                          segsize,
7298+                                          datalen)
7299+            self.failUnlessEqual(expected_prefix, prefix)
7300+            self.failUnlessEqual(self.rref.read_count, 0)
7301+        d.addCallback(_check_verinfo)
7302+        # This is not enough data to read a block and a share, so the
7303+        # wrapper should attempt to read this from the remote server.
7304+        d.addCallback(_make_mr, 107)
7305+        d.addCallback(lambda mr:
7306+            mr.get_block_and_salt(0))
7307+        def _check_block_and_salt((block, salt)):
7308+            self.failUnlessEqual(block, self.block)
7309+            self.failUnlessEqual(salt, self.salt)
7310+            self.failUnlessEqual(self.rref.read_count, 1)
7311+        # This should be enough data to read one block.
7312+        d.addCallback(_make_mr, 249)
7313+        d.addCallback(lambda mr:
7314+            mr.get_block_and_salt(0))
7315+        d.addCallback(_check_block_and_salt)
7316+        return d
7317+
7318+
7319+    def test_read_with_prefetched_sdmf_data(self):
7320+        sdmf_data = self.build_test_sdmf_share()
7321+        self.write_sdmf_share_to_server("si1")
7322+        def _make_mr(ignored, length):
7323+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7324+            return mr
7325+
7326+        d = defer.succeed(None)
7327+        # This should be enough to get us the encoding parameters,
7328+        # offset table, and everything else we need to build a verinfo
7329+        # string.
7330+        d.addCallback(_make_mr, 107)
7331+        d.addCallback(lambda mr:
7332+            mr.get_verinfo())
7333+        def _check_verinfo(verinfo):
7334+            self.failUnless(verinfo)
7335+            self.failUnlessEqual(len(verinfo), 9)
7336+            (seqnum,
7337+             root_hash,
7338+             salt,
7339+             segsize,
7340+             datalen,
7341+             k,
7342+             n,
7343+             prefix,
7344+             offsets) = verinfo
7345+            self.failUnlessEqual(seqnum, 0)
7346+            self.failUnlessEqual(root_hash, self.root_hash)
7347+            self.failUnlessEqual(salt, self.salt)
7348+            self.failUnlessEqual(segsize, 36)
7349+            self.failUnlessEqual(datalen, 36)
7350+            self.failUnlessEqual(k, 3)
7351+            self.failUnlessEqual(n, 10)
7352+            expected_prefix = struct.pack(SIGNED_PREFIX,
7353+                                          0,
7354+                                          seqnum,
7355+                                          root_hash,
7356+                                          salt,
7357+                                          k,
7358+                                          n,
7359+                                          segsize,
7360+                                          datalen)
7361+            self.failUnlessEqual(expected_prefix, prefix)
7362+            self.failUnlessEqual(self.rref.read_count, 0)
7363+        d.addCallback(_check_verinfo)
7364+        # This shouldn't be enough to read any share data.
7365+        d.addCallback(_make_mr, 107)
7366+        d.addCallback(lambda mr:
7367+            mr.get_block_and_salt(0))
7368+        def _check_block_and_salt((block, salt)):
7369+            self.failUnlessEqual(block, self.block * 6)
7370+            self.failUnlessEqual(salt, self.salt)
7371+            # TODO: Fix the read routine so that it reads only the data
7372+            #       that it has cached if it can't read all of it.
7373+            self.failUnlessEqual(self.rref.read_count, 2)
7374+
7375+        # This should be enough to read share data.
7376+        d.addCallback(_make_mr, self.offsets['share_data'])
7377+        d.addCallback(lambda mr:
7378+            mr.get_block_and_salt(0))
7379+        d.addCallback(_check_block_and_salt)
7380+        return d
7381+
7382+
7383+    def test_read_with_empty_mdmf_file(self):
7384+        # Some tests upload a file with no contents to test things
7385+        # unrelated to the actual handling of the content of the file.
7386+        # The reader should behave intelligently in these cases.
7387+        self.write_test_share_to_server("si1", empty=True)
7388+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7389+        # We should be able to get the encoding parameters, and they
7390+        # should be correct.
7391+        d = defer.succeed(None)
7392+        d.addCallback(lambda ignored:
7393+            mr.get_encoding_parameters())
7394+        def _check_encoding_parameters(params):
7395+            self.failUnlessEqual(len(params), 4)
7396+            k, n, segsize, datalen = params
7397+            self.failUnlessEqual(k, 3)
7398+            self.failUnlessEqual(n, 10)
7399+            self.failUnlessEqual(segsize, 0)
7400+            self.failUnlessEqual(datalen, 0)
7401+        d.addCallback(_check_encoding_parameters)
7402+
7403+        # We should not be able to fetch a block, since there are no
7404+        # blocks to fetch
7405+        d.addCallback(lambda ignored:
7406+            self.shouldFail(LayoutInvalid, "get block on empty file",
7407+                            None,
7408+                            mr.get_block_and_salt, 0))
7409+        return d
7410+
7411+
7412+    def test_read_with_empty_sdmf_file(self):
7413+        self.write_sdmf_share_to_server("si1", empty=True)
7414+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7415+        # We should be able to get the encoding parameters, and they
7416+        # should be correct
7417+        d = defer.succeed(None)
7418+        d.addCallback(lambda ignored:
7419+            mr.get_encoding_parameters())
7420+        def _check_encoding_parameters(params):
7421+            self.failUnlessEqual(len(params), 4)
7422+            k, n, segsize, datalen = params
7423+            self.failUnlessEqual(k, 3)
7424+            self.failUnlessEqual(n, 10)
7425+            self.failUnlessEqual(segsize, 0)
7426+            self.failUnlessEqual(datalen, 0)
7427+        d.addCallback(_check_encoding_parameters)
7428+
7429+        # It does not make sense to get a block in this format, so we
7430+        # should not be able to.
7431+        d.addCallback(lambda ignored:
7432+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7433+                            None,
7434+                            mr.get_block_and_salt, 0))
7435+        return d
7436+
7437+
7438+    def test_verinfo_with_sdmf_file(self):
7439+        self.write_sdmf_share_to_server("si1")
7440+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7441+        # We should be able to get the version information.
7442+        d = defer.succeed(None)
7443+        d.addCallback(lambda ignored:
7444+            mr.get_verinfo())
7445+        def _check_verinfo(verinfo):
7446+            self.failUnless(verinfo)
7447+            self.failUnlessEqual(len(verinfo), 9)
7448+            (seqnum,
7449+             root_hash,
7450+             salt,
7451+             segsize,
7452+             datalen,
7453+             k,
7454+             n,
7455+             prefix,
7456+             offsets) = verinfo
7457+            self.failUnlessEqual(seqnum, 0)
7458+            self.failUnlessEqual(root_hash, self.root_hash)
7459+            self.failUnlessEqual(salt, self.salt)
7460+            self.failUnlessEqual(segsize, 36)
7461+            self.failUnlessEqual(datalen, 36)
7462+            self.failUnlessEqual(k, 3)
7463+            self.failUnlessEqual(n, 10)
7464+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7465+                                          0,
7466+                                          seqnum,
7467+                                          root_hash,
7468+                                          salt,
7469+                                          k,
7470+                                          n,
7471+                                          segsize,
7472+                                          datalen)
7473+            self.failUnlessEqual(prefix, expected_prefix)
7474+            self.failUnlessEqual(offsets, self.offsets)
7475+        d.addCallback(_check_verinfo)
7476+        return d
7477+
7478+
7479+    def test_verinfo_with_mdmf_file(self):
7480+        self.write_test_share_to_server("si1")
7481+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7482+        d = defer.succeed(None)
7483+        d.addCallback(lambda ignored:
7484+            mr.get_verinfo())
7485+        def _check_verinfo(verinfo):
7486+            self.failUnless(verinfo)
7487+            self.failUnlessEqual(len(verinfo), 9)
7488+            (seqnum,
7489+             root_hash,
7490+             IV,
7491+             segsize,
7492+             datalen,
7493+             k,
7494+             n,
7495+             prefix,
7496+             offsets) = verinfo
7497+            self.failUnlessEqual(seqnum, 0)
7498+            self.failUnlessEqual(root_hash, self.root_hash)
7499+            self.failIf(IV)
7500+            self.failUnlessEqual(segsize, 6)
7501+            self.failUnlessEqual(datalen, 36)
7502+            self.failUnlessEqual(k, 3)
7503+            self.failUnlessEqual(n, 10)
7504+            expected_prefix = struct.pack(">BQ32s BBQQ",
7505+                                          1,
7506+                                          seqnum,
7507+                                          root_hash,
7508+                                          k,
7509+                                          n,
7510+                                          segsize,
7511+                                          datalen)
7512+            self.failUnlessEqual(prefix, expected_prefix)
7513+            self.failUnlessEqual(offsets, self.offsets)
7514+        d.addCallback(_check_verinfo)
7515+        return d
7516+
7517+
7518+    def test_reader_queue(self):
7519+        self.write_test_share_to_server('si1')
7520+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7521+        d1 = mr.get_block_and_salt(0, queue=True)
7522+        d2 = mr.get_blockhashes(queue=True)
7523+        d3 = mr.get_sharehashes(queue=True)
7524+        d4 = mr.get_signature(queue=True)
7525+        d5 = mr.get_verification_key(queue=True)
7526+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7527+        mr.flush()
7528+        def _print(results):
7529+            self.failUnlessEqual(len(results), 5)
7530+            # We have one read for version information and offsets, and
7531+            # one for everything else.
7532+            self.failUnlessEqual(self.rref.read_count, 2)
7533+            block, salt = results[0][1] # results[0] is a boolean that says
7534+                                           # whether or not the operation
7535+                                           # worked.
7536+            self.failUnlessEqual(self.block, block)
7537+            self.failUnlessEqual(self.salt, salt)
7538+
7539+            blockhashes = results[1][1]
7540+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7541+
7542+            sharehashes = results[2][1]
7543+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7544+
7545+            signature = results[3][1]
7546+            self.failUnlessEqual(self.signature, signature)
7547+
7548+            verification_key = results[4][1]
7549+            self.failUnlessEqual(self.verification_key, verification_key)
7550+        dl.addCallback(_print)
7551+        return dl
7552+
7553+
7554+    def test_sdmf_writer(self):
7555+        # Go through the motions of writing an SDMF share to the storage
7556+        # server. Then read the storage server to see that the share got
7557+        # written in the way that we think it should have.
7558+
7559+        # We do this first so that the necessary instance variables get
7560+        # set the way we want them for the tests below.
7561+        data = self.build_test_sdmf_share()
7562+        sdmfr = SDMFSlotWriteProxy(0,
7563+                                   self.rref,
7564+                                   "si1",
7565+                                   self.secrets,
7566+                                   0, 3, 10, 36, 36)
7567+        # Put the block and salt.
7568+        sdmfr.put_block(self.blockdata, 0, self.salt)
7569+
7570+        # Put the encprivkey
7571+        sdmfr.put_encprivkey(self.encprivkey)
7572+
7573+        # Put the block and share hash chains
7574+        sdmfr.put_blockhashes(self.block_hash_tree)
7575+        sdmfr.put_sharehashes(self.share_hash_chain)
7576+        sdmfr.put_root_hash(self.root_hash)
7577+
7578+        # Put the signature
7579+        sdmfr.put_signature(self.signature)
7580+
7581+        # Put the verification key
7582+        sdmfr.put_verification_key(self.verification_key)
7583+
7584+        # Now check to make sure that nothing has been written yet.
7585+        self.failUnlessEqual(self.rref.write_count, 0)
7586+
7587+        # Now finish publishing
7588+        d = sdmfr.finish_publishing()
7589+        def _then(ignored):
7590+            self.failUnlessEqual(self.rref.write_count, 1)
7591+            read = self.ss.remote_slot_readv
7592+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7593+                                 {0: [data]})
7594+        d.addCallback(_then)
7595+        return d
7596+
7597+
7598+    def test_sdmf_writer_preexisting_share(self):
7599+        data = self.build_test_sdmf_share()
7600+        self.write_sdmf_share_to_server("si1")
7601+
7602+        # Now there is a share on the storage server. To successfully
7603+        # write, we need to set the checkstring correctly. When we
7604+        # don't, no write should occur.
7605+        sdmfw = SDMFSlotWriteProxy(0,
7606+                                   self.rref,
7607+                                   "si1",
7608+                                   self.secrets,
7609+                                   1, 3, 10, 36, 36)
7610+        sdmfw.put_block(self.blockdata, 0, self.salt)
7611+
7612+        # Put the encprivkey
7613+        sdmfw.put_encprivkey(self.encprivkey)
7614+
7615+        # Put the block and share hash chains
7616+        sdmfw.put_blockhashes(self.block_hash_tree)
7617+        sdmfw.put_sharehashes(self.share_hash_chain)
7618+
7619+        # Put the root hash
7620+        sdmfw.put_root_hash(self.root_hash)
7621+
7622+        # Put the signature
7623+        sdmfw.put_signature(self.signature)
7624+
7625+        # Put the verification key
7626+        sdmfw.put_verification_key(self.verification_key)
7627+
7628+        # We shouldn't have a checkstring yet
7629+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7630+
7631+        d = sdmfw.finish_publishing()
7632+        def _then(results):
7633+            self.failIf(results[0])
7634+            # this is the correct checkstring
7635+            self._expected_checkstring = results[1][0][0]
7636+            return self._expected_checkstring
7637+
7638+        d.addCallback(_then)
7639+        d.addCallback(sdmfw.set_checkstring)
7640+        d.addCallback(lambda ignored:
7641+            sdmfw.get_checkstring())
7642+        d.addCallback(lambda checkstring:
7643+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7644+        d.addCallback(lambda ignored:
7645+            sdmfw.finish_publishing())
7646+        def _then_again(results):
7647+            self.failUnless(results[0])
7648+            read = self.ss.remote_slot_readv
7649+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7650+                                 {0: [struct.pack(">Q", 1)]})
7651+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7652+                                 {0: [data[9:]]})
7653+        d.addCallback(_then_again)
7654+        return d
7655+
7656+
7657 class Stats(unittest.TestCase):
7658 
7659     def setUp(self):
7660}
7661[mutable/retrieve.py: Modify the retrieval process to support MDMF
7662Kevan Carstensen <kevan@isnotajoke.com>**20100819003409
7663 Ignore-this: c03f4e41aaa0366a9bf44847f2caf9db
7664 
7665 The logic behind a mutable file download had to be adapted to work with
7666 segmented mutable files; this patch performs those adaptations. It also
7667 exposes some decoding and decrypting functionality to make partial-file
7668 updates a little easier, and supports efficient random-access downloads
7669 of parts of an MDMF file.
7670] {
7671hunk ./src/allmydata/mutable/retrieve.py 2
7672 
7673-import struct, time
7674+import time
7675 from itertools import count
7676 from zope.interface import implements
7677 from twisted.internet import defer
7678merger 0.0 (
7679hunk ./src/allmydata/mutable/retrieve.py 10
7680+from allmydata.util.dictutil import DictOfSets
7681hunk ./src/allmydata/mutable/retrieve.py 7
7682-from foolscap.api import DeadReferenceError, eventually, fireEventually
7683-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
7684-from allmydata.util import hashutil, idlib, log
7685+from twisted.internet.interfaces import IPushProducer, IConsumer
7686+from foolscap.api import eventually, fireEventually
7687+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
7688+                                 MDMF_VERSION, SDMF_VERSION
7689+from allmydata.util import hashutil, log, mathutil
7690)
7691hunk ./src/allmydata/mutable/retrieve.py 16
7692 from pycryptopp.publickey import rsa
7693 
7694 from allmydata.mutable.common import CorruptShareError, UncoordinatedWriteError
7695-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
7696+from allmydata.mutable.layout import MDMFSlotReadProxy
7697 
7698 class RetrieveStatus:
7699     implements(IRetrieveStatus)
7700hunk ./src/allmydata/mutable/retrieve.py 83
7701     # times, and each will have a separate response chain. However the
7702     # Retrieve object will remain tied to a specific version of the file, and
7703     # will use a single ServerMap instance.
7704+    implements(IPushProducer)
7705 
7706hunk ./src/allmydata/mutable/retrieve.py 85
7707-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
7708+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
7709+                 verify=False):
7710         self._node = filenode
7711         assert self._node.get_pubkey()
7712         self._storage_index = filenode.get_storage_index()
7713hunk ./src/allmydata/mutable/retrieve.py 104
7714         self.verinfo = verinfo
7715         # during repair, we may be called upon to grab the private key, since
7716         # it wasn't picked up during a verify=False checker run, and we'll
7717-        # need it for repair to generate the a new version.
7718-        self._need_privkey = fetch_privkey
7719-        if self._node.get_privkey():
7720+        # need it for repair to generate a new version.
7721+        self._need_privkey = fetch_privkey or verify
7722+        if self._node.get_privkey() and not verify:
7723             self._need_privkey = False
7724 
7725hunk ./src/allmydata/mutable/retrieve.py 109
7726+        if self._need_privkey:
7727+            # TODO: Evaluate the need for this. We'll use it if we want
7728+            # to limit how many queries are on the wire for the privkey
7729+            # at once.
7730+            self._privkey_query_markers = [] # one Marker for each time we've
7731+                                             # tried to get the privkey.
7732+
7733+        # verify means that we are using the downloader logic to verify all
7734+        # of our shares. This tells the downloader a few things.
7735+        #
7736+        # 1. We need to download all of the shares.
7737+        # 2. We don't need to decode or decrypt the shares, since our
7738+        #    caller doesn't care about the plaintext, only the
7739+        #    information about which shares are or are not valid.
7740+        # 3. When we are validating readers, we need to validate the
7741+        #    signature on the prefix. Do we? We already do this in the
7742+        #    servermap update?
7743+        self._verify = False
7744+        if verify:
7745+            self._verify = True
7746+
7747         self._status = RetrieveStatus()
7748         self._status.set_storage_index(self._storage_index)
7749         self._status.set_helper(False)
7750hunk ./src/allmydata/mutable/retrieve.py 139
7751          offsets_tuple) = self.verinfo
7752         self._status.set_size(datalength)
7753         self._status.set_encoding(k, N)
7754+        self.readers = {}
7755+        self._paused = False
7756+        self._paused_deferred = None
7757+        self._offset = None
7758+        self._read_length = None
7759+        self.log("got seqnum %d" % self.verinfo[0])
7760+
7761 
7762     def get_status(self):
7763         return self._status
7764hunk ./src/allmydata/mutable/retrieve.py 157
7765             kwargs["facility"] = "tahoe.mutable.retrieve"
7766         return log.msg(*args, **kwargs)
7767 
7768-    def download(self):
7769+
7770+    ###################
7771+    # IPushProducer
7772+
7773+    def pauseProducing(self):
7774+        """
7775+        I am called by my download target if we have produced too much
7776+        data for it to handle. I make the downloader stop producing new
7777+        data until my resumeProducing method is called.
7778+        """
7779+        if self._paused:
7780+            return
7781+
7782+        # fired when the download is unpaused.
7783+        self._old_status = self._status.get_status()
7784+        self._status.set_status("Paused")
7785+
7786+        self._pause_deferred = defer.Deferred()
7787+        self._paused = True
7788+
7789+
7790+    def resumeProducing(self):
7791+        """
7792+        I am called by my download target once it is ready to begin
7793+        receiving data again.
7794+        """
7795+        if not self._paused:
7796+            return
7797+
7798+        self._paused = False
7799+        p = self._pause_deferred
7800+        self._pause_deferred = None
7801+        self._status.set_status(self._old_status)
7802+
7803+        eventually(p.callback, None)
7804+
7805+
7806+    def _check_for_paused(self, res):
7807+        """
7808+        I am called just before a write to the consumer. I return a
7809+        Deferred that eventually fires with the data that is to be
7810+        written to the consumer. If the download has not been paused,
7811+        the Deferred fires immediately. Otherwise, the Deferred fires
7812+        when the downloader is unpaused.
7813+        """
7814+        if self._paused:
7815+            d = defer.Deferred()
7816+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
7817+            return d
7818+        return defer.succeed(res)
7819+
7820+
7821+    def download(self, consumer=None, offset=0, size=None):
7822+        assert IConsumer.providedBy(consumer) or self._verify
7823+
7824+        if consumer:
7825+            self._consumer = consumer
7826+            # we provide IPushProducer, so streaming=True, per
7827+            # IConsumer.
7828+            self._consumer.registerProducer(self, streaming=True)
7829+
7830         self._done_deferred = defer.Deferred()
7831         self._started = time.time()
7832         self._status.set_status("Retrieving Shares")
7833hunk ./src/allmydata/mutable/retrieve.py 222
7834 
7835+        self._offset = offset
7836+        self._read_length = size
7837+
7838         # first, which servers can we use?
7839         versionmap = self.servermap.make_versionmap()
7840         shares = versionmap[self.verinfo]
7841hunk ./src/allmydata/mutable/retrieve.py 232
7842         self.remaining_sharemap = DictOfSets()
7843         for (shnum, peerid, timestamp) in shares:
7844             self.remaining_sharemap.add(shnum, peerid)
7845+            # If the servermap update fetched anything, it fetched at least 1
7846+            # KiB, so we ask for that much.
7847+            # TODO: Change the cache methods to allow us to fetch all of the
7848+            # data that they have, then change this method to do that.
7849+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
7850+                                                               shnum,
7851+                                                               0,
7852+                                                               1000)
7853+            ss = self.servermap.connections[peerid]
7854+            reader = MDMFSlotReadProxy(ss,
7855+                                       self._storage_index,
7856+                                       shnum,
7857+                                       any_cache)
7858+            reader.peerid = peerid
7859+            self.readers[shnum] = reader
7860+
7861 
7862         self.shares = {} # maps shnum to validated blocks
7863hunk ./src/allmydata/mutable/retrieve.py 250
7864+        self._active_readers = [] # list of active readers for this dl.
7865+        self._validated_readers = set() # set of readers that we have
7866+                                        # validated the prefix of
7867+        self._block_hash_trees = {} # shnum => hashtree
7868 
7869         # how many shares do we need?
7870hunk ./src/allmydata/mutable/retrieve.py 256
7871-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
7872+        (seqnum,
7873+         root_hash,
7874+         IV,
7875+         segsize,
7876+         datalength,
7877+         k,
7878+         N,
7879+         prefix,
7880          offsets_tuple) = self.verinfo
7881hunk ./src/allmydata/mutable/retrieve.py 265
7882-        assert len(self.remaining_sharemap) >= k
7883-        # we start with the lowest shnums we have available, since FEC is
7884-        # faster if we're using "primary shares"
7885-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
7886-        for shnum in self.active_shnums:
7887-            # we use an arbitrary peer who has the share. If shares are
7888-            # doubled up (more than one share per peer), we could make this
7889-            # run faster by spreading the load among multiple peers. But the
7890-            # algorithm to do that is more complicated than I want to write
7891-            # right now, and a well-provisioned grid shouldn't have multiple
7892-            # shares per peer.
7893-            peerid = list(self.remaining_sharemap[shnum])[0]
7894-            self.get_data(shnum, peerid)
7895 
7896hunk ./src/allmydata/mutable/retrieve.py 266
7897-        # control flow beyond this point: state machine. Receiving responses
7898-        # from queries is the input. We might send out more queries, or we
7899-        # might produce a result.
7900 
7901hunk ./src/allmydata/mutable/retrieve.py 267
7902+        # We need one share hash tree for the entire file; its leaves
7903+        # are the roots of the block hash trees for the shares that
7904+        # comprise it, and its root is in the verinfo.
7905+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
7906+        self.share_hash_tree.set_hashes({0: root_hash})
7907+
7908+        # This will set up both the segment decoder and the tail segment
7909+        # decoder, as well as a variety of other instance variables that
7910+        # the download process will use.
7911+        self._setup_encoding_parameters()
7912+        assert len(self.remaining_sharemap) >= k
7913+
7914+        self.log("starting download")
7915+        self._paused = False
7916+        self._started_fetching = time.time()
7917+
7918+        self._add_active_peers()
7919+        # The download process beyond this is a state machine.
7920+        # _add_active_peers will select the peers that we want to use
7921+        # for the download, and then attempt to start downloading. After
7922+        # each segment, it will check for doneness, reacting to broken
7923+        # peers and corrupt shares as necessary. If it runs out of good
7924+        # peers before downloading all of the segments, _done_deferred
7925+        # will errback.  Otherwise, it will eventually callback with the
7926+        # contents of the mutable file.
7927         return self._done_deferred
7928 
7929hunk ./src/allmydata/mutable/retrieve.py 294
7930-    def get_data(self, shnum, peerid):
7931-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
7932-                 shnum=shnum,
7933-                 peerid=idlib.shortnodeid_b2a(peerid),
7934-                 level=log.NOISY)
7935-        ss = self.servermap.connections[peerid]
7936-        started = time.time()
7937-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
7938+
7939+    def decode(self, blocks_and_salts, segnum):
7940+        """
7941+        I am a helper method that the mutable file update process uses
7942+        as a shortcut to decode and decrypt the segments that it needs
7943+        to fetch in order to perform a file update. I take in a
7944+        collection of blocks and salts, and pick some of those to make a
7945+        segment with. I return the plaintext associated with that
7946+        segment.
7947+        """
7948+        # shnum => block hash tree. Unusued, but setup_encoding_parameters will
7949+        # want to set this.
7950+        # XXX: Make it so that it won't set this if we're just decoding.
7951+        self._block_hash_trees = {}
7952+        self._setup_encoding_parameters()
7953+        # This is the form expected by decode.
7954+        blocks_and_salts = blocks_and_salts.items()
7955+        blocks_and_salts = [(True, [d]) for d in blocks_and_salts]
7956+
7957+        d = self._decode_blocks(blocks_and_salts, segnum)
7958+        d.addCallback(self._decrypt_segment)
7959+        return d
7960+
7961+
7962+    def _setup_encoding_parameters(self):
7963+        """
7964+        I set up the encoding parameters, including k, n, the number
7965+        of segments associated with this file, and the segment decoder.
7966+        """
7967+        (seqnum,
7968+         root_hash,
7969+         IV,
7970+         segsize,
7971+         datalength,
7972+         k,
7973+         n,
7974+         known_prefix,
7975          offsets_tuple) = self.verinfo
7976hunk ./src/allmydata/mutable/retrieve.py 332
7977-        offsets = dict(offsets_tuple)
7978+        self._required_shares = k
7979+        self._total_shares = n
7980+        self._segment_size = segsize
7981+        self._data_length = datalength
7982 
7983hunk ./src/allmydata/mutable/retrieve.py 337
7984-        # we read the checkstring, to make sure that the data we grab is from
7985-        # the right version.
7986-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
7987+        if not IV:
7988+            self._version = MDMF_VERSION
7989+        else:
7990+            self._version = SDMF_VERSION
7991 
7992hunk ./src/allmydata/mutable/retrieve.py 342
7993-        # We also read the data, and the hashes necessary to validate them
7994-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
7995-        # signature or the pubkey, since that was handled during the
7996-        # servermap phase, and we'll be comparing the share hash chain
7997-        # against the roothash that was validated back then.
7998+        if datalength and segsize:
7999+            self._num_segments = mathutil.div_ceil(datalength, segsize)
8000+            self._tail_data_size = datalength % segsize
8001+        else:
8002+            self._num_segments = 0
8003+            self._tail_data_size = 0
8004 
8005hunk ./src/allmydata/mutable/retrieve.py 349
8006-        readv.append( (offsets['share_hash_chain'],
8007-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
8008+        self._segment_decoder = codec.CRSDecoder()
8009+        self._segment_decoder.set_params(segsize, k, n)
8010 
8011hunk ./src/allmydata/mutable/retrieve.py 352
8012-        # if we need the private key (for repair), we also fetch that
8013-        if self._need_privkey:
8014-            readv.append( (offsets['enc_privkey'],
8015-                           offsets['EOF'] - offsets['enc_privkey']) )
8016+        if  not self._tail_data_size:
8017+            self._tail_data_size = segsize
8018+
8019+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
8020+                                                         self._required_shares)
8021+        if self._tail_segment_size == self._segment_size:
8022+            self._tail_decoder = self._segment_decoder
8023+        else:
8024+            self._tail_decoder = codec.CRSDecoder()
8025+            self._tail_decoder.set_params(self._tail_segment_size,
8026+                                          self._required_shares,
8027+                                          self._total_shares)
8028 
8029hunk ./src/allmydata/mutable/retrieve.py 365
8030-        m = Marker()
8031-        self._outstanding_queries[m] = (peerid, shnum, started)
8032+        self.log("got encoding parameters: "
8033+                 "k: %d "
8034+                 "n: %d "
8035+                 "%d segments of %d bytes each (%d byte tail segment)" % \
8036+                 (k, n, self._num_segments, self._segment_size,
8037+                  self._tail_segment_size))
8038 
8039         # ask the cache first
8040         got_from_cache = False
8041merger 0.0 (
8042hunk ./src/allmydata/mutable/retrieve.py 376
8043-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
8044-                                                            offset, length)
8045+            data = self._node._read_from_cache(self.verinfo, shnum, offset, length)
8046hunk ./src/allmydata/mutable/retrieve.py 372
8047-        # ask the cache first
8048-        got_from_cache = False
8049-        datavs = []
8050-        for (offset, length) in readv:
8051-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
8052-                                                            offset, length)
8053-            if data is not None:
8054-                datavs.append(data)
8055-        if len(datavs) == len(readv):
8056-            self.log("got data from cache")
8057-            got_from_cache = True
8058-            d = fireEventually({shnum: datavs})
8059-            # datavs is a dict mapping shnum to a pair of strings
8060+        for i in xrange(self._total_shares):
8061+            # So we don't have to do this later.
8062+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
8063+
8064+        # Our last task is to tell the downloader where to start and
8065+        # where to stop. We use three parameters for that:
8066+        #   - self._start_segment: the segment that we need to start
8067+        #     downloading from.
8068+        #   - self._current_segment: the next segment that we need to
8069+        #     download.
8070+        #   - self._last_segment: The last segment that we were asked to
8071+        #     download.
8072+        #
8073+        #  We say that the download is complete when
8074+        #  self._current_segment > self._last_segment. We use
8075+        #  self._start_segment and self._last_segment to know when to
8076+        #  strip things off of segments, and how much to strip.
8077+        if self._offset:
8078+            self.log("got offset: %d" % self._offset)
8079+            # our start segment is the first segment containing the
8080+            # offset we were given.
8081+            start = mathutil.div_ceil(self._offset,
8082+                                      self._segment_size)
8083+            # this gets us the first segment after self._offset. Then
8084+            # our start segment is the one before it.
8085+            start -= 1
8086+
8087+            assert start < self._num_segments
8088+            self._start_segment = start
8089+            self.log("got start segment: %d" % self._start_segment)
8090)
8091hunk ./src/allmydata/mutable/retrieve.py 386
8092             d = fireEventually({shnum: datavs})
8093             # datavs is a dict mapping shnum to a pair of strings
8094         else:
8095-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
8096-        self.remaining_sharemap.discard(shnum, peerid)
8097+            self._start_segment = 0
8098 
8099hunk ./src/allmydata/mutable/retrieve.py 388
8100-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
8101-        d.addErrback(self._query_failed, m, peerid)
8102-        # errors that aren't handled by _query_failed (and errors caused by
8103-        # _query_failed) get logged, but we still want to check for doneness.
8104-        def _oops(f):
8105-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
8106-                     shnum=shnum,
8107-                     peerid=idlib.shortnodeid_b2a(peerid),
8108-                     failure=f,
8109-                     level=log.WEIRD, umid="W0xnQA")
8110-        d.addErrback(_oops)
8111-        d.addBoth(self._check_for_done)
8112-        # any error during _check_for_done means the download fails. If the
8113-        # download is successful, _check_for_done will fire _done by itself.
8114-        d.addErrback(self._done)
8115-        d.addErrback(log.err)
8116-        return d # purely for testing convenience
8117 
8118hunk ./src/allmydata/mutable/retrieve.py 389
8119-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
8120-        # isolate the callRemote to a separate method, so tests can subclass
8121-        # Publish and override it
8122-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
8123-        return d
8124+        if self._read_length:
8125+            # our end segment is the last segment containing part of the
8126+            # segment that we were asked to read.
8127+            self.log("got read length %d" % self._read_length)
8128+            end_data = self._offset + self._read_length
8129+            end = mathutil.div_ceil(end_data,
8130+                                    self._segment_size)
8131+            end -= 1
8132+            assert end < self._num_segments
8133+            self._last_segment = end
8134+            self.log("got end segment: %d" % self._last_segment)
8135+        else:
8136+            self._last_segment = self._num_segments - 1
8137 
8138hunk ./src/allmydata/mutable/retrieve.py 403
8139-    def remove_peer(self, peerid):
8140-        for shnum in list(self.remaining_sharemap.keys()):
8141-            self.remaining_sharemap.discard(shnum, peerid)
8142+        self._current_segment = self._start_segment
8143 
8144hunk ./src/allmydata/mutable/retrieve.py 405
8145-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
8146-        now = time.time()
8147-        elapsed = now - started
8148-        if not got_from_cache:
8149-            self._status.add_fetch_timing(peerid, elapsed)
8150-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
8151-                 shares=len(datavs),
8152-                 peerid=idlib.shortnodeid_b2a(peerid),
8153-                 level=log.NOISY)
8154-        self._outstanding_queries.pop(marker, None)
8155-        if not self._running:
8156-            return
8157+    def _add_active_peers(self):
8158+        """
8159+        I populate self._active_readers with enough active readers to
8160+        retrieve the contents of this mutable file. I am called before
8161+        downloading starts, and (eventually) after each validation
8162+        error, connection error, or other problem in the download.
8163+        """
8164+        # TODO: It would be cool to investigate other heuristics for
8165+        # reader selection. For instance, the cost (in time the user
8166+        # spends waiting for their file) of selecting a really slow peer
8167+        # that happens to have a primary share is probably more than
8168+        # selecting a really fast peer that doesn't have a primary
8169+        # share. Maybe the servermap could be extended to provide this
8170+        # information; it could keep track of latency information while
8171+        # it gathers more important data, and then this routine could
8172+        # use that to select active readers.
8173+        #
8174+        # (these and other questions would be easier to answer with a
8175+        #  robust, configurable tahoe-lafs simulator, which modeled node
8176+        #  failures, differences in node speed, and other characteristics
8177+        #  that we expect storage servers to have.  You could have
8178+        #  presets for really stable grids (like allmydata.com),
8179+        #  friendnets, make it easy to configure your own settings, and
8180+        #  then simulate the effect of big changes on these use cases
8181+        #  instead of just reasoning about what the effect might be. Out
8182+        #  of scope for MDMF, though.)
8183 
8184hunk ./src/allmydata/mutable/retrieve.py 432
8185-        # note that we only ask for a single share per query, so we only
8186-        # expect a single share back. On the other hand, we use the extra
8187-        # shares if we get them.. seems better than an assert().
8188+        # We need at least self._required_shares readers to download a
8189+        # segment.
8190+        if self._verify:
8191+            needed = self._total_shares
8192+        else:
8193+            needed = self._required_shares - len(self._active_readers)
8194+        # XXX: Why don't format= log messages work here?
8195+        self.log("adding %d peers to the active peers list" % needed)
8196 
8197hunk ./src/allmydata/mutable/retrieve.py 441
8198-        for shnum,datav in datavs.items():
8199-            (prefix, hash_and_data) = datav[:2]
8200-            try:
8201-                self._got_results_one_share(shnum, peerid,
8202-                                            prefix, hash_and_data)
8203-            except CorruptShareError, e:
8204-                # log it and give the other shares a chance to be processed
8205-                f = failure.Failure()
8206-                self.log(format="bad share: %(f_value)s",
8207-                         f_value=str(f.value), failure=f,
8208-                         level=log.WEIRD, umid="7fzWZw")
8209-                self.notify_server_corruption(peerid, shnum, str(e))
8210-                self.remove_peer(peerid)
8211-                self.servermap.mark_bad_share(peerid, shnum, prefix)
8212-                self._bad_shares.add( (peerid, shnum) )
8213-                self._status.problems[peerid] = f
8214-                self._last_failure = f
8215-                pass
8216-            if self._need_privkey and len(datav) > 2:
8217-                lp = None
8218-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
8219-        # all done!
8220+        # We favor lower numbered shares, since FEC is faster with
8221+        # primary shares than with other shares, and lower-numbered
8222+        # shares are more likely to be primary than higher numbered
8223+        # shares.
8224+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
8225+        # We shouldn't consider adding shares that we already have; this
8226+        # will cause problems later.
8227+        active_shnums -= set([reader.shnum for reader in self._active_readers])
8228+        active_shnums = list(active_shnums)[:needed]
8229+        if len(active_shnums) < needed and not self._verify:
8230+            # We don't have enough readers to retrieve the file; fail.
8231+            return self._failed()
8232 
8233hunk ./src/allmydata/mutable/retrieve.py 454
8234-    def notify_server_corruption(self, peerid, shnum, reason):
8235-        ss = self.servermap.connections[peerid]
8236-        ss.callRemoteOnly("advise_corrupt_share",
8237-                          "mutable", self._storage_index, shnum, reason)
8238+        for shnum in active_shnums:
8239+            self._active_readers.append(self.readers[shnum])
8240+            self.log("added reader for share %d" % shnum)
8241+        assert len(self._active_readers) >= self._required_shares
8242+        # Conceptually, this is part of the _add_active_peers step. It
8243+        # validates the prefixes of newly added readers to make sure
8244+        # that they match what we are expecting for self.verinfo. If
8245+        # validation is successful, _validate_active_prefixes will call
8246+        # _download_current_segment for us. If validation is
8247+        # unsuccessful, then _validate_prefixes will remove the peer and
8248+        # call _add_active_peers again, where we will attempt to rectify
8249+        # the problem by choosing another peer.
8250+        return self._validate_active_prefixes()
8251 
8252hunk ./src/allmydata/mutable/retrieve.py 468
8253-    def _got_results_one_share(self, shnum, peerid,
8254-                               got_prefix, got_hash_and_data):
8255-        self.log("_got_results: got shnum #%d from peerid %s"
8256-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
8257-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8258-         offsets_tuple) = self.verinfo
8259-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
8260-        if got_prefix != prefix:
8261-            msg = "someone wrote to the data since we read the servermap: prefix changed"
8262-            raise UncoordinatedWriteError(msg)
8263-        (share_hash_chain, block_hash_tree,
8264-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
8265 
8266hunk ./src/allmydata/mutable/retrieve.py 469
8267-        assert isinstance(share_data, str)
8268-        # build the block hash tree. SDMF has only one leaf.
8269-        leaves = [hashutil.block_hash(share_data)]
8270-        t = hashtree.HashTree(leaves)
8271-        if list(t) != block_hash_tree:
8272-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
8273-        share_hash_leaf = t[0]
8274-        t2 = hashtree.IncompleteHashTree(N)
8275-        # root_hash was checked by the signature
8276-        t2.set_hashes({0: root_hash})
8277-        try:
8278-            t2.set_hashes(hashes=share_hash_chain,
8279-                          leaves={shnum: share_hash_leaf})
8280-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
8281-                IndexError), e:
8282-            msg = "corrupt hashes: %s" % (e,)
8283-            raise CorruptShareError(peerid, shnum, msg)
8284-        self.log(" data valid! len=%d" % len(share_data))
8285-        # each query comes down to this: placing validated share data into
8286-        # self.shares
8287-        self.shares[shnum] = share_data
8288+    def _validate_active_prefixes(self):
8289+        """
8290+        I check to make sure that the prefixes on the peers that I am
8291+        currently reading from match the prefix that we want to see, as
8292+        said in self.verinfo.
8293 
8294hunk ./src/allmydata/mutable/retrieve.py 475
8295-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
8296+        If I find that all of the active peers have acceptable prefixes,
8297+        I pass control to _download_current_segment, which will use
8298+        those peers to do cool things. If I find that some of the active
8299+        peers have unacceptable prefixes, I will remove them from active
8300+        peers (and from further consideration) and call
8301+        _add_active_peers to attempt to rectify the situation. I keep
8302+        track of which peers I have already validated so that I don't
8303+        need to do so again.
8304+        """
8305+        assert self._active_readers, "No more active readers"
8306 
8307hunk ./src/allmydata/mutable/retrieve.py 486
8308-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
8309-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
8310-        if alleged_writekey != self._node.get_writekey():
8311-            self.log("invalid privkey from %s shnum %d" %
8312-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
8313-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
8314-            return
8315+        ds = []
8316+        new_readers = set(self._active_readers) - self._validated_readers
8317+        self.log('validating %d newly-added active readers' % len(new_readers))
8318 
8319hunk ./src/allmydata/mutable/retrieve.py 490
8320-        # it's good
8321-        self.log("got valid privkey from shnum %d on peerid %s" %
8322-                 (shnum, idlib.shortnodeid_b2a(peerid)),
8323-                 parent=lp)
8324-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
8325-        self._node._populate_encprivkey(enc_privkey)
8326-        self._node._populate_privkey(privkey)
8327-        self._need_privkey = False
8328+        for reader in new_readers:
8329+            # We force a remote read here -- otherwise, we are relying
8330+            # on cached data that we already verified as valid, and we
8331+            # won't detect an uncoordinated write that has occurred
8332+            # since the last servermap update.
8333+            d = reader.get_prefix(force_remote=True)
8334+            d.addCallback(self._try_to_validate_prefix, reader)
8335+            ds.append(d)
8336+        dl = defer.DeferredList(ds, consumeErrors=True)
8337+        def _check_results(results):
8338+            # Each result in results will be of the form (success, msg).
8339+            # We don't care about msg, but success will tell us whether
8340+            # or not the checkstring validated. If it didn't, we need to
8341+            # remove the offending (peer,share) from our active readers,
8342+            # and ensure that active readers is again populated.
8343+            bad_readers = []
8344+            for i, result in enumerate(results):
8345+                if not result[0]:
8346+                    reader = self._active_readers[i]
8347+                    f = result[1]
8348+                    assert isinstance(f, failure.Failure)
8349 
8350hunk ./src/allmydata/mutable/retrieve.py 512
8351-    def _query_failed(self, f, marker, peerid):
8352-        self.log(format="query to [%(peerid)s] failed",
8353-                 peerid=idlib.shortnodeid_b2a(peerid),
8354-                 level=log.NOISY)
8355-        self._status.problems[peerid] = f
8356-        self._outstanding_queries.pop(marker, None)
8357-        if not self._running:
8358-            return
8359-        self._last_failure = f
8360-        self.remove_peer(peerid)
8361-        level = log.WEIRD
8362-        if f.check(DeadReferenceError):
8363-            level = log.UNUSUAL
8364-        self.log(format="error during query: %(f_value)s",
8365-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
8366+                    self.log("The reader %s failed to "
8367+                             "properly validate: %s" % \
8368+                             (reader, str(f.value)))
8369+                    bad_readers.append((reader, f))
8370+                else:
8371+                    reader = self._active_readers[i]
8372+                    self.log("the reader %s checks out, so we'll use it" % \
8373+                             reader)
8374+                    self._validated_readers.add(reader)
8375+                    # Each time we validate a reader, we check to see if
8376+                    # we need the private key. If we do, we politely ask
8377+                    # for it and then continue computing. If we find
8378+                    # that we haven't gotten it at the end of
8379+                    # segment decoding, then we'll take more drastic
8380+                    # measures.
8381+                    if self._need_privkey and not self._node.is_readonly():
8382+                        d = reader.get_encprivkey()
8383+                        d.addCallback(self._try_to_validate_privkey, reader)
8384+            if bad_readers:
8385+                # We do them all at once, or else we screw up list indexing.
8386+                for (reader, f) in bad_readers:
8387+                    self._mark_bad_share(reader, f)
8388+                if self._verify:
8389+                    if len(self._active_readers) >= self._required_shares:
8390+                        return self._download_current_segment()
8391+                    else:
8392+                        return self._failed()
8393+                else:
8394+                    return self._add_active_peers()
8395+            else:
8396+                return self._download_current_segment()
8397+            # The next step will assert that it has enough active
8398+            # readers to fetch shares; we just need to remove it.
8399+        dl.addCallback(_check_results)
8400+        return dl
8401 
8402hunk ./src/allmydata/mutable/retrieve.py 548
8403-    def _check_for_done(self, res):
8404-        # exit paths:
8405-        #  return : keep waiting, no new queries
8406-        #  return self._send_more_queries(outstanding) : send some more queries
8407-        #  fire self._done(plaintext) : download successful
8408-        #  raise exception : download fails
8409 
8410hunk ./src/allmydata/mutable/retrieve.py 549
8411-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
8412-                 running=self._running, decoding=self._decoding,
8413-                 level=log.NOISY)
8414-        if not self._running:
8415-            return
8416-        if self._decoding:
8417-            return
8418-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8419+    def _try_to_validate_prefix(self, prefix, reader):
8420+        """
8421+        I check that the prefix returned by a candidate server for
8422+        retrieval matches the prefix that the servermap knows about
8423+        (and, hence, the prefix that was validated earlier). If it does,
8424+        I return True, which means that I approve of the use of the
8425+        candidate server for segment retrieval. If it doesn't, I return
8426+        False, which means that another server must be chosen.
8427+        """
8428+        (seqnum,
8429+         root_hash,
8430+         IV,
8431+         segsize,
8432+         datalength,
8433+         k,
8434+         N,
8435+         known_prefix,
8436          offsets_tuple) = self.verinfo
8437hunk ./src/allmydata/mutable/retrieve.py 567
8438+        if known_prefix != prefix:
8439+            self.log("prefix from share %d doesn't match" % reader.shnum)
8440+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
8441+                                          "indicate an uncoordinated write")
8442+        # Otherwise, we're okay -- no issues.
8443 
8444hunk ./src/allmydata/mutable/retrieve.py 573
8445-        if len(self.shares) < k:
8446-            # we don't have enough shares yet
8447-            return self._maybe_send_more_queries(k)
8448-        if self._need_privkey:
8449-            # we got k shares, but none of them had a valid privkey. TODO:
8450-            # look further. Adding code to do this is a bit complicated, and
8451-            # I want to avoid that complication, and this should be pretty
8452-            # rare (k shares with bitflips in the enc_privkey but not in the
8453-            # data blocks). If we actually do get here, the subsequent repair
8454-            # will fail for lack of a privkey.
8455-            self.log("got k shares but still need_privkey, bummer",
8456-                     level=log.WEIRD, umid="MdRHPA")
8457 
8458hunk ./src/allmydata/mutable/retrieve.py 574
8459-        # we have enough to finish. All the shares have had their hashes
8460-        # checked, so if something fails at this point, we don't know how
8461-        # to fix it, so the download will fail.
8462+    def _remove_reader(self, reader):
8463+        """
8464+        At various points, we will wish to remove a peer from
8465+        consideration and/or use. These include, but are not necessarily
8466+        limited to:
8467 
8468hunk ./src/allmydata/mutable/retrieve.py 580
8469-        self._decoding = True # avoid reentrancy
8470-        self._status.set_status("decoding")
8471-        now = time.time()
8472-        elapsed = now - self._started
8473-        self._status.timings["fetch"] = elapsed
8474+            - A connection error.
8475+            - A mismatched prefix (that is, a prefix that does not match
8476+              our conception of the version information string).
8477+            - A failing block hash, salt hash, or share hash, which can
8478+              indicate disk failure/bit flips, or network trouble.
8479 
8480hunk ./src/allmydata/mutable/retrieve.py 586
8481-        d = defer.maybeDeferred(self._decode)
8482-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
8483-        d.addBoth(self._done)
8484-        return d # purely for test convenience
8485+        This method will do that. I will make sure that the
8486+        (shnum,reader) combination represented by my reader argument is
8487+        not used for anything else during this download. I will not
8488+        advise the reader of any corruption, something that my callers
8489+        may wish to do on their own.
8490+        """
8491+        # TODO: When you're done writing this, see if this is ever
8492+        # actually used for something that _mark_bad_share isn't. I have
8493+        # a feeling that they will be used for very similar things, and
8494+        # that having them both here is just going to be an epic amount
8495+        # of code duplication.
8496+        #
8497+        # (well, okay, not epic, but meaningful)
8498+        self.log("removing reader %s" % reader)
8499+        # Remove the reader from _active_readers
8500+        self._active_readers.remove(reader)
8501+        # TODO: self.readers.remove(reader)?
8502+        for shnum in list(self.remaining_sharemap.keys()):
8503+            self.remaining_sharemap.discard(shnum, reader.peerid)
8504 
8505hunk ./src/allmydata/mutable/retrieve.py 606
8506-    def _maybe_send_more_queries(self, k):
8507-        # we don't have enough shares yet. Should we send out more queries?
8508-        # There are some number of queries outstanding, each for a single
8509-        # share. If we can generate 'needed_shares' additional queries, we do
8510-        # so. If we can't, then we know this file is a goner, and we raise
8511-        # NotEnoughSharesError.
8512-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
8513-                         "outstanding=%(outstanding)d"),
8514-                 have=len(self.shares), k=k,
8515-                 outstanding=len(self._outstanding_queries),
8516-                 level=log.NOISY)
8517 
8518hunk ./src/allmydata/mutable/retrieve.py 607
8519-        remaining_shares = k - len(self.shares)
8520-        needed = remaining_shares - len(self._outstanding_queries)
8521-        if not needed:
8522-            # we have enough queries in flight already
8523+    def _mark_bad_share(self, reader, f):
8524+        """
8525+        I mark the (peerid, shnum) encapsulated by my reader argument as
8526+        a bad share, which means that it will not be used anywhere else.
8527 
8528hunk ./src/allmydata/mutable/retrieve.py 612
8529-            # TODO: but if they've been in flight for a long time, and we
8530-            # have reason to believe that new queries might respond faster
8531-            # (i.e. we've seen other queries come back faster, then consider
8532-            # sending out new queries. This could help with peers which have
8533-            # silently gone away since the servermap was updated, for which
8534-            # we're still waiting for the 15-minute TCP disconnect to happen.
8535-            self.log("enough queries are in flight, no more are needed",
8536-                     level=log.NOISY)
8537-            return
8538+        There are several reasons to want to mark something as a bad
8539+        share. These include:
8540+
8541+            - A connection error to the peer.
8542+            - A mismatched prefix (that is, a prefix that does not match
8543+              our local conception of the version information string).
8544+            - A failing block hash, salt hash, share hash, or other
8545+              integrity check.
8546 
8547hunk ./src/allmydata/mutable/retrieve.py 621
8548-        outstanding_shnums = set([shnum
8549-                                  for (peerid, shnum, started)
8550-                                  in self._outstanding_queries.values()])
8551-        # prefer low-numbered shares, they are more likely to be primary
8552-        available_shnums = sorted(self.remaining_sharemap.keys())
8553-        for shnum in available_shnums:
8554-            if shnum in outstanding_shnums:
8555-                # skip ones that are already in transit
8556-                continue
8557-            if shnum not in self.remaining_sharemap:
8558-                # no servers for that shnum. note that DictOfSets removes
8559-                # empty sets from the dict for us.
8560-                continue
8561-            peerid = list(self.remaining_sharemap[shnum])[0]
8562-            # get_data will remove that peerid from the sharemap, and add the
8563-            # query to self._outstanding_queries
8564-            self._status.set_status("Retrieving More Shares")
8565-            self.get_data(shnum, peerid)
8566-            needed -= 1
8567-            if not needed:
8568+        This method will ensure that readers that we wish to mark bad
8569+        (for these reasons or other reasons) are not used for the rest
8570+        of the download. Additionally, it will attempt to tell the
8571+        remote peer (with no guarantee of success) that its share is
8572+        corrupt.
8573+        """
8574+        self.log("marking share %d on server %s as bad" % \
8575+                 (reader.shnum, reader))
8576+        prefix = self.verinfo[-2]
8577+        self.servermap.mark_bad_share(reader.peerid,
8578+                                      reader.shnum,
8579+                                      prefix)
8580+        self._remove_reader(reader)
8581+        self._bad_shares.add((reader.peerid, reader.shnum, f))
8582+        self._status.problems[reader.peerid] = f
8583+        self._last_failure = f
8584+        self.notify_server_corruption(reader.peerid, reader.shnum,
8585+                                      str(f.value))
8586+
8587+
8588+    def _download_current_segment(self):
8589+        """
8590+        I download, validate, decode, decrypt, and assemble the segment
8591+        that this Retrieve is currently responsible for downloading.
8592+        """
8593+        assert len(self._active_readers) >= self._required_shares
8594+        if self._current_segment <= self._last_segment:
8595+            d = self._process_segment(self._current_segment)
8596+        else:
8597+            d = defer.succeed(None)
8598+        d.addBoth(self._turn_barrier)
8599+        d.addCallback(self._check_for_done)
8600+        return d
8601+
8602+
8603+    def _turn_barrier(self, result):
8604+        """
8605+        I help the download process avoid the recursion limit issues
8606+        discussed in #237.
8607+        """
8608+        return fireEventually(result)
8609+
8610+
8611+    def _process_segment(self, segnum):
8612+        """
8613+        I download, validate, decode, and decrypt one segment of the
8614+        file that this Retrieve is retrieving. This means coordinating
8615+        the process of getting k blocks of that file, validating them,
8616+        assembling them into one segment with the decoder, and then
8617+        decrypting them.
8618+        """
8619+        self.log("processing segment %d" % segnum)
8620+
8621+        # TODO: The old code uses a marker. Should this code do that
8622+        # too? What did the Marker do?
8623+        assert len(self._active_readers) >= self._required_shares
8624+
8625+        # We need to ask each of our active readers for its block and
8626+        # salt. We will then validate those. If validation is
8627+        # successful, we will assemble the results into plaintext.
8628+        ds = []
8629+        for reader in self._active_readers:
8630+            started = time.time()
8631+            d = reader.get_block_and_salt(segnum, queue=True)
8632+            d2 = self._get_needed_hashes(reader, segnum)
8633+            dl = defer.DeferredList([d, d2], consumeErrors=True)
8634+            dl.addCallback(self._validate_block, segnum, reader, started)
8635+            dl.addErrback(self._validation_or_decoding_failed, [reader])
8636+            ds.append(dl)
8637+            reader.flush()
8638+        dl = defer.DeferredList(ds)
8639+        if self._verify:
8640+            dl.addCallback(lambda ignored: "")
8641+            dl.addCallback(self._set_segment)
8642+        else:
8643+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
8644+        return dl
8645+
8646+
8647+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
8648+        """
8649+        I take the results of fetching and validating the blocks from a
8650+        callback chain in another method. If the results are such that
8651+        they tell me that validation and fetching succeeded without
8652+        incident, I will proceed with decoding and decryption.
8653+        Otherwise, I will do nothing.
8654+        """
8655+        self.log("trying to decode and decrypt segment %d" % segnum)
8656+        failures = False
8657+        for block_and_salt in blocks_and_salts:
8658+            if not block_and_salt[0] or block_and_salt[1] == None:
8659+                self.log("some validation operations failed; not proceeding")
8660+                failures = True
8661                 break
8662hunk ./src/allmydata/mutable/retrieve.py 715
8663+        if not failures:
8664+            self.log("everything looks ok, building segment %d" % segnum)
8665+            d = self._decode_blocks(blocks_and_salts, segnum)
8666+            d.addCallback(self._decrypt_segment)
8667+            d.addErrback(self._validation_or_decoding_failed,
8668+                         self._active_readers)
8669+            # check to see whether we've been paused before writing
8670+            # anything.
8671+            d.addCallback(self._check_for_paused)
8672+            d.addCallback(self._set_segment)
8673+            return d
8674+        else:
8675+            return defer.succeed(None)
8676+
8677+
8678+    def _set_segment(self, segment):
8679+        """
8680+        Given a plaintext segment, I register that segment with the
8681+        target that is handling the file download.
8682+        """
8683+        self.log("got plaintext for segment %d" % self._current_segment)
8684+        if self._current_segment == self._start_segment:
8685+            # We're on the first segment. It's possible that we want
8686+            # only some part of the end of this segment, and that we
8687+            # just downloaded the whole thing to get that part. If so,
8688+            # we need to account for that and give the reader just the
8689+            # data that they want.
8690+            n = self._offset % self._segment_size
8691+            self.log("stripping %d bytes off of the first segment" % n)
8692+            self.log("original segment length: %d" % len(segment))
8693+            segment = segment[n:]
8694+            self.log("new segment length: %d" % len(segment))
8695+
8696+        if self._current_segment == self._last_segment and self._read_length is not None:
8697+            # We're on the last segment. It's possible that we only want
8698+            # part of the beginning of this segment, and that we
8699+            # downloaded the whole thing anyway. Make sure to give the
8700+            # caller only the portion of the segment that they want to
8701+            # receive.
8702+            extra = self._read_length
8703+            if self._start_segment != self._last_segment:
8704+                extra -= self._segment_size - \
8705+                            (self._offset % self._segment_size)
8706+            extra %= self._segment_size
8707+            self.log("original segment length: %d" % len(segment))
8708+            segment = segment[:extra]
8709+            self.log("new segment length: %d" % len(segment))
8710+            self.log("only taking %d bytes of the last segment" % extra)
8711+
8712+        if not self._verify:
8713+            self._consumer.write(segment)
8714+        else:
8715+            # we don't care about the plaintext if we are doing a verify.
8716+            segment = None
8717+        self._current_segment += 1
8718 
8719hunk ./src/allmydata/mutable/retrieve.py 771
8720-        # at this point, we have as many outstanding queries as we can. If
8721-        # needed!=0 then we might not have enough to recover the file.
8722-        if needed:
8723-            format = ("ran out of peers: "
8724-                      "have %(have)d shares (k=%(k)d), "
8725-                      "%(outstanding)d queries in flight, "
8726-                      "need %(need)d more, "
8727-                      "found %(bad)d bad shares")
8728-            args = {"have": len(self.shares),
8729-                    "k": k,
8730-                    "outstanding": len(self._outstanding_queries),
8731-                    "need": needed,
8732-                    "bad": len(self._bad_shares),
8733-                    }
8734-            self.log(format=format,
8735-                     level=log.WEIRD, umid="ezTfjw", **args)
8736-            err = NotEnoughSharesError("%s, last failure: %s" %
8737-                                      (format % args, self._last_failure))
8738-            if self._bad_shares:
8739-                self.log("We found some bad shares this pass. You should "
8740-                         "update the servermap and try again to check "
8741-                         "more peers",
8742-                         level=log.WEIRD, umid="EFkOlA")
8743-                err.servermap = self.servermap
8744-            raise err
8745 
8746hunk ./src/allmydata/mutable/retrieve.py 772
8747+    def _validation_or_decoding_failed(self, f, readers):
8748+        """
8749+        I am called when a block or a salt fails to correctly validate, or when
8750+        the decryption or decoding operation fails for some reason.  I react to
8751+        this failure by notifying the remote server of corruption, and then
8752+        removing the remote peer from further activity.
8753+        """
8754+        assert isinstance(readers, list)
8755+        bad_shnums = [reader.shnum for reader in readers]
8756+
8757+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
8758+                 ", segment %d: %s" % \
8759+                 (bad_shnums, readers, self._current_segment, str(f)))
8760+        for reader in readers:
8761+            self._mark_bad_share(reader, f)
8762         return
8763 
8764hunk ./src/allmydata/mutable/retrieve.py 789
8765-    def _decode(self):
8766-        started = time.time()
8767-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8768-         offsets_tuple) = self.verinfo
8769 
8770hunk ./src/allmydata/mutable/retrieve.py 790
8771-        # shares_dict is a dict mapping shnum to share data, but the codec
8772-        # wants two lists.
8773-        shareids = []; shares = []
8774-        for shareid, share in self.shares.items():
8775+    def _validate_block(self, results, segnum, reader, started):
8776+        """
8777+        I validate a block from one share on a remote server.
8778+        """
8779+        # Grab the part of the block hash tree that is necessary to
8780+        # validate this block, then generate the block hash root.
8781+        self.log("validating share %d for segment %d" % (reader.shnum,
8782+                                                             segnum))
8783+        self._status.add_fetch_timing(reader.peerid, started)
8784+        self._status.set_status("Valdiating blocks for segment %d" % segnum)
8785+        # Did we fail to fetch either of the things that we were
8786+        # supposed to? Fail if so.
8787+        if not results[0][0] and results[1][0]:
8788+            # handled by the errback handler.
8789+
8790+            # These all get batched into one query, so the resulting
8791+            # failure should be the same for all of them, so we can just
8792+            # use the first one.
8793+            assert isinstance(results[0][1], failure.Failure)
8794+
8795+            f = results[0][1]
8796+            raise CorruptShareError(reader.peerid,
8797+                                    reader.shnum,
8798+                                    "Connection error: %s" % str(f))
8799+
8800+        block_and_salt, block_and_sharehashes = results
8801+        block, salt = block_and_salt[1]
8802+        blockhashes, sharehashes = block_and_sharehashes[1]
8803+
8804+        blockhashes = dict(enumerate(blockhashes[1]))
8805+        self.log("the reader gave me the following blockhashes: %s" % \
8806+                 blockhashes.keys())
8807+        self.log("the reader gave me the following sharehashes: %s" % \
8808+                 sharehashes[1].keys())
8809+        bht = self._block_hash_trees[reader.shnum]
8810+
8811+        if bht.needed_hashes(segnum, include_leaf=True):
8812+            try:
8813+                bht.set_hashes(blockhashes)
8814+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8815+                    IndexError), e:
8816+                raise CorruptShareError(reader.peerid,
8817+                                        reader.shnum,
8818+                                        "block hash tree failure: %s" % e)
8819+
8820+        if self._version == MDMF_VERSION:
8821+            blockhash = hashutil.block_hash(salt + block)
8822+        else:
8823+            blockhash = hashutil.block_hash(block)
8824+        # If this works without an error, then validation is
8825+        # successful.
8826+        try:
8827+           bht.set_hashes(leaves={segnum: blockhash})
8828+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8829+                IndexError), e:
8830+            raise CorruptShareError(reader.peerid,
8831+                                    reader.shnum,
8832+                                    "block hash tree failure: %s" % e)
8833+
8834+        # Reaching this point means that we know that this segment
8835+        # is correct. Now we need to check to see whether the share
8836+        # hash chain is also correct.
8837+        # SDMF wrote share hash chains that didn't contain the
8838+        # leaves, which would be produced from the block hash tree.
8839+        # So we need to validate the block hash tree first. If
8840+        # successful, then bht[0] will contain the root for the
8841+        # shnum, which will be a leaf in the share hash tree, which
8842+        # will allow us to validate the rest of the tree.
8843+        if self.share_hash_tree.needed_hashes(reader.shnum,
8844+                                              include_leaf=True) or \
8845+                                              self._verify:
8846+            try:
8847+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
8848+                                            leaves={reader.shnum: bht[0]})
8849+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8850+                    IndexError), e:
8851+                raise CorruptShareError(reader.peerid,
8852+                                        reader.shnum,
8853+                                        "corrupt hashes: %s" % e)
8854+
8855+        self.log('share %d is valid for segment %d' % (reader.shnum,
8856+                                                       segnum))
8857+        return {reader.shnum: (block, salt)}
8858+
8859+
8860+    def _get_needed_hashes(self, reader, segnum):
8861+        """
8862+        I get the hashes needed to validate segnum from the reader, then return
8863+        to my caller when this is done.
8864+        """
8865+        bht = self._block_hash_trees[reader.shnum]
8866+        needed = bht.needed_hashes(segnum, include_leaf=True)
8867+        # The root of the block hash tree is also a leaf in the share
8868+        # hash tree. So we don't need to fetch it from the remote
8869+        # server. In the case of files with one segment, this means that
8870+        # we won't fetch any block hash tree from the remote server,
8871+        # since the hash of each share of the file is the entire block
8872+        # hash tree, and is a leaf in the share hash tree. This is fine,
8873+        # since any share corruption will be detected in the share hash
8874+        # tree.
8875+        #needed.discard(0)
8876+        self.log("getting blockhashes for segment %d, share %d: %s" % \
8877+                 (segnum, reader.shnum, str(needed)))
8878+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
8879+        if self.share_hash_tree.needed_hashes(reader.shnum):
8880+            need = self.share_hash_tree.needed_hashes(reader.shnum)
8881+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
8882+                                                                 str(need)))
8883+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
8884+        else:
8885+            d2 = defer.succeed({}) # the logic in the next method
8886+                                   # expects a dict
8887+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
8888+        return dl
8889+
8890+
8891+    def _decode_blocks(self, blocks_and_salts, segnum):
8892+        """
8893+        I take a list of k blocks and salts, and decode that into a
8894+        single encrypted segment.
8895+        """
8896+        d = {}
8897+        # We want to merge our dictionaries to the form
8898+        # {shnum: blocks_and_salts}
8899+        #
8900+        # The dictionaries come from validate block that way, so we just
8901+        # need to merge them.
8902+        for block_and_salt in blocks_and_salts:
8903+            d.update(block_and_salt[1])
8904+
8905+        # All of these blocks should have the same salt; in SDMF, it is
8906+        # the file-wide IV, while in MDMF it is the per-segment salt. In
8907+        # either case, we just need to get one of them and use it.
8908+        #
8909+        # d.items()[0] is like (shnum, (block, salt))
8910+        # d.items()[0][1] is like (block, salt)
8911+        # d.items()[0][1][1] is the salt.
8912+        salt = d.items()[0][1][1]
8913+        # Next, extract just the blocks from the dict. We'll use the
8914+        # salt in the next step.
8915+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
8916+        d2 = dict(share_and_shareids)
8917+        shareids = []
8918+        shares = []
8919+        for shareid, share in d2.items():
8920             shareids.append(shareid)
8921             shares.append(share)
8922 
8923hunk ./src/allmydata/mutable/retrieve.py 938
8924-        assert len(shareids) >= k, len(shareids)
8925+        self._status.set_status("Decoding")
8926+        started = time.time()
8927+        assert len(shareids) >= self._required_shares, len(shareids)
8928         # zfec really doesn't want extra shares
8929hunk ./src/allmydata/mutable/retrieve.py 942
8930-        shareids = shareids[:k]
8931-        shares = shares[:k]
8932-
8933-        fec = codec.CRSDecoder()
8934-        fec.set_params(segsize, k, N)
8935-
8936-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
8937-        self.log("about to decode, shareids=%s" % (shareids,))
8938-        d = defer.maybeDeferred(fec.decode, shares, shareids)
8939-        def _done(buffers):
8940-            self._status.timings["decode"] = time.time() - started
8941-            self.log(" decode done, %d buffers" % len(buffers))
8942+        shareids = shareids[:self._required_shares]
8943+        shares = shares[:self._required_shares]
8944+        self.log("decoding segment %d" % segnum)
8945+        if segnum == self._num_segments - 1:
8946+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
8947+        else:
8948+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
8949+        def _process(buffers):
8950             segment = "".join(buffers)
8951hunk ./src/allmydata/mutable/retrieve.py 951
8952+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
8953+                     segnum=segnum,
8954+                     numsegs=self._num_segments,
8955+                     level=log.NOISY)
8956             self.log(" joined length %d, datalength %d" %
8957hunk ./src/allmydata/mutable/retrieve.py 956
8958-                     (len(segment), datalength))
8959-            segment = segment[:datalength]
8960+                     (len(segment), self._data_length))
8961+            if segnum == self._num_segments - 1:
8962+                size_to_use = self._tail_data_size
8963+            else:
8964+                size_to_use = self._segment_size
8965+            segment = segment[:size_to_use]
8966             self.log(" segment len=%d" % len(segment))
8967hunk ./src/allmydata/mutable/retrieve.py 963
8968-            return segment
8969-        def _err(f):
8970-            self.log(" decode failed: %s" % f)
8971-            return f
8972-        d.addCallback(_done)
8973-        d.addErrback(_err)
8974+            self._status.timings.setdefault("decode", 0)
8975+            self._status.timings['decode'] = time.time() - started
8976+            return segment, salt
8977+        d.addCallback(_process)
8978         return d
8979 
8980hunk ./src/allmydata/mutable/retrieve.py 969
8981-    def _decrypt(self, crypttext, IV, readkey):
8982+
8983+    def _decrypt_segment(self, segment_and_salt):
8984+        """
8985+        I take a single segment and its salt, and decrypt it. I return
8986+        the plaintext of the segment that is in my argument.
8987+        """
8988+        segment, salt = segment_and_salt
8989         self._status.set_status("decrypting")
8990hunk ./src/allmydata/mutable/retrieve.py 977
8991+        self.log("decrypting segment %d" % self._current_segment)
8992         started = time.time()
8993hunk ./src/allmydata/mutable/retrieve.py 979
8994-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
8995+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
8996         decryptor = AES(key)
8997hunk ./src/allmydata/mutable/retrieve.py 981
8998-        plaintext = decryptor.process(crypttext)
8999-        self._status.timings["decrypt"] = time.time() - started
9000+        plaintext = decryptor.process(segment)
9001+        self._status.timings.setdefault("decrypt", 0)
9002+        self._status.timings['decrypt'] = time.time() - started
9003         return plaintext
9004 
9005hunk ./src/allmydata/mutable/retrieve.py 986
9006-    def _done(self, res):
9007-        if not self._running:
9008+
9009+    def notify_server_corruption(self, peerid, shnum, reason):
9010+        ss = self.servermap.connections[peerid]
9011+        ss.callRemoteOnly("advise_corrupt_share",
9012+                          "mutable", self._storage_index, shnum, reason)
9013+
9014+
9015+    def _try_to_validate_privkey(self, enc_privkey, reader):
9016+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
9017+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
9018+        if alleged_writekey != self._node.get_writekey():
9019+            self.log("invalid privkey from %s shnum %d" %
9020+                     (reader, reader.shnum),
9021+                     level=log.WEIRD, umid="YIw4tA")
9022+            if self._verify:
9023+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
9024+                                              self.verinfo[-2])
9025+                e = CorruptShareError(reader.peerid,
9026+                                      reader.shnum,
9027+                                      "invalid privkey")
9028+                f = failure.Failure(e)
9029+                self._bad_shares.add((reader.peerid, reader.shnum, f))
9030             return
9031hunk ./src/allmydata/mutable/retrieve.py 1009
9032+
9033+        # it's good
9034+        self.log("got valid privkey from shnum %d on reader %s" %
9035+                 (reader.shnum, reader))
9036+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
9037+        self._node._populate_encprivkey(enc_privkey)
9038+        self._node._populate_privkey(privkey)
9039+        self._need_privkey = False
9040+
9041+
9042+    def _check_for_done(self, res):
9043+        """
9044+        I check to see if this Retrieve object has successfully finished
9045+        its work.
9046+
9047+        I can exit in the following ways:
9048+            - If there are no more segments to download, then I exit by
9049+              causing self._done_deferred to fire with the plaintext
9050+              content requested by the caller.
9051+            - If there are still segments to be downloaded, and there
9052+              are enough active readers (readers which have not broken
9053+              and have not given us corrupt data) to continue
9054+              downloading, I send control back to
9055+              _download_current_segment.
9056+            - If there are still segments to be downloaded but there are
9057+              not enough active peers to download them, I ask
9058+              _add_active_peers to add more peers. If it is successful,
9059+              it will call _download_current_segment. If there are not
9060+              enough peers to retrieve the file, then that will cause
9061+              _done_deferred to errback.
9062+        """
9063+        self.log("checking for doneness")
9064+        if self._current_segment > self._last_segment:
9065+            # No more segments to download, we're done.
9066+            self.log("got plaintext, done")
9067+            return self._done()
9068+
9069+        if len(self._active_readers) >= self._required_shares:
9070+            # More segments to download, but we have enough good peers
9071+            # in self._active_readers that we can do that without issue,
9072+            # so go nab the next segment.
9073+            self.log("not done yet: on segment %d of %d" % \
9074+                     (self._current_segment + 1, self._num_segments))
9075+            return self._download_current_segment()
9076+
9077+        self.log("not done yet: on segment %d of %d, need to add peers" % \
9078+                 (self._current_segment + 1, self._num_segments))
9079+        return self._add_active_peers()
9080+
9081+
9082+    def _done(self):
9083+        """
9084+        I am called by _check_for_done when the download process has
9085+        finished successfully. After making some useful logging
9086+        statements, I return the decrypted contents to the owner of this
9087+        Retrieve object through self._done_deferred.
9088+        """
9089         self._running = False
9090         self._status.set_active(False)
9091hunk ./src/allmydata/mutable/retrieve.py 1068
9092-        self._status.timings["total"] = time.time() - self._started
9093-        # res is either the new contents, or a Failure
9094-        if isinstance(res, failure.Failure):
9095-            self.log("Retrieve done, with failure", failure=res,
9096-                     level=log.UNUSUAL)
9097-            self._status.set_status("Failed")
9098+        now = time.time()
9099+        self._status.timings['total'] = now - self._started
9100+        self._status.timings['fetch'] = now - self._started_fetching
9101+
9102+        if self._verify:
9103+            ret = list(self._bad_shares)
9104+            self.log("done verifying, found %d bad shares" % len(ret))
9105         else:
9106hunk ./src/allmydata/mutable/retrieve.py 1076
9107-            self.log("Retrieve done, success!")
9108-            self._status.set_status("Finished")
9109-            self._status.set_progress(1.0)
9110-            # remember the encoding parameters, use them again next time
9111-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9112-             offsets_tuple) = self.verinfo
9113-            self._node._populate_required_shares(k)
9114-            self._node._populate_total_shares(N)
9115-        eventually(self._done_deferred.callback, res)
9116+            # TODO: upload status here?
9117+            ret = self._consumer
9118+            self._consumer.unregisterProducer()
9119+        eventually(self._done_deferred.callback, ret)
9120+
9121 
9122hunk ./src/allmydata/mutable/retrieve.py 1082
9123+    def _failed(self):
9124+        """
9125+        I am called by _add_active_peers when there are not enough
9126+        active peers left to complete the download. After making some
9127+        useful logging statements, I return an exception to that effect
9128+        to the caller of this Retrieve object through
9129+        self._done_deferred.
9130+        """
9131+        self._running = False
9132+        self._status.set_active(False)
9133+        now = time.time()
9134+        self._status.timings['total'] = now - self._started
9135+        self._status.timings['fetch'] = now - self._started_fetching
9136+
9137+        if self._verify:
9138+            ret = list(self._bad_shares)
9139+        else:
9140+            format = ("ran out of peers: "
9141+                      "have %(have)d of %(total)d segments "
9142+                      "found %(bad)d bad shares "
9143+                      "encoding %(k)d-of-%(n)d")
9144+            args = {"have": self._current_segment,
9145+                    "total": self._num_segments,
9146+                    "need": self._last_segment,
9147+                    "k": self._required_shares,
9148+                    "n": self._total_shares,
9149+                    "bad": len(self._bad_shares)}
9150+            e = NotEnoughSharesError("%s, last failure: %s" % \
9151+                                     (format % args, str(self._last_failure)))
9152+            f = failure.Failure(e)
9153+            ret = f
9154+        eventually(self._done_deferred.callback, ret)
9155}
9156[mutable/servermap.py: Alter the servermap updater to work with MDMF files
9157Kevan Carstensen <kevan@isnotajoke.com>**20100819003439
9158 Ignore-this: 7e408303194834bd59a2f27efab3bdb
9159 
9160 These modifications were basically all to the end of having the
9161 servermap updater use the unified MDMF + SDMF read interface whenever
9162 possible -- this reduces the complexity of the code, making it easier to
9163 read and maintain. To do this, I needed to modify the process of
9164 updating the servermap a little bit.
9165 
9166 To support partial-file updates, I also modified the servermap updater
9167 to fetch the block hash trees and certain segments of files while it
9168 performed a servermap update (this can be done without adding any new
9169 roundtrips because of batch-read functionality that the read proxy has).
9170 
9171] {
9172hunk ./src/allmydata/mutable/servermap.py 2
9173 
9174-import sys, time
9175+import sys, time, struct
9176 from zope.interface import implements
9177 from itertools import count
9178 from twisted.internet import defer
9179merger 0.0 (
9180hunk ./src/allmydata/mutable/servermap.py 9
9181+from allmydata.util.dictutil import DictOfSets
9182hunk ./src/allmydata/mutable/servermap.py 7
9183-from foolscap.api import DeadReferenceError, RemoteException, eventually
9184-from allmydata.util import base32, hashutil, idlib, log
9185+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
9186+                         fireEventually
9187+from allmydata.util import base32, hashutil, idlib, log, deferredutil
9188)
9189merger 0.0 (
9190hunk ./src/allmydata/mutable/servermap.py 14
9191-     DictOfSets, CorruptShareError, NeedMoreDataError
9192+     CorruptShareError, NeedMoreDataError
9193hunk ./src/allmydata/mutable/servermap.py 14
9194-     DictOfSets, CorruptShareError, NeedMoreDataError
9195-from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
9196-     SIGNED_PREFIX_LENGTH
9197+     DictOfSets, CorruptShareError
9198+from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
9199)
9200hunk ./src/allmydata/mutable/servermap.py 123
9201         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
9202         self.last_update_mode = None
9203         self.last_update_time = 0
9204+        self.update_data = {} # (verinfo,shnum) => data
9205 
9206     def copy(self):
9207         s = ServerMap()
9208hunk ./src/allmydata/mutable/servermap.py 254
9209         """Return a set of versionids, one for each version that is currently
9210         recoverable."""
9211         versionmap = self.make_versionmap()
9212-
9213         recoverable_versions = set()
9214         for (verinfo, shares) in versionmap.items():
9215             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9216hunk ./src/allmydata/mutable/servermap.py 339
9217         return False
9218 
9219 
9220+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
9221+        """
9222+        I return the update data for the given shnum
9223+        """
9224+        update_data = self.update_data[shnum]
9225+        update_datum = [i[1] for i in update_data if i[0] == verinfo][0]
9226+        return update_datum
9227+
9228+
9229+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
9230+        """
9231+        I record the block hash tree for the given shnum.
9232+        """
9233+        self.update_data.setdefault(shnum , []).append((verinfo, data))
9234+
9235+
9236 class ServermapUpdater:
9237     def __init__(self, filenode, storage_broker, monitor, servermap,
9238hunk ./src/allmydata/mutable/servermap.py 357
9239-                 mode=MODE_READ, add_lease=False):
9240+                 mode=MODE_READ, add_lease=False, update_range=None):
9241         """I update a servermap, locating a sufficient number of useful
9242         shares and remembering where they are located.
9243 
9244hunk ./src/allmydata/mutable/servermap.py 382
9245         self._servers_responded = set()
9246 
9247         # how much data should we read?
9248+        # SDMF:
9249         #  * if we only need the checkstring, then [0:75]
9250         #  * if we need to validate the checkstring sig, then [543ish:799ish]
9251         #  * if we need the verification key, then [107:436ish]
9252merger 0.0 (
9253hunk ./src/allmydata/mutable/servermap.py 392
9254-        # read 2000 bytes, which also happens to read enough actual data to
9255-        # pre-fetch a 9-entry dirnode.
9256+        # read 4000 bytes, which also happens to read enough actual data to
9257+        # pre-fetch an 18-entry dirnode.
9258hunk ./src/allmydata/mutable/servermap.py 390
9259-        # A future version of the SMDF slot format should consider using
9260-        # fixed-size slots so we can retrieve less data. For now, we'll just
9261-        # read 2000 bytes, which also happens to read enough actual data to
9262-        # pre-fetch a 9-entry dirnode.
9263+        # MDMF:
9264+        #  * Checkstring? [0:72]
9265+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
9266+        #    the offset table will tell us for sure.
9267+        #  * If we need the verification key, we have to consult the offset
9268+        #    table as well.
9269+        # At this point, we don't know which we are. Our filenode can
9270+        # tell us, but it might be lying -- in some cases, we're
9271+        # responsible for telling it which kind of file it is.
9272)
9273hunk ./src/allmydata/mutable/servermap.py 399
9274             # we use unpack_prefix_and_signature, so we need 1k
9275             self._read_size = 1000
9276         self._need_privkey = False
9277+
9278         if mode == MODE_WRITE and not self._node.get_privkey():
9279             self._need_privkey = True
9280         # check+repair: repair requires the privkey, so if we didn't happen
9281hunk ./src/allmydata/mutable/servermap.py 406
9282         # to ask for it during the check, we'll have problems doing the
9283         # publish.
9284 
9285+        self.fetch_update_data = False
9286+        if mode == MODE_WRITE and update_range:
9287+            # We're updating the servermap in preparation for an
9288+            # in-place file update, so we need to fetch some additional
9289+            # data from each share that we find.
9290+            assert len(update_range) == 2
9291+
9292+            self.start_segment = update_range[0]
9293+            self.end_segment = update_range[1]
9294+            self.fetch_update_data = True
9295+
9296         prefix = si_b2a(self._storage_index)[:5]
9297         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
9298                                    si=prefix, mode=mode)
9299merger 0.0 (
9300hunk ./src/allmydata/mutable/servermap.py 455
9301-        full_peerlist = sb.get_servers_for_index(self._storage_index)
9302+        full_peerlist = [(s.get_serverid(), s.get_rref())
9303+                         for s in sb.get_servers_for_psi(self._storage_index)]
9304hunk ./src/allmydata/mutable/servermap.py 455
9305+        # All of the peers, permuted by the storage index, as usual.
9306)
9307hunk ./src/allmydata/mutable/servermap.py 461
9308         self._good_peers = set() # peers who had some shares
9309         self._empty_peers = set() # peers who don't have any shares
9310         self._bad_peers = set() # peers to whom our queries failed
9311+        self._readers = {} # peerid -> dict(sharewriters), filled in
9312+                           # after responses come in.
9313 
9314         k = self._node.get_required_shares()
9315hunk ./src/allmydata/mutable/servermap.py 465
9316+        # For what cases can these conditions work?
9317         if k is None:
9318             # make a guess
9319             k = 3
9320hunk ./src/allmydata/mutable/servermap.py 478
9321         self.num_peers_to_query = k + self.EPSILON
9322 
9323         if self.mode == MODE_CHECK:
9324+            # We want to query all of the peers.
9325             initial_peers_to_query = dict(full_peerlist)
9326             must_query = set(initial_peers_to_query.keys())
9327             self.extra_peers = []
9328hunk ./src/allmydata/mutable/servermap.py 486
9329             # we're planning to replace all the shares, so we want a good
9330             # chance of finding them all. We will keep searching until we've
9331             # seen epsilon that don't have a share.
9332+            # We don't query all of the peers because that could take a while.
9333             self.num_peers_to_query = N + self.EPSILON
9334             initial_peers_to_query, must_query = self._build_initial_querylist()
9335             self.required_num_empty_peers = self.EPSILON
9336hunk ./src/allmydata/mutable/servermap.py 496
9337             # might also avoid the round trip required to read the encrypted
9338             # private key.
9339 
9340-        else:
9341+        else: # MODE_READ, MODE_ANYTHING
9342+            # 2k peers is good enough.
9343             initial_peers_to_query, must_query = self._build_initial_querylist()
9344 
9345         # this is a set of peers that we are required to get responses from:
9346hunk ./src/allmydata/mutable/servermap.py 512
9347         # before we can consider ourselves finished, and self.extra_peers
9348         # contains the overflow (peers that we should tap if we don't get
9349         # enough responses)
9350+        # I guess that self._must_query is a subset of
9351+        # initial_peers_to_query?
9352+        assert set(must_query).issubset(set(initial_peers_to_query))
9353 
9354         self._send_initial_requests(initial_peers_to_query)
9355         self._status.timings["initial_queries"] = time.time() - self._started
9356hunk ./src/allmydata/mutable/servermap.py 571
9357         # errors that aren't handled by _query_failed (and errors caused by
9358         # _query_failed) get logged, but we still want to check for doneness.
9359         d.addErrback(log.err)
9360-        d.addBoth(self._check_for_done)
9361         d.addErrback(self._fatal_error)
9362hunk ./src/allmydata/mutable/servermap.py 572
9363+        d.addCallback(self._check_for_done)
9364         return d
9365 
9366     def _do_read(self, ss, peerid, storage_index, shnums, readv):
9367hunk ./src/allmydata/mutable/servermap.py 591
9368         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
9369         return d
9370 
9371+
9372+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
9373+        """
9374+        I am called when a remote server returns a corrupt share in
9375+        response to one of our queries. By corrupt, I mean a share
9376+        without a valid signature. I then record the failure, notify the
9377+        server of the corruption, and record the share as bad.
9378+        """
9379+        f = failure.Failure(e)
9380+        self.log(format="bad share: %(f_value)s", f_value=str(f),
9381+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
9382+        # Notify the server that its share is corrupt.
9383+        self.notify_server_corruption(peerid, shnum, str(e))
9384+        # By flagging this as a bad peer, we won't count any of
9385+        # the other shares on that peer as valid, though if we
9386+        # happen to find a valid version string amongst those
9387+        # shares, we'll keep track of it so that we don't need
9388+        # to validate the signature on those again.
9389+        self._bad_peers.add(peerid)
9390+        self._last_failure = f
9391+        # XXX: Use the reader for this?
9392+        checkstring = data[:SIGNED_PREFIX_LENGTH]
9393+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
9394+        self._servermap.problems.append(f)
9395+
9396+
9397+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
9398+        """
9399+        If one of my queries returns successfully (which means that we
9400+        were able to and successfully did validate the signature), I
9401+        cache the data that we initially fetched from the storage
9402+        server. This will help reduce the number of roundtrips that need
9403+        to occur when the file is downloaded, or when the file is
9404+        updated.
9405+        """
9406+        if verinfo:
9407+            self._node._add_to_cache(verinfo, shnum, 0, data, now)
9408+
9409+
9410     def _got_results(self, datavs, peerid, readsize, stuff, started):
9411         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
9412                       peerid=idlib.shortnodeid_b2a(peerid),
9413hunk ./src/allmydata/mutable/servermap.py 633
9414-                      numshares=len(datavs),
9415-                      level=log.NOISY)
9416+                      numshares=len(datavs))
9417         now = time.time()
9418         elapsed = now - started
9419hunk ./src/allmydata/mutable/servermap.py 636
9420-        self._queries_outstanding.discard(peerid)
9421-        self._servermap.reachable_peers.add(peerid)
9422-        self._must_query.discard(peerid)
9423-        self._queries_completed += 1
9424+        def _done_processing(ignored=None):
9425+            self._queries_outstanding.discard(peerid)
9426+            self._servermap.reachable_peers.add(peerid)
9427+            self._must_query.discard(peerid)
9428+            self._queries_completed += 1
9429         if not self._running:
9430hunk ./src/allmydata/mutable/servermap.py 642
9431-            self.log("but we're not running, so we'll ignore it", parent=lp,
9432-                     level=log.NOISY)
9433+            self.log("but we're not running, so we'll ignore it", parent=lp)
9434+            _done_processing()
9435             self._status.add_per_server_time(peerid, "late", started, elapsed)
9436             return
9437         self._status.add_per_server_time(peerid, "query", started, elapsed)
9438hunk ./src/allmydata/mutable/servermap.py 653
9439         else:
9440             self._empty_peers.add(peerid)
9441 
9442-        last_verinfo = None
9443-        last_shnum = None
9444+        ss, storage_index = stuff
9445+        ds = []
9446+
9447         for shnum,datav in datavs.items():
9448             data = datav[0]
9449             try:
9450merger 0.0 (
9451hunk ./src/allmydata/mutable/servermap.py 662
9452-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
9453+                self._node._add_to_cache(verinfo, shnum, 0, data)
9454hunk ./src/allmydata/mutable/servermap.py 658
9455-            try:
9456-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
9457-                last_verinfo = verinfo
9458-                last_shnum = shnum
9459-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
9460-            except CorruptShareError, e:
9461-                # log it and give the other shares a chance to be processed
9462-                f = failure.Failure()
9463-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
9464-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
9465-                self.notify_server_corruption(peerid, shnum, str(e))
9466-                self._bad_peers.add(peerid)
9467-                self._last_failure = f
9468-                checkstring = data[:SIGNED_PREFIX_LENGTH]
9469-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
9470-                self._servermap.problems.append(f)
9471-                pass
9472+            reader = MDMFSlotReadProxy(ss,
9473+                                       storage_index,
9474+                                       shnum,
9475+                                       data)
9476+            self._readers.setdefault(peerid, dict())[shnum] = reader
9477+            # our goal, with each response, is to validate the version
9478+            # information and share data as best we can at this point --
9479+            # we do this by validating the signature. To do this, we
9480+            # need to do the following:
9481+            #   - If we don't already have the public key, fetch the
9482+            #     public key. We use this to validate the signature.
9483+            if not self._node.get_pubkey():
9484+                # fetch and set the public key.
9485+                d = reader.get_verification_key(queue=True)
9486+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
9487+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
9488+                # XXX: Make self._pubkey_query_failed?
9489+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
9490+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
9491+            else:
9492+                # we already have the public key.
9493+                d = defer.succeed(None)
9494)
9495hunk ./src/allmydata/mutable/servermap.py 676
9496                 self._servermap.problems.append(f)
9497                 pass
9498 
9499-        self._status.timings["cumulative_verify"] += (time.time() - now)
9500+            # Neither of these two branches return anything of
9501+            # consequence, so the first entry in our deferredlist will
9502+            # be None.
9503 
9504hunk ./src/allmydata/mutable/servermap.py 680
9505-        if self._need_privkey and last_verinfo:
9506-            # send them a request for the privkey. We send one request per
9507-            # server.
9508-            lp2 = self.log("sending privkey request",
9509-                           parent=lp, level=log.NOISY)
9510-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9511-             offsets_tuple) = last_verinfo
9512-            o = dict(offsets_tuple)
9513+            # - Next, we need the version information. We almost
9514+            #   certainly got this by reading the first thousand or so
9515+            #   bytes of the share on the storage server, so we
9516+            #   shouldn't need to fetch anything at this step.
9517+            d2 = reader.get_verinfo()
9518+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
9519+                self._got_corrupt_share(error, shnum, peerid, data, lp))
9520+            # - Next, we need the signature. For an SDMF share, it is
9521+            #   likely that we fetched this when doing our initial fetch
9522+            #   to get the version information. In MDMF, this lives at
9523+            #   the end of the share, so unless the file is quite small,
9524+            #   we'll need to do a remote fetch to get it.
9525+            d3 = reader.get_signature(queue=True)
9526+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
9527+                self._got_corrupt_share(error, shnum, peerid, data, lp))
9528+            #  Once we have all three of these responses, we can move on
9529+            #  to validating the signature
9530 
9531hunk ./src/allmydata/mutable/servermap.py 698
9532-            self._queries_outstanding.add(peerid)
9533-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
9534-            ss = self._servermap.connections[peerid]
9535-            privkey_started = time.time()
9536-            d = self._do_read(ss, peerid, self._storage_index,
9537-                              [last_shnum], readv)
9538-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
9539-                          privkey_started, lp2)
9540-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
9541-            d.addErrback(log.err)
9542-            d.addCallback(self._check_for_done)
9543-            d.addErrback(self._fatal_error)
9544+            # Does the node already have a privkey? If not, we'll try to
9545+            # fetch it here.
9546+            if self._need_privkey:
9547+                d4 = reader.get_encprivkey(queue=True)
9548+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
9549+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
9550+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
9551+                    self._privkey_query_failed(error, shnum, data, lp))
9552+            else:
9553+                d4 = defer.succeed(None)
9554+
9555+
9556+            if self.fetch_update_data:
9557+                # fetch the block hash tree and first + last segment, as
9558+                # configured earlier.
9559+                # Then set them in wherever we happen to want to set
9560+                # them.
9561+                ds = []
9562+                # XXX: We do this above, too. Is there a good way to
9563+                # make the two routines share the value without
9564+                # introducing more roundtrips?
9565+                ds.append(reader.get_verinfo())
9566+                ds.append(reader.get_blockhashes(queue=True))
9567+                ds.append(reader.get_block_and_salt(self.start_segment,
9568+                                                    queue=True))
9569+                ds.append(reader.get_block_and_salt(self.end_segment,
9570+                                                    queue=True))
9571+                d5 = deferredutil.gatherResults(ds)
9572+                d5.addCallback(self._got_update_results_one_share, shnum)
9573+            else:
9574+                d5 = defer.succeed(None)
9575 
9576hunk ./src/allmydata/mutable/servermap.py 730
9577+            dl = defer.DeferredList([d, d2, d3, d4, d5])
9578+            dl.addBoth(self._turn_barrier)
9579+            reader.flush()
9580+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
9581+                self._got_signature_one_share(results, shnum, peerid, lp))
9582+            dl.addErrback(lambda error, shnum=shnum, data=data:
9583+               self._got_corrupt_share(error, shnum, peerid, data, lp))
9584+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
9585+                self._cache_good_sharedata(verinfo, shnum, now, data))
9586+            ds.append(dl)
9587+        # dl is a deferred list that will fire when all of the shares
9588+        # that we found on this peer are done processing. When dl fires,
9589+        # we know that processing is done, so we can decrement the
9590+        # semaphore-like thing that we incremented earlier.
9591+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
9592+        # Are we done? Done means that there are no more queries to
9593+        # send, that there are no outstanding queries, and that we
9594+        # haven't received any queries that are still processing. If we
9595+        # are done, self._check_for_done will cause the done deferred
9596+        # that we returned to our caller to fire, which tells them that
9597+        # they have a complete servermap, and that we won't be touching
9598+        # the servermap anymore.
9599+        dl.addCallback(_done_processing)
9600+        dl.addCallback(self._check_for_done)
9601+        dl.addErrback(self._fatal_error)
9602         # all done!
9603         self.log("_got_results done", parent=lp, level=log.NOISY)
9604hunk ./src/allmydata/mutable/servermap.py 757
9605+        return dl
9606+
9607+
9608+    def _turn_barrier(self, result):
9609+        """
9610+        I help the servermap updater avoid the recursion limit issues
9611+        discussed in #237.
9612+        """
9613+        return fireEventually(result)
9614+
9615+
9616+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
9617+        if self._node.get_pubkey():
9618+            return # don't go through this again if we don't have to
9619+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
9620+        assert len(fingerprint) == 32
9621+        if fingerprint != self._node.get_fingerprint():
9622+            raise CorruptShareError(peerid, shnum,
9623+                                "pubkey doesn't match fingerprint")
9624+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
9625+        assert self._node.get_pubkey()
9626+
9627 
9628     def notify_server_corruption(self, peerid, shnum, reason):
9629         ss = self._servermap.connections[peerid]
9630hunk ./src/allmydata/mutable/servermap.py 785
9631         ss.callRemoteOnly("advise_corrupt_share",
9632                           "mutable", self._storage_index, shnum, reason)
9633 
9634-    def _got_results_one_share(self, shnum, data, peerid, lp):
9635+
9636+    def _got_signature_one_share(self, results, shnum, peerid, lp):
9637+        # It is our job to give versioninfo to our caller. We need to
9638+        # raise CorruptShareError if the share is corrupt for any
9639+        # reason, something that our caller will handle.
9640         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
9641                  shnum=shnum,
9642                  peerid=idlib.shortnodeid_b2a(peerid),
9643hunk ./src/allmydata/mutable/servermap.py 795
9644                  level=log.NOISY,
9645                  parent=lp)
9646+        if not self._running:
9647+            # We can't process the results, since we can't touch the
9648+            # servermap anymore.
9649+            self.log("but we're not running anymore.")
9650+            return None
9651 
9652hunk ./src/allmydata/mutable/servermap.py 801
9653-        # this might raise NeedMoreDataError, if the pubkey and signature
9654-        # live at some weird offset. That shouldn't happen, so I'm going to
9655-        # treat it as a bad share.
9656-        (seqnum, root_hash, IV, k, N, segsize, datalength,
9657-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
9658-
9659-        if not self._node.get_pubkey():
9660-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
9661-            assert len(fingerprint) == 32
9662-            if fingerprint != self._node.get_fingerprint():
9663-                raise CorruptShareError(peerid, shnum,
9664-                                        "pubkey doesn't match fingerprint")
9665-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
9666-
9667-        if self._need_privkey:
9668-            self._try_to_extract_privkey(data, peerid, shnum, lp)
9669-
9670-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
9671-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
9672+        _, verinfo, signature, __, ___ = results
9673+        (seqnum,
9674+         root_hash,
9675+         saltish,
9676+         segsize,
9677+         datalen,
9678+         k,
9679+         n,
9680+         prefix,
9681+         offsets) = verinfo[1]
9682         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
9683 
9684hunk ./src/allmydata/mutable/servermap.py 813
9685-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9686+        # XXX: This should be done for us in the method, so
9687+        # presumably you can go in there and fix it.
9688+        verinfo = (seqnum,
9689+                   root_hash,
9690+                   saltish,
9691+                   segsize,
9692+                   datalen,
9693+                   k,
9694+                   n,
9695+                   prefix,
9696                    offsets_tuple)
9697hunk ./src/allmydata/mutable/servermap.py 824
9698+        # This tuple uniquely identifies a share on the grid; we use it
9699+        # to keep track of the ones that we've already seen.
9700 
9701         if verinfo not in self._valid_versions:
9702hunk ./src/allmydata/mutable/servermap.py 828
9703-            # it's a new pair. Verify the signature.
9704-            valid = self._node.get_pubkey().verify(prefix, signature)
9705+            # This is a new version tuple, and we need to validate it
9706+            # against the public key before keeping track of it.
9707+            assert self._node.get_pubkey()
9708+            valid = self._node.get_pubkey().verify(prefix, signature[1])
9709             if not valid:
9710hunk ./src/allmydata/mutable/servermap.py 833
9711-                raise CorruptShareError(peerid, shnum, "signature is invalid")
9712+                raise CorruptShareError(peerid, shnum,
9713+                                        "signature is invalid")
9714 
9715hunk ./src/allmydata/mutable/servermap.py 836
9716-            # ok, it's a valid verinfo. Add it to the list of validated
9717-            # versions.
9718-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
9719-                     % (seqnum, base32.b2a(root_hash)[:4],
9720-                        idlib.shortnodeid_b2a(peerid), shnum,
9721-                        k, N, segsize, datalength),
9722-                     parent=lp)
9723-            self._valid_versions.add(verinfo)
9724-        # We now know that this is a valid candidate verinfo.
9725+        # ok, it's a valid verinfo. Add it to the list of validated
9726+        # versions.
9727+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
9728+                 % (seqnum, base32.b2a(root_hash)[:4],
9729+                    idlib.shortnodeid_b2a(peerid), shnum,
9730+                    k, n, segsize, datalen),
9731+                    parent=lp)
9732+        self._valid_versions.add(verinfo)
9733+        # We now know that this is a valid candidate verinfo. Whether or
9734+        # not this instance of it is valid is a matter for the next
9735+        # statement; at this point, we just know that if we see this
9736+        # version info again, that its signature checks out and that
9737+        # we're okay to skip the signature-checking step.
9738 
9739hunk ./src/allmydata/mutable/servermap.py 850
9740+        # (peerid, shnum) are bound in the method invocation.
9741         if (peerid, shnum) in self._servermap.bad_shares:
9742             # we've been told that the rest of the data in this share is
9743             # unusable, so don't add it to the servermap.
9744hunk ./src/allmydata/mutable/servermap.py 863
9745         self._servermap.add_new_share(peerid, shnum, verinfo, timestamp)
9746         # and the versionmap
9747         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
9748+
9749+        # It's our job to set the protocol version of our parent
9750+        # filenode if it isn't already set.
9751+        if not self._node.get_version():
9752+            # The first byte of the prefix is the version.
9753+            v = struct.unpack(">B", prefix[:1])[0]
9754+            self.log("got version %d" % v)
9755+            self._node.set_version(v)
9756+
9757         return verinfo
9758 
9759hunk ./src/allmydata/mutable/servermap.py 874
9760-    def _deserialize_pubkey(self, pubkey_s):
9761-        verifier = rsa.create_verifying_key_from_string(pubkey_s)
9762-        return verifier
9763 
9764hunk ./src/allmydata/mutable/servermap.py 875
9765-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
9766-        try:
9767-            r = unpack_share(data)
9768-        except NeedMoreDataError, e:
9769-            # this share won't help us. oh well.
9770-            offset = e.encprivkey_offset
9771-            length = e.encprivkey_length
9772-            self.log("shnum %d on peerid %s: share was too short (%dB) "
9773-                     "to get the encprivkey; [%d:%d] ought to hold it" %
9774-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
9775-                      offset, offset+length),
9776-                     parent=lp)
9777-            # NOTE: if uncoordinated writes are taking place, someone might
9778-            # change the share (and most probably move the encprivkey) before
9779-            # we get a chance to do one of these reads and fetch it. This
9780-            # will cause us to see a NotEnoughSharesError(unable to fetch
9781-            # privkey) instead of an UncoordinatedWriteError . This is a
9782-            # nuisance, but it will go away when we move to DSA-based mutable
9783-            # files (since the privkey will be small enough to fit in the
9784-            # write cap).
9785+    def _got_update_results_one_share(self, results, share):
9786+        """
9787+        I record the update results in results.
9788+        """
9789+        assert len(results) == 4
9790+        verinfo, blockhashes, start, end = results
9791+        (seqnum,
9792+         root_hash,
9793+         saltish,
9794+         segsize,
9795+         datalen,
9796+         k,
9797+         n,
9798+         prefix,
9799+         offsets) = verinfo
9800+        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
9801 
9802hunk ./src/allmydata/mutable/servermap.py 892
9803-            return
9804+        # XXX: This should be done for us in the method, so
9805+        # presumably you can go in there and fix it.
9806+        verinfo = (seqnum,
9807+                   root_hash,
9808+                   saltish,
9809+                   segsize,
9810+                   datalen,
9811+                   k,
9812+                   n,
9813+                   prefix,
9814+                   offsets_tuple)
9815 
9816hunk ./src/allmydata/mutable/servermap.py 904
9817-        (seqnum, root_hash, IV, k, N, segsize, datalen,
9818-         pubkey, signature, share_hash_chain, block_hash_tree,
9819-         share_data, enc_privkey) = r
9820+        update_data = (blockhashes, start, end)
9821+        self._servermap.set_update_data_for_share_and_verinfo(share,
9822+                                                              verinfo,
9823+                                                              update_data)
9824 
9825hunk ./src/allmydata/mutable/servermap.py 909
9826-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
9827+
9828+    def _deserialize_pubkey(self, pubkey_s):
9829+        verifier = rsa.create_verifying_key_from_string(pubkey_s)
9830+        return verifier
9831 
9832hunk ./src/allmydata/mutable/servermap.py 914
9833-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
9834 
9835hunk ./src/allmydata/mutable/servermap.py 915
9836+    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
9837+        """
9838+        Given a writekey from a remote server, I validate it against the
9839+        writekey stored in my node. If it is valid, then I set the
9840+        privkey and encprivkey properties of the node.
9841+        """
9842         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
9843         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
9844         if alleged_writekey != self._node.get_writekey():
9845hunk ./src/allmydata/mutable/servermap.py 993
9846         self._queries_completed += 1
9847         self._last_failure = f
9848 
9849-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
9850-        now = time.time()
9851-        elapsed = now - started
9852-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
9853-        self._queries_outstanding.discard(peerid)
9854-        if not self._need_privkey:
9855-            return
9856-        if shnum not in datavs:
9857-            self.log("privkey wasn't there when we asked it",
9858-                     level=log.WEIRD, umid="VA9uDQ")
9859-            return
9860-        datav = datavs[shnum]
9861-        enc_privkey = datav[0]
9862-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
9863 
9864     def _privkey_query_failed(self, f, peerid, shnum, lp):
9865         self._queries_outstanding.discard(peerid)
9866hunk ./src/allmydata/mutable/servermap.py 1007
9867         self._servermap.problems.append(f)
9868         self._last_failure = f
9869 
9870+
9871     def _check_for_done(self, res):
9872         # exit paths:
9873         #  return self._send_more_queries(outstanding) : send some more queries
9874hunk ./src/allmydata/mutable/servermap.py 1013
9875         #  return self._done() : all done
9876         #  return : keep waiting, no new queries
9877-
9878         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
9879                               "%(outstanding)d queries outstanding, "
9880                               "%(extra)d extra peers available, "
9881hunk ./src/allmydata/mutable/servermap.py 1204
9882 
9883     def _done(self):
9884         if not self._running:
9885+            self.log("not running; we're already done")
9886             return
9887         self._running = False
9888         now = time.time()
9889hunk ./src/allmydata/mutable/servermap.py 1219
9890         self._servermap.last_update_time = self._started
9891         # the servermap will not be touched after this
9892         self.log("servermap: %s" % self._servermap.summarize_versions())
9893+
9894         eventually(self._done_deferred.callback, self._servermap)
9895 
9896     def _fatal_error(self, f):
9897}
9898[tests:
9899Kevan Carstensen <kevan@isnotajoke.com>**20100819003531
9900 Ignore-this: 314e8bbcce532ea4d5d2cecc9f31cca0
9901 
9902     - A lot of existing tests relied on aspects of the mutable file
9903       implementation that were changed. This patch updates those tests
9904       to work with the changes.
9905     - This patch also adds tests for new features.
9906] {
9907hunk ./src/allmydata/test/common.py 11
9908 from foolscap.api import flushEventualQueue, fireEventually
9909 from allmydata import uri, dirnode, client
9910 from allmydata.introducer.server import IntroducerNode
9911-from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
9912-     FileTooLargeError, NotEnoughSharesError, ICheckable
9913+from allmydata.interfaces import IMutableFileNode, IImmutableFileNode,\
9914+                                 NotEnoughSharesError, ICheckable, \
9915+                                 IMutableUploadable, SDMF_VERSION, \
9916+                                 MDMF_VERSION
9917 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9918      DeepCheckResults, DeepCheckAndRepairResults
9919 from allmydata.mutable.common import CorruptShareError
9920hunk ./src/allmydata/test/common.py 19
9921 from allmydata.mutable.layout import unpack_header
9922+from allmydata.mutable.publish import MutableData
9923 from allmydata.storage.server import storage_index_to_dir
9924 from allmydata.storage.mutable import MutableShareFile
9925 from allmydata.util import hashutil, log, fileutil, pollmixin
9926hunk ./src/allmydata/test/common.py 153
9927         consumer.write(data[start:end])
9928         return consumer
9929 
9930+
9931+    def get_best_readable_version(self):
9932+        return defer.succeed(self)
9933+
9934+
9935+    download_best_version = download_to_data
9936+
9937+
9938+    def download_to_data(self):
9939+        return download_to_data(self)
9940+
9941+
9942+    def get_size_of_best_version(self):
9943+        return defer.succeed(self.get_size)
9944+
9945+
9946 def make_chk_file_cap(size):
9947     return uri.CHKFileURI(key=os.urandom(16),
9948                           uri_extension_hash=os.urandom(32),
9949hunk ./src/allmydata/test/common.py 193
9950     MUTABLE_SIZELIMIT = 10000
9951     all_contents = {}
9952     bad_shares = {}
9953+    file_types = {} # storage index => MDMF_VERSION or SDMF_VERSION
9954 
9955     def __init__(self, storage_broker, secret_holder,
9956                  default_encoding_parameters, history):
9957hunk ./src/allmydata/test/common.py 200
9958         self.init_from_cap(make_mutable_file_cap())
9959     def create(self, contents, key_generator=None, keysize=None):
9960         initial_contents = self._get_initial_contents(contents)
9961-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9962-            raise FileTooLargeError("SDMF is limited to one segment, and "
9963-                                    "%d > %d" % (len(initial_contents),
9964-                                                 self.MUTABLE_SIZELIMIT))
9965-        self.all_contents[self.storage_index] = initial_contents
9966+        data = initial_contents.read(initial_contents.get_size())
9967+        data = "".join(data)
9968+        self.all_contents[self.storage_index] = data
9969         return defer.succeed(self)
9970     def _get_initial_contents(self, contents):
9971hunk ./src/allmydata/test/common.py 205
9972-        if isinstance(contents, str):
9973-            return contents
9974         if contents is None:
9975hunk ./src/allmydata/test/common.py 206
9976-            return ""
9977+            return MutableData("")
9978+
9979+        if IMutableUploadable.providedBy(contents):
9980+            return contents
9981+
9982         assert callable(contents), "%s should be callable, not %s" % \
9983                (contents, type(contents))
9984         return contents(self)
9985hunk ./src/allmydata/test/common.py 258
9986     def get_storage_index(self):
9987         return self.storage_index
9988 
9989+    def get_servermap(self, mode):
9990+        return defer.succeed(None)
9991+
9992+    def set_version(self, version):
9993+        assert version in (SDMF_VERSION, MDMF_VERSION)
9994+        self.file_types[self.storage_index] = version
9995+
9996+    def get_version(self):
9997+        assert self.storage_index in self.file_types
9998+        return self.file_types[self.storage_index]
9999+
10000     def check(self, monitor, verify=False, add_lease=False):
10001         r = CheckResults(self.my_uri, self.storage_index)
10002         is_bad = self.bad_shares.get(self.storage_index, None)
10003hunk ./src/allmydata/test/common.py 327
10004         return d
10005 
10006     def download_best_version(self):
10007+        return defer.succeed(self._download_best_version())
10008+
10009+
10010+    def _download_best_version(self, ignored=None):
10011         if isinstance(self.my_uri, uri.LiteralFileURI):
10012hunk ./src/allmydata/test/common.py 332
10013-            return defer.succeed(self.my_uri.data)
10014+            return self.my_uri.data
10015         if self.storage_index not in self.all_contents:
10016hunk ./src/allmydata/test/common.py 334
10017-            return defer.fail(NotEnoughSharesError(None, 0, 3))
10018-        return defer.succeed(self.all_contents[self.storage_index])
10019+            raise NotEnoughSharesError(None, 0, 3)
10020+        return self.all_contents[self.storage_index]
10021+
10022 
10023     def overwrite(self, new_contents):
10024hunk ./src/allmydata/test/common.py 339
10025-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
10026-            raise FileTooLargeError("SDMF is limited to one segment, and "
10027-                                    "%d > %d" % (len(new_contents),
10028-                                                 self.MUTABLE_SIZELIMIT))
10029         assert not self.is_readonly()
10030hunk ./src/allmydata/test/common.py 340
10031-        self.all_contents[self.storage_index] = new_contents
10032+        new_data = new_contents.read(new_contents.get_size())
10033+        new_data = "".join(new_data)
10034+        self.all_contents[self.storage_index] = new_data
10035         return defer.succeed(None)
10036     def modify(self, modifier):
10037         # this does not implement FileTooLargeError, but the real one does
10038hunk ./src/allmydata/test/common.py 350
10039     def _modify(self, modifier):
10040         assert not self.is_readonly()
10041         old_contents = self.all_contents[self.storage_index]
10042-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
10043+        new_data = modifier(old_contents, None, True)
10044+        self.all_contents[self.storage_index] = new_data
10045         return None
10046 
10047hunk ./src/allmydata/test/common.py 354
10048+    # As actually implemented, MutableFilenode and MutableFileVersion
10049+    # are distinct. However, nothing in the webapi uses (yet) that
10050+    # distinction -- it just uses the unified download interface
10051+    # provided by get_best_readable_version and read. When we start
10052+    # doing cooler things like LDMF, we will want to revise this code to
10053+    # be less simplistic.
10054+    def get_best_readable_version(self):
10055+        return defer.succeed(self)
10056+
10057+
10058+    def get_best_mutable_version(self):
10059+        return defer.succeed(self)
10060+
10061+    # Ditto for this, which is an implementation of IWritable.
10062+    # XXX: Declare that the same is implemented.
10063+    def update(self, data, offset):
10064+        assert not self.is_readonly()
10065+        def modifier(old, servermap, first_time):
10066+            new = old[:offset] + "".join(data.read(data.get_size()))
10067+            new += old[len(new):]
10068+            return new
10069+        return self.modify(modifier)
10070+
10071+
10072+    def read(self, consumer, offset=0, size=None):
10073+        data = self._download_best_version()
10074+        if size:
10075+            data = data[offset:offset+size]
10076+        consumer.write(data)
10077+        return defer.succeed(consumer)
10078+
10079+
10080 def make_mutable_file_cap():
10081     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
10082                                    fingerprint=os.urandom(32))
10083hunk ./src/allmydata/test/test_checker.py 11
10084 from allmydata.test.no_network import GridTestMixin
10085 from allmydata.immutable.upload import Data
10086 from allmydata.test.common_web import WebRenderingMixin
10087+from allmydata.mutable.publish import MutableData
10088 
10089 class FakeClient:
10090     def get_storage_broker(self):
10091hunk ./src/allmydata/test/test_checker.py 291
10092         def _stash_immutable(ur):
10093             self.imm = c0.create_node_from_uri(ur.uri)
10094         d.addCallback(_stash_immutable)
10095-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
10096+        d.addCallback(lambda ign:
10097+            c0.create_mutable_file(MutableData("contents")))
10098         def _stash_mutable(node):
10099             self.mut = node
10100         d.addCallback(_stash_mutable)
10101hunk ./src/allmydata/test/test_cli.py 13
10102 from allmydata.util import fileutil, hashutil, base32
10103 from allmydata import uri
10104 from allmydata.immutable import upload
10105+from allmydata.mutable.publish import MutableData
10106 from allmydata.dirnode import normalize
10107 
10108 # Test that the scripts can be imported.
10109hunk ./src/allmydata/test/test_cli.py 662
10110 
10111         d = self.do_cli("create-alias", etudes_arg)
10112         def _check_create_unicode((rc, out, err)):
10113-            self.failUnlessReallyEqual(rc, 0)
10114+            #self.failUnlessReallyEqual(rc, 0)
10115             self.failUnlessReallyEqual(err, "")
10116             self.failUnlessIn("Alias %s created" % quote_output(u"\u00E9tudes"), out)
10117 
10118hunk ./src/allmydata/test/test_cli.py 967
10119         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
10120         return d
10121 
10122+    def test_mutable_type(self):
10123+        self.basedir = "cli/Put/mutable_type"
10124+        self.set_up_grid()
10125+        data = "data" * 100000
10126+        fn1 = os.path.join(self.basedir, "data")
10127+        fileutil.write(fn1, data)
10128+        d = self.do_cli("create-alias", "tahoe")
10129+        d.addCallback(lambda ignored:
10130+            self.do_cli("put", "--mutable", "--mutable-type=mdmf",
10131+                        fn1, "tahoe:uploaded.txt"))
10132+        d.addCallback(lambda ignored:
10133+            self.do_cli("ls", "--json", "tahoe:uploaded.txt"))
10134+        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
10135+        d.addCallback(lambda ignored:
10136+            self.do_cli("put", "--mutable", "--mutable-type=sdmf",
10137+                        fn1, "tahoe:uploaded2.txt"))
10138+        d.addCallback(lambda ignored:
10139+            self.do_cli("ls", "--json", "tahoe:uploaded2.txt"))
10140+        d.addCallback(lambda (rc, json, err):
10141+            self.failUnlessIn("sdmf", json))
10142+        return d
10143+
10144+    def test_mutable_type_unlinked(self):
10145+        self.basedir = "cli/Put/mutable_type_unlinked"
10146+        self.set_up_grid()
10147+        data = "data" * 100000
10148+        fn1 = os.path.join(self.basedir, "data")
10149+        fileutil.write(fn1, data)
10150+        d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
10151+        d.addCallback(lambda (rc, cap, err):
10152+            self.do_cli("ls", "--json", cap))
10153+        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
10154+        d.addCallback(lambda ignored:
10155+            self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1))
10156+        d.addCallback(lambda (rc, cap, err):
10157+            self.do_cli("ls", "--json", cap))
10158+        d.addCallback(lambda (rc, json, err):
10159+            self.failUnlessIn("sdmf", json))
10160+        return d
10161+
10162+    def test_mutable_type_invalid_format(self):
10163+        self.basedir = "cli/Put/mutable_type_invalid_format"
10164+        self.set_up_grid()
10165+        data = "data" * 100000
10166+        fn1 = os.path.join(self.basedir, "data")
10167+        fileutil.write(fn1, data)
10168+        d = self.do_cli("put", "--mutable", "--mutable-type=ldmf", fn1)
10169+        def _check_failure((rc, out, err)):
10170+            self.failIfEqual(rc, 0)
10171+            self.failUnlessIn("invalid", err)
10172+        d.addCallback(_check_failure)
10173+        return d
10174+
10175     def test_put_with_nonexistent_alias(self):
10176         # when invoked with an alias that doesn't exist, 'tahoe put'
10177         # should output a useful error message, not a stack trace
10178hunk ./src/allmydata/test/test_cli.py 2136
10179         self.set_up_grid()
10180         c0 = self.g.clients[0]
10181         DATA = "data" * 100
10182-        d = c0.create_mutable_file(DATA)
10183+        DATA_uploadable = MutableData(DATA)
10184+        d = c0.create_mutable_file(DATA_uploadable)
10185         def _stash_uri(n):
10186             self.uri = n.get_uri()
10187         d.addCallback(_stash_uri)
10188hunk ./src/allmydata/test/test_cli.py 2238
10189                                            upload.Data("literal",
10190                                                         convergence="")))
10191         d.addCallback(_stash_uri, "small")
10192-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
10193+        d.addCallback(lambda ign:
10194+            c0.create_mutable_file(MutableData(DATA+"1")))
10195         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
10196         d.addCallback(_stash_uri, "mutable")
10197 
10198hunk ./src/allmydata/test/test_cli.py 2257
10199         # root/small
10200         # root/mutable
10201 
10202+        # We haven't broken anything yet, so this should all be healthy.
10203         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
10204                                               self.rooturi))
10205         def _check2((rc, out, err)):
10206hunk ./src/allmydata/test/test_cli.py 2272
10207                             in lines, out)
10208         d.addCallback(_check2)
10209 
10210+        # Similarly, all of these results should be as we expect them to
10211+        # be for a healthy file layout.
10212         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
10213         def _check_stats((rc, out, err)):
10214             self.failUnlessReallyEqual(err, "")
10215hunk ./src/allmydata/test/test_cli.py 2289
10216             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
10217         d.addCallback(_check_stats)
10218 
10219+        # Now we break things.
10220         def _clobber_shares(ignored):
10221             shares = self.find_uri_shares(self.uris[u"g\u00F6\u00F6d"])
10222             self.failUnlessReallyEqual(len(shares), 10)
10223hunk ./src/allmydata/test/test_cli.py 2314
10224 
10225         d.addCallback(lambda ign:
10226                       self.do_cli("deep-check", "--verbose", self.rooturi))
10227+        # This should reveal the missing share, but not the corrupt
10228+        # share, since we didn't tell the deep check operation to also
10229+        # verify.
10230         def _check3((rc, out, err)):
10231             self.failUnlessReallyEqual(err, "")
10232             self.failUnlessReallyEqual(rc, 0)
10233hunk ./src/allmydata/test/test_cli.py 2365
10234                                   "--verbose", "--verify", "--repair",
10235                                   self.rooturi))
10236         def _check6((rc, out, err)):
10237+            # We've just repaired the directory. There is no reason for
10238+            # that repair to be unsuccessful.
10239             self.failUnlessReallyEqual(err, "")
10240             self.failUnlessReallyEqual(rc, 0)
10241             lines = out.splitlines()
10242hunk ./src/allmydata/test/test_deepcheck.py 9
10243 from twisted.internet import threads # CLI tests use deferToThread
10244 from allmydata.immutable import upload
10245 from allmydata.mutable.common import UnrecoverableFileError
10246+from allmydata.mutable.publish import MutableData
10247 from allmydata.util import idlib
10248 from allmydata.util import base32
10249 from allmydata.scripts import runner
10250hunk ./src/allmydata/test/test_deepcheck.py 38
10251         self.basedir = "deepcheck/MutableChecker/good"
10252         self.set_up_grid()
10253         CONTENTS = "a little bit of data"
10254-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10255+        CONTENTS_uploadable = MutableData(CONTENTS)
10256+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10257         def _created(node):
10258             self.node = node
10259             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10260hunk ./src/allmydata/test/test_deepcheck.py 61
10261         self.basedir = "deepcheck/MutableChecker/corrupt"
10262         self.set_up_grid()
10263         CONTENTS = "a little bit of data"
10264-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10265+        CONTENTS_uploadable = MutableData(CONTENTS)
10266+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10267         def _stash_and_corrupt(node):
10268             self.node = node
10269             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10270hunk ./src/allmydata/test/test_deepcheck.py 99
10271         self.basedir = "deepcheck/MutableChecker/delete_share"
10272         self.set_up_grid()
10273         CONTENTS = "a little bit of data"
10274-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10275+        CONTENTS_uploadable = MutableData(CONTENTS)
10276+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10277         def _stash_and_delete(node):
10278             self.node = node
10279             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10280hunk ./src/allmydata/test/test_deepcheck.py 223
10281             self.root = n
10282             self.root_uri = n.get_uri()
10283         d.addCallback(_created_root)
10284-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
10285+        d.addCallback(lambda ign:
10286+            c0.create_mutable_file(MutableData("mutable file contents")))
10287         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
10288         def _created_mutable(n):
10289             self.mutable = n
10290hunk ./src/allmydata/test/test_deepcheck.py 965
10291     def create_mangled(self, ignored, name):
10292         nodetype, mangletype = name.split("-", 1)
10293         if nodetype == "mutable":
10294-            d = self.g.clients[0].create_mutable_file("mutable file contents")
10295+            mutable_uploadable = MutableData("mutable file contents")
10296+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
10297             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
10298         elif nodetype == "large":
10299             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
10300hunk ./src/allmydata/test/test_dirnode.py 1304
10301     implements(IMutableFileNode)
10302     counter = 0
10303     def __init__(self, initial_contents=""):
10304-        self.data = self._get_initial_contents(initial_contents)
10305+        data = self._get_initial_contents(initial_contents)
10306+        self.data = data.read(data.get_size())
10307+        self.data = "".join(self.data)
10308+
10309         counter = FakeMutableFile.counter
10310         FakeMutableFile.counter += 1
10311         writekey = hashutil.ssk_writekey_hash(str(counter))
10312hunk ./src/allmydata/test/test_dirnode.py 1354
10313         pass
10314 
10315     def modify(self, modifier):
10316-        self.data = modifier(self.data, None, True)
10317+        data = modifier(self.data, None, True)
10318+        self.data = data
10319         return defer.succeed(None)
10320 
10321 class FakeNodeMaker(NodeMaker):
10322hunk ./src/allmydata/test/test_dirnode.py 1359
10323-    def create_mutable_file(self, contents="", keysize=None):
10324+    def create_mutable_file(self, contents="", keysize=None, version=None):
10325         return defer.succeed(FakeMutableFile(contents))
10326 
10327 class FakeClient2(Client):
10328hunk ./src/allmydata/test/test_filenode.py 98
10329         def _check_segment(res):
10330             self.failUnlessEqual(res, DATA[1:1+5])
10331         d.addCallback(_check_segment)
10332+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
10333+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
10334+        d.addCallback(lambda ignored:
10335+            fn1.get_size_of_best_version())
10336+        d.addCallback(lambda size:
10337+            self.failUnlessEqual(size, len(DATA)))
10338+        d.addCallback(lambda ignored:
10339+            fn1.download_to_data())
10340+        d.addCallback(lambda data:
10341+            self.failUnlessEqual(data, DATA))
10342+        d.addCallback(lambda ignored:
10343+            fn1.download_best_version())
10344+        d.addCallback(lambda data:
10345+            self.failUnlessEqual(data, DATA))
10346 
10347         return d
10348 
10349hunk ./src/allmydata/test/test_hung_server.py 10
10350 from allmydata.util.consumer import download_to_data
10351 from allmydata.immutable import upload
10352 from allmydata.mutable.common import UnrecoverableFileError
10353+from allmydata.mutable.publish import MutableData
10354 from allmydata.storage.common import storage_index_to_dir
10355 from allmydata.test.no_network import GridTestMixin
10356 from allmydata.test.common import ShouldFailMixin
10357hunk ./src/allmydata/test/test_hung_server.py 110
10358         self.servers = self.servers[5:] + self.servers[:5]
10359 
10360         if mutable:
10361-            d = nm.create_mutable_file(mutable_plaintext)
10362+            uploadable = MutableData(mutable_plaintext)
10363+            d = nm.create_mutable_file(uploadable)
10364             def _uploaded_mutable(node):
10365                 self.uri = node.get_uri()
10366                 self.shares = self.find_uri_shares(self.uri)
10367hunk ./src/allmydata/test/test_immutable.py 267
10368         d.addCallback(_after_attempt)
10369         return d
10370 
10371+    def test_download_to_data(self):
10372+        d = self.n.download_to_data()
10373+        d.addCallback(lambda data:
10374+            self.failUnlessEqual(data, common.TEST_DATA))
10375+        return d
10376 
10377hunk ./src/allmydata/test/test_immutable.py 273
10378+
10379+    def test_download_best_version(self):
10380+        d = self.n.download_best_version()
10381+        d.addCallback(lambda data:
10382+            self.failUnlessEqual(data, common.TEST_DATA))
10383+        return d
10384+
10385+
10386+    def test_get_best_readable_version(self):
10387+        d = self.n.get_best_readable_version()
10388+        d.addCallback(lambda n2:
10389+            self.failUnlessEqual(n2, self.n))
10390+        return d
10391+
10392+    def test_get_size_of_best_version(self):
10393+        d = self.n.get_size_of_best_version()
10394+        d.addCallback(lambda size:
10395+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10396+        return d
10397+
10398+
10399 # XXX extend these tests to show bad behavior of various kinds from servers:
10400 # raising exception from each remove_foo() method, for example
10401 
10402hunk ./src/allmydata/test/test_mutable.py 2
10403 
10404-import struct
10405+import os
10406 from cStringIO import StringIO
10407 from twisted.trial import unittest
10408 from twisted.internet import defer, reactor
10409hunk ./src/allmydata/test/test_mutable.py 8
10410 from allmydata import uri, client
10411 from allmydata.nodemaker import NodeMaker
10412-from allmydata.util import base32
10413+from allmydata.util import base32, consumer
10414 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
10415      ssk_pubkey_fingerprint_hash
10416hunk ./src/allmydata/test/test_mutable.py 11
10417+from allmydata.util.deferredutil import gatherResults
10418 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
10419hunk ./src/allmydata/test/test_mutable.py 13
10420-     NotEnoughSharesError
10421+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
10422 from allmydata.monitor import Monitor
10423 from allmydata.test.common import ShouldFailMixin
10424 from allmydata.test.no_network import GridTestMixin
10425hunk ./src/allmydata/test/test_mutable.py 27
10426      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
10427      NotEnoughServersError, CorruptShareError
10428 from allmydata.mutable.retrieve import Retrieve
10429-from allmydata.mutable.publish import Publish
10430+from allmydata.mutable.publish import Publish, MutableFileHandle, \
10431+                                      MutableData, \
10432+                                      DEFAULT_MAX_SEGMENT_SIZE
10433 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10434hunk ./src/allmydata/test/test_mutable.py 31
10435-from allmydata.mutable.layout import unpack_header, unpack_share
10436+from allmydata.mutable.layout import unpack_header, MDMFSlotReadProxy
10437 from allmydata.mutable.repairer import MustForceRepairError
10438 
10439 import allmydata.test.common_util as testutil
10440hunk ./src/allmydata/test/test_mutable.py 100
10441         self.storage = storage
10442         self.queries = 0
10443     def callRemote(self, methname, *args, **kwargs):
10444+        self.queries += 1
10445         def _call():
10446             meth = getattr(self, methname)
10447             return meth(*args, **kwargs)
10448hunk ./src/allmydata/test/test_mutable.py 107
10449         d = fireEventually()
10450         d.addCallback(lambda res: _call())
10451         return d
10452+
10453     def callRemoteOnly(self, methname, *args, **kwargs):
10454hunk ./src/allmydata/test/test_mutable.py 109
10455+        self.queries += 1
10456         d = self.callRemote(methname, *args, **kwargs)
10457         d.addBoth(lambda ignore: None)
10458         pass
10459hunk ./src/allmydata/test/test_mutable.py 157
10460             chr(ord(original[byte_offset]) ^ 0x01) +
10461             original[byte_offset+1:])
10462 
10463+def add_two(original, byte_offset):
10464+    # It isn't enough to simply flip the bit for the version number,
10465+    # because 1 is a valid version number. So we add two instead.
10466+    return (original[:byte_offset] +
10467+            chr(ord(original[byte_offset]) ^ 0x02) +
10468+            original[byte_offset+1:])
10469+
10470 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
10471     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
10472     # list of shnums to corrupt.
10473hunk ./src/allmydata/test/test_mutable.py 167
10474+    ds = []
10475     for peerid in s._peers:
10476         shares = s._peers[peerid]
10477         for shnum in shares:
10478hunk ./src/allmydata/test/test_mutable.py 175
10479                 and shnum not in shnums_to_corrupt):
10480                 continue
10481             data = shares[shnum]
10482-            (version,
10483-             seqnum,
10484-             root_hash,
10485-             IV,
10486-             k, N, segsize, datalen,
10487-             o) = unpack_header(data)
10488-            if isinstance(offset, tuple):
10489-                offset1, offset2 = offset
10490-            else:
10491-                offset1 = offset
10492-                offset2 = 0
10493-            if offset1 == "pubkey":
10494-                real_offset = 107
10495-            elif offset1 in o:
10496-                real_offset = o[offset1]
10497-            else:
10498-                real_offset = offset1
10499-            real_offset = int(real_offset) + offset2 + offset_offset
10500-            assert isinstance(real_offset, int), offset
10501-            shares[shnum] = flip_bit(data, real_offset)
10502-    return res
10503+            # We're feeding the reader all of the share data, so it
10504+            # won't need to use the rref that we didn't provide, nor the
10505+            # storage index that we didn't provide. We do this because
10506+            # the reader will work for both MDMF and SDMF.
10507+            reader = MDMFSlotReadProxy(None, None, shnum, data)
10508+            # We need to get the offsets for the next part.
10509+            d = reader.get_verinfo()
10510+            def _do_corruption(verinfo, data, shnum):
10511+                (seqnum,
10512+                 root_hash,
10513+                 IV,
10514+                 segsize,
10515+                 datalen,
10516+                 k, n, prefix, o) = verinfo
10517+                if isinstance(offset, tuple):
10518+                    offset1, offset2 = offset
10519+                else:
10520+                    offset1 = offset
10521+                    offset2 = 0
10522+                if offset1 == "pubkey" and IV:
10523+                    real_offset = 107
10524+                elif offset1 == "share_data" and not IV:
10525+                    real_offset = 107
10526+                elif offset1 in o:
10527+                    real_offset = o[offset1]
10528+                else:
10529+                    real_offset = offset1
10530+                real_offset = int(real_offset) + offset2 + offset_offset
10531+                assert isinstance(real_offset, int), offset
10532+                if offset1 == 0: # verbyte
10533+                    f = add_two
10534+                else:
10535+                    f = flip_bit
10536+                shares[shnum] = f(data, real_offset)
10537+            d.addCallback(_do_corruption, data, shnum)
10538+            ds.append(d)
10539+    dl = defer.DeferredList(ds)
10540+    dl.addCallback(lambda ignored: res)
10541+    return dl
10542 
10543 def make_storagebroker(s=None, num_peers=10):
10544     if not s:
10545hunk ./src/allmydata/test/test_mutable.py 256
10546             self.failUnlessEqual(len(shnums), 1)
10547         d.addCallback(_created)
10548         return d
10549+    test_create.timeout = 15
10550+
10551+
10552+    def test_create_mdmf(self):
10553+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
10554+        def _created(n):
10555+            self.failUnless(isinstance(n, MutableFileNode))
10556+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
10557+            sb = self.nodemaker.storage_broker
10558+            peer0 = sorted(sb.get_all_serverids())[0]
10559+            shnums = self._storage._peers[peer0].keys()
10560+            self.failUnlessEqual(len(shnums), 1)
10561+        d.addCallback(_created)
10562+        return d
10563+
10564 
10565     def test_serialize(self):
10566         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
10567hunk ./src/allmydata/test/test_mutable.py 301
10568             d.addCallback(lambda smap: smap.dump(StringIO()))
10569             d.addCallback(lambda sio:
10570                           self.failUnless("3-of-10" in sio.getvalue()))
10571-            d.addCallback(lambda res: n.overwrite("contents 1"))
10572+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
10573             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
10574             d.addCallback(lambda res: n.download_best_version())
10575             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10576hunk ./src/allmydata/test/test_mutable.py 308
10577             d.addCallback(lambda res: n.get_size_of_best_version())
10578             d.addCallback(lambda size:
10579                           self.failUnlessEqual(size, len("contents 1")))
10580-            d.addCallback(lambda res: n.overwrite("contents 2"))
10581+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
10582             d.addCallback(lambda res: n.download_best_version())
10583             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10584             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
10585hunk ./src/allmydata/test/test_mutable.py 312
10586-            d.addCallback(lambda smap: n.upload("contents 3", smap))
10587+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
10588             d.addCallback(lambda res: n.download_best_version())
10589             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
10590             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
10591hunk ./src/allmydata/test/test_mutable.py 324
10592             # mapupdate-to-retrieve data caching (i.e. make the shares larger
10593             # than the default readsize, which is 2000 bytes). A 15kB file
10594             # will have 5kB shares.
10595-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
10596+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
10597             d.addCallback(lambda res: n.download_best_version())
10598             d.addCallback(lambda res:
10599                           self.failUnlessEqual(res, "large size file" * 1000))
10600hunk ./src/allmydata/test/test_mutable.py 332
10601         d.addCallback(_created)
10602         return d
10603 
10604+
10605+    def test_upload_and_download_mdmf(self):
10606+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
10607+        def _created(n):
10608+            d = defer.succeed(None)
10609+            d.addCallback(lambda ignored:
10610+                n.get_servermap(MODE_READ))
10611+            def _then(servermap):
10612+                dumped = servermap.dump(StringIO())
10613+                self.failUnlessIn("3-of-10", dumped.getvalue())
10614+            d.addCallback(_then)
10615+            # Now overwrite the contents with some new contents. We want
10616+            # to make them big enough to force the file to be uploaded
10617+            # in more than one segment.
10618+            big_contents = "contents1" * 100000 # about 900 KiB
10619+            big_contents_uploadable = MutableData(big_contents)
10620+            d.addCallback(lambda ignored:
10621+                n.overwrite(big_contents_uploadable))
10622+            d.addCallback(lambda ignored:
10623+                n.download_best_version())
10624+            d.addCallback(lambda data:
10625+                self.failUnlessEqual(data, big_contents))
10626+            # Overwrite the contents again with some new contents. As
10627+            # before, they need to be big enough to force multiple
10628+            # segments, so that we make the downloader deal with
10629+            # multiple segments.
10630+            bigger_contents = "contents2" * 1000000 # about 9MiB
10631+            bigger_contents_uploadable = MutableData(bigger_contents)
10632+            d.addCallback(lambda ignored:
10633+                n.overwrite(bigger_contents_uploadable))
10634+            d.addCallback(lambda ignored:
10635+                n.download_best_version())
10636+            d.addCallback(lambda data:
10637+                self.failUnlessEqual(data, bigger_contents))
10638+            return d
10639+        d.addCallback(_created)
10640+        return d
10641+
10642+
10643+    def test_mdmf_write_count(self):
10644+        # Publishing an MDMF file should only cause one write for each
10645+        # share that is to be published. Otherwise, we introduce
10646+        # undesirable semantics that are a regression from SDMF
10647+        upload = MutableData("MDMF" * 100000) # about 400 KiB
10648+        d = self.nodemaker.create_mutable_file(upload,
10649+                                               version=MDMF_VERSION)
10650+        def _check_server_write_counts(ignored):
10651+            sb = self.nodemaker.storage_broker
10652+            peers = sb.test_servers.values()
10653+            for peer in peers:
10654+                self.failUnlessEqual(peer.queries, 1)
10655+        d.addCallback(_check_server_write_counts)
10656+        return d
10657+
10658+
10659     def test_create_with_initial_contents(self):
10660hunk ./src/allmydata/test/test_mutable.py 388
10661-        d = self.nodemaker.create_mutable_file("contents 1")
10662+        upload1 = MutableData("contents 1")
10663+        d = self.nodemaker.create_mutable_file(upload1)
10664         def _created(n):
10665             d = n.download_best_version()
10666             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10667hunk ./src/allmydata/test/test_mutable.py 393
10668-            d.addCallback(lambda res: n.overwrite("contents 2"))
10669+            upload2 = MutableData("contents 2")
10670+            d.addCallback(lambda res: n.overwrite(upload2))
10671             d.addCallback(lambda res: n.download_best_version())
10672             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10673             return d
10674hunk ./src/allmydata/test/test_mutable.py 400
10675         d.addCallback(_created)
10676         return d
10677+    test_create_with_initial_contents.timeout = 15
10678+
10679+
10680+    def test_create_mdmf_with_initial_contents(self):
10681+        initial_contents = "foobarbaz" * 131072 # 900KiB
10682+        initial_contents_uploadable = MutableData(initial_contents)
10683+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
10684+                                               version=MDMF_VERSION)
10685+        def _created(n):
10686+            d = n.download_best_version()
10687+            d.addCallback(lambda data:
10688+                self.failUnlessEqual(data, initial_contents))
10689+            uploadable2 = MutableData(initial_contents + "foobarbaz")
10690+            d.addCallback(lambda ignored:
10691+                n.overwrite(uploadable2))
10692+            d.addCallback(lambda ignored:
10693+                n.download_best_version())
10694+            d.addCallback(lambda data:
10695+                self.failUnlessEqual(data, initial_contents +
10696+                                           "foobarbaz"))
10697+            return d
10698+        d.addCallback(_created)
10699+        return d
10700+    test_create_mdmf_with_initial_contents.timeout = 20
10701+
10702 
10703     def test_response_cache_memory_leak(self):
10704         d = self.nodemaker.create_mutable_file("contents")
10705hunk ./src/allmydata/test/test_mutable.py 451
10706             key = n.get_writekey()
10707             self.failUnless(isinstance(key, str), key)
10708             self.failUnlessEqual(len(key), 16) # AES key size
10709-            return data
10710+            return MutableData(data)
10711         d = self.nodemaker.create_mutable_file(_make_contents)
10712         def _created(n):
10713             return n.download_best_version()
10714hunk ./src/allmydata/test/test_mutable.py 459
10715         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
10716         return d
10717 
10718+
10719+    def test_create_mdmf_with_initial_contents_function(self):
10720+        data = "initial contents" * 100000
10721+        def _make_contents(n):
10722+            self.failUnless(isinstance(n, MutableFileNode))
10723+            key = n.get_writekey()
10724+            self.failUnless(isinstance(key, str), key)
10725+            self.failUnlessEqual(len(key), 16)
10726+            return MutableData(data)
10727+        d = self.nodemaker.create_mutable_file(_make_contents,
10728+                                               version=MDMF_VERSION)
10729+        d.addCallback(lambda n:
10730+            n.download_best_version())
10731+        d.addCallback(lambda data2:
10732+            self.failUnlessEqual(data2, data))
10733+        return d
10734+
10735+
10736     def test_create_with_too_large_contents(self):
10737         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
10738hunk ./src/allmydata/test/test_mutable.py 479
10739-        d = self.nodemaker.create_mutable_file(BIG)
10740+        BIG_uploadable = MutableData(BIG)
10741+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
10742         def _created(n):
10743hunk ./src/allmydata/test/test_mutable.py 482
10744-            d = n.overwrite(BIG)
10745+            other_BIG_uploadable = MutableData(BIG)
10746+            d = n.overwrite(other_BIG_uploadable)
10747             return d
10748         d.addCallback(_created)
10749         return d
10750hunk ./src/allmydata/test/test_mutable.py 497
10751 
10752     def test_modify(self):
10753         def _modifier(old_contents, servermap, first_time):
10754-            return old_contents + "line2"
10755+            new_contents = old_contents + "line2"
10756+            return new_contents
10757         def _non_modifier(old_contents, servermap, first_time):
10758             return old_contents
10759         def _none_modifier(old_contents, servermap, first_time):
10760hunk ./src/allmydata/test/test_mutable.py 506
10761         def _error_modifier(old_contents, servermap, first_time):
10762             raise ValueError("oops")
10763         def _toobig_modifier(old_contents, servermap, first_time):
10764-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
10765+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
10766+            return new_content
10767         calls = []
10768         def _ucw_error_modifier(old_contents, servermap, first_time):
10769             # simulate an UncoordinatedWriteError once
10770hunk ./src/allmydata/test/test_mutable.py 514
10771             calls.append(1)
10772             if len(calls) <= 1:
10773                 raise UncoordinatedWriteError("simulated")
10774-            return old_contents + "line3"
10775+            new_contents = old_contents + "line3"
10776+            return new_contents
10777         def _ucw_error_non_modifier(old_contents, servermap, first_time):
10778             # simulate an UncoordinatedWriteError once, and don't actually
10779             # modify the contents on subsequent invocations
10780hunk ./src/allmydata/test/test_mutable.py 524
10781                 raise UncoordinatedWriteError("simulated")
10782             return old_contents
10783 
10784-        d = self.nodemaker.create_mutable_file("line1")
10785+        initial_contents = "line1"
10786+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
10787         def _created(n):
10788             d = n.modify(_modifier)
10789             d.addCallback(lambda res: n.download_best_version())
10790hunk ./src/allmydata/test/test_mutable.py 582
10791             return d
10792         d.addCallback(_created)
10793         return d
10794+    test_modify.timeout = 15
10795+
10796 
10797     def test_modify_backoffer(self):
10798         def _modifier(old_contents, servermap, first_time):
10799hunk ./src/allmydata/test/test_mutable.py 609
10800         giveuper._delay = 0.1
10801         giveuper.factor = 1
10802 
10803-        d = self.nodemaker.create_mutable_file("line1")
10804+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
10805         def _created(n):
10806             d = n.modify(_modifier)
10807             d.addCallback(lambda res: n.download_best_version())
10808hunk ./src/allmydata/test/test_mutable.py 659
10809             d.addCallback(lambda smap: smap.dump(StringIO()))
10810             d.addCallback(lambda sio:
10811                           self.failUnless("3-of-10" in sio.getvalue()))
10812-            d.addCallback(lambda res: n.overwrite("contents 1"))
10813+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
10814             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
10815             d.addCallback(lambda res: n.download_best_version())
10816             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10817hunk ./src/allmydata/test/test_mutable.py 663
10818-            d.addCallback(lambda res: n.overwrite("contents 2"))
10819+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
10820             d.addCallback(lambda res: n.download_best_version())
10821             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10822             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
10823hunk ./src/allmydata/test/test_mutable.py 667
10824-            d.addCallback(lambda smap: n.upload("contents 3", smap))
10825+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
10826             d.addCallback(lambda res: n.download_best_version())
10827             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
10828             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
10829hunk ./src/allmydata/test/test_mutable.py 680
10830         return d
10831 
10832 
10833-class MakeShares(unittest.TestCase):
10834-    def test_encrypt(self):
10835-        nm = make_nodemaker()
10836-        CONTENTS = "some initial contents"
10837-        d = nm.create_mutable_file(CONTENTS)
10838-        def _created(fn):
10839-            p = Publish(fn, nm.storage_broker, None)
10840-            p.salt = "SALT" * 4
10841-            p.readkey = "\x00" * 16
10842-            p.newdata = CONTENTS
10843-            p.required_shares = 3
10844-            p.total_shares = 10
10845-            p.setup_encoding_parameters()
10846-            return p._encrypt_and_encode()
10847+    def test_size_after_servermap_update(self):
10848+        # a mutable file node should have something to say about how big
10849+        # it is after a servermap update is performed, since this tells
10850+        # us how large the best version of that mutable file is.
10851+        d = self.nodemaker.create_mutable_file()
10852+        def _created(n):
10853+            self.n = n
10854+            return n.get_servermap(MODE_READ)
10855+        d.addCallback(_created)
10856+        d.addCallback(lambda ignored:
10857+            self.failUnlessEqual(self.n.get_size(), 0))
10858+        d.addCallback(lambda ignored:
10859+            self.n.overwrite(MutableData("foobarbaz")))
10860+        d.addCallback(lambda ignored:
10861+            self.failUnlessEqual(self.n.get_size(), 9))
10862+        d.addCallback(lambda ignored:
10863+            self.nodemaker.create_mutable_file(MutableData("foobarbaz")))
10864+        d.addCallback(_created)
10865+        d.addCallback(lambda ignored:
10866+            self.failUnlessEqual(self.n.get_size(), 9))
10867+        return d
10868+
10869+
10870+class PublishMixin:
10871+    def publish_one(self):
10872+        # publish a file and create shares, which can then be manipulated
10873+        # later.
10874+        self.CONTENTS = "New contents go here" * 1000
10875+        self.uploadable = MutableData(self.CONTENTS)
10876+        self._storage = FakeStorage()
10877+        self._nodemaker = make_nodemaker(self._storage)
10878+        self._storage_broker = self._nodemaker.storage_broker
10879+        d = self._nodemaker.create_mutable_file(self.uploadable)
10880+        def _created(node):
10881+            self._fn = node
10882+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10883         d.addCallback(_created)
10884hunk ./src/allmydata/test/test_mutable.py 717
10885-        def _done(shares_and_shareids):
10886-            (shares, share_ids) = shares_and_shareids
10887-            self.failUnlessEqual(len(shares), 10)
10888-            for sh in shares:
10889-                self.failUnless(isinstance(sh, str))
10890-                self.failUnlessEqual(len(sh), 7)
10891-            self.failUnlessEqual(len(share_ids), 10)
10892-        d.addCallback(_done)
10893         return d
10894 
10895hunk ./src/allmydata/test/test_mutable.py 719
10896-    def test_generate(self):
10897-        nm = make_nodemaker()
10898-        CONTENTS = "some initial contents"
10899-        d = nm.create_mutable_file(CONTENTS)
10900-        def _created(fn):
10901-            self._fn = fn
10902-            p = Publish(fn, nm.storage_broker, None)
10903-            self._p = p
10904-            p.newdata = CONTENTS
10905-            p.required_shares = 3
10906-            p.total_shares = 10
10907-            p.setup_encoding_parameters()
10908-            p._new_seqnum = 3
10909-            p.salt = "SALT" * 4
10910-            # make some fake shares
10911-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
10912-            p._privkey = fn.get_privkey()
10913-            p._encprivkey = fn.get_encprivkey()
10914-            p._pubkey = fn.get_pubkey()
10915-            return p._generate_shares(shares_and_ids)
10916+    def publish_mdmf(self):
10917+        # like publish_one, except that the result is guaranteed to be
10918+        # an MDMF file.
10919+        # self.CONTENTS should have more than one segment.
10920+        self.CONTENTS = "This is an MDMF file" * 100000
10921+        self.uploadable = MutableData(self.CONTENTS)
10922+        self._storage = FakeStorage()
10923+        self._nodemaker = make_nodemaker(self._storage)
10924+        self._storage_broker = self._nodemaker.storage_broker
10925+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
10926+        def _created(node):
10927+            self._fn = node
10928+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10929         d.addCallback(_created)
10930hunk ./src/allmydata/test/test_mutable.py 733
10931-        def _generated(res):
10932-            p = self._p
10933-            final_shares = p.shares
10934-            root_hash = p.root_hash
10935-            self.failUnlessEqual(len(root_hash), 32)
10936-            self.failUnless(isinstance(final_shares, dict))
10937-            self.failUnlessEqual(len(final_shares), 10)
10938-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
10939-            for i,sh in final_shares.items():
10940-                self.failUnless(isinstance(sh, str))
10941-                # feed the share through the unpacker as a sanity-check
10942-                pieces = unpack_share(sh)
10943-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
10944-                 pubkey, signature, share_hash_chain, block_hash_tree,
10945-                 share_data, enc_privkey) = pieces
10946-                self.failUnlessEqual(u_seqnum, 3)
10947-                self.failUnlessEqual(u_root_hash, root_hash)
10948-                self.failUnlessEqual(k, 3)
10949-                self.failUnlessEqual(N, 10)
10950-                self.failUnlessEqual(segsize, 21)
10951-                self.failUnlessEqual(datalen, len(CONTENTS))
10952-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
10953-                sig_material = struct.pack(">BQ32s16s BBQQ",
10954-                                           0, p._new_seqnum, root_hash, IV,
10955-                                           k, N, segsize, datalen)
10956-                self.failUnless(p._pubkey.verify(sig_material, signature))
10957-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
10958-                self.failUnless(isinstance(share_hash_chain, dict))
10959-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
10960-                for shnum,share_hash in share_hash_chain.items():
10961-                    self.failUnless(isinstance(shnum, int))
10962-                    self.failUnless(isinstance(share_hash, str))
10963-                    self.failUnlessEqual(len(share_hash), 32)
10964-                self.failUnless(isinstance(block_hash_tree, list))
10965-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
10966-                self.failUnlessEqual(IV, "SALT"*4)
10967-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
10968-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
10969-        d.addCallback(_generated)
10970         return d
10971 
10972hunk ./src/allmydata/test/test_mutable.py 735
10973-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
10974-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
10975-    # when we publish to zero peers, we should get a NotEnoughSharesError
10976 
10977hunk ./src/allmydata/test/test_mutable.py 736
10978-class PublishMixin:
10979-    def publish_one(self):
10980-        # publish a file and create shares, which can then be manipulated
10981-        # later.
10982-        self.CONTENTS = "New contents go here" * 1000
10983+    def publish_sdmf(self):
10984+        # like publish_one, except that the result is guaranteed to be
10985+        # an SDMF file
10986+        self.CONTENTS = "This is an SDMF file" * 1000
10987+        self.uploadable = MutableData(self.CONTENTS)
10988         self._storage = FakeStorage()
10989         self._nodemaker = make_nodemaker(self._storage)
10990         self._storage_broker = self._nodemaker.storage_broker
10991hunk ./src/allmydata/test/test_mutable.py 744
10992-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
10993+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
10994         def _created(node):
10995             self._fn = node
10996             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10997hunk ./src/allmydata/test/test_mutable.py 751
10998         d.addCallback(_created)
10999         return d
11000 
11001-    def publish_multiple(self):
11002+
11003+    def publish_multiple(self, version=0):
11004         self.CONTENTS = ["Contents 0",
11005                          "Contents 1",
11006                          "Contents 2",
11007hunk ./src/allmydata/test/test_mutable.py 758
11008                          "Contents 3a",
11009                          "Contents 3b"]
11010+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
11011         self._copied_shares = {}
11012         self._storage = FakeStorage()
11013         self._nodemaker = make_nodemaker(self._storage)
11014hunk ./src/allmydata/test/test_mutable.py 762
11015-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
11016+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
11017         def _created(node):
11018             self._fn = node
11019             # now create multiple versions of the same file, and accumulate
11020hunk ./src/allmydata/test/test_mutable.py 769
11021             # their shares, so we can mix and match them later.
11022             d = defer.succeed(None)
11023             d.addCallback(self._copy_shares, 0)
11024-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
11025+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
11026             d.addCallback(self._copy_shares, 1)
11027hunk ./src/allmydata/test/test_mutable.py 771
11028-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
11029+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
11030             d.addCallback(self._copy_shares, 2)
11031hunk ./src/allmydata/test/test_mutable.py 773
11032-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
11033+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
11034             d.addCallback(self._copy_shares, 3)
11035             # now we replace all the shares with version s3, and upload a new
11036             # version to get s4b.
11037hunk ./src/allmydata/test/test_mutable.py 779
11038             rollback = dict([(i,2) for i in range(10)])
11039             d.addCallback(lambda res: self._set_versions(rollback))
11040-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
11041+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
11042             d.addCallback(self._copy_shares, 4)
11043             # we leave the storage in state 4
11044             return d
11045hunk ./src/allmydata/test/test_mutable.py 786
11046         d.addCallback(_created)
11047         return d
11048 
11049+
11050     def _copy_shares(self, ignored, index):
11051         shares = self._storage._peers
11052         # we need a deep copy
11053hunk ./src/allmydata/test/test_mutable.py 810
11054                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
11055 
11056 
11057+
11058+
11059 class Servermap(unittest.TestCase, PublishMixin):
11060     def setUp(self):
11061         return self.publish_one()
11062hunk ./src/allmydata/test/test_mutable.py 816
11063 
11064-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
11065+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
11066+                       update_range=None):
11067         if fn is None:
11068             fn = self._fn
11069         if sb is None:
11070hunk ./src/allmydata/test/test_mutable.py 823
11071             sb = self._storage_broker
11072         smu = ServermapUpdater(fn, sb, Monitor(),
11073-                               ServerMap(), mode)
11074+                               ServerMap(), mode, update_range=update_range)
11075         d = smu.update()
11076         return d
11077 
11078hunk ./src/allmydata/test/test_mutable.py 889
11079         # create a new file, which is large enough to knock the privkey out
11080         # of the early part of the file
11081         LARGE = "These are Larger contents" * 200 # about 5KB
11082-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
11083+        LARGE_uploadable = MutableData(LARGE)
11084+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
11085         def _created(large_fn):
11086             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
11087             return self.make_servermap(MODE_WRITE, large_fn2)
11088hunk ./src/allmydata/test/test_mutable.py 898
11089         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
11090         return d
11091 
11092+
11093     def test_mark_bad(self):
11094         d = defer.succeed(None)
11095         ms = self.make_servermap
11096hunk ./src/allmydata/test/test_mutable.py 944
11097         self._storage._peers = {} # delete all shares
11098         ms = self.make_servermap
11099         d = defer.succeed(None)
11100-
11101+#
11102         d.addCallback(lambda res: ms(mode=MODE_CHECK))
11103         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
11104 
11105hunk ./src/allmydata/test/test_mutable.py 996
11106         return d
11107 
11108 
11109+    def test_servermapupdater_finds_mdmf_files(self):
11110+        # setUp already published an MDMF file for us. We just need to
11111+        # make sure that when we run the ServermapUpdater, the file is
11112+        # reported to have one recoverable version.
11113+        d = defer.succeed(None)
11114+        d.addCallback(lambda ignored:
11115+            self.publish_mdmf())
11116+        d.addCallback(lambda ignored:
11117+            self.make_servermap(mode=MODE_CHECK))
11118+        # Calling make_servermap also updates the servermap in the mode
11119+        # that we specify, so we just need to see what it says.
11120+        def _check_servermap(sm):
11121+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
11122+        d.addCallback(_check_servermap)
11123+        return d
11124+
11125+
11126+    def test_fetch_update(self):
11127+        d = defer.succeed(None)
11128+        d.addCallback(lambda ignored:
11129+            self.publish_mdmf())
11130+        d.addCallback(lambda ignored:
11131+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
11132+        def _check_servermap(sm):
11133+            # 10 shares
11134+            self.failUnlessEqual(len(sm.update_data), 10)
11135+            # one version
11136+            for data in sm.update_data.itervalues():
11137+                self.failUnlessEqual(len(data), 1)
11138+        d.addCallback(_check_servermap)
11139+        return d
11140+
11141+
11142+    def test_servermapupdater_finds_sdmf_files(self):
11143+        d = defer.succeed(None)
11144+        d.addCallback(lambda ignored:
11145+            self.publish_sdmf())
11146+        d.addCallback(lambda ignored:
11147+            self.make_servermap(mode=MODE_CHECK))
11148+        d.addCallback(lambda servermap:
11149+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
11150+        return d
11151+
11152 
11153 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
11154     def setUp(self):
11155hunk ./src/allmydata/test/test_mutable.py 1079
11156         if version is None:
11157             version = servermap.best_recoverable_version()
11158         r = Retrieve(self._fn, servermap, version)
11159-        return r.download()
11160+        c = consumer.MemoryConsumer()
11161+        d = r.download(consumer=c)
11162+        d.addCallback(lambda mc: "".join(mc.chunks))
11163+        return d
11164+
11165 
11166     def test_basic(self):
11167         d = self.make_servermap()
11168hunk ./src/allmydata/test/test_mutable.py 1160
11169         return d
11170     test_no_servers_download.timeout = 15
11171 
11172+
11173     def _test_corrupt_all(self, offset, substring,
11174hunk ./src/allmydata/test/test_mutable.py 1162
11175-                          should_succeed=False, corrupt_early=True,
11176-                          failure_checker=None):
11177+                          should_succeed=False,
11178+                          corrupt_early=True,
11179+                          failure_checker=None,
11180+                          fetch_privkey=False):
11181         d = defer.succeed(None)
11182         if corrupt_early:
11183             d.addCallback(corrupt, self._storage, offset)
11184hunk ./src/allmydata/test/test_mutable.py 1182
11185                     self.failUnlessIn(substring, "".join(allproblems))
11186                 return servermap
11187             if should_succeed:
11188-                d1 = self._fn.download_version(servermap, ver)
11189+                d1 = self._fn.download_version(servermap, ver,
11190+                                               fetch_privkey)
11191                 d1.addCallback(lambda new_contents:
11192                                self.failUnlessEqual(new_contents, self.CONTENTS))
11193             else:
11194hunk ./src/allmydata/test/test_mutable.py 1190
11195                 d1 = self.shouldFail(NotEnoughSharesError,
11196                                      "_corrupt_all(offset=%s)" % (offset,),
11197                                      substring,
11198-                                     self._fn.download_version, servermap, ver)
11199+                                     self._fn.download_version, servermap,
11200+                                                                ver,
11201+                                                                fetch_privkey)
11202             if failure_checker:
11203                 d1.addCallback(failure_checker)
11204             d1.addCallback(lambda res: servermap)
11205hunk ./src/allmydata/test/test_mutable.py 1201
11206         return d
11207 
11208     def test_corrupt_all_verbyte(self):
11209-        # when the version byte is not 0, we hit an UnknownVersionError error
11210-        # in unpack_share().
11211+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
11212+        # error in unpack_share().
11213         d = self._test_corrupt_all(0, "UnknownVersionError")
11214         def _check_servermap(servermap):
11215             # and the dump should mention the problems
11216hunk ./src/allmydata/test/test_mutable.py 1208
11217             s = StringIO()
11218             dump = servermap.dump(s).getvalue()
11219-            self.failUnless("10 PROBLEMS" in dump, dump)
11220+            self.failUnless("30 PROBLEMS" in dump, dump)
11221         d.addCallback(_check_servermap)
11222         return d
11223 
11224hunk ./src/allmydata/test/test_mutable.py 1278
11225         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
11226 
11227 
11228+    def test_corrupt_all_encprivkey_late(self):
11229+        # this should work for the same reason as above, but we corrupt
11230+        # after the servermap update to exercise the error handling
11231+        # code.
11232+        # We need to remove the privkey from the node, or the retrieve
11233+        # process won't know to update it.
11234+        self._fn._privkey = None
11235+        return self._test_corrupt_all("enc_privkey",
11236+                                      None, # this shouldn't fail
11237+                                      should_succeed=True,
11238+                                      corrupt_early=False,
11239+                                      fetch_privkey=True)
11240+
11241+
11242     def test_corrupt_all_seqnum_late(self):
11243         # corrupting the seqnum between mapupdate and retrieve should result
11244         # in NotEnoughSharesError, since each share will look invalid
11245hunk ./src/allmydata/test/test_mutable.py 1298
11246         def _check(res):
11247             f = res[0]
11248             self.failUnless(f.check(NotEnoughSharesError))
11249-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
11250+            self.failUnless("uncoordinated write" in str(f))
11251         return self._test_corrupt_all(1, "ran out of peers",
11252                                       corrupt_early=False,
11253                                       failure_checker=_check)
11254hunk ./src/allmydata/test/test_mutable.py 1342
11255                             in str(servermap.problems[0]))
11256             ver = servermap.best_recoverable_version()
11257             r = Retrieve(self._fn, servermap, ver)
11258-            return r.download()
11259+            c = consumer.MemoryConsumer()
11260+            return r.download(c)
11261         d.addCallback(_do_retrieve)
11262hunk ./src/allmydata/test/test_mutable.py 1345
11263+        d.addCallback(lambda mc: "".join(mc.chunks))
11264         d.addCallback(lambda new_contents:
11265                       self.failUnlessEqual(new_contents, self.CONTENTS))
11266         return d
11267hunk ./src/allmydata/test/test_mutable.py 1350
11268 
11269-    def test_corrupt_some(self):
11270-        # corrupt the data of first five shares (so the servermap thinks
11271-        # they're good but retrieve marks them as bad), so that the
11272-        # MODE_READ set of 6 will be insufficient, forcing node.download to
11273-        # retry with more servers.
11274-        corrupt(None, self._storage, "share_data", range(5))
11275-        d = self.make_servermap()
11276+
11277+    def _test_corrupt_some(self, offset, mdmf=False):
11278+        if mdmf:
11279+            d = self.publish_mdmf()
11280+        else:
11281+            d = defer.succeed(None)
11282+        d.addCallback(lambda ignored:
11283+            corrupt(None, self._storage, offset, range(5)))
11284+        d.addCallback(lambda ignored:
11285+            self.make_servermap())
11286         def _do_retrieve(servermap):
11287             ver = servermap.best_recoverable_version()
11288             self.failUnless(ver)
11289hunk ./src/allmydata/test/test_mutable.py 1366
11290             return self._fn.download_best_version()
11291         d.addCallback(_do_retrieve)
11292         d.addCallback(lambda new_contents:
11293-                      self.failUnlessEqual(new_contents, self.CONTENTS))
11294+            self.failUnlessEqual(new_contents, self.CONTENTS))
11295         return d
11296 
11297hunk ./src/allmydata/test/test_mutable.py 1369
11298+
11299+    def test_corrupt_some(self):
11300+        # corrupt the data of first five shares (so the servermap thinks
11301+        # they're good but retrieve marks them as bad), so that the
11302+        # MODE_READ set of 6 will be insufficient, forcing node.download to
11303+        # retry with more servers.
11304+        return self._test_corrupt_some("share_data")
11305+
11306+
11307     def test_download_fails(self):
11308hunk ./src/allmydata/test/test_mutable.py 1379
11309-        corrupt(None, self._storage, "signature")
11310-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
11311+        d = corrupt(None, self._storage, "signature")
11312+        d.addCallback(lambda ignored:
11313+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
11314                             "no recoverable versions",
11315hunk ./src/allmydata/test/test_mutable.py 1383
11316-                            self._fn.download_best_version)
11317+                            self._fn.download_best_version))
11318         return d
11319 
11320 
11321hunk ./src/allmydata/test/test_mutable.py 1387
11322+
11323+    def test_corrupt_mdmf_block_hash_tree(self):
11324+        d = self.publish_mdmf()
11325+        d.addCallback(lambda ignored:
11326+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
11327+                                   "block hash tree failure",
11328+                                   corrupt_early=False,
11329+                                   should_succeed=False))
11330+        return d
11331+
11332+
11333+    def test_corrupt_mdmf_block_hash_tree_late(self):
11334+        d = self.publish_mdmf()
11335+        d.addCallback(lambda ignored:
11336+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
11337+                                   "block hash tree failure",
11338+                                   corrupt_early=True,
11339+                                   should_succeed=False))
11340+        return d
11341+
11342+
11343+    def test_corrupt_mdmf_share_data(self):
11344+        d = self.publish_mdmf()
11345+        d.addCallback(lambda ignored:
11346+            # TODO: Find out what the block size is and corrupt a
11347+            # specific block, rather than just guessing.
11348+            self._test_corrupt_all(("share_data", 12 * 40),
11349+                                    "block hash tree failure",
11350+                                    corrupt_early=True,
11351+                                    should_succeed=False))
11352+        return d
11353+
11354+
11355+    def test_corrupt_some_mdmf(self):
11356+        return self._test_corrupt_some(("share_data", 12 * 40),
11357+                                       mdmf=True)
11358+
11359+
11360 class CheckerMixin:
11361     def check_good(self, r, where):
11362         self.failUnless(r.is_healthy(), where)
11363hunk ./src/allmydata/test/test_mutable.py 1455
11364         d.addCallback(self.check_good, "test_check_good")
11365         return d
11366 
11367+    def test_check_mdmf_good(self):
11368+        d = self.publish_mdmf()
11369+        d.addCallback(lambda ignored:
11370+            self._fn.check(Monitor()))
11371+        d.addCallback(self.check_good, "test_check_mdmf_good")
11372+        return d
11373+
11374     def test_check_no_shares(self):
11375         for shares in self._storage._peers.values():
11376             shares.clear()
11377hunk ./src/allmydata/test/test_mutable.py 1469
11378         d.addCallback(self.check_bad, "test_check_no_shares")
11379         return d
11380 
11381+    def test_check_mdmf_no_shares(self):
11382+        d = self.publish_mdmf()
11383+        def _then(ignored):
11384+            for share in self._storage._peers.values():
11385+                share.clear()
11386+        d.addCallback(_then)
11387+        d.addCallback(lambda ignored:
11388+            self._fn.check(Monitor()))
11389+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
11390+        return d
11391+
11392     def test_check_not_enough_shares(self):
11393         for shares in self._storage._peers.values():
11394             for shnum in shares.keys():
11395hunk ./src/allmydata/test/test_mutable.py 1489
11396         d.addCallback(self.check_bad, "test_check_not_enough_shares")
11397         return d
11398 
11399+    def test_check_mdmf_not_enough_shares(self):
11400+        d = self.publish_mdmf()
11401+        def _then(ignored):
11402+            for shares in self._storage._peers.values():
11403+                for shnum in shares.keys():
11404+                    if shnum > 0:
11405+                        del shares[shnum]
11406+        d.addCallback(_then)
11407+        d.addCallback(lambda ignored:
11408+            self._fn.check(Monitor()))
11409+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
11410+        return d
11411+
11412+
11413     def test_check_all_bad_sig(self):
11414hunk ./src/allmydata/test/test_mutable.py 1504
11415-        corrupt(None, self._storage, 1) # bad sig
11416-        d = self._fn.check(Monitor())
11417+        d = corrupt(None, self._storage, 1) # bad sig
11418+        d.addCallback(lambda ignored:
11419+            self._fn.check(Monitor()))
11420         d.addCallback(self.check_bad, "test_check_all_bad_sig")
11421         return d
11422 
11423hunk ./src/allmydata/test/test_mutable.py 1510
11424+    def test_check_mdmf_all_bad_sig(self):
11425+        d = self.publish_mdmf()
11426+        d.addCallback(lambda ignored:
11427+            corrupt(None, self._storage, 1))
11428+        d.addCallback(lambda ignored:
11429+            self._fn.check(Monitor()))
11430+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
11431+        return d
11432+
11433     def test_check_all_bad_blocks(self):
11434hunk ./src/allmydata/test/test_mutable.py 1520
11435-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
11436+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
11437         # the Checker won't notice this.. it doesn't look at actual data
11438hunk ./src/allmydata/test/test_mutable.py 1522
11439-        d = self._fn.check(Monitor())
11440+        d.addCallback(lambda ignored:
11441+            self._fn.check(Monitor()))
11442         d.addCallback(self.check_good, "test_check_all_bad_blocks")
11443         return d
11444 
11445hunk ./src/allmydata/test/test_mutable.py 1527
11446+
11447+    def test_check_mdmf_all_bad_blocks(self):
11448+        d = self.publish_mdmf()
11449+        d.addCallback(lambda ignored:
11450+            corrupt(None, self._storage, "share_data"))
11451+        d.addCallback(lambda ignored:
11452+            self._fn.check(Monitor()))
11453+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
11454+        return d
11455+
11456     def test_verify_good(self):
11457         d = self._fn.check(Monitor(), verify=True)
11458         d.addCallback(self.check_good, "test_verify_good")
11459hunk ./src/allmydata/test/test_mutable.py 1541
11460         return d
11461+    test_verify_good.timeout = 15
11462 
11463     def test_verify_all_bad_sig(self):
11464hunk ./src/allmydata/test/test_mutable.py 1544
11465-        corrupt(None, self._storage, 1) # bad sig
11466-        d = self._fn.check(Monitor(), verify=True)
11467+        d = corrupt(None, self._storage, 1) # bad sig
11468+        d.addCallback(lambda ignored:
11469+            self._fn.check(Monitor(), verify=True))
11470         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
11471         return d
11472 
11473hunk ./src/allmydata/test/test_mutable.py 1551
11474     def test_verify_one_bad_sig(self):
11475-        corrupt(None, self._storage, 1, [9]) # bad sig
11476-        d = self._fn.check(Monitor(), verify=True)
11477+        d = corrupt(None, self._storage, 1, [9]) # bad sig
11478+        d.addCallback(lambda ignored:
11479+            self._fn.check(Monitor(), verify=True))
11480         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
11481         return d
11482 
11483hunk ./src/allmydata/test/test_mutable.py 1558
11484     def test_verify_one_bad_block(self):
11485-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
11486+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
11487         # the Verifier *will* notice this, since it examines every byte
11488hunk ./src/allmydata/test/test_mutable.py 1560
11489-        d = self._fn.check(Monitor(), verify=True)
11490+        d.addCallback(lambda ignored:
11491+            self._fn.check(Monitor(), verify=True))
11492         d.addCallback(self.check_bad, "test_verify_one_bad_block")
11493         d.addCallback(self.check_expected_failure,
11494                       CorruptShareError, "block hash tree failure",
11495hunk ./src/allmydata/test/test_mutable.py 1569
11496         return d
11497 
11498     def test_verify_one_bad_sharehash(self):
11499-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
11500-        d = self._fn.check(Monitor(), verify=True)
11501+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
11502+        d.addCallback(lambda ignored:
11503+            self._fn.check(Monitor(), verify=True))
11504         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
11505         d.addCallback(self.check_expected_failure,
11506                       CorruptShareError, "corrupt hashes",
11507hunk ./src/allmydata/test/test_mutable.py 1579
11508         return d
11509 
11510     def test_verify_one_bad_encprivkey(self):
11511-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11512-        d = self._fn.check(Monitor(), verify=True)
11513+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11514+        d.addCallback(lambda ignored:
11515+            self._fn.check(Monitor(), verify=True))
11516         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
11517         d.addCallback(self.check_expected_failure,
11518                       CorruptShareError, "invalid privkey",
11519hunk ./src/allmydata/test/test_mutable.py 1589
11520         return d
11521 
11522     def test_verify_one_bad_encprivkey_uncheckable(self):
11523-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11524+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11525         readonly_fn = self._fn.get_readonly()
11526         # a read-only node has no way to validate the privkey
11527hunk ./src/allmydata/test/test_mutable.py 1592
11528-        d = readonly_fn.check(Monitor(), verify=True)
11529+        d.addCallback(lambda ignored:
11530+            readonly_fn.check(Monitor(), verify=True))
11531         d.addCallback(self.check_good,
11532                       "test_verify_one_bad_encprivkey_uncheckable")
11533         return d
11534hunk ./src/allmydata/test/test_mutable.py 1598
11535 
11536+
11537+    def test_verify_mdmf_good(self):
11538+        d = self.publish_mdmf()
11539+        d.addCallback(lambda ignored:
11540+            self._fn.check(Monitor(), verify=True))
11541+        d.addCallback(self.check_good, "test_verify_mdmf_good")
11542+        return d
11543+
11544+
11545+    def test_verify_mdmf_one_bad_block(self):
11546+        d = self.publish_mdmf()
11547+        d.addCallback(lambda ignored:
11548+            corrupt(None, self._storage, "share_data", [1]))
11549+        d.addCallback(lambda ignored:
11550+            self._fn.check(Monitor(), verify=True))
11551+        # We should find one bad block here
11552+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
11553+        d.addCallback(self.check_expected_failure,
11554+                      CorruptShareError, "block hash tree failure",
11555+                      "test_verify_mdmf_one_bad_block")
11556+        return d
11557+
11558+
11559+    def test_verify_mdmf_bad_encprivkey(self):
11560+        d = self.publish_mdmf()
11561+        d.addCallback(lambda ignored:
11562+            corrupt(None, self._storage, "enc_privkey", [1]))
11563+        d.addCallback(lambda ignored:
11564+            self._fn.check(Monitor(), verify=True))
11565+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
11566+        d.addCallback(self.check_expected_failure,
11567+                      CorruptShareError, "privkey",
11568+                      "test_verify_mdmf_bad_encprivkey")
11569+        return d
11570+
11571+
11572+    def test_verify_mdmf_bad_sig(self):
11573+        d = self.publish_mdmf()
11574+        d.addCallback(lambda ignored:
11575+            corrupt(None, self._storage, 1, [1]))
11576+        d.addCallback(lambda ignored:
11577+            self._fn.check(Monitor(), verify=True))
11578+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
11579+        return d
11580+
11581+
11582+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
11583+        d = self.publish_mdmf()
11584+        d.addCallback(lambda ignored:
11585+            corrupt(None, self._storage, "enc_privkey", [1]))
11586+        d.addCallback(lambda ignored:
11587+            self._fn.get_readonly())
11588+        d.addCallback(lambda fn:
11589+            fn.check(Monitor(), verify=True))
11590+        d.addCallback(self.check_good,
11591+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
11592+        return d
11593+
11594+
11595 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
11596 
11597     def get_shares(self, s):
11598hunk ./src/allmydata/test/test_mutable.py 1722
11599         current_shares = self.old_shares[-1]
11600         self.failUnlessEqual(old_shares, current_shares)
11601 
11602+
11603     def test_unrepairable_0shares(self):
11604         d = self.publish_one()
11605         def _delete_all_shares(ign):
11606hunk ./src/allmydata/test/test_mutable.py 1737
11607         d.addCallback(_check)
11608         return d
11609 
11610+    def test_mdmf_unrepairable_0shares(self):
11611+        d = self.publish_mdmf()
11612+        def _delete_all_shares(ign):
11613+            shares = self._storage._peers
11614+            for peerid in shares:
11615+                shares[peerid] = {}
11616+        d.addCallback(_delete_all_shares)
11617+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11618+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11619+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
11620+        return d
11621+
11622+
11623     def test_unrepairable_1share(self):
11624         d = self.publish_one()
11625         def _delete_all_shares(ign):
11626hunk ./src/allmydata/test/test_mutable.py 1766
11627         d.addCallback(_check)
11628         return d
11629 
11630+    def test_mdmf_unrepairable_1share(self):
11631+        d = self.publish_mdmf()
11632+        def _delete_all_shares(ign):
11633+            shares = self._storage._peers
11634+            for peerid in shares:
11635+                for shnum in list(shares[peerid]):
11636+                    if shnum > 0:
11637+                        del shares[peerid][shnum]
11638+        d.addCallback(_delete_all_shares)
11639+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11640+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11641+        def _check(crr):
11642+            self.failUnlessEqual(crr.get_successful(), False)
11643+        d.addCallback(_check)
11644+        return d
11645+
11646+    def test_repairable_5shares(self):
11647+        d = self.publish_mdmf()
11648+        def _delete_all_shares(ign):
11649+            shares = self._storage._peers
11650+            for peerid in shares:
11651+                for shnum in list(shares[peerid]):
11652+                    if shnum > 4:
11653+                        del shares[peerid][shnum]
11654+        d.addCallback(_delete_all_shares)
11655+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11656+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11657+        def _check(crr):
11658+            self.failUnlessEqual(crr.get_successful(), True)
11659+        d.addCallback(_check)
11660+        return d
11661+
11662+    def test_mdmf_repairable_5shares(self):
11663+        d = self.publish_mdmf()
11664+        def _delete_some_shares(ign):
11665+            shares = self._storage._peers
11666+            for peerid in shares:
11667+                for shnum in list(shares[peerid]):
11668+                    if shnum > 5:
11669+                        del shares[peerid][shnum]
11670+        d.addCallback(_delete_some_shares)
11671+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11672+        def _check(cr):
11673+            self.failIf(cr.is_healthy())
11674+            self.failUnless(cr.is_recoverable())
11675+            return cr
11676+        d.addCallback(_check)
11677+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11678+        def _check1(crr):
11679+            self.failUnlessEqual(crr.get_successful(), True)
11680+        d.addCallback(_check1)
11681+        return d
11682+
11683+
11684     def test_merge(self):
11685         self.old_shares = []
11686         d = self.publish_multiple()
11687hunk ./src/allmydata/test/test_mutable.py 1934
11688 class MultipleEncodings(unittest.TestCase):
11689     def setUp(self):
11690         self.CONTENTS = "New contents go here"
11691+        self.uploadable = MutableData(self.CONTENTS)
11692         self._storage = FakeStorage()
11693         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
11694         self._storage_broker = self._nodemaker.storage_broker
11695hunk ./src/allmydata/test/test_mutable.py 1938
11696-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
11697+        d = self._nodemaker.create_mutable_file(self.uploadable)
11698         def _created(node):
11699             self._fn = node
11700         d.addCallback(_created)
11701hunk ./src/allmydata/test/test_mutable.py 1944
11702         return d
11703 
11704-    def _encode(self, k, n, data):
11705+    def _encode(self, k, n, data, version=SDMF_VERSION):
11706         # encode 'data' into a peerid->shares dict.
11707 
11708         fn = self._fn
11709hunk ./src/allmydata/test/test_mutable.py 1960
11710         # and set the encoding parameters to something completely different
11711         fn2._required_shares = k
11712         fn2._total_shares = n
11713+        # Normally a servermap update would occur before a publish.
11714+        # Here, it doesn't, so we have to do it ourselves.
11715+        fn2.set_version(version)
11716 
11717         s = self._storage
11718         s._peers = {} # clear existing storage
11719hunk ./src/allmydata/test/test_mutable.py 1967
11720         p2 = Publish(fn2, self._storage_broker, None)
11721-        d = p2.publish(data)
11722+        uploadable = MutableData(data)
11723+        d = p2.publish(uploadable)
11724         def _published(res):
11725             shares = s._peers
11726             s._peers = {}
11727hunk ./src/allmydata/test/test_mutable.py 2235
11728         self.basedir = "mutable/Problems/test_publish_surprise"
11729         self.set_up_grid()
11730         nm = self.g.clients[0].nodemaker
11731-        d = nm.create_mutable_file("contents 1")
11732+        d = nm.create_mutable_file(MutableData("contents 1"))
11733         def _created(n):
11734             d = defer.succeed(None)
11735             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
11736hunk ./src/allmydata/test/test_mutable.py 2245
11737             d.addCallback(_got_smap1)
11738             # then modify the file, leaving the old map untouched
11739             d.addCallback(lambda res: log.msg("starting winning write"))
11740-            d.addCallback(lambda res: n.overwrite("contents 2"))
11741+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11742             # now attempt to modify the file with the old servermap. This
11743             # will look just like an uncoordinated write, in which every
11744             # single share got updated between our mapupdate and our publish
11745hunk ./src/allmydata/test/test_mutable.py 2254
11746                           self.shouldFail(UncoordinatedWriteError,
11747                                           "test_publish_surprise", None,
11748                                           n.upload,
11749-                                          "contents 2a", self.old_map))
11750+                                          MutableData("contents 2a"), self.old_map))
11751             return d
11752         d.addCallback(_created)
11753         return d
11754hunk ./src/allmydata/test/test_mutable.py 2263
11755         self.basedir = "mutable/Problems/test_retrieve_surprise"
11756         self.set_up_grid()
11757         nm = self.g.clients[0].nodemaker
11758-        d = nm.create_mutable_file("contents 1")
11759+        d = nm.create_mutable_file(MutableData("contents 1"))
11760         def _created(n):
11761             d = defer.succeed(None)
11762             d.addCallback(lambda res: n.get_servermap(MODE_READ))
11763hunk ./src/allmydata/test/test_mutable.py 2273
11764             d.addCallback(_got_smap1)
11765             # then modify the file, leaving the old map untouched
11766             d.addCallback(lambda res: log.msg("starting winning write"))
11767-            d.addCallback(lambda res: n.overwrite("contents 2"))
11768+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11769             # now attempt to retrieve the old version with the old servermap.
11770             # This will look like someone has changed the file since we
11771             # updated the servermap.
11772hunk ./src/allmydata/test/test_mutable.py 2282
11773             d.addCallback(lambda res:
11774                           self.shouldFail(NotEnoughSharesError,
11775                                           "test_retrieve_surprise",
11776-                                          "ran out of peers: have 0 shares (k=3)",
11777+                                          "ran out of peers: have 0 of 1",
11778                                           n.download_version,
11779                                           self.old_map,
11780                                           self.old_map.best_recoverable_version(),
11781hunk ./src/allmydata/test/test_mutable.py 2291
11782         d.addCallback(_created)
11783         return d
11784 
11785+
11786     def test_unexpected_shares(self):
11787         # upload the file, take a servermap, shut down one of the servers,
11788         # upload it again (causing shares to appear on a new server), then
11789hunk ./src/allmydata/test/test_mutable.py 2301
11790         self.basedir = "mutable/Problems/test_unexpected_shares"
11791         self.set_up_grid()
11792         nm = self.g.clients[0].nodemaker
11793-        d = nm.create_mutable_file("contents 1")
11794+        d = nm.create_mutable_file(MutableData("contents 1"))
11795         def _created(n):
11796             d = defer.succeed(None)
11797             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
11798hunk ./src/allmydata/test/test_mutable.py 2313
11799                 self.g.remove_server(peer0)
11800                 # then modify the file, leaving the old map untouched
11801                 log.msg("starting winning write")
11802-                return n.overwrite("contents 2")
11803+                return n.overwrite(MutableData("contents 2"))
11804             d.addCallback(_got_smap1)
11805             # now attempt to modify the file with the old servermap. This
11806             # will look just like an uncoordinated write, in which every
11807hunk ./src/allmydata/test/test_mutable.py 2323
11808                           self.shouldFail(UncoordinatedWriteError,
11809                                           "test_surprise", None,
11810                                           n.upload,
11811-                                          "contents 2a", self.old_map))
11812+                                          MutableData("contents 2a"), self.old_map))
11813             return d
11814         d.addCallback(_created)
11815         return d
11816hunk ./src/allmydata/test/test_mutable.py 2327
11817+    test_unexpected_shares.timeout = 15
11818 
11819     def test_bad_server(self):
11820         # Break one server, then create the file: the initial publish should
11821hunk ./src/allmydata/test/test_mutable.py 2361
11822         d.addCallback(_break_peer0)
11823         # now "create" the file, using the pre-established key, and let the
11824         # initial publish finally happen
11825-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
11826+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
11827         # that ought to work
11828         def _got_node(n):
11829             d = n.download_best_version()
11830hunk ./src/allmydata/test/test_mutable.py 2370
11831             def _break_peer1(res):
11832                 self.g.break_server(self.server1.get_serverid())
11833             d.addCallback(_break_peer1)
11834-            d.addCallback(lambda res: n.overwrite("contents 2"))
11835+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11836             # that ought to work too
11837             d.addCallback(lambda res: n.download_best_version())
11838             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
11839hunk ./src/allmydata/test/test_mutable.py 2402
11840         peerids = [s.get_serverid() for s in sb.get_connected_servers()]
11841         self.g.break_server(peerids[0])
11842 
11843-        d = nm.create_mutable_file("contents 1")
11844+        d = nm.create_mutable_file(MutableData("contents 1"))
11845         def _created(n):
11846             d = n.download_best_version()
11847             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
11848hunk ./src/allmydata/test/test_mutable.py 2410
11849             def _break_second_server(res):
11850                 self.g.break_server(peerids[1])
11851             d.addCallback(_break_second_server)
11852-            d.addCallback(lambda res: n.overwrite("contents 2"))
11853+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11854             # that ought to work too
11855             d.addCallback(lambda res: n.download_best_version())
11856             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
11857hunk ./src/allmydata/test/test_mutable.py 2429
11858         d = self.shouldFail(NotEnoughServersError,
11859                             "test_publish_all_servers_bad",
11860                             "Ran out of non-bad servers",
11861-                            nm.create_mutable_file, "contents")
11862+                            nm.create_mutable_file, MutableData("contents"))
11863         return d
11864 
11865     def test_publish_no_servers(self):
11866hunk ./src/allmydata/test/test_mutable.py 2441
11867         d = self.shouldFail(NotEnoughServersError,
11868                             "test_publish_no_servers",
11869                             "Ran out of non-bad servers",
11870-                            nm.create_mutable_file, "contents")
11871+                            nm.create_mutable_file, MutableData("contents"))
11872         return d
11873     test_publish_no_servers.timeout = 30
11874 
11875hunk ./src/allmydata/test/test_mutable.py 2459
11876         # we need some contents that are large enough to push the privkey out
11877         # of the early part of the file
11878         LARGE = "These are Larger contents" * 2000 # about 50KB
11879-        d = nm.create_mutable_file(LARGE)
11880+        LARGE_uploadable = MutableData(LARGE)
11881+        d = nm.create_mutable_file(LARGE_uploadable)
11882         def _created(n):
11883             self.uri = n.get_uri()
11884             self.n2 = nm.create_from_cap(self.uri)
11885hunk ./src/allmydata/test/test_mutable.py 2495
11886         self.basedir = "mutable/Problems/test_privkey_query_missing"
11887         self.set_up_grid(num_servers=20)
11888         nm = self.g.clients[0].nodemaker
11889-        LARGE = "These are Larger contents" * 2000 # about 50KB
11890+        LARGE = "These are Larger contents" * 2000 # about 50KiB
11891+        LARGE_uploadable = MutableData(LARGE)
11892         nm._node_cache = DevNullDictionary() # disable the nodecache
11893 
11894hunk ./src/allmydata/test/test_mutable.py 2499
11895-        d = nm.create_mutable_file(LARGE)
11896+        d = nm.create_mutable_file(LARGE_uploadable)
11897         def _created(n):
11898             self.uri = n.get_uri()
11899             self.n2 = nm.create_from_cap(self.uri)
11900hunk ./src/allmydata/test/test_mutable.py 2509
11901         d.addCallback(_created)
11902         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
11903         return d
11904+
11905+
11906+    def test_block_and_hash_query_error(self):
11907+        # This tests for what happens when a query to a remote server
11908+        # fails in either the hash validation step or the block getting
11909+        # step (because of batching, this is the same actual query).
11910+        # We need to have the storage server persist up until the point
11911+        # that its prefix is validated, then suddenly die. This
11912+        # exercises some exception handling code in Retrieve.
11913+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
11914+        self.set_up_grid(num_servers=20)
11915+        nm = self.g.clients[0].nodemaker
11916+        CONTENTS = "contents" * 2000
11917+        CONTENTS_uploadable = MutableData(CONTENTS)
11918+        d = nm.create_mutable_file(CONTENTS_uploadable)
11919+        def _created(node):
11920+            self._node = node
11921+        d.addCallback(_created)
11922+        d.addCallback(lambda ignored:
11923+            self._node.get_servermap(MODE_READ))
11924+        def _then(servermap):
11925+            # we have our servermap. Now we set up the servers like the
11926+            # tests above -- the first one that gets a read call should
11927+            # start throwing errors, but only after returning its prefix
11928+            # for validation. Since we'll download without fetching the
11929+            # private key, the next query to the remote server will be
11930+            # for either a block and salt or for hashes, either of which
11931+            # will exercise the error handling code.
11932+            killer = FirstServerGetsKilled()
11933+            for (serverid, ss) in nm.storage_broker.get_all_servers():
11934+                ss.post_call_notifier = killer.notify
11935+            ver = servermap.best_recoverable_version()
11936+            assert ver
11937+            return self._node.download_version(servermap, ver)
11938+        d.addCallback(_then)
11939+        d.addCallback(lambda data:
11940+            self.failUnlessEqual(data, CONTENTS))
11941+        return d
11942+
11943+
11944+class FileHandle(unittest.TestCase):
11945+    def setUp(self):
11946+        self.test_data = "Test Data" * 50000
11947+        self.sio = StringIO(self.test_data)
11948+        self.uploadable = MutableFileHandle(self.sio)
11949+
11950+
11951+    def test_filehandle_read(self):
11952+        self.basedir = "mutable/FileHandle/test_filehandle_read"
11953+        chunk_size = 10
11954+        for i in xrange(0, len(self.test_data), chunk_size):
11955+            data = self.uploadable.read(chunk_size)
11956+            data = "".join(data)
11957+            start = i
11958+            end = i + chunk_size
11959+            self.failUnlessEqual(data, self.test_data[start:end])
11960+
11961+
11962+    def test_filehandle_get_size(self):
11963+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
11964+        actual_size = len(self.test_data)
11965+        size = self.uploadable.get_size()
11966+        self.failUnlessEqual(size, actual_size)
11967+
11968+
11969+    def test_filehandle_get_size_out_of_order(self):
11970+        # We should be able to call get_size whenever we want without
11971+        # disturbing the location of the seek pointer.
11972+        chunk_size = 100
11973+        data = self.uploadable.read(chunk_size)
11974+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
11975+
11976+        # Now get the size.
11977+        size = self.uploadable.get_size()
11978+        self.failUnlessEqual(size, len(self.test_data))
11979+
11980+        # Now get more data. We should be right where we left off.
11981+        more_data = self.uploadable.read(chunk_size)
11982+        start = chunk_size
11983+        end = chunk_size * 2
11984+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
11985+
11986+
11987+    def test_filehandle_file(self):
11988+        # Make sure that the MutableFileHandle works on a file as well
11989+        # as a StringIO object, since in some cases it will be asked to
11990+        # deal with files.
11991+        self.basedir = self.mktemp()
11992+        # necessary? What am I doing wrong here?
11993+        os.mkdir(self.basedir)
11994+        f_path = os.path.join(self.basedir, "test_file")
11995+        f = open(f_path, "w")
11996+        f.write(self.test_data)
11997+        f.close()
11998+        f = open(f_path, "r")
11999+
12000+        uploadable = MutableFileHandle(f)
12001+
12002+        data = uploadable.read(len(self.test_data))
12003+        self.failUnlessEqual("".join(data), self.test_data)
12004+        size = uploadable.get_size()
12005+        self.failUnlessEqual(size, len(self.test_data))
12006+
12007+
12008+    def test_close(self):
12009+        # Make sure that the MutableFileHandle closes its handle when
12010+        # told to do so.
12011+        self.uploadable.close()
12012+        self.failUnless(self.sio.closed)
12013+
12014+
12015+class DataHandle(unittest.TestCase):
12016+    def setUp(self):
12017+        self.test_data = "Test Data" * 50000
12018+        self.uploadable = MutableData(self.test_data)
12019+
12020+
12021+    def test_datahandle_read(self):
12022+        chunk_size = 10
12023+        for i in xrange(0, len(self.test_data), chunk_size):
12024+            data = self.uploadable.read(chunk_size)
12025+            data = "".join(data)
12026+            start = i
12027+            end = i + chunk_size
12028+            self.failUnlessEqual(data, self.test_data[start:end])
12029+
12030+
12031+    def test_datahandle_get_size(self):
12032+        actual_size = len(self.test_data)
12033+        size = self.uploadable.get_size()
12034+        self.failUnlessEqual(size, actual_size)
12035+
12036+
12037+    def test_datahandle_get_size_out_of_order(self):
12038+        # We should be able to call get_size whenever we want without
12039+        # disturbing the location of the seek pointer.
12040+        chunk_size = 100
12041+        data = self.uploadable.read(chunk_size)
12042+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
12043+
12044+        # Now get the size.
12045+        size = self.uploadable.get_size()
12046+        self.failUnlessEqual(size, len(self.test_data))
12047+
12048+        # Now get more data. We should be right where we left off.
12049+        more_data = self.uploadable.read(chunk_size)
12050+        start = chunk_size
12051+        end = chunk_size * 2
12052+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12053+
12054+
12055+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin, \
12056+              PublishMixin):
12057+    def setUp(self):
12058+        GridTestMixin.setUp(self)
12059+        self.basedir = self.mktemp()
12060+        self.set_up_grid()
12061+        self.c = self.g.clients[0]
12062+        self.nm = self.c.nodemaker
12063+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12064+        self.small_data = "test data" * 10 # about 90 B; SDMF
12065+        return self.do_upload()
12066+
12067+
12068+    def do_upload(self):
12069+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12070+                                         version=MDMF_VERSION)
12071+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12072+        dl = gatherResults([d1, d2])
12073+        def _then((n1, n2)):
12074+            assert isinstance(n1, MutableFileNode)
12075+            assert isinstance(n2, MutableFileNode)
12076+
12077+            self.mdmf_node = n1
12078+            self.sdmf_node = n2
12079+        dl.addCallback(_then)
12080+        return dl
12081+
12082+
12083+    def test_get_readonly_mutable_version(self):
12084+        # Attempting to get a mutable version of a mutable file from a
12085+        # filenode initialized with a readcap should return a readonly
12086+        # version of that same node.
12087+        ro = self.mdmf_node.get_readonly()
12088+        d = ro.get_best_mutable_version()
12089+        d.addCallback(lambda version:
12090+            self.failUnless(version.is_readonly()))
12091+        d.addCallback(lambda ignored:
12092+            self.sdmf_node.get_readonly())
12093+        d.addCallback(lambda version:
12094+            self.failUnless(version.is_readonly()))
12095+        return d
12096+
12097+
12098+    def test_get_sequence_number(self):
12099+        d = self.mdmf_node.get_best_readable_version()
12100+        d.addCallback(lambda bv:
12101+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12102+        d.addCallback(lambda ignored:
12103+            self.sdmf_node.get_best_readable_version())
12104+        d.addCallback(lambda bv:
12105+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12106+        # Now update. The sequence number in both cases should be 1 in
12107+        # both cases.
12108+        def _do_update(ignored):
12109+            new_data = MutableData("foo bar baz" * 100000)
12110+            new_small_data = MutableData("foo bar baz" * 10)
12111+            d1 = self.mdmf_node.overwrite(new_data)
12112+            d2 = self.sdmf_node.overwrite(new_small_data)
12113+            dl = gatherResults([d1, d2])
12114+            return dl
12115+        d.addCallback(_do_update)
12116+        d.addCallback(lambda ignored:
12117+            self.mdmf_node.get_best_readable_version())
12118+        d.addCallback(lambda bv:
12119+            self.failUnlessEqual(bv.get_sequence_number(), 2))
12120+        d.addCallback(lambda ignored:
12121+            self.sdmf_node.get_best_readable_version())
12122+        d.addCallback(lambda bv:
12123+            self.failUnlessEqual(bv.get_sequence_number(), 2))
12124+        return d
12125+
12126+
12127+    def test_get_writekey(self):
12128+        d = self.mdmf_node.get_best_mutable_version()
12129+        d.addCallback(lambda bv:
12130+            self.failUnlessEqual(bv.get_writekey(),
12131+                                 self.mdmf_node.get_writekey()))
12132+        d.addCallback(lambda ignored:
12133+            self.sdmf_node.get_best_mutable_version())
12134+        d.addCallback(lambda bv:
12135+            self.failUnlessEqual(bv.get_writekey(),
12136+                                 self.sdmf_node.get_writekey()))
12137+        return d
12138+
12139+
12140+    def test_get_storage_index(self):
12141+        d = self.mdmf_node.get_best_mutable_version()
12142+        d.addCallback(lambda bv:
12143+            self.failUnlessEqual(bv.get_storage_index(),
12144+                                 self.mdmf_node.get_storage_index()))
12145+        d.addCallback(lambda ignored:
12146+            self.sdmf_node.get_best_mutable_version())
12147+        d.addCallback(lambda bv:
12148+            self.failUnlessEqual(bv.get_storage_index(),
12149+                                 self.sdmf_node.get_storage_index()))
12150+        return d
12151+
12152+
12153+    def test_get_readonly_version(self):
12154+        d = self.mdmf_node.get_best_readable_version()
12155+        d.addCallback(lambda bv:
12156+            self.failUnless(bv.is_readonly()))
12157+        d.addCallback(lambda ignored:
12158+            self.sdmf_node.get_best_readable_version())
12159+        d.addCallback(lambda bv:
12160+            self.failUnless(bv.is_readonly()))
12161+        return d
12162+
12163+
12164+    def test_get_mutable_version(self):
12165+        d = self.mdmf_node.get_best_mutable_version()
12166+        d.addCallback(lambda bv:
12167+            self.failIf(bv.is_readonly()))
12168+        d.addCallback(lambda ignored:
12169+            self.sdmf_node.get_best_mutable_version())
12170+        d.addCallback(lambda bv:
12171+            self.failIf(bv.is_readonly()))
12172+        return d
12173+
12174+
12175+    def test_toplevel_overwrite(self):
12176+        new_data = MutableData("foo bar baz" * 100000)
12177+        new_small_data = MutableData("foo bar baz" * 10)
12178+        d = self.mdmf_node.overwrite(new_data)
12179+        d.addCallback(lambda ignored:
12180+            self.mdmf_node.download_best_version())
12181+        d.addCallback(lambda data:
12182+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12183+        d.addCallback(lambda ignored:
12184+            self.sdmf_node.overwrite(new_small_data))
12185+        d.addCallback(lambda ignored:
12186+            self.sdmf_node.download_best_version())
12187+        d.addCallback(lambda data:
12188+            self.failUnlessEqual(data, "foo bar baz" * 10))
12189+        return d
12190+
12191+
12192+    def test_toplevel_modify(self):
12193+        def modifier(old_contents, servermap, first_time):
12194+            return old_contents + "modified"
12195+        d = self.mdmf_node.modify(modifier)
12196+        d.addCallback(lambda ignored:
12197+            self.mdmf_node.download_best_version())
12198+        d.addCallback(lambda data:
12199+            self.failUnlessIn("modified", data))
12200+        d.addCallback(lambda ignored:
12201+            self.sdmf_node.modify(modifier))
12202+        d.addCallback(lambda ignored:
12203+            self.sdmf_node.download_best_version())
12204+        d.addCallback(lambda data:
12205+            self.failUnlessIn("modified", data))
12206+        return d
12207+
12208+
12209+    def test_version_modify(self):
12210+        # TODO: When we can publish multiple versions, alter this test
12211+        # to modify a version other than the best usable version, then
12212+        # test to see that the best recoverable version is that.
12213+        def modifier(old_contents, servermap, first_time):
12214+            return old_contents + "modified"
12215+        d = self.mdmf_node.modify(modifier)
12216+        d.addCallback(lambda ignored:
12217+            self.mdmf_node.download_best_version())
12218+        d.addCallback(lambda data:
12219+            self.failUnlessIn("modified", data))
12220+        d.addCallback(lambda ignored:
12221+            self.sdmf_node.modify(modifier))
12222+        d.addCallback(lambda ignored:
12223+            self.sdmf_node.download_best_version())
12224+        d.addCallback(lambda data:
12225+            self.failUnlessIn("modified", data))
12226+        return d
12227+
12228+
12229+    def test_download_version(self):
12230+        d = self.publish_multiple()
12231+        # We want to have two recoverable versions on the grid.
12232+        d.addCallback(lambda res:
12233+                      self._set_versions({0:0,2:0,4:0,6:0,8:0,
12234+                                          1:1,3:1,5:1,7:1,9:1}))
12235+        # Now try to download each version. We should get the plaintext
12236+        # associated with that version.
12237+        d.addCallback(lambda ignored:
12238+            self._fn.get_servermap(mode=MODE_READ))
12239+        def _got_servermap(smap):
12240+            versions = smap.recoverable_versions()
12241+            assert len(versions) == 2
12242+
12243+            self.servermap = smap
12244+            self.version1, self.version2 = versions
12245+            assert self.version1 != self.version2
12246+
12247+            self.version1_seqnum = self.version1[0]
12248+            self.version2_seqnum = self.version2[0]
12249+            self.version1_index = self.version1_seqnum - 1
12250+            self.version2_index = self.version2_seqnum - 1
12251+
12252+        d.addCallback(_got_servermap)
12253+        d.addCallback(lambda ignored:
12254+            self._fn.download_version(self.servermap, self.version1))
12255+        d.addCallback(lambda results:
12256+            self.failUnlessEqual(self.CONTENTS[self.version1_index],
12257+                                 results))
12258+        d.addCallback(lambda ignored:
12259+            self._fn.download_version(self.servermap, self.version2))
12260+        d.addCallback(lambda results:
12261+            self.failUnlessEqual(self.CONTENTS[self.version2_index],
12262+                                 results))
12263+        return d
12264+
12265+
12266+    def test_download_nonexistent_version(self):
12267+        d = self.mdmf_node.get_servermap(mode=MODE_WRITE)
12268+        def _set_servermap(servermap):
12269+            self.servermap = servermap
12270+        d.addCallback(_set_servermap)
12271+        d.addCallback(lambda ignored:
12272+           self.shouldFail(UnrecoverableFileError, "nonexistent version",
12273+                           None,
12274+                           self.mdmf_node.download_version, self.servermap,
12275+                           "not a version"))
12276+        return d
12277+
12278+
12279+    def test_partial_read(self):
12280+        # read only a few bytes at a time, and see that the results are
12281+        # what we expect.
12282+        d = self.mdmf_node.get_best_readable_version()
12283+        def _read_data(version):
12284+            c = consumer.MemoryConsumer()
12285+            d2 = defer.succeed(None)
12286+            for i in xrange(0, len(self.data), 10000):
12287+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12288+            d2.addCallback(lambda ignored:
12289+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12290+            return d2
12291+        d.addCallback(_read_data)
12292+        return d
12293+
12294+
12295+    def test_read(self):
12296+        d = self.mdmf_node.get_best_readable_version()
12297+        def _read_data(version):
12298+            c = consumer.MemoryConsumer()
12299+            d2 = defer.succeed(None)
12300+            d2.addCallback(lambda ignored: version.read(c))
12301+            d2.addCallback(lambda ignored:
12302+                self.failUnlessEqual("".join(c.chunks), self.data))
12303+            return d2
12304+        d.addCallback(_read_data)
12305+        return d
12306+
12307+
12308+    def test_download_best_version(self):
12309+        d = self.mdmf_node.download_best_version()
12310+        d.addCallback(lambda data:
12311+            self.failUnlessEqual(data, self.data))
12312+        d.addCallback(lambda ignored:
12313+            self.sdmf_node.download_best_version())
12314+        d.addCallback(lambda data:
12315+            self.failUnlessEqual(data, self.small_data))
12316+        return d
12317+
12318+
12319+class Update(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12320+    def setUp(self):
12321+        GridTestMixin.setUp(self)
12322+        self.basedir = self.mktemp()
12323+        self.set_up_grid()
12324+        self.c = self.g.clients[0]
12325+        self.nm = self.c.nodemaker
12326+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12327+        self.small_data = "test data" * 10 # about 90 B; SDMF
12328+        return self.do_upload()
12329+
12330+
12331+    def do_upload(self):
12332+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12333+                                         version=MDMF_VERSION)
12334+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12335+        dl = gatherResults([d1, d2])
12336+        def _then((n1, n2)):
12337+            assert isinstance(n1, MutableFileNode)
12338+            assert isinstance(n2, MutableFileNode)
12339+
12340+            self.mdmf_node = n1
12341+            self.sdmf_node = n2
12342+        dl.addCallback(_then)
12343+        return dl
12344+
12345+
12346+    def test_append(self):
12347+        # We should be able to append data to the middle of a mutable
12348+        # file and get what we expect.
12349+        new_data = self.data + "appended"
12350+        d = self.mdmf_node.get_best_mutable_version()
12351+        d.addCallback(lambda mv:
12352+            mv.update(MutableData("appended"), len(self.data)))
12353+        d.addCallback(lambda ignored:
12354+            self.mdmf_node.download_best_version())
12355+        d.addCallback(lambda results:
12356+            self.failUnlessEqual(results, new_data))
12357+        return d
12358+    test_append.timeout = 15
12359+
12360+
12361+    def test_replace(self):
12362+        # We should be able to replace data in the middle of a mutable
12363+        # file and get what we expect back.
12364+        new_data = self.data[:100]
12365+        new_data += "appended"
12366+        new_data += self.data[108:]
12367+        d = self.mdmf_node.get_best_mutable_version()
12368+        d.addCallback(lambda mv:
12369+            mv.update(MutableData("appended"), 100))
12370+        d.addCallback(lambda ignored:
12371+            self.mdmf_node.download_best_version())
12372+        d.addCallback(lambda results:
12373+            self.failUnlessEqual(results, new_data))
12374+        return d
12375+
12376+
12377+    def test_replace_and_extend(self):
12378+        # We should be able to replace data in the middle of a mutable
12379+        # file and extend that mutable file and get what we expect.
12380+        new_data = self.data[:100]
12381+        new_data += "modified " * 100000
12382+        d = self.mdmf_node.get_best_mutable_version()
12383+        d.addCallback(lambda mv:
12384+            mv.update(MutableData("modified " * 100000), 100))
12385+        d.addCallback(lambda ignored:
12386+            self.mdmf_node.download_best_version())
12387+        d.addCallback(lambda results:
12388+            self.failUnlessEqual(results, new_data))
12389+        return d
12390+
12391+
12392+    def test_append_power_of_two(self):
12393+        # If we attempt to extend a mutable file so that its segment
12394+        # count crosses a power-of-two boundary, the update operation
12395+        # should know how to reencode the file.
12396+
12397+        # Note that the data populating self.mdmf_node is about 900 KiB
12398+        # long -- this is 7 segments in the default segment size. So we
12399+        # need to add 2 segments worth of data to push it over a
12400+        # power-of-two boundary.
12401+        segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
12402+        new_data = self.data + (segment * 2)
12403+        d = self.mdmf_node.get_best_mutable_version()
12404+        d.addCallback(lambda mv:
12405+            mv.update(MutableData(segment * 2), len(self.data)))
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+    test_append_power_of_two.timeout = 15
12412+
12413+
12414+    def test_update_sdmf(self):
12415+        # Running update on a single-segment file should still work.
12416+        new_data = self.small_data + "appended"
12417+        d = self.sdmf_node.get_best_mutable_version()
12418+        d.addCallback(lambda mv:
12419+            mv.update(MutableData("appended"), len(self.small_data)))
12420+        d.addCallback(lambda ignored:
12421+            self.sdmf_node.download_best_version())
12422+        d.addCallback(lambda results:
12423+            self.failUnlessEqual(results, new_data))
12424+        return d
12425+
12426+    def test_replace_in_last_segment(self):
12427+        # The wrapper should know how to handle the tail segment
12428+        # appropriately.
12429+        replace_offset = len(self.data) - 100
12430+        new_data = self.data[:replace_offset] + "replaced"
12431+        rest_offset = replace_offset + len("replaced")
12432+        new_data += self.data[rest_offset:]
12433+        d = self.mdmf_node.get_best_mutable_version()
12434+        d.addCallback(lambda mv:
12435+            mv.update(MutableData("replaced"), replace_offset))
12436+        d.addCallback(lambda ignored:
12437+            self.mdmf_node.download_best_version())
12438+        d.addCallback(lambda results:
12439+            self.failUnlessEqual(results, new_data))
12440+        return d
12441+
12442+
12443+    def test_multiple_segment_replace(self):
12444+        replace_offset = 2 * DEFAULT_MAX_SEGMENT_SIZE
12445+        new_data = self.data[:replace_offset]
12446+        new_segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
12447+        new_data += 2 * new_segment
12448+        new_data += "replaced"
12449+        rest_offset = len(new_data)
12450+        new_data += self.data[rest_offset:]
12451+        d = self.mdmf_node.get_best_mutable_version()
12452+        d.addCallback(lambda mv:
12453+            mv.update(MutableData((2 * new_segment) + "replaced"),
12454+                      replace_offset))
12455+        d.addCallback(lambda ignored:
12456+            self.mdmf_node.download_best_version())
12457+        d.addCallback(lambda results:
12458+            self.failUnlessEqual(results, new_data))
12459+        return d
12460hunk ./src/allmydata/test/test_sftp.py 32
12461 
12462 from allmydata.util.consumer import download_to_data
12463 from allmydata.immutable import upload
12464+from allmydata.mutable import publish
12465 from allmydata.test.no_network import GridTestMixin
12466 from allmydata.test.common import ShouldFailMixin
12467 from allmydata.test.common_util import ReallyEqualMixin
12468hunk ./src/allmydata/test/test_sftp.py 84
12469         return d
12470 
12471     def _set_up_tree(self):
12472-        d = self.client.create_mutable_file("mutable file contents")
12473+        u = publish.MutableData("mutable file contents")
12474+        d = self.client.create_mutable_file(u)
12475         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12476         def _created_mutable(n):
12477             self.mutable = n
12478hunk ./src/allmydata/test/test_sftp.py 1334
12479         d.addCallback(lambda ign: self.failUnlessEqual(sftpd.all_heisenfiles, {}))
12480         d.addCallback(lambda ign: self.failUnlessEqual(self.handler._heisenfiles, {}))
12481         return d
12482+    test_makeDirectory.timeout = 15
12483 
12484     def test_execCommand_and_openShell(self):
12485         class FakeProtocol:
12486hunk ./src/allmydata/test/test_storage.py 27
12487                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
12488                                      SIGNED_PREFIX, MDMFHEADER, \
12489                                      MDMFOFFSETS, SDMFSlotWriteProxy
12490-from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
12491-                                 SDMF_VERSION
12492+from allmydata.interfaces import BadWriteEnablerError
12493 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
12494 from allmydata.test.common_web import WebRenderingMixin
12495 from allmydata.web.storage import StorageStatus, remove_prefix
12496hunk ./src/allmydata/test/test_system.py 26
12497 from allmydata.monitor import Monitor
12498 from allmydata.mutable.common import NotWriteableError
12499 from allmydata.mutable import layout as mutable_layout
12500+from allmydata.mutable.publish import MutableData
12501 from foolscap.api import DeadReferenceError
12502 from twisted.python.failure import Failure
12503 from twisted.web.client import getPage
12504hunk ./src/allmydata/test/test_system.py 467
12505     def test_mutable(self):
12506         self.basedir = "system/SystemTest/test_mutable"
12507         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12508+        DATA_uploadable = MutableData(DATA)
12509         NEWDATA = "new contents yay"
12510hunk ./src/allmydata/test/test_system.py 469
12511+        NEWDATA_uploadable = MutableData(NEWDATA)
12512         NEWERDATA = "this is getting old"
12513hunk ./src/allmydata/test/test_system.py 471
12514+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12515 
12516         d = self.set_up_nodes(use_key_generator=True)
12517 
12518hunk ./src/allmydata/test/test_system.py 478
12519         def _create_mutable(res):
12520             c = self.clients[0]
12521             log.msg("starting create_mutable_file")
12522-            d1 = c.create_mutable_file(DATA)
12523+            d1 = c.create_mutable_file(DATA_uploadable)
12524             def _done(res):
12525                 log.msg("DONE: %s" % (res,))
12526                 self._mutable_node_1 = res
12527hunk ./src/allmydata/test/test_system.py 565
12528             self.failUnlessEqual(res, DATA)
12529             # replace the data
12530             log.msg("starting replace1")
12531-            d1 = newnode.overwrite(NEWDATA)
12532+            d1 = newnode.overwrite(NEWDATA_uploadable)
12533             d1.addCallback(lambda res: newnode.download_best_version())
12534             return d1
12535         d.addCallback(_check_download_3)
12536hunk ./src/allmydata/test/test_system.py 579
12537             newnode2 = self.clients[3].create_node_from_uri(uri)
12538             self._newnode3 = self.clients[3].create_node_from_uri(uri)
12539             log.msg("starting replace2")
12540-            d1 = newnode1.overwrite(NEWERDATA)
12541+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
12542             d1.addCallback(lambda res: newnode2.download_best_version())
12543             return d1
12544         d.addCallback(_check_download_4)
12545hunk ./src/allmydata/test/test_system.py 649
12546         def _check_empty_file(res):
12547             # make sure we can create empty files, this usually screws up the
12548             # segsize math
12549-            d1 = self.clients[2].create_mutable_file("")
12550+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12551             d1.addCallback(lambda newnode: newnode.download_best_version())
12552             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12553             return d1
12554hunk ./src/allmydata/test/test_system.py 680
12555                                  self.key_generator_svc.key_generator.pool_size + size_delta)
12556 
12557         d.addCallback(check_kg_poolsize, 0)
12558-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
12559+        d.addCallback(lambda junk:
12560+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12561         d.addCallback(check_kg_poolsize, -1)
12562         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12563         d.addCallback(check_kg_poolsize, -2)
12564hunk ./src/allmydata/test/test_web.py 28
12565 from allmydata.util.encodingutil import to_str
12566 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
12567      create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
12568-from allmydata.interfaces import IMutableFileNode
12569+from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
12570 from allmydata.mutable import servermap, publish, retrieve
12571 import allmydata.test.common_util as testutil
12572 from allmydata.test.no_network import GridTestMixin
12573hunk ./src/allmydata/test/test_web.py 57
12574         return FakeCHKFileNode(cap)
12575     def _create_mutable(self, cap):
12576         return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
12577-    def create_mutable_file(self, contents="", keysize=None):
12578+    def create_mutable_file(self, contents="", keysize=None,
12579+                            version=SDMF_VERSION):
12580         n = FakeMutableFileNode(None, None, None, None)
12581hunk ./src/allmydata/test/test_web.py 60
12582+        n.set_version(version)
12583         return n.create(contents)
12584 
12585 class FakeUploader(service.Service):
12586hunk ./src/allmydata/test/test_web.py 157
12587         self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
12588                                        self.uploader, None,
12589                                        None, None)
12590+        self.mutable_file_default = SDMF_VERSION
12591 
12592     def startService(self):
12593         return service.MultiService.startService(self)
12594hunk ./src/allmydata/test/test_web.py 762
12595                              self.PUT, base + "/@@name=/blah.txt", "")
12596         return d
12597 
12598+
12599     def test_GET_DIRURL_named_bad(self):
12600         base = "/file/%s" % urllib.quote(self._foo_uri)
12601         d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
12602hunk ./src/allmydata/test/test_web.py 878
12603                                                       self.NEWFILE_CONTENTS))
12604         return d
12605 
12606+    def test_PUT_NEWFILEURL_unlinked_mdmf(self):
12607+        # this should get us a few segments of an MDMF mutable file,
12608+        # which we can then test for.
12609+        contents = self.NEWFILE_CONTENTS * 300000
12610+        d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
12611+                     contents)
12612+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12613+        d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
12614+        return d
12615+
12616+    def test_PUT_NEWFILEURL_unlinked_sdmf(self):
12617+        contents = self.NEWFILE_CONTENTS * 300000
12618+        d = self.PUT("/uri?mutable=true&mutable-type=sdmf",
12619+                     contents)
12620+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12621+        d.addCallback(lambda json: self.failUnlessIn("sdmf", json))
12622+        return d
12623+
12624     def test_PUT_NEWFILEURL_range_bad(self):
12625         headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
12626         target = self.public_url + "/foo/new.txt"
12627hunk ./src/allmydata/test/test_web.py 928
12628         return d
12629 
12630     def test_PUT_NEWFILEURL_mutable_toobig(self):
12631-        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
12632-                             "413 Request Entity Too Large",
12633-                             "SDMF is limited to one segment, and 10001 > 10000",
12634-                             self.PUT,
12635-                             self.public_url + "/foo/new.txt?mutable=true",
12636-                             "b" * (self.s.MUTABLE_SIZELIMIT+1))
12637+        # It is okay to upload large mutable files, so we should be able
12638+        # to do that.
12639+        d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
12640+                     "b" * (self.s.MUTABLE_SIZELIMIT + 1))
12641         return d
12642 
12643     def test_PUT_NEWFILEURL_replace(self):
12644hunk ./src/allmydata/test/test_web.py 1026
12645         d.addCallback(_check1)
12646         return d
12647 
12648+    def test_GET_FILEURL_json_mutable_type(self):
12649+        # The JSON should include mutable-type, which says whether the
12650+        # file is SDMF or MDMF
12651+        d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
12652+                     self.NEWFILE_CONTENTS * 300000)
12653+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12654+        def _got_json(json, version):
12655+            data = simplejson.loads(json)
12656+            assert "filenode" == data[0]
12657+            data = data[1]
12658+            assert isinstance(data, dict)
12659+
12660+            self.failUnlessIn("mutable-type", data)
12661+            self.failUnlessEqual(data['mutable-type'], version)
12662+
12663+        d.addCallback(_got_json, "mdmf")
12664+        # Now make an SDMF file and check that it is reported correctly.
12665+        d.addCallback(lambda ignored:
12666+            self.PUT("/uri?mutable=true&mutable-type=sdmf",
12667+                      self.NEWFILE_CONTENTS * 300000))
12668+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12669+        d.addCallback(_got_json, "sdmf")
12670+        return d
12671+
12672     def test_GET_FILEURL_json_missing(self):
12673         d = self.GET(self.public_url + "/foo/missing?json")
12674         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
12675hunk ./src/allmydata/test/test_web.py 1088
12676         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
12677         return d
12678 
12679-    def test_GET_DIRECTORY_html_banner(self):
12680+    def test_GET_DIRECTORY_html(self):
12681         d = self.GET(self.public_url + "/foo", followRedirect=True)
12682         def _check(res):
12683             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
12684hunk ./src/allmydata/test/test_web.py 1092
12685+            self.failUnlessIn("mutable-type-mdmf", res)
12686+            self.failUnlessIn("mutable-type-sdmf", res)
12687         d.addCallback(_check)
12688         return d
12689 
12690hunk ./src/allmydata/test/test_web.py 1097
12691+    def test_GET_root_html(self):
12692+        # make sure that we have the option to upload an unlinked
12693+        # mutable file in SDMF and MDMF formats.
12694+        d = self.GET("/")
12695+        def _got_html(html):
12696+            # These are radio buttons that allow the user to toggle
12697+            # whether a particular mutable file is MDMF or SDMF.
12698+            self.failUnlessIn("mutable-type-mdmf", html)
12699+            self.failUnlessIn("mutable-type-sdmf", html)
12700+        d.addCallback(_got_html)
12701+        return d
12702+
12703+    def test_mutable_type_defaults(self):
12704+        # The checked="checked" attribute of the inputs corresponding to
12705+        # the mutable-type parameter should change as expected with the
12706+        # value configured in tahoe.cfg.
12707+        #
12708+        # By default, the value configured with the client is
12709+        # SDMF_VERSION, so that should be checked.
12710+        assert self.s.mutable_file_default == SDMF_VERSION
12711+
12712+        d = self.GET("/")
12713+        def _got_html(html, value):
12714+            i = 'input checked="checked" type="radio" id="mutable-type-%s"'
12715+            self.failUnlessIn(i % value, html)
12716+        d.addCallback(_got_html, "sdmf")
12717+        d.addCallback(lambda ignored:
12718+            self.GET(self.public_url + "/foo", followRedirect=True))
12719+        d.addCallback(_got_html, "sdmf")
12720+        # Now switch the configuration value to MDMF. The MDMF radio
12721+        # buttons should now be checked on these pages.
12722+        def _swap_values(ignored):
12723+            self.s.mutable_file_default = MDMF_VERSION
12724+        d.addCallback(_swap_values)
12725+        d.addCallback(lambda ignored: self.GET("/"))
12726+        d.addCallback(_got_html, "mdmf")
12727+        d.addCallback(lambda ignored:
12728+            self.GET(self.public_url + "/foo", followRedirect=True))
12729+        d.addCallback(_got_html, "mdmf")
12730+        return d
12731+
12732     def test_GET_DIRURL(self):
12733         # the addSlash means we get a redirect here
12734         # from /uri/$URI/foo/ , we need ../../../ to get back to the root
12735hunk ./src/allmydata/test/test_web.py 1227
12736         d.addCallback(self.failUnlessIsFooJSON)
12737         return d
12738 
12739+    def test_GET_DIRURL_json_mutable_type(self):
12740+        d = self.PUT(self.public_url + \
12741+                     "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
12742+                     self.NEWFILE_CONTENTS * 300000)
12743+        d.addCallback(lambda ignored:
12744+            self.PUT(self.public_url + \
12745+                     "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
12746+                     self.NEWFILE_CONTENTS * 300000))
12747+        # Now we have an MDMF and SDMF file in the directory. If we GET
12748+        # its JSON, we should see their encodings.
12749+        d.addCallback(lambda ignored:
12750+            self.GET(self.public_url + "/foo?t=json"))
12751+        def _got_json(json):
12752+            data = simplejson.loads(json)
12753+            assert data[0] == "dirnode"
12754+
12755+            data = data[1]
12756+            kids = data['children']
12757+
12758+            mdmf_data = kids['mdmf.txt'][1]
12759+            self.failUnlessIn("mutable-type", mdmf_data)
12760+            self.failUnlessEqual(mdmf_data['mutable-type'], "mdmf")
12761+
12762+            sdmf_data = kids['sdmf.txt'][1]
12763+            self.failUnlessIn("mutable-type", sdmf_data)
12764+            self.failUnlessEqual(sdmf_data['mutable-type'], "sdmf")
12765+        d.addCallback(_got_json)
12766+        return d
12767+
12768 
12769     def test_POST_DIRURL_manifest_no_ophandle(self):
12770         d = self.shouldFail2(error.Error,
12771hunk ./src/allmydata/test/test_web.py 1810
12772         return d
12773 
12774     def test_POST_upload_no_link_mutable_toobig(self):
12775-        d = self.shouldFail2(error.Error,
12776-                             "test_POST_upload_no_link_mutable_toobig",
12777-                             "413 Request Entity Too Large",
12778-                             "SDMF is limited to one segment, and 10001 > 10000",
12779-                             self.POST,
12780-                             "/uri", t="upload", mutable="true",
12781-                             file=("new.txt",
12782-                                   "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
12783+        # The SDMF size limit is no longer in place, so we should be
12784+        # able to upload mutable files that are as large as we want them
12785+        # to be.
12786+        d = self.POST("/uri", t="upload", mutable="true",
12787+                      file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
12788         return d
12789 
12790hunk ./src/allmydata/test/test_web.py 1817
12791+
12792+    def test_POST_upload_mutable_type_unlinked(self):
12793+        d = self.POST("/uri?t=upload&mutable=true&mutable-type=sdmf",
12794+                      file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
12795+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12796+        def _got_json(json, version):
12797+            data = simplejson.loads(json)
12798+            data = data[1]
12799+
12800+            self.failUnlessIn("mutable-type", data)
12801+            self.failUnlessEqual(data['mutable-type'], version)
12802+        d.addCallback(_got_json, "sdmf")
12803+        d.addCallback(lambda ignored:
12804+            self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
12805+                      file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
12806+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12807+        d.addCallback(_got_json, "mdmf")
12808+        return d
12809+
12810+    def test_POST_upload_mutable_type(self):
12811+        d = self.POST(self.public_url + \
12812+                      "/foo?t=upload&mutable=true&mutable-type=sdmf",
12813+                      file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
12814+        fn = self._foo_node
12815+        def _got_cap(filecap, filename):
12816+            filenameu = unicode(filename)
12817+            self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
12818+            return self.GET(self.public_url + "/foo/%s?t=json" % filename)
12819+        d.addCallback(_got_cap, "sdmf.txt")
12820+        def _got_json(json, version):
12821+            data = simplejson.loads(json)
12822+            data = data[1]
12823+
12824+            self.failUnlessIn("mutable-type", data)
12825+            self.failUnlessEqual(data['mutable-type'], version)
12826+        d.addCallback(_got_json, "sdmf")
12827+        d.addCallback(lambda ignored:
12828+            self.POST(self.public_url + \
12829+                      "/foo?t=upload&mutable=true&mutable-type=mdmf",
12830+                      file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
12831+        d.addCallback(_got_cap, "mdmf.txt")
12832+        d.addCallback(_got_json, "mdmf")
12833+        return d
12834+
12835     def test_POST_upload_mutable(self):
12836         # this creates a mutable file
12837         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
12838hunk ./src/allmydata/test/test_web.py 1985
12839             self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
12840         d.addCallback(_got_headers)
12841 
12842-        # make sure that size errors are displayed correctly for overwrite
12843-        d.addCallback(lambda res:
12844-                      self.shouldFail2(error.Error,
12845-                                       "test_POST_upload_mutable-toobig",
12846-                                       "413 Request Entity Too Large",
12847-                                       "SDMF is limited to one segment, and 10001 > 10000",
12848-                                       self.POST,
12849-                                       self.public_url + "/foo", t="upload",
12850-                                       mutable="true",
12851-                                       file=("new.txt",
12852-                                             "b" * (self.s.MUTABLE_SIZELIMIT+1)),
12853-                                       ))
12854-
12855+        # make sure that outdated size limits aren't enforced anymore.
12856+        d.addCallback(lambda ignored:
12857+            self.POST(self.public_url + "/foo", t="upload",
12858+                      mutable="true",
12859+                      file=("new.txt",
12860+                            "b" * (self.s.MUTABLE_SIZELIMIT+1))))
12861         d.addErrback(self.dump_error)
12862         return d
12863 
12864hunk ./src/allmydata/test/test_web.py 1995
12865     def test_POST_upload_mutable_toobig(self):
12866-        d = self.shouldFail2(error.Error,
12867-                             "test_POST_upload_mutable_toobig",
12868-                             "413 Request Entity Too Large",
12869-                             "SDMF is limited to one segment, and 10001 > 10000",
12870-                             self.POST,
12871-                             self.public_url + "/foo",
12872-                             t="upload", mutable="true",
12873-                             file=("new.txt",
12874-                                   "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
12875+        # SDMF had a size limti that was removed a while ago. MDMF has
12876+        # never had a size limit. Test to make sure that we do not
12877+        # encounter errors when trying to upload large mutable files,
12878+        # since there should be no coded prohibitions regarding large
12879+        # mutable files.
12880+        d = self.POST(self.public_url + "/foo",
12881+                      t="upload", mutable="true",
12882+                      file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
12883         return d
12884 
12885     def dump_error(self, f):
12886hunk ./src/allmydata/test/test_web.py 3005
12887                                                       contents))
12888         return d
12889 
12890+    def test_PUT_NEWFILEURL_mdmf(self):
12891+        new_contents = self.NEWFILE_CONTENTS * 300000
12892+        d = self.PUT(self.public_url + \
12893+                     "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
12894+                     new_contents)
12895+        d.addCallback(lambda ignored:
12896+            self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
12897+        def _got_json(json):
12898+            data = simplejson.loads(json)
12899+            data = data[1]
12900+            self.failUnlessIn("mutable-type", data)
12901+            self.failUnlessEqual(data['mutable-type'], "mdmf")
12902+        d.addCallback(_got_json)
12903+        return d
12904+
12905+    def test_PUT_NEWFILEURL_sdmf(self):
12906+        new_contents = self.NEWFILE_CONTENTS * 300000
12907+        d = self.PUT(self.public_url + \
12908+                     "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
12909+                     new_contents)
12910+        d.addCallback(lambda ignored:
12911+            self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
12912+        def _got_json(json):
12913+            data = simplejson.loads(json)
12914+            data = data[1]
12915+            self.failUnlessIn("mutable-type", data)
12916+            self.failUnlessEqual(data['mutable-type'], "sdmf")
12917+        d.addCallback(_got_json)
12918+        return d
12919+
12920     def test_PUT_NEWFILEURL_uri_replace(self):
12921         contents, n, new_uri = self.makefile(8)
12922         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
12923hunk ./src/allmydata/test/test_web.py 3156
12924         d.addCallback(_done)
12925         return d
12926 
12927+
12928+    def test_PUT_update_at_offset(self):
12929+        file_contents = "test file" * 100000 # about 900 KiB
12930+        d = self.PUT("/uri?mutable=true", file_contents)
12931+        def _then(filecap):
12932+            self.filecap = filecap
12933+            new_data = file_contents[:100]
12934+            new = "replaced and so on"
12935+            new_data += new
12936+            new_data += file_contents[len(new_data):]
12937+            assert len(new_data) == len(file_contents)
12938+            self.new_data = new_data
12939+        d.addCallback(_then)
12940+        d.addCallback(lambda ignored:
12941+            self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
12942+                     "replaced and so on"))
12943+        def _get_data(filecap):
12944+            n = self.s.create_node_from_uri(filecap)
12945+            return n.download_best_version()
12946+        d.addCallback(_get_data)
12947+        d.addCallback(lambda results:
12948+            self.failUnlessEqual(results, self.new_data))
12949+        # Now try appending things to the file
12950+        d.addCallback(lambda ignored:
12951+            self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
12952+                     "puppies" * 100))
12953+        d.addCallback(_get_data)
12954+        d.addCallback(lambda results:
12955+            self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
12956+        return d
12957+
12958+
12959+    def test_PUT_update_at_offset_immutable(self):
12960+        file_contents = "Test file" * 100000
12961+        d = self.PUT("/uri", file_contents)
12962+        def _then(filecap):
12963+            self.filecap = filecap
12964+        d.addCallback(_then)
12965+        d.addCallback(lambda ignored:
12966+            self.shouldHTTPError("test immutable update",
12967+                                 400, "Bad Request",
12968+                                 "immutable",
12969+                                 self.PUT,
12970+                                 "/uri/%s?offset=50" % self.filecap,
12971+                                 "foo"))
12972+        return d
12973+
12974+
12975     def test_bad_method(self):
12976         url = self.webish_url + self.public_url + "/foo/bar.txt"
12977         d = self.shouldHTTPError("test_bad_method",
12978hunk ./src/allmydata/test/test_web.py 3473
12979         def _stash_mutable_uri(n, which):
12980             self.uris[which] = n.get_uri()
12981             assert isinstance(self.uris[which], str)
12982-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
12983+        d.addCallback(lambda ign:
12984+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12985         d.addCallback(_stash_mutable_uri, "corrupt")
12986         d.addCallback(lambda ign:
12987                       c0.upload(upload.Data("literal", convergence="")))
12988hunk ./src/allmydata/test/test_web.py 3620
12989         def _stash_mutable_uri(n, which):
12990             self.uris[which] = n.get_uri()
12991             assert isinstance(self.uris[which], str)
12992-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
12993+        d.addCallback(lambda ign:
12994+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12995         d.addCallback(_stash_mutable_uri, "corrupt")
12996 
12997         def _compute_fileurls(ignored):
12998hunk ./src/allmydata/test/test_web.py 4283
12999         def _stash_mutable_uri(n, which):
13000             self.uris[which] = n.get_uri()
13001             assert isinstance(self.uris[which], str)
13002-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
13003+        d.addCallback(lambda ign:
13004+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
13005         d.addCallback(_stash_mutable_uri, "mutable")
13006 
13007         def _compute_fileurls(ignored):
13008hunk ./src/allmydata/test/test_web.py 4383
13009                                                         convergence="")))
13010         d.addCallback(_stash_uri, "small")
13011 
13012-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
13013+        d.addCallback(lambda ign:
13014+            c0.create_mutable_file(publish.MutableData("mutable")))
13015         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
13016         d.addCallback(_stash_uri, "mutable")
13017 
13018}
13019[resolve conflicts between 393-MDMF patches and trunk as of 1.8.2
13020"Brian Warner <warner@lothar.com>"**20110220230201
13021 Ignore-this: 9bbf5d26c994e8069202331dcb4cdd95
13022] {
13023merger 0.0 (
13024merger 0.0 (
13025merger 0.0 (
13026replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13027merger 0.0 (
13028hunk ./docs/configuration.rst 384
13029-shares.needed = (int, optional) aka "k", default 3
13030-shares.total = (int, optional) aka "N", N >= k, default 10
13031-shares.happy = (int, optional) 1 <= happy <= N, default 7
13032-
13033- These three values set the default encoding parameters. Each time a new file
13034- is uploaded, erasure-coding is used to break the ciphertext into separate
13035- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13036- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13037- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13038- Setting k to 1 is equivalent to simple replication (uploading N copies of
13039- the file).
13040-
13041- These values control the tradeoff between storage overhead, performance, and
13042- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13043- backend storage space (the actual value will be a bit more, because of other
13044- forms of overhead). Up to N-k shares can be lost before the file becomes
13045- unrecoverable, so assuming there are at least N servers, up to N-k servers
13046- can be offline without losing the file. So large N/k ratios are more
13047- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13048- smaller than N.
13049-
13050- Large values of N will slow down upload operations slightly, since more
13051- servers must be involved, and will slightly increase storage overhead due to
13052- the hash trees that are created. Large values of k will cause downloads to
13053- be marginally slower, because more servers must be involved. N cannot be
13054- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13055- uses.
13056-
13057- shares.happy allows you control over the distribution of your immutable file.
13058- For a successful upload, shares are guaranteed to be initially placed on
13059- at least 'shares.happy' distinct servers, the correct functioning of any
13060- k of which is sufficient to guarantee the availability of the uploaded file.
13061- This value should not be larger than the number of servers on your grid.
13062-
13063- A value of shares.happy <= k is allowed, but does not provide any redundancy
13064- if some servers fail or lose shares.
13065-
13066- (Mutable files use a different share placement algorithm that does not
13067-  consider this parameter.)
13068-
13069-
13070-== Storage Server Configuration ==
13071-
13072-[storage]
13073-enabled = (boolean, optional)
13074-
13075- If this is True, the node will run a storage server, offering space to other
13076- clients. If it is False, the node will not run a storage server, meaning
13077- that no shares will be stored on this node. Use False this for clients who
13078- do not wish to provide storage service. The default value is True.
13079-
13080-readonly = (boolean, optional)
13081-
13082- If True, the node will run a storage server but will not accept any shares,
13083- making it effectively read-only. Use this for storage servers which are
13084- being decommissioned: the storage/ directory could be mounted read-only,
13085- while shares are moved to other servers. Note that this currently only
13086- affects immutable shares. Mutable shares (used for directories) will be
13087- written and modified anyway. See ticket #390 for the current status of this
13088- bug. The default value is False.
13089-
13090-reserved_space = (str, optional)
13091-
13092- If provided, this value defines how much disk space is reserved: the storage
13093- server will not accept any share which causes the amount of free disk space
13094- to drop below this value. (The free space is measured by a call to statvfs(2)
13095- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13096- user account under which the storage server runs.)
13097-
13098- This string contains a number, with an optional case-insensitive scale
13099- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13100- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13101- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13102-
13103-expire.enabled =
13104-expire.mode =
13105-expire.override_lease_duration =
13106-expire.cutoff_date =
13107-expire.immutable =
13108-expire.mutable =
13109-
13110- These settings control garbage-collection, in which the server will delete
13111- shares that no longer have an up-to-date lease on them. Please see the
13112- neighboring "garbage-collection.txt" document for full details.
13113-
13114-
13115-== Running A Helper ==
13116+Running A Helper
13117+================
13118hunk ./docs/configuration.rst 424
13119+mutable.format = sdmf or mdmf
13120+
13121+ This value tells Tahoe-LAFS what the default mutable file format should
13122+ be. If mutable.format=sdmf, then newly created mutable files will be in
13123+ the old SDMF format. This is desirable for clients that operate on
13124+ grids where some peers run older versions of Tahoe-LAFS, as these older
13125+ versions cannot read the new MDMF mutable file format. If
13126+ mutable.format = mdmf, then newly created mutable files will use the
13127+ new MDMF format, which supports efficient in-place modification and
13128+ streaming downloads. You can overwrite this value using a special
13129+ mutable-type parameter in the webapi. If you do not specify a value
13130+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13131+
13132+ Note that this parameter only applies to mutable files. Mutable
13133+ directories, which are stored as mutable files, are not controlled by
13134+ this parameter and will always use SDMF. We may revisit this decision
13135+ in future versions of Tahoe-LAFS.
13136)
13137)
13138hunk ./docs/configuration.rst 324
13139+Frontend Configuration
13140+======================
13141+
13142+The Tahoe client process can run a variety of frontend file-access protocols.
13143+You will use these to create and retrieve files from the virtual filesystem.
13144+Configuration details for each are documented in the following
13145+protocol-specific guides:
13146+
13147+HTTP
13148+
13149+    Tahoe runs a webserver by default on port 3456. This interface provides a
13150+    human-oriented "WUI", with pages to create, modify, and browse
13151+    directories and files, as well as a number of pages to check on the
13152+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13153+    with a REST-ful HTTP interface that can be used by other programs
13154+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13155+    details, and the ``web.port`` and ``web.static`` config variables above.
13156+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13157+    status pages.
13158+
13159+CLI
13160+
13161+    The main "bin/tahoe" executable includes subcommands for manipulating the
13162+    filesystem, uploading/downloading files, and creating/running Tahoe
13163+    nodes. See `<frontends/CLI.rst>`_ for details.
13164+
13165+FTP, SFTP
13166+
13167+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13168+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13169+    for instructions on configuring these services, and the ``[ftpd]`` and
13170+    ``[sftpd]`` sections of ``tahoe.cfg``.
13171+
13172)
13173hunk ./docs/configuration.rst 324
13174+``mutable.format = sdmf or mdmf``
13175+
13176+    This value tells Tahoe what the default mutable file format should
13177+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13178+    in the old SDMF format. This is desirable for clients that operate on
13179+    grids where some peers run older versions of Tahoe, as these older
13180+    versions cannot read the new MDMF mutable file format. If
13181+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13182+    the new MDMF format, which supports efficient in-place modification and
13183+    streaming downloads. You can overwrite this value using a special
13184+    mutable-type parameter in the webapi. If you do not specify a value here,
13185+    Tahoe will use SDMF for all newly-created mutable files.
13186+
13187+    Note that this parameter only applies to mutable files. Mutable
13188+    directories, which are stored as mutable files, are not controlled by
13189+    this parameter and will always use SDMF. We may revisit this decision
13190+    in future versions of Tahoe-LAFS.
13191+
13192)
13193merger 0.0 (
13194merger 0.0 (
13195hunk ./docs/configuration.rst 324
13196+``mutable.format = sdmf or mdmf``
13197+
13198+    This value tells Tahoe what the default mutable file format should
13199+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13200+    in the old SDMF format. This is desirable for clients that operate on
13201+    grids where some peers run older versions of Tahoe, as these older
13202+    versions cannot read the new MDMF mutable file format. If
13203+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13204+    the new MDMF format, which supports efficient in-place modification and
13205+    streaming downloads. You can overwrite this value using a special
13206+    mutable-type parameter in the webapi. If you do not specify a value here,
13207+    Tahoe will use SDMF for all newly-created mutable files.
13208+
13209+    Note that this parameter only applies to mutable files. Mutable
13210+    directories, which are stored as mutable files, are not controlled by
13211+    this parameter and will always use SDMF. We may revisit this decision
13212+    in future versions of Tahoe-LAFS.
13213+
13214merger 0.0 (
13215merger 0.0 (
13216replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13217merger 0.0 (
13218hunk ./docs/configuration.rst 384
13219-shares.needed = (int, optional) aka "k", default 3
13220-shares.total = (int, optional) aka "N", N >= k, default 10
13221-shares.happy = (int, optional) 1 <= happy <= N, default 7
13222-
13223- These three values set the default encoding parameters. Each time a new file
13224- is uploaded, erasure-coding is used to break the ciphertext into separate
13225- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13226- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13227- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13228- Setting k to 1 is equivalent to simple replication (uploading N copies of
13229- the file).
13230-
13231- These values control the tradeoff between storage overhead, performance, and
13232- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13233- backend storage space (the actual value will be a bit more, because of other
13234- forms of overhead). Up to N-k shares can be lost before the file becomes
13235- unrecoverable, so assuming there are at least N servers, up to N-k servers
13236- can be offline without losing the file. So large N/k ratios are more
13237- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13238- smaller than N.
13239-
13240- Large values of N will slow down upload operations slightly, since more
13241- servers must be involved, and will slightly increase storage overhead due to
13242- the hash trees that are created. Large values of k will cause downloads to
13243- be marginally slower, because more servers must be involved. N cannot be
13244- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13245- uses.
13246-
13247- shares.happy allows you control over the distribution of your immutable file.
13248- For a successful upload, shares are guaranteed to be initially placed on
13249- at least 'shares.happy' distinct servers, the correct functioning of any
13250- k of which is sufficient to guarantee the availability of the uploaded file.
13251- This value should not be larger than the number of servers on your grid.
13252-
13253- A value of shares.happy <= k is allowed, but does not provide any redundancy
13254- if some servers fail or lose shares.
13255-
13256- (Mutable files use a different share placement algorithm that does not
13257-  consider this parameter.)
13258-
13259-
13260-== Storage Server Configuration ==
13261-
13262-[storage]
13263-enabled = (boolean, optional)
13264-
13265- If this is True, the node will run a storage server, offering space to other
13266- clients. If it is False, the node will not run a storage server, meaning
13267- that no shares will be stored on this node. Use False this for clients who
13268- do not wish to provide storage service. The default value is True.
13269-
13270-readonly = (boolean, optional)
13271-
13272- If True, the node will run a storage server but will not accept any shares,
13273- making it effectively read-only. Use this for storage servers which are
13274- being decommissioned: the storage/ directory could be mounted read-only,
13275- while shares are moved to other servers. Note that this currently only
13276- affects immutable shares. Mutable shares (used for directories) will be
13277- written and modified anyway. See ticket #390 for the current status of this
13278- bug. The default value is False.
13279-
13280-reserved_space = (str, optional)
13281-
13282- If provided, this value defines how much disk space is reserved: the storage
13283- server will not accept any share which causes the amount of free disk space
13284- to drop below this value. (The free space is measured by a call to statvfs(2)
13285- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13286- user account under which the storage server runs.)
13287-
13288- This string contains a number, with an optional case-insensitive scale
13289- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13290- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13291- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13292-
13293-expire.enabled =
13294-expire.mode =
13295-expire.override_lease_duration =
13296-expire.cutoff_date =
13297-expire.immutable =
13298-expire.mutable =
13299-
13300- These settings control garbage-collection, in which the server will delete
13301- shares that no longer have an up-to-date lease on them. Please see the
13302- neighboring "garbage-collection.txt" document for full details.
13303-
13304-
13305-== Running A Helper ==
13306+Running A Helper
13307+================
13308hunk ./docs/configuration.rst 424
13309+mutable.format = sdmf or mdmf
13310+
13311+ This value tells Tahoe-LAFS what the default mutable file format should
13312+ be. If mutable.format=sdmf, then newly created mutable files will be in
13313+ the old SDMF format. This is desirable for clients that operate on
13314+ grids where some peers run older versions of Tahoe-LAFS, as these older
13315+ versions cannot read the new MDMF mutable file format. If
13316+ mutable.format = mdmf, then newly created mutable files will use the
13317+ new MDMF format, which supports efficient in-place modification and
13318+ streaming downloads. You can overwrite this value using a special
13319+ mutable-type parameter in the webapi. If you do not specify a value
13320+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13321+
13322+ Note that this parameter only applies to mutable files. Mutable
13323+ directories, which are stored as mutable files, are not controlled by
13324+ this parameter and will always use SDMF. We may revisit this decision
13325+ in future versions of Tahoe-LAFS.
13326)
13327)
13328hunk ./docs/configuration.rst 324
13329+Frontend Configuration
13330+======================
13331+
13332+The Tahoe client process can run a variety of frontend file-access protocols.
13333+You will use these to create and retrieve files from the virtual filesystem.
13334+Configuration details for each are documented in the following
13335+protocol-specific guides:
13336+
13337+HTTP
13338+
13339+    Tahoe runs a webserver by default on port 3456. This interface provides a
13340+    human-oriented "WUI", with pages to create, modify, and browse
13341+    directories and files, as well as a number of pages to check on the
13342+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13343+    with a REST-ful HTTP interface that can be used by other programs
13344+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13345+    details, and the ``web.port`` and ``web.static`` config variables above.
13346+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13347+    status pages.
13348+
13349+CLI
13350+
13351+    The main "bin/tahoe" executable includes subcommands for manipulating the
13352+    filesystem, uploading/downloading files, and creating/running Tahoe
13353+    nodes. See `<frontends/CLI.rst>`_ for details.
13354+
13355+FTP, SFTP
13356+
13357+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13358+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13359+    for instructions on configuring these services, and the ``[ftpd]`` and
13360+    ``[sftpd]`` sections of ``tahoe.cfg``.
13361+
13362)
13363)
13364hunk ./docs/configuration.rst 402
13365-shares.needed = (int, optional) aka "k", default 3
13366-shares.total = (int, optional) aka "N", N >= k, default 10
13367-shares.happy = (int, optional) 1 <= happy <= N, default 7
13368-
13369- These three values set the default encoding parameters. Each time a new file
13370- is uploaded, erasure-coding is used to break the ciphertext into separate
13371- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13372- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13373- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13374- Setting k to 1 is equivalent to simple replication (uploading N copies of
13375- the file).
13376-
13377- These values control the tradeoff between storage overhead, performance, and
13378- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13379- backend storage space (the actual value will be a bit more, because of other
13380- forms of overhead). Up to N-k shares can be lost before the file becomes
13381- unrecoverable, so assuming there are at least N servers, up to N-k servers
13382- can be offline without losing the file. So large N/k ratios are more
13383- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13384- smaller than N.
13385-
13386- Large values of N will slow down upload operations slightly, since more
13387- servers must be involved, and will slightly increase storage overhead due to
13388- the hash trees that are created. Large values of k will cause downloads to
13389- be marginally slower, because more servers must be involved. N cannot be
13390- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13391- uses.
13392-
13393- shares.happy allows you control over the distribution of your immutable file.
13394- For a successful upload, shares are guaranteed to be initially placed on
13395- at least 'shares.happy' distinct servers, the correct functioning of any
13396- k of which is sufficient to guarantee the availability of the uploaded file.
13397- This value should not be larger than the number of servers on your grid.
13398-
13399- A value of shares.happy <= k is allowed, but does not provide any redundancy
13400- if some servers fail or lose shares.
13401-
13402- (Mutable files use a different share placement algorithm that does not
13403-  consider this parameter.)
13404-
13405-
13406-== Storage Server Configuration ==
13407-
13408-[storage]
13409-enabled = (boolean, optional)
13410-
13411- If this is True, the node will run a storage server, offering space to other
13412- clients. If it is False, the node will not run a storage server, meaning
13413- that no shares will be stored on this node. Use False this for clients who
13414- do not wish to provide storage service. The default value is True.
13415-
13416-readonly = (boolean, optional)
13417-
13418- If True, the node will run a storage server but will not accept any shares,
13419- making it effectively read-only. Use this for storage servers which are
13420- being decommissioned: the storage/ directory could be mounted read-only,
13421- while shares are moved to other servers. Note that this currently only
13422- affects immutable shares. Mutable shares (used for directories) will be
13423- written and modified anyway. See ticket #390 for the current status of this
13424- bug. The default value is False.
13425-
13426-reserved_space = (str, optional)
13427-
13428- If provided, this value defines how much disk space is reserved: the storage
13429- server will not accept any share which causes the amount of free disk space
13430- to drop below this value. (The free space is measured by a call to statvfs(2)
13431- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13432- user account under which the storage server runs.)
13433-
13434- This string contains a number, with an optional case-insensitive scale
13435- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13436- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13437- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13438-
13439-expire.enabled =
13440-expire.mode =
13441-expire.override_lease_duration =
13442-expire.cutoff_date =
13443-expire.immutable =
13444-expire.mutable =
13445-
13446- These settings control garbage-collection, in which the server will delete
13447- shares that no longer have an up-to-date lease on them. Please see the
13448- neighboring "garbage-collection.txt" document for full details.
13449-
13450-
13451-== Running A Helper ==
13452+Running A Helper
13453+================
13454)
13455merger 0.0 (
13456merger 0.0 (
13457hunk ./docs/configuration.rst 402
13458-shares.needed = (int, optional) aka "k", default 3
13459-shares.total = (int, optional) aka "N", N >= k, default 10
13460-shares.happy = (int, optional) 1 <= happy <= N, default 7
13461-
13462- These three values set the default encoding parameters. Each time a new file
13463- is uploaded, erasure-coding is used to break the ciphertext into separate
13464- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13465- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13466- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13467- Setting k to 1 is equivalent to simple replication (uploading N copies of
13468- the file).
13469-
13470- These values control the tradeoff between storage overhead, performance, and
13471- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13472- backend storage space (the actual value will be a bit more, because of other
13473- forms of overhead). Up to N-k shares can be lost before the file becomes
13474- unrecoverable, so assuming there are at least N servers, up to N-k servers
13475- can be offline without losing the file. So large N/k ratios are more
13476- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13477- smaller than N.
13478-
13479- Large values of N will slow down upload operations slightly, since more
13480- servers must be involved, and will slightly increase storage overhead due to
13481- the hash trees that are created. Large values of k will cause downloads to
13482- be marginally slower, because more servers must be involved. N cannot be
13483- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13484- uses.
13485-
13486- shares.happy allows you control over the distribution of your immutable file.
13487- For a successful upload, shares are guaranteed to be initially placed on
13488- at least 'shares.happy' distinct servers, the correct functioning of any
13489- k of which is sufficient to guarantee the availability of the uploaded file.
13490- This value should not be larger than the number of servers on your grid.
13491-
13492- A value of shares.happy <= k is allowed, but does not provide any redundancy
13493- if some servers fail or lose shares.
13494-
13495- (Mutable files use a different share placement algorithm that does not
13496-  consider this parameter.)
13497-
13498-
13499-== Storage Server Configuration ==
13500-
13501-[storage]
13502-enabled = (boolean, optional)
13503-
13504- If this is True, the node will run a storage server, offering space to other
13505- clients. If it is False, the node will not run a storage server, meaning
13506- that no shares will be stored on this node. Use False this for clients who
13507- do not wish to provide storage service. The default value is True.
13508-
13509-readonly = (boolean, optional)
13510-
13511- If True, the node will run a storage server but will not accept any shares,
13512- making it effectively read-only. Use this for storage servers which are
13513- being decommissioned: the storage/ directory could be mounted read-only,
13514- while shares are moved to other servers. Note that this currently only
13515- affects immutable shares. Mutable shares (used for directories) will be
13516- written and modified anyway. See ticket #390 for the current status of this
13517- bug. The default value is False.
13518-
13519-reserved_space = (str, optional)
13520-
13521- If provided, this value defines how much disk space is reserved: the storage
13522- server will not accept any share which causes the amount of free disk space
13523- to drop below this value. (The free space is measured by a call to statvfs(2)
13524- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13525- user account under which the storage server runs.)
13526-
13527- This string contains a number, with an optional case-insensitive scale
13528- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13529- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13530- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13531-
13532-expire.enabled =
13533-expire.mode =
13534-expire.override_lease_duration =
13535-expire.cutoff_date =
13536-expire.immutable =
13537-expire.mutable =
13538-
13539- These settings control garbage-collection, in which the server will delete
13540- shares that no longer have an up-to-date lease on them. Please see the
13541- neighboring "garbage-collection.txt" document for full details.
13542-
13543-
13544-== Running A Helper ==
13545+Running A Helper
13546+================
13547merger 0.0 (
13548hunk ./docs/configuration.rst 324
13549+``mutable.format = sdmf or mdmf``
13550+
13551+    This value tells Tahoe what the default mutable file format should
13552+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13553+    in the old SDMF format. This is desirable for clients that operate on
13554+    grids where some peers run older versions of Tahoe, as these older
13555+    versions cannot read the new MDMF mutable file format. If
13556+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13557+    the new MDMF format, which supports efficient in-place modification and
13558+    streaming downloads. You can overwrite this value using a special
13559+    mutable-type parameter in the webapi. If you do not specify a value here,
13560+    Tahoe will use SDMF for all newly-created mutable files.
13561+
13562+    Note that this parameter only applies to mutable files. Mutable
13563+    directories, which are stored as mutable files, are not controlled by
13564+    this parameter and will always use SDMF. We may revisit this decision
13565+    in future versions of Tahoe-LAFS.
13566+
13567merger 0.0 (
13568merger 0.0 (
13569replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13570merger 0.0 (
13571hunk ./docs/configuration.rst 384
13572-shares.needed = (int, optional) aka "k", default 3
13573-shares.total = (int, optional) aka "N", N >= k, default 10
13574-shares.happy = (int, optional) 1 <= happy <= N, default 7
13575-
13576- These three values set the default encoding parameters. Each time a new file
13577- is uploaded, erasure-coding is used to break the ciphertext into separate
13578- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13579- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13580- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13581- Setting k to 1 is equivalent to simple replication (uploading N copies of
13582- the file).
13583-
13584- These values control the tradeoff between storage overhead, performance, and
13585- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13586- backend storage space (the actual value will be a bit more, because of other
13587- forms of overhead). Up to N-k shares can be lost before the file becomes
13588- unrecoverable, so assuming there are at least N servers, up to N-k servers
13589- can be offline without losing the file. So large N/k ratios are more
13590- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13591- smaller than N.
13592-
13593- Large values of N will slow down upload operations slightly, since more
13594- servers must be involved, and will slightly increase storage overhead due to
13595- the hash trees that are created. Large values of k will cause downloads to
13596- be marginally slower, because more servers must be involved. N cannot be
13597- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13598- uses.
13599-
13600- shares.happy allows you control over the distribution of your immutable file.
13601- For a successful upload, shares are guaranteed to be initially placed on
13602- at least 'shares.happy' distinct servers, the correct functioning of any
13603- k of which is sufficient to guarantee the availability of the uploaded file.
13604- This value should not be larger than the number of servers on your grid.
13605-
13606- A value of shares.happy <= k is allowed, but does not provide any redundancy
13607- if some servers fail or lose shares.
13608-
13609- (Mutable files use a different share placement algorithm that does not
13610-  consider this parameter.)
13611-
13612-
13613-== Storage Server Configuration ==
13614-
13615-[storage]
13616-enabled = (boolean, optional)
13617-
13618- If this is True, the node will run a storage server, offering space to other
13619- clients. If it is False, the node will not run a storage server, meaning
13620- that no shares will be stored on this node. Use False this for clients who
13621- do not wish to provide storage service. The default value is True.
13622-
13623-readonly = (boolean, optional)
13624-
13625- If True, the node will run a storage server but will not accept any shares,
13626- making it effectively read-only. Use this for storage servers which are
13627- being decommissioned: the storage/ directory could be mounted read-only,
13628- while shares are moved to other servers. Note that this currently only
13629- affects immutable shares. Mutable shares (used for directories) will be
13630- written and modified anyway. See ticket #390 for the current status of this
13631- bug. The default value is False.
13632-
13633-reserved_space = (str, optional)
13634-
13635- If provided, this value defines how much disk space is reserved: the storage
13636- server will not accept any share which causes the amount of free disk space
13637- to drop below this value. (The free space is measured by a call to statvfs(2)
13638- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13639- user account under which the storage server runs.)
13640-
13641- This string contains a number, with an optional case-insensitive scale
13642- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13643- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13644- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13645-
13646-expire.enabled =
13647-expire.mode =
13648-expire.override_lease_duration =
13649-expire.cutoff_date =
13650-expire.immutable =
13651-expire.mutable =
13652-
13653- These settings control garbage-collection, in which the server will delete
13654- shares that no longer have an up-to-date lease on them. Please see the
13655- neighboring "garbage-collection.txt" document for full details.
13656-
13657-
13658-== Running A Helper ==
13659+Running A Helper
13660+================
13661hunk ./docs/configuration.rst 424
13662+mutable.format = sdmf or mdmf
13663+
13664+ This value tells Tahoe-LAFS what the default mutable file format should
13665+ be. If mutable.format=sdmf, then newly created mutable files will be in
13666+ the old SDMF format. This is desirable for clients that operate on
13667+ grids where some peers run older versions of Tahoe-LAFS, as these older
13668+ versions cannot read the new MDMF mutable file format. If
13669+ mutable.format = mdmf, then newly created mutable files will use the
13670+ new MDMF format, which supports efficient in-place modification and
13671+ streaming downloads. You can overwrite this value using a special
13672+ mutable-type parameter in the webapi. If you do not specify a value
13673+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13674+
13675+ Note that this parameter only applies to mutable files. Mutable
13676+ directories, which are stored as mutable files, are not controlled by
13677+ this parameter and will always use SDMF. We may revisit this decision
13678+ in future versions of Tahoe-LAFS.
13679)
13680)
13681hunk ./docs/configuration.rst 324
13682+Frontend Configuration
13683+======================
13684+
13685+The Tahoe client process can run a variety of frontend file-access protocols.
13686+You will use these to create and retrieve files from the virtual filesystem.
13687+Configuration details for each are documented in the following
13688+protocol-specific guides:
13689+
13690+HTTP
13691+
13692+    Tahoe runs a webserver by default on port 3456. This interface provides a
13693+    human-oriented "WUI", with pages to create, modify, and browse
13694+    directories and files, as well as a number of pages to check on the
13695+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13696+    with a REST-ful HTTP interface that can be used by other programs
13697+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13698+    details, and the ``web.port`` and ``web.static`` config variables above.
13699+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13700+    status pages.
13701+
13702+CLI
13703+
13704+    The main "bin/tahoe" executable includes subcommands for manipulating the
13705+    filesystem, uploading/downloading files, and creating/running Tahoe
13706+    nodes. See `<frontends/CLI.rst>`_ for details.
13707+
13708+FTP, SFTP
13709+
13710+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13711+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13712+    for instructions on configuring these services, and the ``[ftpd]`` and
13713+    ``[sftpd]`` sections of ``tahoe.cfg``.
13714+
13715)
13716)
13717)
13718replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13719)
13720hunk ./src/allmydata/mutable/retrieve.py 7
13721 from zope.interface import implements
13722 from twisted.internet import defer
13723 from twisted.python import failure
13724-from foolscap.api import DeadReferenceError, eventually, fireEventually
13725-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
13726-from allmydata.util import hashutil, idlib, log
13727+from twisted.internet.interfaces import IPushProducer, IConsumer
13728+from foolscap.api import eventually, fireEventually
13729+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
13730+                                 MDMF_VERSION, SDMF_VERSION
13731+from allmydata.util import hashutil, log, mathutil
13732+from allmydata.util.dictutil import DictOfSets
13733 from allmydata import hashtree, codec
13734 from allmydata.storage.server import si_b2a
13735 from pycryptopp.cipher.aes import AES
13736hunk ./src/allmydata/mutable/retrieve.py 239
13737             # KiB, so we ask for that much.
13738             # TODO: Change the cache methods to allow us to fetch all of the
13739             # data that they have, then change this method to do that.
13740-            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
13741-                                                               shnum,
13742-                                                               0,
13743-                                                               1000)
13744+            any_cache = self._node._read_from_cache(self.verinfo, shnum,
13745+                                                    0, 1000)
13746             ss = self.servermap.connections[peerid]
13747             reader = MDMFSlotReadProxy(ss,
13748                                        self._storage_index,
13749hunk ./src/allmydata/mutable/retrieve.py 373
13750                  (k, n, self._num_segments, self._segment_size,
13751                   self._tail_segment_size))
13752 
13753-        # ask the cache first
13754-        got_from_cache = False
13755-        datavs = []
13756-        for (offset, length) in readv:
13757-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
13758-                                                            offset, length)
13759-            if data is not None:
13760-                datavs.append(data)
13761-        if len(datavs) == len(readv):
13762-            self.log("got data from cache")
13763-            got_from_cache = True
13764-            d = fireEventually({shnum: datavs})
13765-            # datavs is a dict mapping shnum to a pair of strings
13766+        for i in xrange(self._total_shares):
13767+            # So we don't have to do this later.
13768+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
13769+
13770+        # Our last task is to tell the downloader where to start and
13771+        # where to stop. We use three parameters for that:
13772+        #   - self._start_segment: the segment that we need to start
13773+        #     downloading from.
13774+        #   - self._current_segment: the next segment that we need to
13775+        #     download.
13776+        #   - self._last_segment: The last segment that we were asked to
13777+        #     download.
13778+        #
13779+        #  We say that the download is complete when
13780+        #  self._current_segment > self._last_segment. We use
13781+        #  self._start_segment and self._last_segment to know when to
13782+        #  strip things off of segments, and how much to strip.
13783+        if self._offset:
13784+            self.log("got offset: %d" % self._offset)
13785+            # our start segment is the first segment containing the
13786+            # offset we were given.
13787+            start = mathutil.div_ceil(self._offset,
13788+                                      self._segment_size)
13789+            # this gets us the first segment after self._offset. Then
13790+            # our start segment is the one before it.
13791+            start -= 1
13792+
13793+            assert start < self._num_segments
13794+            self._start_segment = start
13795+            self.log("got start segment: %d" % self._start_segment)
13796         else:
13797             self._start_segment = 0
13798 
13799hunk ./src/allmydata/mutable/servermap.py 7
13800 from itertools import count
13801 from twisted.internet import defer
13802 from twisted.python import failure
13803-from foolscap.api import DeadReferenceError, RemoteException, eventually
13804-from allmydata.util import base32, hashutil, idlib, log
13805+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
13806+                         fireEventually
13807+from allmydata.util import base32, hashutil, idlib, log, deferredutil
13808+from allmydata.util.dictutil import DictOfSets
13809 from allmydata.storage.server import si_b2a
13810 from allmydata.interfaces import IServermapUpdaterStatus
13811 from pycryptopp.publickey import rsa
13812hunk ./src/allmydata/mutable/servermap.py 16
13813 
13814 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
13815-     DictOfSets, CorruptShareError, NeedMoreDataError
13816-from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
13817-     SIGNED_PREFIX_LENGTH
13818+     CorruptShareError
13819+from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
13820 
13821 class UpdateStatus:
13822     implements(IServermapUpdaterStatus)
13823hunk ./src/allmydata/mutable/servermap.py 391
13824         #  * if we need the encrypted private key, we want [-1216ish:]
13825         #   * but we can't read from negative offsets
13826         #   * the offset table tells us the 'ish', also the positive offset
13827-        # A future version of the SMDF slot format should consider using
13828-        # fixed-size slots so we can retrieve less data. For now, we'll just
13829-        # read 2000 bytes, which also happens to read enough actual data to
13830-        # pre-fetch a 9-entry dirnode.
13831+        # MDMF:
13832+        #  * Checkstring? [0:72]
13833+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
13834+        #    the offset table will tell us for sure.
13835+        #  * If we need the verification key, we have to consult the offset
13836+        #    table as well.
13837+        # At this point, we don't know which we are. Our filenode can
13838+        # tell us, but it might be lying -- in some cases, we're
13839+        # responsible for telling it which kind of file it is.
13840         self._read_size = 4000
13841         if mode == MODE_CHECK:
13842             # we use unpack_prefix_and_signature, so we need 1k
13843hunk ./src/allmydata/mutable/servermap.py 633
13844         updated.
13845         """
13846         if verinfo:
13847-            self._node._add_to_cache(verinfo, shnum, 0, data, now)
13848+            self._node._add_to_cache(verinfo, shnum, 0, data)
13849 
13850 
13851     def _got_results(self, datavs, peerid, readsize, stuff, started):
13852hunk ./src/allmydata/mutable/servermap.py 664
13853 
13854         for shnum,datav in datavs.items():
13855             data = datav[0]
13856-            try:
13857-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
13858-                last_verinfo = verinfo
13859-                last_shnum = shnum
13860-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
13861-            except CorruptShareError, e:
13862-                # log it and give the other shares a chance to be processed
13863-                f = failure.Failure()
13864-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
13865-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
13866-                self.notify_server_corruption(peerid, shnum, str(e))
13867-                self._bad_peers.add(peerid)
13868-                self._last_failure = f
13869-                checkstring = data[:SIGNED_PREFIX_LENGTH]
13870-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
13871-                self._servermap.problems.append(f)
13872-                pass
13873+            reader = MDMFSlotReadProxy(ss,
13874+                                       storage_index,
13875+                                       shnum,
13876+                                       data)
13877+            self._readers.setdefault(peerid, dict())[shnum] = reader
13878+            # our goal, with each response, is to validate the version
13879+            # information and share data as best we can at this point --
13880+            # we do this by validating the signature. To do this, we
13881+            # need to do the following:
13882+            #   - If we don't already have the public key, fetch the
13883+            #     public key. We use this to validate the signature.
13884+            if not self._node.get_pubkey():
13885+                # fetch and set the public key.
13886+                d = reader.get_verification_key(queue=True)
13887+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
13888+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
13889+                # XXX: Make self._pubkey_query_failed?
13890+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
13891+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
13892+            else:
13893+                # we already have the public key.
13894+                d = defer.succeed(None)
13895 
13896             # Neither of these two branches return anything of
13897             # consequence, so the first entry in our deferredlist will
13898hunk ./src/allmydata/test/test_storage.py 1
13899-import time, os.path, platform, stat, re, simplejson, struct
13900+import time, os.path, platform, stat, re, simplejson, struct, shutil
13901 
13902hunk ./src/allmydata/test/test_storage.py 3
13903-import time, os.path, stat, re, simplejson, struct
13904+import mock
13905 
13906 from twisted.trial import unittest
13907 
13908}
13909[mutable/filenode.py: fix create_mutable_file('string')
13910"Brian Warner <warner@lothar.com>"**20110221014659
13911 Ignore-this: dc6bdad761089f0199681eeb784f1001
13912] hunk ./src/allmydata/mutable/filenode.py 137
13913         if contents is None:
13914             return MutableData("")
13915 
13916+        if isinstance(contents, str):
13917+            return MutableData(contents)
13918+
13919         if IMutableUploadable.providedBy(contents):
13920             return contents
13921 
13922[resolve more conflicts with current trunk
13923"Brian Warner <warner@lothar.com>"**20110221055600
13924 Ignore-this: 77ad038a478dbf5d9b34f7a68159a3e0
13925] hunk ./src/allmydata/mutable/servermap.py 461
13926         self._queries_completed = 0
13927 
13928         sb = self._storage_broker
13929-        full_peerlist = sb.get_servers_for_index(self._storage_index)
13930+        # All of the peers, permuted by the storage index, as usual.
13931+        full_peerlist = [(s.get_serverid(), s.get_rref())
13932+                         for s in sb.get_servers_for_psi(self._storage_index)]
13933         self.full_peerlist = full_peerlist # for use later, immutable
13934         self.extra_peers = full_peerlist[:] # peers are removed as we use them
13935         self._good_peers = set() # peers who had some shares
13936[update MDMF code with StorageFarmBroker changes
13937"Brian Warner <warner@lothar.com>"**20110221061004
13938 Ignore-this: a693b201d31125b391cebe0412ddd027
13939] {
13940hunk ./src/allmydata/mutable/publish.py 203
13941         self._encprivkey = self._node.get_encprivkey()
13942 
13943         sb = self._storage_broker
13944-        full_peerlist = sb.get_servers_for_index(self._storage_index)
13945+        full_peerlist = [(s.get_serverid(), s.get_rref())
13946+                         for s in sb.get_servers_for_psi(self._storage_index)]
13947         self.full_peerlist = full_peerlist # for use later, immutable
13948         self.bad_peers = set() # peerids who have errbacked/refused requests
13949 
13950hunk ./src/allmydata/test/test_mutable.py 2538
13951             # for either a block and salt or for hashes, either of which
13952             # will exercise the error handling code.
13953             killer = FirstServerGetsKilled()
13954-            for (serverid, ss) in nm.storage_broker.get_all_servers():
13955-                ss.post_call_notifier = killer.notify
13956+            for s in nm.storage_broker.get_connected_servers():
13957+                s.get_rref().post_call_notifier = killer.notify
13958             ver = servermap.best_recoverable_version()
13959             assert ver
13960             return self._node.download_version(servermap, ver)
13961}
13962[mutable/filenode: Clean up servermap handling in MutableFileVersion
13963Kevan Carstensen <kevan@isnotajoke.com>**20110226010433
13964 Ignore-this: 2257c9f65502098789f5ea355b94f130
13965 
13966 We want to update the servermap before attempting to modify a file,
13967 which we now do. This introduced code duplication, which was addressed
13968 by refactoring the servermap update into its own method, and then
13969 eliminating duplicate servermap updates throughout the
13970 MutableFileVersion.
13971] {
13972hunk ./src/allmydata/mutable/filenode.py 19
13973 from allmydata.mutable.publish import Publish, MutableData,\
13974                                       DEFAULT_MAX_SEGMENT_SIZE, \
13975                                       TransformingUploadable
13976-from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
13977+from allmydata.mutable.common import MODE_READ, MODE_WRITE, MODE_CHECK, UnrecoverableFileError, \
13978      ResponseCache, UncoordinatedWriteError
13979 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
13980 from allmydata.mutable.retrieve import Retrieve
13981hunk ./src/allmydata/mutable/filenode.py 807
13982         a little bit.
13983         """
13984         log.msg("doing modify")
13985-        d = self._modify_once(modifier, first_time)
13986+        if first_time:
13987+            d = self._update_servermap()
13988+        else:
13989+            # We ran into trouble; do MODE_CHECK so we're a little more
13990+            # careful on subsequent tries.
13991+            d = self._update_servermap(mode=MODE_CHECK)
13992+
13993+        d.addCallback(lambda ignored:
13994+            self._modify_once(modifier, first_time))
13995         def _retry(f):
13996             f.trap(UncoordinatedWriteError)
13997hunk ./src/allmydata/mutable/filenode.py 818
13998+            # Uh oh, it broke. We're allowed to trust the servermap for our
13999+            # first try, but after that we need to update it. It's
14000+            # possible that we've failed due to a race with another
14001+            # uploader, and if the race is to converge correctly, we
14002+            # need to know about that upload.
14003             d2 = defer.maybeDeferred(backoffer, self, f)
14004             d2.addCallback(lambda ignored:
14005                            self._modify_and_retry(modifier,
14006hunk ./src/allmydata/mutable/filenode.py 837
14007         I attempt to apply a modifier to the contents of the mutable
14008         file.
14009         """
14010-        # XXX: This is wrong -- we could get more servers if we updated
14011-        # in MODE_ANYTHING and possibly MODE_CHECK. Probably we want to
14012-        # assert that the last update wasn't MODE_READ
14013-        assert self._servermap.last_update_mode == MODE_WRITE
14014+        assert self._servermap.last_update_mode != MODE_READ
14015 
14016         # download_to_data is serialized, so we have to call this to
14017         # avoid deadlock.
14018hunk ./src/allmydata/mutable/filenode.py 1076
14019 
14020         # Now ask for the servermap to be updated in MODE_WRITE with
14021         # this update range.
14022-        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14023-                             self._servermap,
14024-                             mode=MODE_WRITE,
14025-                             update_range=(start_segment, end_segment))
14026-        return u.update()
14027+        return self._update_servermap(update_range=(start_segment,
14028+                                                    end_segment))
14029 
14030 
14031     def _decode_and_decrypt_segments(self, ignored, data, offset):
14032hunk ./src/allmydata/mutable/filenode.py 1135
14033                                    segments_and_bht[1])
14034         p = Publish(self._node, self._storage_broker, self._servermap)
14035         return p.update(u, offset, segments_and_bht[2], self._version)
14036+
14037+
14038+    def _update_servermap(self, mode=MODE_WRITE, update_range=None):
14039+        """
14040+        I update the servermap. I return a Deferred that fires when the
14041+        servermap update is done.
14042+        """
14043+        if update_range:
14044+            u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14045+                                 self._servermap,
14046+                                 mode=mode,
14047+                                 update_range=update_range)
14048+        else:
14049+            u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14050+                                 self._servermap,
14051+                                 mode=mode)
14052+        return u.update()
14053}
14054[web: Use the string "replace" to trigger whole-file replacement when processing an offset parameter.
14055Kevan Carstensen <kevan@isnotajoke.com>**20110227231643
14056 Ignore-this: 5bbf0b90d68efe20d4c531bb98a8321a
14057] {
14058hunk ./docs/frontends/webapi.rst 360
14059  To use the /uri/$FILECAP form, $FILECAP must be a write-cap for a mutable file.
14060 
14061  In the /uri/$DIRCAP/[SUBDIRS../]FILENAME form, if the target file is a
14062- writeable mutable file, that file's contents will be overwritten in-place. If
14063- it is a read-cap for a mutable file, an error will occur. If it is an
14064- immutable file, the old file will be discarded, and a new one will be put in
14065- its place. If the target file is a writable mutable file, you may also
14066- specify an "offset" parameter -- a byte offset that determines where in
14067- the mutable file the data from the HTTP request body is placed. This
14068- operation is relatively efficient for MDMF mutable files, and is
14069- relatively inefficient (but still supported) for SDMF mutable files.
14070+ writeable mutable file, that file's contents will be overwritten
14071+ in-place. If it is a read-cap for a mutable file, an error will occur.
14072+ If it is an immutable file, the old file will be discarded, and a new
14073+ one will be put in its place. If the target file is a writable mutable
14074+ file, you may also specify an "offset" parameter -- a byte offset that
14075+ determines where in the mutable file the data from the HTTP request
14076+ body is placed. This operation is relatively efficient for MDMF mutable
14077+ files, and is relatively inefficient (but still supported) for SDMF
14078+ mutable files. If no offset parameter is specified, then the entire
14079+ file is replaced with the data from the HTTP request body. For an
14080+ immutable file, the "offset" parameter is not valid.
14081 
14082  When creating a new file, if "mutable=true" is in the query arguments, the
14083  operation will create a mutable file instead of an immutable one.
14084hunk ./src/allmydata/test/test_web.py 3187
14085             self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
14086         return d
14087 
14088+    def test_PUT_update_at_invalid_offset(self):
14089+        file_contents = "test file" * 100000 # about 900 KiB
14090+        d = self.PUT("/uri?mutable=true", file_contents)
14091+        def _then(filecap):
14092+            self.filecap = filecap
14093+        d.addCallback(_then)
14094+        # Negative offsets should cause an error.
14095+        d.addCallback(lambda ignored:
14096+            self.shouldHTTPError("test mutable invalid offset negative",
14097+                                 400, "Bad Request",
14098+                                 "Invalid offset",
14099+                                 self.PUT,
14100+                                 "/uri/%s?offset=-1" % self.filecap,
14101+                                 "foo"))
14102+        return d
14103 
14104     def test_PUT_update_at_offset_immutable(self):
14105         file_contents = "Test file" * 100000
14106hunk ./src/allmydata/web/common.py 55
14107     # message? Since this call is going to be used by programmers and
14108     # their tools rather than users (through the wui), it is not
14109     # inconsistent to return that, I guess.
14110-    offset = int(offset)
14111-    return offset
14112+    return int(offset)
14113 
14114 
14115 def get_root(ctx_or_req):
14116hunk ./src/allmydata/web/filenode.py 219
14117         req = IRequest(ctx)
14118         t = get_arg(req, "t", "").strip()
14119         replace = parse_replace_arg(get_arg(req, "replace", "true"))
14120-        offset = parse_offset_arg(get_arg(req, "offset", -1))
14121+        offset = parse_offset_arg(get_arg(req, "offset", False))
14122 
14123         if not t:
14124hunk ./src/allmydata/web/filenode.py 222
14125-            if self.node.is_mutable() and offset >= 0:
14126-                return self.update_my_contents(req, offset)
14127-
14128-            elif self.node.is_mutable():
14129-                return self.replace_my_contents(req)
14130             if not replace:
14131                 # this is the early trap: if someone else modifies the
14132                 # directory while we're uploading, the add_file(overwrite=)
14133hunk ./src/allmydata/web/filenode.py 227
14134                 # call in replace_me_with_a_child will do the late trap.
14135                 raise ExistingChildError()
14136-            if offset >= 0:
14137-                raise WebError("PUT to a file: append operation invoked "
14138-                               "on an immutable cap")
14139 
14140hunk ./src/allmydata/web/filenode.py 228
14141+            if self.node.is_mutable():
14142+                if offset == False:
14143+                    return self.replace_my_contents(req)
14144+
14145+                if offset >= 0:
14146+                    return self.update_my_contents(req, offset)
14147+
14148+                raise WebError("PUT to a mutable file: Invalid offset")
14149+
14150+            else:
14151+                if offset != False:
14152+                    raise WebError("PUT to a file: append operation invoked "
14153+                                   "on an immutable cap")
14154+
14155+                assert self.parentnode and self.name
14156+                return self.replace_me_with_a_child(req, self.client, replace)
14157 
14158hunk ./src/allmydata/web/filenode.py 245
14159-            assert self.parentnode and self.name
14160-            return self.replace_me_with_a_child(req, self.client, replace)
14161         if t == "uri":
14162             if not replace:
14163                 raise ExistingChildError()
14164}
14165[docs/configuration.rst: fix more conflicts between #393 and trunk
14166Kevan Carstensen <kevan@isnotajoke.com>**20110228003426
14167 Ignore-this: 7917effdeecab00d634a06f1df8fe2cf
14168] {
14169replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
14170hunk ./docs/configuration.rst 324
14171     (Mutable files use a different share placement algorithm that does not
14172     currently consider this parameter.)
14173 
14174+``mutable.format = sdmf or mdmf``
14175+
14176+    This value tells Tahoe-LAFS what the default mutable file format should
14177+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
14178+    in the old SDMF format. This is desirable for clients that operate on
14179+    grids where some peers run older versions of Tahoe-LAFS, as these older
14180+    versions cannot read the new MDMF mutable file format. If
14181+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
14182+    the new MDMF format, which supports efficient in-place modification and
14183+    streaming downloads. You can overwrite this value using a special
14184+    mutable-type parameter in the webapi. If you do not specify a value here,
14185+    Tahoe-LAFS will use SDMF for all newly-created mutable files.
14186+
14187+    Note that this parameter only applies to mutable files. Mutable
14188+    directories, which are stored as mutable files, are not controlled by
14189+    this parameter and will always use SDMF. We may revisit this decision
14190+    in future versions of Tahoe-LAFS.
14191+
14192+
14193+Frontend Configuration
14194+======================
14195+
14196+The Tahoe client process can run a variety of frontend file-access protocols.
14197+You will use these to create and retrieve files from the virtual filesystem.
14198+Configuration details for each are documented in the following
14199+protocol-specific guides:
14200+
14201+HTTP
14202+
14203+    Tahoe runs a webserver by default on port 3456. This interface provides a
14204+    human-oriented "WUI", with pages to create, modify, and browse
14205+    directories and files, as well as a number of pages to check on the
14206+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
14207+    with a REST-ful HTTP interface that can be used by other programs
14208+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
14209+    details, and the ``web.port`` and ``web.static`` config variables above.
14210+    The `<frontends/download-status.rst>`_ document also describes a few WUI
14211+    status pages.
14212+
14213+CLI
14214+
14215+    The main "bin/tahoe" executable includes subcommands for manipulating the
14216+    filesystem, uploading/downloading files, and creating/running Tahoe
14217+    nodes. See `<frontends/CLI.rst>`_ for details.
14218+
14219+FTP, SFTP
14220+
14221+    Tahoe can also run both FTP and SFTP servers, and map a username/password
14222+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
14223+    for instructions on configuring these services, and the ``[ftpd]`` and
14224+    ``[sftpd]`` sections of ``tahoe.cfg``.
14225+
14226 
14227 Storage Server Configuration
14228 ============================
14229hunk ./docs/configuration.rst 436
14230     `<garbage-collection.rst>`_ for full details.
14231 
14232 
14233-shares.needed = (int, optional) aka "k", default 3
14234-shares.total = (int, optional) aka "N", N >= k, default 10
14235-shares.happy = (int, optional) 1 <= happy <= N, default 7
14236-
14237- These three values set the default encoding parameters. Each time a new file
14238- is uploaded, erasure-coding is used to break the ciphertext into separate
14239- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
14240- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
14241- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
14242- Setting k to 1 is equivalent to simple replication (uploading N copies of
14243- the file).
14244-
14245- These values control the tradeoff between storage overhead, performance, and
14246- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
14247- backend storage space (the actual value will be a bit more, because of other
14248- forms of overhead). Up to N-k shares can be lost before the file becomes
14249- unrecoverable, so assuming there are at least N servers, up to N-k servers
14250- can be offline without losing the file. So large N/k ratios are more
14251- reliable, and small N/k ratios use less disk space. Clearly, k must never be
14252- smaller than N.
14253-
14254- Large values of N will slow down upload operations slightly, since more
14255- servers must be involved, and will slightly increase storage overhead due to
14256- the hash trees that are created. Large values of k will cause downloads to
14257- be marginally slower, because more servers must be involved. N cannot be
14258- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe-LAFS
14259- uses.
14260-
14261- shares.happy allows you control over the distribution of your immutable file.
14262- For a successful upload, shares are guaranteed to be initially placed on
14263- at least 'shares.happy' distinct servers, the correct functioning of any
14264- k of which is sufficient to guarantee the availability of the uploaded file.
14265- This value should not be larger than the number of servers on your grid.
14266-
14267- A value of shares.happy <= k is allowed, but does not provide any redundancy
14268- if some servers fail or lose shares.
14269-
14270- (Mutable files use a different share placement algorithm that does not
14271-  consider this parameter.)
14272-
14273-
14274-== Storage Server Configuration ==
14275-
14276-[storage]
14277-enabled = (boolean, optional)
14278-
14279- If this is True, the node will run a storage server, offering space to other
14280- clients. If it is False, the node will not run a storage server, meaning
14281- that no shares will be stored on this node. Use False this for clients who
14282- do not wish to provide storage service. The default value is True.
14283-
14284-readonly = (boolean, optional)
14285-
14286- If True, the node will run a storage server but will not accept any shares,
14287- making it effectively read-only. Use this for storage servers which are
14288- being decommissioned: the storage/ directory could be mounted read-only,
14289- while shares are moved to other servers. Note that this currently only
14290- affects immutable shares. Mutable shares (used for directories) will be
14291- written and modified anyway. See ticket #390 for the current status of this
14292- bug. The default value is False.
14293-
14294-reserved_space = (str, optional)
14295-
14296- If provided, this value defines how much disk space is reserved: the storage
14297- server will not accept any share which causes the amount of free disk space
14298- to drop below this value. (The free space is measured by a call to statvfs(2)
14299- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
14300- user account under which the storage server runs.)
14301-
14302- This string contains a number, with an optional case-insensitive scale
14303- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
14304- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
14305- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
14306-
14307-expire.enabled =
14308-expire.mode =
14309-expire.override_lease_duration =
14310-expire.cutoff_date =
14311-expire.immutable =
14312-expire.mutable =
14313-
14314- These settings control garbage-collection, in which the server will delete
14315- shares that no longer have an up-to-date lease on them. Please see the
14316- neighboring "garbage-collection.txt" document for full details.
14317-
14318-
14319-== Running A Helper ==
14320+Running A Helper
14321+================
14322 
14323 A "helper" is a regular client node that also offers the "upload helper"
14324 service.
14325}
14326[mutable/layout: remove references to the salt hash tree.
14327Kevan Carstensen <kevan@isnotajoke.com>**20110228010637
14328 Ignore-this: b3b2963ba4d0b42c78b6bba219d4deb5
14329] {
14330hunk ./src/allmydata/mutable/layout.py 577
14331     # 99          8           The offset of the EOF
14332     #
14333     # followed by salts and share data, the encrypted private key, the
14334-    # block hash tree, the salt hash tree, the share hash chain, a
14335-    # signature over the first eight fields, and a verification key.
14336+    # block hash tree, the share hash chain, a signature over the first
14337+    # eight fields, and a verification key.
14338     #
14339     # The checkstring is the first three fields -- the version number,
14340     # sequence number, root hash and root salt hash. This is consistent
14341hunk ./src/allmydata/mutable/layout.py 628
14342     #      calculate the offset for the share hash chain, and fill that
14343     #      into the offsets table.
14344     #
14345-    #   4: At the same time, we're in a position to upload the salt hash
14346-    #      tree. This is a Merkle tree over all of the salts. We use a
14347-    #      Merkle tree so that we can validate each block,salt pair as
14348-    #      we download them later. We do this using
14349-    #
14350-    #        put_salthashes(salt_hash_tree)
14351-    #
14352-    #      When you do this, I automatically put the root of the tree
14353-    #      (the hash at index 0 of the list) in its appropriate slot in
14354-    #      the signed prefix of the share.
14355-    #
14356-    #   5: We're now in a position to upload the share hash chain for
14357+    #   4: We're now in a position to upload the share hash chain for
14358     #      a share. Do that with something like:
14359     #     
14360     #        put_sharehashes(share_hash_chain)
14361hunk ./src/allmydata/mutable/layout.py 639
14362     #      The root of this tree will be put explicitly in the next
14363     #      step.
14364     #
14365-    #      TODO: Why? Why not just include it in the tree here?
14366-    #
14367-    #   6: Before putting the signature, we must first put the
14368+    #   5: Before putting the signature, we must first put the
14369     #      root_hash. Do this with:
14370     #
14371     #        put_root_hash(root_hash).
14372hunk ./src/allmydata/mutable/layout.py 872
14373             raise LayoutInvalid("I was given the wrong size block to write")
14374 
14375         # We want to write at len(MDMFHEADER) + segnum * block_size.
14376-
14377         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
14378         data = salt + data
14379 
14380hunk ./src/allmydata/mutable/layout.py 889
14381         # tree is written, since that could cause the private key to run
14382         # into the block hash tree. Before it writes the block hash
14383         # tree, the block hash tree writing method writes the offset of
14384-        # the salt hash tree. So that's a good indicator of whether or
14385+        # the share hash chain. So that's a good indicator of whether or
14386         # not the block hash tree has been written.
14387         if "share_hash_chain" in self._offsets:
14388             raise LayoutInvalid("You must write this before the block hash tree")
14389hunk ./src/allmydata/mutable/layout.py 907
14390         The encrypted private key must be queued before the block hash
14391         tree, since we need to know how large it is to know where the
14392         block hash tree should go. The block hash tree must be put
14393-        before the salt hash tree, since its size determines the
14394+        before the share hash chain, since its size determines the
14395         offset of the share hash chain.
14396         """
14397         assert self._offsets
14398hunk ./src/allmydata/mutable/layout.py 932
14399         I queue a write vector to put the share hash chain in my
14400         argument onto the remote server.
14401 
14402-        The salt hash tree must be queued before the share hash chain,
14403-        since we need to know where the salt hash tree ends before we
14404+        The block hash tree must be queued before the share hash chain,
14405+        since we need to know where the block hash tree ends before we
14406         can know where the share hash chain starts. The share hash chain
14407         must be put before the signature, since the length of the packed
14408         share hash chain determines the offset of the signature. Also,
14409hunk ./src/allmydata/mutable/layout.py 937
14410-        semantically, you must know what the root of the salt hash tree
14411+        semantically, you must know what the root of the block hash tree
14412         is before you can generate a valid signature.
14413         """
14414         assert isinstance(sharehashes, dict)
14415hunk ./src/allmydata/mutable/layout.py 942
14416         if "share_hash_chain" not in self._offsets:
14417-            raise LayoutInvalid("You need to put the salt hash tree before "
14418+            raise LayoutInvalid("You need to put the block hash tree before "
14419                                 "you can put the share hash chain")
14420         # The signature comes after the share hash chain. If the
14421         # signature has already been written, we must not write another
14422}
14423[test_mutable.py: add test to exercise fencepost bug
14424warner@lothar.com**20110228021056
14425 Ignore-this: d2f9cf237ce6db42fb250c8ad71a4fc3
14426] {
14427hunk ./src/allmydata/test/test_mutable.py 2
14428 
14429-import os
14430+import os, re
14431 from cStringIO import StringIO
14432 from twisted.trial import unittest
14433 from twisted.internet import defer, reactor
14434hunk ./src/allmydata/test/test_mutable.py 2931
14435         self.set_up_grid()
14436         self.c = self.g.clients[0]
14437         self.nm = self.c.nodemaker
14438-        self.data = "test data" * 100000 # about 900 KiB; MDMF
14439+        self.data = "testdata " * 100000 # about 900 KiB; MDMF
14440         self.small_data = "test data" * 10 # about 90 B; SDMF
14441         return self.do_upload()
14442 
14443hunk ./src/allmydata/test/test_mutable.py 2981
14444             self.failUnlessEqual(results, new_data))
14445         return d
14446 
14447+    def test_replace_segstart1(self):
14448+        offset = 128*1024+1
14449+        new_data = "NNNN"
14450+        expected = self.data[:offset]+new_data+self.data[offset+4:]
14451+        d = self.mdmf_node.get_best_mutable_version()
14452+        d.addCallback(lambda mv:
14453+            mv.update(MutableData(new_data), offset))
14454+        d.addCallback(lambda ignored:
14455+            self.mdmf_node.download_best_version())
14456+        def _check(results):
14457+            if results != expected:
14458+                print
14459+                print "got: %s ... %s" % (results[:20], results[-20:])
14460+                print "exp: %s ... %s" % (expected[:20], expected[-20:])
14461+                self.fail("results != expected")
14462+        d.addCallback(_check)
14463+        return d
14464+
14465+    def _check_differences(self, got, expected):
14466+        # displaying arbitrary file corruption is tricky for a
14467+        # 1MB file of repeating data,, so look for likely places
14468+        # with problems and display them separately
14469+        gotmods = [mo.span() for mo in re.finditer('([A-Z]+)', got)]
14470+        expmods = [mo.span() for mo in re.finditer('([A-Z]+)', expected)]
14471+        gotspans = ["%d:%d=%s" % (start,end,got[start:end])
14472+                    for (start,end) in gotmods]
14473+        expspans = ["%d:%d=%s" % (start,end,expected[start:end])
14474+                    for (start,end) in expmods]
14475+        #print "expecting: %s" % expspans
14476+
14477+        SEGSIZE = 128*1024
14478+        if got != expected:
14479+            print "differences:"
14480+            for segnum in range(len(expected)//SEGSIZE):
14481+                start = segnum * SEGSIZE
14482+                end = (segnum+1) * SEGSIZE
14483+                got_ends = "%s .. %s" % (got[start:start+20], got[end-20:end])
14484+                exp_ends = "%s .. %s" % (expected[start:start+20], expected[end-20:end])
14485+                if got_ends != exp_ends:
14486+                    print "expected[%d]: %s" % (start, exp_ends)
14487+                    print "got     [%d]: %s" % (start, got_ends)
14488+            if expspans != gotspans:
14489+                print "expected: %s" % expspans
14490+                print "got     : %s" % gotspans
14491+            open("EXPECTED","wb").write(expected)
14492+            open("GOT","wb").write(got)
14493+            print "wrote data to EXPECTED and GOT"
14494+            self.fail("didn't get expected data")
14495+
14496+
14497+    def test_replace_locations(self):
14498+        # exercise fencepost conditions
14499+        expected = self.data
14500+        SEGSIZE = 128*1024
14501+        suspects = range(SEGSIZE-3, SEGSIZE+1)+range(2*SEGSIZE-3, 2*SEGSIZE+1)
14502+        letters = iter("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
14503+        d = defer.succeed(None)
14504+        for offset in suspects:
14505+            new_data = letters.next()*2 # "AA", then "BB", etc
14506+            expected = expected[:offset]+new_data+expected[offset+2:]
14507+            d.addCallback(lambda ign:
14508+                          self.mdmf_node.get_best_mutable_version())
14509+            def _modify(mv, offset=offset, new_data=new_data):
14510+                # close over 'offset','new_data'
14511+                md = MutableData(new_data)
14512+                return mv.update(md, offset)
14513+            d.addCallback(_modify)
14514+            d.addCallback(lambda ignored:
14515+                          self.mdmf_node.download_best_version())
14516+            d.addCallback(self._check_differences, expected)
14517+        return d
14518+
14519 
14520     def test_replace_and_extend(self):
14521         # We should be able to replace data in the middle of a mutable
14522}
14523[mutable/publish: account for offsets on segment boundaries.
14524Kevan Carstensen <kevan@isnotajoke.com>**20110228083327
14525 Ignore-this: c8758a0580fcc15a22c2f8582d758a6b
14526] {
14527hunk ./src/allmydata/mutable/filenode.py 17
14528 from pycryptopp.cipher.aes import AES
14529 
14530 from allmydata.mutable.publish import Publish, MutableData,\
14531-                                      DEFAULT_MAX_SEGMENT_SIZE, \
14532                                       TransformingUploadable
14533 from allmydata.mutable.common import MODE_READ, MODE_WRITE, MODE_CHECK, UnrecoverableFileError, \
14534      ResponseCache, UncoordinatedWriteError
14535hunk ./src/allmydata/mutable/filenode.py 1058
14536         # appending data to the file.
14537         assert offset <= self.get_size()
14538 
14539+        segsize = self._version[3]
14540         # We'll need the segment that the data starts in, regardless of
14541         # what we'll do later.
14542hunk ./src/allmydata/mutable/filenode.py 1061
14543-        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
14544+        start_segment = mathutil.div_ceil(offset, segsize)
14545         start_segment -= 1
14546 
14547         # We only need the end segment if the data we append does not go
14548hunk ./src/allmydata/mutable/filenode.py 1069
14549         end_segment = start_segment
14550         if offset + data.get_size() < self.get_size():
14551             end_data = offset + data.get_size()
14552-            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
14553+            end_segment = mathutil.div_ceil(end_data, segsize)
14554             end_segment -= 1
14555         self._start_segment = start_segment
14556         self._end_segment = end_segment
14557hunk ./src/allmydata/mutable/publish.py 551
14558                                                   segment_size)
14559             self.starting_segment = mathutil.div_ceil(offset,
14560                                                       segment_size)
14561-            self.starting_segment -= 1
14562+            if offset % segment_size != 0:
14563+                self.starting_segment -= 1
14564             if offset == 0:
14565                 self.starting_segment = 0
14566 
14567}
14568[tahoe-put: raise UsageError when given a nonsensical mutable type, move option validation code to the option parser.
14569Kevan Carstensen <kevan@isnotajoke.com>**20110301030807
14570 Ignore-this: 2dc19d8bd741842eff458ca553d0bf2a
14571] {
14572hunk ./src/allmydata/scripts/cli.py 179
14573         if self.from_file == u"-":
14574             self.from_file = None
14575 
14576+        if self['mutable-type'] and self['mutable-type'] not in ("sdmf", "mdmf"):
14577+            raise usage.UsageError("%s is an invalid format" % self['mutable-type'])
14578+
14579+
14580     def getSynopsis(self):
14581         return "Usage:  %s put LOCAL_FILE REMOTE_FILE" % (os.path.basename(sys.argv[0]),)
14582 
14583hunk ./src/allmydata/scripts/tahoe_put.py 33
14584     stdout = options.stdout
14585     stderr = options.stderr
14586 
14587-    if mutable_type and mutable_type not in ('sdmf', 'mdmf'):
14588-        # Don't try to pass unsupported types to the webapi
14589-        print >>stderr, "error: %s is an invalid format" % mutable_type
14590-        return 1
14591-
14592     if nodeurl[-1] != "/":
14593         nodeurl += "/"
14594     if to_file:
14595hunk ./src/allmydata/test/test_cli.py 1008
14596         return d
14597 
14598     def test_mutable_type_invalid_format(self):
14599-        self.basedir = "cli/Put/mutable_type_invalid_format"
14600-        self.set_up_grid()
14601-        data = "data" * 100000
14602-        fn1 = os.path.join(self.basedir, "data")
14603-        fileutil.write(fn1, data)
14604-        d = self.do_cli("put", "--mutable", "--mutable-type=ldmf", fn1)
14605-        def _check_failure((rc, out, err)):
14606-            self.failIfEqual(rc, 0)
14607-            self.failUnlessIn("invalid", err)
14608-        d.addCallback(_check_failure)
14609-        return d
14610+        o = cli.PutOptions()
14611+        self.failUnlessRaises(usage.UsageError,
14612+                              o.parseOptions,
14613+                              ["--mutable", "--mutable-type=ldmf"])
14614 
14615     def test_put_with_nonexistent_alias(self):
14616         # when invoked with an alias that doesn't exist, 'tahoe put'
14617}
14618[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.
14619Kevan Carstensen <kevan@isnotajoke.com>**20110305010858
14620 Ignore-this: 14b7550ca95ce423c9b0b7f6f14ffd2f
14621] {
14622hunk ./src/allmydata/test/test_mutable.py 2981
14623             self.failUnlessEqual(results, new_data))
14624         return d
14625 
14626+    def test_replace_beginning(self):
14627+        # We should be able to replace data at the beginning of the file
14628+        # without truncating the file
14629+        B = "beginning"
14630+        new_data = B + self.data[len(B):]
14631+        d = self.mdmf_node.get_best_mutable_version()
14632+        d.addCallback(lambda mv: mv.update(MutableData(B), 0))
14633+        d.addCallback(lambda ignored: self.mdmf_node.download_best_version())
14634+        d.addCallback(lambda results: self.failUnlessEqual(results, new_data))
14635+        return d
14636+
14637     def test_replace_segstart1(self):
14638         offset = 128*1024+1
14639         new_data = "NNNN"
14640hunk ./src/allmydata/test/test_web.py 3185
14641         d.addCallback(_get_data)
14642         d.addCallback(lambda results:
14643             self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
14644+        # and try replacing the beginning of the file
14645+        d.addCallback(lambda ignored:
14646+            self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
14647+        d.addCallback(_get_data)
14648+        d.addCallback(lambda results:
14649+            self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
14650         return d
14651 
14652     def test_PUT_update_at_invalid_offset(self):
14653hunk ./src/allmydata/web/common.py 55
14654     # message? Since this call is going to be used by programmers and
14655     # their tools rather than users (through the wui), it is not
14656     # inconsistent to return that, I guess.
14657-    return int(offset)
14658+    if offset is not None:
14659+        offset = int(offset)
14660+
14661+    return offset
14662 
14663 
14664 def get_root(ctx_or_req):
14665hunk ./src/allmydata/web/filenode.py 219
14666         req = IRequest(ctx)
14667         t = get_arg(req, "t", "").strip()
14668         replace = parse_replace_arg(get_arg(req, "replace", "true"))
14669-        offset = parse_offset_arg(get_arg(req, "offset", False))
14670+        offset = parse_offset_arg(get_arg(req, "offset", None))
14671 
14672         if not t:
14673             if not replace:
14674hunk ./src/allmydata/web/filenode.py 229
14675                 raise ExistingChildError()
14676 
14677             if self.node.is_mutable():
14678-                if offset == False:
14679+                if offset is None:
14680                     return self.replace_my_contents(req)
14681 
14682                 if offset >= 0:
14683hunk ./src/allmydata/web/filenode.py 238
14684                 raise WebError("PUT to a mutable file: Invalid offset")
14685 
14686             else:
14687-                if offset != False:
14688+                if offset is not None:
14689                     raise WebError("PUT to a file: append operation invoked "
14690                                    "on an immutable cap")
14691 
14692}
14693[mutable/filenode: remove incorrect comments about segment boundaries
14694Kevan Carstensen <kevan@isnotajoke.com>**20110307081713
14695 Ignore-this: 7008644c3d9588815000a86edbf9c568
14696] {
14697hunk ./src/allmydata/mutable/filenode.py 1001
14698         offset. I return a Deferred that fires when this has been
14699         completed.
14700         """
14701-        # We have two cases here:
14702-        # 1. The new data will add few enough segments so that it does
14703-        #    not cross into the next power-of-two boundary.
14704-        # 2. It doesn't.
14705-        #
14706-        # In the former case, we can modify the file in place. In the
14707-        # latter case, we need to re-encode the file.
14708         new_size = data.get_size() + offset
14709         old_size = self.get_size()
14710         segment_size = self._version[3]
14711hunk ./src/allmydata/mutable/filenode.py 1011
14712         log.msg("got %d old segments, %d new segments" % \
14713                         (num_old_segments, num_new_segments))
14714 
14715-        # We also do a whole file re-encode if the file is an SDMF file.
14716+        # We do a whole file re-encode if the file is an SDMF file.
14717         if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
14718             log.msg("doing re-encode instead of in-place update")
14719             return self._do_modify_update(data, offset)
14720hunk ./src/allmydata/mutable/filenode.py 1016
14721 
14722+        # Otherwise, we can replace just the parts that are changing.
14723         log.msg("updating in place")
14724         d = self._do_update_update(data, offset)
14725         d.addCallback(self._decode_and_decrypt_segments, data, offset)
14726}
14727[mutable: use integer division where appropriate
14728Kevan Carstensen <kevan@isnotajoke.com>**20110307082229
14729 Ignore-this: a8767e89d919c9f2a5d5fef3953d53f9
14730] {
14731hunk ./src/allmydata/mutable/filenode.py 1055
14732         segsize = self._version[3]
14733         # We'll need the segment that the data starts in, regardless of
14734         # what we'll do later.
14735-        start_segment = mathutil.div_ceil(offset, segsize)
14736-        start_segment -= 1
14737+        start_segment = offset // segsize
14738 
14739         # We only need the end segment if the data we append does not go
14740         # beyond the current end-of-file.
14741hunk ./src/allmydata/mutable/filenode.py 1062
14742         end_segment = start_segment
14743         if offset + data.get_size() < self.get_size():
14744             end_data = offset + data.get_size()
14745-            end_segment = mathutil.div_ceil(end_data, segsize)
14746-            end_segment -= 1
14747+            end_segment = end_data // segsize
14748+
14749         self._start_segment = start_segment
14750         self._end_segment = end_segment
14751 
14752hunk ./src/allmydata/mutable/publish.py 547
14753 
14754         # Calculate the starting segment for the upload.
14755         if segment_size:
14756+            # We use div_ceil instead of integer division here because
14757+            # it is semantically correct.
14758+            # If datalength isn't an even multiple of segment_size, but
14759+            # is larger than segment_size, datalength // segment_size
14760+            # will be the largest number such that num <= datalength and
14761+            # num % segment_size == 0. But that's not what we want,
14762+            # because it ignores the extra data. div_ceil will give us
14763+            # the right number of segments for the data that we're
14764+            # given.
14765             self.num_segments = mathutil.div_ceil(self.datalength,
14766                                                   segment_size)
14767hunk ./src/allmydata/mutable/publish.py 558
14768-            self.starting_segment = mathutil.div_ceil(offset,
14769-                                                      segment_size)
14770-            if offset % segment_size != 0:
14771-                self.starting_segment -= 1
14772-            if offset == 0:
14773-                self.starting_segment = 0
14774+
14775+            self.starting_segment = offset // segment_size
14776 
14777         else:
14778             self.num_segments = 0
14779hunk ./src/allmydata/mutable/publish.py 604
14780         self.end_segment = self.num_segments - 1
14781         # Now figure out where the last segment should be.
14782         if self.data.get_size() != self.datalength:
14783+            # We're updating a few segments in the middle of a mutable
14784+            # file, so we don't want to republish the whole thing.
14785+            # (we don't have enough data to do that even if we wanted
14786+            # to)
14787             end = self.data.get_size()
14788hunk ./src/allmydata/mutable/publish.py 609
14789-            self.end_segment = mathutil.div_ceil(end,
14790-                                                 segment_size)
14791-            self.end_segment -= 1
14792+            self.end_segment = end // segment_size
14793+            if end % segment_size == 0:
14794+                self.end_segment -= 1
14795+
14796         self.log("got start segment %d" % self.starting_segment)
14797         self.log("got end segment %d" % self.end_segment)
14798 
14799}
14800[mutable/layout.py: reorder on-disk format to aput variable-length fields at the end of the share, after a predictably long preamble
14801Kevan Carstensen <kevan@isnotajoke.com>**20110501224125
14802 Ignore-this: 8b2c5d29b8984dfe675c1a2ada5205cf
14803] {
14804hunk ./src/allmydata/mutable/layout.py 539
14805                                      self._readvs)
14806 
14807 
14808-MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
14809+MDMFHEADER = ">BQ32sBBQQ QQQQQQQQ"
14810 MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
14811 MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
14812 MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
14813hunk ./src/allmydata/mutable/layout.py 545
14814 MDMFCHECKSTRING = ">BQ32s"
14815 MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
14816-MDMFOFFSETS = ">QQQQQQ"
14817+MDMFOFFSETS = ">QQQQQQQQ"
14818 MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
14819hunk ./src/allmydata/mutable/layout.py 547
14820+# XXX Fix this.
14821+PRIVATE_KEY_SIZE = 2000
14822+SIGNATURE_SIZE = 10000
14823+VERIFICATION_KEY_SIZE = 2000
14824+# We know we won't ever have more than 256 shares.
14825+# XXX: This, too, can be
14826+SHARE_HASH_CHAIN_SIZE = HASH_SIZE * 256
14827 
14828 class MDMFSlotWriteProxy:
14829     implements(IMutableSlotWriter)
14830hunk ./src/allmydata/mutable/layout.py 577
14831     # 51          8           The data length of the original plaintext
14832     #-- end signed part --
14833     # 59          8           The offset of the encrypted private key
14834-    # 83          8           The offset of the signature
14835-    # 91          8           The offset of the verification key
14836-    # 67          8           The offset of the block hash tree
14837-    # 75          8           The offset of the share hash chain
14838-    # 99          8           The offset of the EOF
14839-    #
14840-    # followed by salts and share data, the encrypted private key, the
14841-    # block hash tree, the share hash chain, a signature over the first
14842-    # eight fields, and a verification key.
14843+    # 67          8           The offset of the signature
14844+    # 75          8           The offset of the verification key
14845+    # 83          8           The offset of the end of the v. key.
14846+    # 92          8           The offset of the share data
14847+    # 100         8           The offset of the block hash tree
14848+    # 108         8           The offset of the share hash chain
14849+    # 116         8           The offset of EOF
14850     #
14851hunk ./src/allmydata/mutable/layout.py 585
14852+    # followed by the encrypted private key, signature, verification
14853+    # key, share hash chain, data, and block hash tree. We order the
14854+    # fields that way to make smart downloaders -- downloaders which
14855+    # prempetively read a big part of the share -- possible.
14856+    #
14857     # The checkstring is the first three fields -- the version number,
14858     # sequence number, root hash and root salt hash. This is consistent
14859     # in meaning to what we have with SDMF files, except now instead of
14860hunk ./src/allmydata/mutable/layout.py 792
14861         data_size += self._tail_block_size
14862         data_size += SALT_SIZE
14863         self._offsets['enc_privkey'] = MDMFHEADERSIZE
14864-        self._offsets['enc_privkey'] += data_size
14865-        # We'll wait for the rest. Callers can now call my "put_block" and
14866-        # "set_checkstring" methods.
14867+
14868+        # We don't define offsets for these because we want them to be
14869+        # tightly packed -- this allows us to ignore the responsibility
14870+        # of padding individual values, and of removing that padding
14871+        # later. So nonconstant_start is where we start writing
14872+        # nonconstant data.
14873+        nonconstant_start = self._offsets['enc_privkey']
14874+        nonconstant_start += PRIVATE_KEY_SIZE
14875+        nonconstant_start += SIGNATURE_SIZE
14876+        nonconstant_start += VERIFICATION_KEY_SIZE
14877+        nonconstant_start += SHARE_HASH_CHAIN_SIZE
14878+
14879+        self._offsets['share_data'] = nonconstant_start
14880+
14881+        # Finally, we know how big the share data will be, so we can
14882+        # figure out where the block hash tree needs to go.
14883+        # XXX: But this will go away if Zooko wants to make it so that
14884+        # you don't need to know the size of the file before you start
14885+        # uploading it.
14886+        self._offsets['block_hash_tree'] = self._offsets['share_data'] + \
14887+                    data_size
14888+
14889+        # Done. We can snow start writing.
14890 
14891 
14892     def set_checkstring(self,
14893hunk ./src/allmydata/mutable/layout.py 891
14894         anything to be written yet.
14895         """
14896         if segnum >= self._num_segments:
14897-            raise LayoutInvalid("I won't overwrite the private key")
14898+            raise LayoutInvalid("I won't overwrite the block hash tree")
14899         if len(salt) != SALT_SIZE:
14900             raise LayoutInvalid("I was given a salt of size %d, but "
14901                                 "I wanted a salt of size %d")
14902hunk ./src/allmydata/mutable/layout.py 902
14903             raise LayoutInvalid("I was given the wrong size block to write")
14904 
14905         # We want to write at len(MDMFHEADER) + segnum * block_size.
14906-        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
14907+        offset = self._offsets['share_data'] + \
14908+            (self._actual_block_size * segnum)
14909         data = salt + data
14910 
14911         self._writevs.append(tuple([offset, data]))
14912hunk ./src/allmydata/mutable/layout.py 922
14913         # tree, the block hash tree writing method writes the offset of
14914         # the share hash chain. So that's a good indicator of whether or
14915         # not the block hash tree has been written.
14916-        if "share_hash_chain" in self._offsets:
14917-            raise LayoutInvalid("You must write this before the block hash tree")
14918+        if "signature" in self._offsets:
14919+            raise LayoutInvalid("You can't put the encrypted private key "
14920+                                "after putting the share hash chain")
14921+
14922+        self._offsets['share_hash_chain'] = self._offsets['enc_privkey'] + \
14923+                len(encprivkey)
14924 
14925hunk ./src/allmydata/mutable/layout.py 929
14926-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
14927-            len(encprivkey)
14928         self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
14929 
14930 
14931hunk ./src/allmydata/mutable/layout.py 944
14932         offset of the share hash chain.
14933         """
14934         assert self._offsets
14935+        assert "block_hash_tree" in self._offsets
14936+
14937         assert isinstance(blockhashes, list)
14938hunk ./src/allmydata/mutable/layout.py 947
14939-        if "block_hash_tree" not in self._offsets:
14940-            raise LayoutInvalid("You must put the encrypted private key "
14941-                                "before you put the block hash tree")
14942-        # If written, the share hash chain causes the signature offset
14943-        # to be defined.
14944-        if "signature" in self._offsets:
14945-            raise LayoutInvalid("You must put the block hash tree before "
14946-                                "you put the share hash chain")
14947+
14948         blockhashes_s = "".join(blockhashes)
14949hunk ./src/allmydata/mutable/layout.py 949
14950-        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
14951+        self._offsets['EOF'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
14952 
14953         self._writevs.append(tuple([self._offsets['block_hash_tree'],
14954                                   blockhashes_s]))
14955hunk ./src/allmydata/mutable/layout.py 969
14956         is before you can generate a valid signature.
14957         """
14958         assert isinstance(sharehashes, dict)
14959+        assert self._offsets
14960         if "share_hash_chain" not in self._offsets:
14961hunk ./src/allmydata/mutable/layout.py 971
14962-            raise LayoutInvalid("You need to put the block hash tree before "
14963-                                "you can put the share hash chain")
14964+            raise LayoutInvalid("You must put the block hash tree before "
14965+                                "putting the share hash chain")
14966+
14967         # The signature comes after the share hash chain. If the
14968         # signature has already been written, we must not write another
14969         # share hash chain. The signature writes the verification key
14970hunk ./src/allmydata/mutable/layout.py 984
14971                                 "before you write the signature")
14972         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
14973                                   for i in sorted(sharehashes.keys())])
14974-        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
14975+        self._offsets['signature'] = self._offsets['share_hash_chain'] + \
14976+            len(sharehashes_s)
14977         self._writevs.append(tuple([self._offsets['share_hash_chain'],
14978                             sharehashes_s]))
14979 
14980hunk ./src/allmydata/mutable/layout.py 1002
14981         # Signature is defined by the routine that places the share hash
14982         # chain, so it's a good thing to look for in finding out whether
14983         # or not the share hash chain exists on the remote server.
14984-        if "signature" not in self._offsets:
14985-            raise LayoutInvalid("You need to put the share hash chain "
14986-                                "before you can put the root share hash")
14987         if len(roothash) != HASH_SIZE:
14988             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
14989                                  % HASH_SIZE)
14990hunk ./src/allmydata/mutable/layout.py 1053
14991         # If we put the signature after we put the verification key, we
14992         # could end up running into the verification key, and will
14993         # probably screw up the offsets as well. So we don't allow that.
14994+        if "verification_key_end" in self._offsets:
14995+            raise LayoutInvalid("You can't put the signature after the "
14996+                                "verification key")
14997         # The method that writes the verification key defines the EOF
14998         # offset before writing the verification key, so look for that.
14999hunk ./src/allmydata/mutable/layout.py 1058
15000-        if "EOF" in self._offsets:
15001-            raise LayoutInvalid("You must write the signature before the verification key")
15002-
15003-        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
15004+        self._offsets['verification_key'] = self._offsets['signature'] +\
15005+            len(signature)
15006         self._writevs.append(tuple([self._offsets['signature'], signature]))
15007 
15008 
15009hunk ./src/allmydata/mutable/layout.py 1074
15010         if "verification_key" not in self._offsets:
15011             raise LayoutInvalid("You must put the signature before you "
15012                                 "can put the verification key")
15013-        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
15014+
15015+        self._offsets['verification_key_end'] = \
15016+            self._offsets['verification_key'] + len(verification_key)
15017         self._writevs.append(tuple([self._offsets['verification_key'],
15018                             verification_key]))
15019 
15020hunk ./src/allmydata/mutable/layout.py 1102
15021         of the write vectors that I've dealt with so far to be published
15022         to the remote server, ending the write process.
15023         """
15024-        if "EOF" not in self._offsets:
15025+        if "verification_key_end" not in self._offsets:
15026             raise LayoutInvalid("You must put the verification key before "
15027                                 "you can publish the offsets")
15028         offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
15029hunk ./src/allmydata/mutable/layout.py 1108
15030         offsets = struct.pack(MDMFOFFSETS,
15031                               self._offsets['enc_privkey'],
15032-                              self._offsets['block_hash_tree'],
15033                               self._offsets['share_hash_chain'],
15034                               self._offsets['signature'],
15035                               self._offsets['verification_key'],
15036hunk ./src/allmydata/mutable/layout.py 1111
15037+                              self._offsets['verification_key_end'],
15038+                              self._offsets['share_data'],
15039+                              self._offsets['block_hash_tree'],
15040                               self._offsets['EOF'])
15041         self._writevs.append(tuple([offsets_offset, offsets]))
15042         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
15043hunk ./src/allmydata/mutable/layout.py 1227
15044         # MDMF, though we'll be left with 4 more bytes than we
15045         # need if this ends up being MDMF. This is probably less
15046         # expensive than the cost of a second roundtrip.
15047-        readvs = [(0, 107)]
15048+        readvs = [(0, 123)]
15049         d = self._read(readvs, force_remote)
15050         d.addCallback(self._process_encoding_parameters)
15051         d.addCallback(self._process_offsets)
15052hunk ./src/allmydata/mutable/layout.py 1330
15053             read_length = MDMFOFFSETS_LENGTH
15054             end = read_offset + read_length
15055             (encprivkey,
15056-             blockhashes,
15057              sharehashes,
15058              signature,
15059              verification_key,
15060hunk ./src/allmydata/mutable/layout.py 1333
15061+             verification_key_end,
15062+             sharedata,
15063+             blockhashes,
15064              eof) = struct.unpack(MDMFOFFSETS,
15065                                   offsets[read_offset:end])
15066             self._offsets = {}
15067hunk ./src/allmydata/mutable/layout.py 1344
15068             self._offsets['share_hash_chain'] = sharehashes
15069             self._offsets['signature'] = signature
15070             self._offsets['verification_key'] = verification_key
15071+            self._offsets['verification_key_end']= \
15072+                verification_key_end
15073             self._offsets['EOF'] = eof
15074hunk ./src/allmydata/mutable/layout.py 1347
15075+            self._offsets['share_data'] = sharedata
15076 
15077 
15078     def get_block_and_salt(self, segnum, queue=False):
15079hunk ./src/allmydata/mutable/layout.py 1357
15080         """
15081         d = self._maybe_fetch_offsets_and_header()
15082         def _then(ignored):
15083-            if self._version_number == 1:
15084-                base_share_offset = MDMFHEADERSIZE
15085-            else:
15086-                base_share_offset = self._offsets['share_data']
15087+            base_share_offset = self._offsets['share_data']
15088 
15089             if segnum + 1 > self._num_segments:
15090                 raise LayoutInvalid("Not a valid segment number")
15091hunk ./src/allmydata/mutable/layout.py 1430
15092         def _then(ignored):
15093             blockhashes_offset = self._offsets['block_hash_tree']
15094             if self._version_number == 1:
15095-                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
15096+                blockhashes_length = self._offsets['EOF'] - blockhashes_offset
15097             else:
15098                 blockhashes_length = self._offsets['share_data'] - blockhashes_offset
15099             readvs = [(blockhashes_offset, blockhashes_length)]
15100hunk ./src/allmydata/mutable/layout.py 1501
15101             if self._version_number == 0:
15102                 privkey_length = self._offsets['EOF'] - privkey_offset
15103             else:
15104-                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
15105+                privkey_length = self._offsets['share_hash_chain'] - privkey_offset
15106             readvs = [(privkey_offset, privkey_length)]
15107             return readvs
15108         d.addCallback(_make_readvs)
15109hunk ./src/allmydata/mutable/layout.py 1549
15110         def _make_readvs(ignored):
15111             if self._version_number == 1:
15112                 vk_offset = self._offsets['verification_key']
15113-                vk_length = self._offsets['EOF'] - vk_offset
15114+                vk_length = self._offsets['verification_key_end'] - vk_offset
15115             else:
15116                 vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
15117                 vk_length = self._offsets['signature'] - vk_offset
15118hunk ./src/allmydata/test/test_storage.py 26
15119 from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
15120                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
15121                                      SIGNED_PREFIX, MDMFHEADER, \
15122-                                     MDMFOFFSETS, SDMFSlotWriteProxy
15123+                                     MDMFOFFSETS, SDMFSlotWriteProxy, \
15124+                                     PRIVATE_KEY_SIZE, \
15125+                                     SIGNATURE_SIZE, \
15126+                                     VERIFICATION_KEY_SIZE, \
15127+                                     SHARE_HASH_CHAIN_SIZE
15128 from allmydata.interfaces import BadWriteEnablerError
15129 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
15130 from allmydata.test.common_web import WebRenderingMixin
15131hunk ./src/allmydata/test/test_storage.py 1408
15132 
15133         # The encrypted private key comes after the shares + salts
15134         offset_size = struct.calcsize(MDMFOFFSETS)
15135-        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
15136-        # The blockhashes come after the private key
15137-        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
15138-        # The sharehashes come after the salt hashes
15139-        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
15140-        # The signature comes after the share hash chain
15141+        encrypted_private_key_offset = len(data) + offset_size
15142+        # The share has chain comes after the private key
15143+        sharehashes_offset = encrypted_private_key_offset + \
15144+            len(self.encprivkey)
15145+
15146+        # The signature comes after the share hash chain.
15147         signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
15148hunk ./src/allmydata/test/test_storage.py 1415
15149-        # The verification key comes after the signature
15150-        verification_offset = signature_offset + len(self.signature)
15151-        # The EOF comes after the verification key
15152-        eof_offset = verification_offset + len(self.verification_key)
15153+
15154+        verification_key_offset = signature_offset + len(self.signature)
15155+        verification_key_end = verification_key_offset + \
15156+            len(self.verification_key)
15157+
15158+        share_data_offset = offset_size
15159+        share_data_offset += PRIVATE_KEY_SIZE
15160+        share_data_offset += SIGNATURE_SIZE
15161+        share_data_offset += VERIFICATION_KEY_SIZE
15162+        share_data_offset += SHARE_HASH_CHAIN_SIZE
15163+
15164+        blockhashes_offset = share_data_offset + len(sharedata)
15165+        eof_offset = blockhashes_offset + len(self.block_hash_tree_s)
15166+
15167         data += struct.pack(MDMFOFFSETS,
15168                             encrypted_private_key_offset,
15169hunk ./src/allmydata/test/test_storage.py 1431
15170-                            blockhashes_offset,
15171                             sharehashes_offset,
15172                             signature_offset,
15173hunk ./src/allmydata/test/test_storage.py 1433
15174-                            verification_offset,
15175+                            verification_key_offset,
15176+                            verification_key_end,
15177+                            share_data_offset,
15178+                            blockhashes_offset,
15179                             eof_offset)
15180hunk ./src/allmydata/test/test_storage.py 1438
15181+
15182         self.offsets = {}
15183         self.offsets['enc_privkey'] = encrypted_private_key_offset
15184         self.offsets['block_hash_tree'] = blockhashes_offset
15185hunk ./src/allmydata/test/test_storage.py 1444
15186         self.offsets['share_hash_chain'] = sharehashes_offset
15187         self.offsets['signature'] = signature_offset
15188-        self.offsets['verification_key'] = verification_offset
15189+        self.offsets['verification_key'] = verification_key_offset
15190+        self.offsets['share_data'] = share_data_offset
15191+        self.offsets['verification_key_end'] = verification_key_end
15192         self.offsets['EOF'] = eof_offset
15193hunk ./src/allmydata/test/test_storage.py 1448
15194-        # Next, we'll add in the salts and share data,
15195-        data += sharedata
15196+
15197         # the private key,
15198         data += self.encprivkey
15199hunk ./src/allmydata/test/test_storage.py 1451
15200-        # the block hash tree,
15201-        data += self.block_hash_tree_s
15202-        # the share hash chain,
15203+        # the sharehashes
15204         data += self.share_hash_chain_s
15205         # the signature,
15206         data += self.signature
15207hunk ./src/allmydata/test/test_storage.py 1457
15208         # and the verification key
15209         data += self.verification_key
15210+        # Then we'll add in gibberish until we get to the right point.
15211+        nulls = "".join([" " for i in xrange(len(data), share_data_offset)])
15212+        data += nulls
15213+
15214+        # Then the share data
15215+        data += sharedata
15216+        # the blockhashes
15217+        data += self.block_hash_tree_s
15218         return data
15219 
15220 
15221hunk ./src/allmydata/test/test_storage.py 1729
15222         return d
15223 
15224 
15225-    def test_blockhashes_after_share_hash_chain(self):
15226+    def test_private_key_after_share_hash_chain(self):
15227         mw = self._make_new_mw("si1", 0)
15228         d = defer.succeed(None)
15229hunk ./src/allmydata/test/test_storage.py 1732
15230-        # Put everything up to and including the share hash chain
15231         for i in xrange(6):
15232             d.addCallback(lambda ignored, i=i:
15233                 mw.put_block(self.block, i, self.salt))
15234hunk ./src/allmydata/test/test_storage.py 1738
15235         d.addCallback(lambda ignored:
15236             mw.put_encprivkey(self.encprivkey))
15237         d.addCallback(lambda ignored:
15238-            mw.put_blockhashes(self.block_hash_tree))
15239-        d.addCallback(lambda ignored:
15240             mw.put_sharehashes(self.share_hash_chain))
15241 
15242hunk ./src/allmydata/test/test_storage.py 1740
15243-        # Now try to put the block hash tree again.
15244+        # Now try to put the private key again.
15245         d.addCallback(lambda ignored:
15246hunk ./src/allmydata/test/test_storage.py 1742
15247-            self.shouldFail(LayoutInvalid, "test repeat salthashes",
15248-                            None,
15249-                            mw.put_blockhashes, self.block_hash_tree))
15250-        return d
15251-
15252-
15253-    def test_encprivkey_after_blockhashes(self):
15254-        mw = self._make_new_mw("si1", 0)
15255-        d = defer.succeed(None)
15256-        # Put everything up to and including the block hash tree
15257-        for i in xrange(6):
15258-            d.addCallback(lambda ignored, i=i:
15259-                mw.put_block(self.block, i, self.salt))
15260-        d.addCallback(lambda ignored:
15261-            mw.put_encprivkey(self.encprivkey))
15262-        d.addCallback(lambda ignored:
15263-            mw.put_blockhashes(self.block_hash_tree))
15264-        d.addCallback(lambda ignored:
15265-            self.shouldFail(LayoutInvalid, "out of order private key",
15266+            self.shouldFail(LayoutInvalid, "test repeat private key",
15267                             None,
15268                             mw.put_encprivkey, self.encprivkey))
15269         return d
15270hunk ./src/allmydata/test/test_storage.py 1748
15271 
15272 
15273-    def test_share_hash_chain_after_signature(self):
15274-        mw = self._make_new_mw("si1", 0)
15275-        d = defer.succeed(None)
15276-        # Put everything up to and including the signature
15277-        for i in xrange(6):
15278-            d.addCallback(lambda ignored, i=i:
15279-                mw.put_block(self.block, i, self.salt))
15280-        d.addCallback(lambda ignored:
15281-            mw.put_encprivkey(self.encprivkey))
15282-        d.addCallback(lambda ignored:
15283-            mw.put_blockhashes(self.block_hash_tree))
15284-        d.addCallback(lambda ignored:
15285-            mw.put_sharehashes(self.share_hash_chain))
15286-        d.addCallback(lambda ignored:
15287-            mw.put_root_hash(self.root_hash))
15288-        d.addCallback(lambda ignored:
15289-            mw.put_signature(self.signature))
15290-        # Now try to put the share hash chain again. This should fail
15291-        d.addCallback(lambda ignored:
15292-            self.shouldFail(LayoutInvalid, "out of order share hash chain",
15293-                            None,
15294-                            mw.put_sharehashes, self.share_hash_chain))
15295-        return d
15296-
15297-
15298     def test_signature_after_verification_key(self):
15299         mw = self._make_new_mw("si1", 0)
15300         d = defer.succeed(None)
15301hunk ./src/allmydata/test/test_storage.py 1877
15302         mw = self._make_new_mw("si1", 0)
15303         # Test writing some blocks.
15304         read = self.ss.remote_slot_readv
15305-        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
15306+        expected_private_key_offset = struct.calcsize(MDMFHEADER)
15307+        expected_sharedata_offset = struct.calcsize(MDMFHEADER) + \
15308+                                    PRIVATE_KEY_SIZE + \
15309+                                    SIGNATURE_SIZE + \
15310+                                    VERIFICATION_KEY_SIZE + \
15311+                                    SHARE_HASH_CHAIN_SIZE
15312         written_block_size = 2 + len(self.salt)
15313         written_block = self.block + self.salt
15314         for i in xrange(6):
15315hunk ./src/allmydata/test/test_storage.py 1903
15316                 self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
15317                                 {0: [written_block]})
15318 
15319-            expected_private_key_offset = expected_sharedata_offset + \
15320-                                      len(written_block) * 6
15321             self.failUnlessEqual(len(self.encprivkey), 7)
15322             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
15323                                  {0: [self.encprivkey]})
15324hunk ./src/allmydata/test/test_storage.py 1907
15325 
15326-            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
15327+            expected_block_hash_offset = expected_sharedata_offset + \
15328+                        (6 * written_block_size)
15329             self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
15330             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
15331                                  {0: [self.block_hash_tree_s]})
15332hunk ./src/allmydata/test/test_storage.py 1913
15333 
15334-            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
15335+            expected_share_hash_offset = expected_private_key_offset + len(self.encprivkey)
15336             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
15337                                  {0: [self.share_hash_chain_s]})
15338 
15339hunk ./src/allmydata/test/test_storage.py 1919
15340             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
15341                                  {0: [self.root_hash]})
15342-            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
15343+            expected_signature_offset = expected_share_hash_offset + \
15344+                len(self.share_hash_chain_s)
15345             self.failUnlessEqual(len(self.signature), 9)
15346             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
15347                                  {0: [self.signature]})
15348hunk ./src/allmydata/test/test_storage.py 1941
15349             self.failUnlessEqual(n, 10)
15350             self.failUnlessEqual(segsize, 6)
15351             self.failUnlessEqual(datalen, 36)
15352-            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
15353+            expected_eof_offset = expected_block_hash_offset + \
15354+                len(self.block_hash_tree_s)
15355 
15356             # Check the version number to make sure that it is correct.
15357             expected_version_number = struct.pack(">B", 1)
15358hunk ./src/allmydata/test/test_storage.py 1969
15359             expected_offset = struct.pack(">Q", expected_private_key_offset)
15360             self.failUnlessEqual(read("si1", [0], [(59, 8)]),
15361                                  {0: [expected_offset]})
15362-            expected_offset = struct.pack(">Q", expected_block_hash_offset)
15363+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
15364             self.failUnlessEqual(read("si1", [0], [(67, 8)]),
15365                                  {0: [expected_offset]})
15366hunk ./src/allmydata/test/test_storage.py 1972
15367-            expected_offset = struct.pack(">Q", expected_share_hash_offset)
15368+            expected_offset = struct.pack(">Q", expected_signature_offset)
15369             self.failUnlessEqual(read("si1", [0], [(75, 8)]),
15370                                  {0: [expected_offset]})
15371hunk ./src/allmydata/test/test_storage.py 1975
15372-            expected_offset = struct.pack(">Q", expected_signature_offset)
15373+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
15374             self.failUnlessEqual(read("si1", [0], [(83, 8)]),
15375                                  {0: [expected_offset]})
15376hunk ./src/allmydata/test/test_storage.py 1978
15377-            expected_offset = struct.pack(">Q", expected_verification_key_offset)
15378+            expected_offset = struct.pack(">Q", expected_verification_key_offset + len(self.verification_key))
15379             self.failUnlessEqual(read("si1", [0], [(91, 8)]),
15380                                  {0: [expected_offset]})
15381hunk ./src/allmydata/test/test_storage.py 1981
15382-            expected_offset = struct.pack(">Q", expected_eof_offset)
15383+            expected_offset = struct.pack(">Q", expected_sharedata_offset)
15384             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
15385                                  {0: [expected_offset]})
15386hunk ./src/allmydata/test/test_storage.py 1984
15387+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
15388+            self.failUnlessEqual(read("si1", [0], [(107, 8)]),
15389+                                 {0: [expected_offset]})
15390+            expected_offset = struct.pack(">Q", expected_eof_offset)
15391+            self.failUnlessEqual(read("si1", [0], [(115, 8)]),
15392+                                 {0: [expected_offset]})
15393         d.addCallback(_check_publish)
15394         return d
15395 
15396hunk ./src/allmydata/test/test_storage.py 2117
15397         for i in xrange(6):
15398             d.addCallback(lambda ignored, i=i:
15399                 mw0.put_block(self.block, i, self.salt))
15400-        # Try to write the block hashes before writing the encrypted
15401-        # private key
15402-        d.addCallback(lambda ignored:
15403-            self.shouldFail(LayoutInvalid, "block hashes before key",
15404-                            None, mw0.put_blockhashes,
15405-                            self.block_hash_tree))
15406-
15407-        # Write the private key.
15408-        d.addCallback(lambda ignored:
15409-            mw0.put_encprivkey(self.encprivkey))
15410-
15411 
15412hunk ./src/allmydata/test/test_storage.py 2118
15413-        # Try to write the share hash chain without writing the block
15414-        # hash tree
15415+        # Try to write the share hash chain without writing the
15416+        # encrypted private key
15417         d.addCallback(lambda ignored:
15418             self.shouldFail(LayoutInvalid, "share hash chain before "
15419hunk ./src/allmydata/test/test_storage.py 2122
15420-                                           "salt hash tree",
15421+                                           "private key",
15422                             None,
15423                             mw0.put_sharehashes, self.share_hash_chain))
15424hunk ./src/allmydata/test/test_storage.py 2125
15425-
15426-        # Try to write the root hash and without writing either the
15427-        # block hashes or the or the share hashes
15428+        # Write the private key.
15429         d.addCallback(lambda ignored:
15430hunk ./src/allmydata/test/test_storage.py 2127
15431-            self.shouldFail(LayoutInvalid, "root hash before share hashes",
15432-                            None,
15433-                            mw0.put_root_hash, self.root_hash))
15434+            mw0.put_encprivkey(self.encprivkey))
15435 
15436         # Now write the block hashes and try again
15437         d.addCallback(lambda ignored:
15438hunk ./src/allmydata/test/test_storage.py 2133
15439             mw0.put_blockhashes(self.block_hash_tree))
15440 
15441-        d.addCallback(lambda ignored:
15442-            self.shouldFail(LayoutInvalid, "root hash before share hashes",
15443-                            None, mw0.put_root_hash, self.root_hash))
15444-
15445         # We haven't yet put the root hash on the share, so we shouldn't
15446         # be able to sign it.
15447         d.addCallback(lambda ignored:
15448hunk ./src/allmydata/test/test_storage.py 2378
15449         # This should be enough to fill in both the encoding parameters
15450         # and the table of offsets, which will complete the version
15451         # information tuple.
15452-        d.addCallback(_make_mr, 107)
15453+        d.addCallback(_make_mr, 123)
15454         d.addCallback(lambda mr:
15455             mr.get_verinfo())
15456         def _check_verinfo(verinfo):
15457hunk ./src/allmydata/test/test_storage.py 2412
15458         d.addCallback(_check_verinfo)
15459         # This is not enough data to read a block and a share, so the
15460         # wrapper should attempt to read this from the remote server.
15461-        d.addCallback(_make_mr, 107)
15462+        d.addCallback(_make_mr, 123)
15463         d.addCallback(lambda mr:
15464             mr.get_block_and_salt(0))
15465         def _check_block_and_salt((block, salt)):
15466hunk ./src/allmydata/test/test_storage.py 2420
15467             self.failUnlessEqual(salt, self.salt)
15468             self.failUnlessEqual(self.rref.read_count, 1)
15469         # This should be enough data to read one block.
15470-        d.addCallback(_make_mr, 249)
15471+        d.addCallback(_make_mr, 123 + PRIVATE_KEY_SIZE + SIGNATURE_SIZE + VERIFICATION_KEY_SIZE + SHARE_HASH_CHAIN_SIZE + 140)
15472         d.addCallback(lambda mr:
15473             mr.get_block_and_salt(0))
15474         d.addCallback(_check_block_and_salt)
15475hunk ./src/allmydata/test/test_storage.py 2438
15476         # This should be enough to get us the encoding parameters,
15477         # offset table, and everything else we need to build a verinfo
15478         # string.
15479-        d.addCallback(_make_mr, 107)
15480+        d.addCallback(_make_mr, 123)
15481         d.addCallback(lambda mr:
15482             mr.get_verinfo())
15483         def _check_verinfo(verinfo):
15484hunk ./src/allmydata/test/test_storage.py 2473
15485             self.failUnlessEqual(self.rref.read_count, 0)
15486         d.addCallback(_check_verinfo)
15487         # This shouldn't be enough to read any share data.
15488-        d.addCallback(_make_mr, 107)
15489+        d.addCallback(_make_mr, 123)
15490         d.addCallback(lambda mr:
15491             mr.get_block_and_salt(0))
15492         def _check_block_and_salt((block, salt)):
15493}
15494[uri.py: Add MDMF cap
15495Kevan Carstensen <kevan@isnotajoke.com>**20110501224249
15496 Ignore-this: a6d1046d33f5cc811c5e8b10af925f33
15497] {
15498hunk ./src/allmydata/interfaces.py 546
15499 
15500 class IMutableFileURI(Interface):
15501     """I am a URI which represents a mutable filenode."""
15502+    def get_extension_params():
15503+        """Return the extension parameters in the URI"""
15504 
15505 class IDirectoryURI(Interface):
15506     pass
15507hunk ./src/allmydata/test/test_uri.py 2
15508 
15509+import re
15510 from twisted.trial import unittest
15511 from allmydata import uri
15512 from allmydata.util import hashutil, base32
15513hunk ./src/allmydata/test/test_uri.py 259
15514         uri.CHKFileURI.init_from_string(fileURI)
15515 
15516 class Mutable(testutil.ReallyEqualMixin, unittest.TestCase):
15517-    def test_pack(self):
15518-        writekey = "\x01" * 16
15519-        fingerprint = "\x02" * 32
15520+    def setUp(self):
15521+        self.writekey = "\x01" * 16
15522+        self.fingerprint = "\x02" * 32
15523+        self.readkey = hashutil.ssk_readkey_hash(self.writekey)
15524+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15525 
15526hunk ./src/allmydata/test/test_uri.py 265
15527-        u = uri.WriteableSSKFileURI(writekey, fingerprint)
15528-        self.failUnlessReallyEqual(u.writekey, writekey)
15529-        self.failUnlessReallyEqual(u.fingerprint, fingerprint)
15530+    def test_pack(self):
15531+        u = uri.WriteableSSKFileURI(self.writekey, self.fingerprint)
15532+        self.failUnlessReallyEqual(u.writekey, self.writekey)
15533+        self.failUnlessReallyEqual(u.fingerprint, self.fingerprint)
15534         self.failIf(u.is_readonly())
15535         self.failUnless(u.is_mutable())
15536         self.failUnless(IURI.providedBy(u))
15537hunk ./src/allmydata/test/test_uri.py 281
15538         self.failUnlessReallyEqual(u, u_h)
15539 
15540         u2 = uri.from_string(u.to_string())
15541-        self.failUnlessReallyEqual(u2.writekey, writekey)
15542-        self.failUnlessReallyEqual(u2.fingerprint, fingerprint)
15543+        self.failUnlessReallyEqual(u2.writekey, self.writekey)
15544+        self.failUnlessReallyEqual(u2.fingerprint, self.fingerprint)
15545         self.failIf(u2.is_readonly())
15546         self.failUnless(u2.is_mutable())
15547         self.failUnless(IURI.providedBy(u2))
15548hunk ./src/allmydata/test/test_uri.py 297
15549         self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm)
15550 
15551         u3 = u2.get_readonly()
15552-        readkey = hashutil.ssk_readkey_hash(writekey)
15553-        self.failUnlessReallyEqual(u3.fingerprint, fingerprint)
15554+        readkey = hashutil.ssk_readkey_hash(self.writekey)
15555+        self.failUnlessReallyEqual(u3.fingerprint, self.fingerprint)
15556         self.failUnlessReallyEqual(u3.readkey, readkey)
15557         self.failUnless(u3.is_readonly())
15558         self.failUnless(u3.is_mutable())
15559hunk ./src/allmydata/test/test_uri.py 317
15560         u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
15561         self.failUnlessReallyEqual(u3, u3_h)
15562 
15563-        u4 = uri.ReadonlySSKFileURI(readkey, fingerprint)
15564-        self.failUnlessReallyEqual(u4.fingerprint, fingerprint)
15565+        u4 = uri.ReadonlySSKFileURI(readkey, self.fingerprint)
15566+        self.failUnlessReallyEqual(u4.fingerprint, self.fingerprint)
15567         self.failUnlessReallyEqual(u4.readkey, readkey)
15568         self.failUnless(u4.is_readonly())
15569         self.failUnless(u4.is_mutable())
15570hunk ./src/allmydata/test/test_uri.py 350
15571         self.failUnlessReallyEqual(u5, u5_h)
15572 
15573 
15574+    def test_writable_mdmf_cap(self):
15575+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15576+        cap = u1.to_string()
15577+        u = uri.WritableMDMFFileURI.init_from_string(cap)
15578+
15579+        self.failUnless(IMutableFileURI.providedBy(u))
15580+        self.failUnlessReallyEqual(u.fingerprint, self.fingerprint)
15581+        self.failUnlessReallyEqual(u.writekey, self.writekey)
15582+        self.failUnless(u.is_mutable())
15583+        self.failIf(u.is_readonly())
15584+        self.failUnlessEqual(cap, u.to_string())
15585+
15586+        # Now get a readonly cap from the writable cap, and test that it
15587+        # degrades gracefully.
15588+        ru = u.get_readonly()
15589+        self.failUnlessReallyEqual(self.readkey, ru.readkey)
15590+        self.failUnlessReallyEqual(self.fingerprint, ru.fingerprint)
15591+        self.failUnless(ru.is_mutable())
15592+        self.failUnless(ru.is_readonly())
15593+
15594+        # Now get a verifier cap.
15595+        vu = ru.get_verify_cap()
15596+        self.failUnlessReallyEqual(self.storage_index, vu.storage_index)
15597+        self.failUnlessReallyEqual(self.fingerprint, vu.fingerprint)
15598+        self.failUnless(IVerifierURI.providedBy(vu))
15599+
15600+    def test_readonly_mdmf_cap(self):
15601+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15602+        cap = u1.to_string()
15603+        u2 = uri.ReadonlyMDMFFileURI.init_from_string(cap)
15604+
15605+        self.failUnlessReallyEqual(u2.fingerprint, self.fingerprint)
15606+        self.failUnlessReallyEqual(u2.readkey, self.readkey)
15607+        self.failUnless(u2.is_readonly())
15608+        self.failUnless(u2.is_mutable())
15609+
15610+        vu = u2.get_verify_cap()
15611+        self.failUnlessEqual(u2.storage_index, self.storage_index)
15612+        self.failUnlessEqual(u2.fingerprint, self.fingerprint)
15613+
15614+    def test_create_writable_mdmf_cap_from_readcap(self):
15615+        # we shouldn't be able to create a writable MDMF cap given only a
15616+        # readcap.
15617+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15618+        cap = u1.to_string()
15619+        self.failUnlessRaises(uri.BadURIError,
15620+                              uri.WritableMDMFFileURI.init_from_string,
15621+                              cap)
15622+
15623+    def test_create_writable_mdmf_cap_from_verifycap(self):
15624+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15625+        cap = u1.to_string()
15626+        self.failUnlessRaises(uri.BadURIError,
15627+                              uri.WritableMDMFFileURI.init_from_string,
15628+                              cap)
15629+
15630+    def test_create_readonly_mdmf_cap_from_verifycap(self):
15631+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15632+        cap = u1.to_string()
15633+        self.failUnlessRaises(uri.BadURIError,
15634+                              uri.ReadonlyMDMFFileURI.init_from_string,
15635+                              cap)
15636+
15637+    def test_mdmf_verifier_cap(self):
15638+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15639+        self.failUnless(u1.is_readonly())
15640+        self.failIf(u1.is_mutable())
15641+        self.failUnlessReallyEqual(self.storage_index, u1.storage_index)
15642+        self.failUnlessReallyEqual(self.fingerprint, u1.fingerprint)
15643+
15644+        cap = u1.to_string()
15645+        u2 = uri.MDMFVerifierURI.init_from_string(cap)
15646+        self.failUnless(u2.is_readonly())
15647+        self.failIf(u2.is_mutable())
15648+        self.failUnlessReallyEqual(self.storage_index, u2.storage_index)
15649+        self.failUnlessReallyEqual(self.fingerprint, u2.fingerprint)
15650+
15651+        u3 = u2.get_readonly()
15652+        self.failUnlessReallyEqual(u3, u2)
15653+
15654+        u4 = u2.get_verify_cap()
15655+        self.failUnlessReallyEqual(u4, u2)
15656+
15657+    def test_mdmf_cap_extra_information(self):
15658+        # MDMF caps can be arbitrarily extended after the fingerprint
15659+        # and key/storage index fields.
15660+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15661+        self.failUnlessEqual([], u1.get_extension_params())
15662+
15663+        cap = u1.to_string()
15664+        # Now let's append some fields. Say, 131073 (the segment size)
15665+        # and 3 (the "k" encoding parameter).
15666+        expected_extensions = []
15667+        for e in ('131073', '3'):
15668+            cap += (":%s" % e)
15669+            expected_extensions.append(e)
15670+
15671+            u2 = uri.WritableMDMFFileURI.init_from_string(cap)
15672+            self.failUnlessReallyEqual(self.writekey, u2.writekey)
15673+            self.failUnlessReallyEqual(self.fingerprint, u2.fingerprint)
15674+            self.failIf(u2.is_readonly())
15675+            self.failUnless(u2.is_mutable())
15676+
15677+            c2 = u2.to_string()
15678+            u2n = uri.WritableMDMFFileURI.init_from_string(c2)
15679+            self.failUnlessReallyEqual(u2, u2n)
15680+
15681+            # We should get the extra back when we ask for it.
15682+            self.failUnlessEqual(expected_extensions, u2.get_extension_params())
15683+
15684+            # These should be preserved through cap attenuation, too.
15685+            u3 = u2.get_readonly()
15686+            self.failUnlessReallyEqual(self.readkey, u3.readkey)
15687+            self.failUnlessReallyEqual(self.fingerprint, u3.fingerprint)
15688+            self.failUnless(u3.is_readonly())
15689+            self.failUnless(u3.is_mutable())
15690+            self.failUnlessEqual(expected_extensions, u3.get_extension_params())
15691+
15692+            c3 = u3.to_string()
15693+            u3n = uri.ReadonlyMDMFFileURI.init_from_string(c3)
15694+            self.failUnlessReallyEqual(u3, u3n)
15695+
15696+            u4 = u3.get_verify_cap()
15697+            self.failUnlessReallyEqual(self.storage_index, u4.storage_index)
15698+            self.failUnlessReallyEqual(self.fingerprint, u4.fingerprint)
15699+            self.failUnless(u4.is_readonly())
15700+            self.failIf(u4.is_mutable())
15701+
15702+            c4 = u4.to_string()
15703+            u4n = uri.MDMFVerifierURI.init_from_string(c4)
15704+            self.failUnlessReallyEqual(u4n, u4)
15705+
15706+            self.failUnlessEqual(expected_extensions, u4.get_extension_params())
15707+
15708+
15709+    def test_sdmf_cap_extra_information(self):
15710+        # For interface consistency, we define a method to get
15711+        # extensions for SDMF files as well. This method must always
15712+        # return no extensions, since SDMF files were not created with
15713+        # extensions and cannot be modified to include extensions
15714+        # without breaking older clients.
15715+        u1 = uri.WriteableSSKFileURI(self.writekey, self.fingerprint)
15716+        cap = u1.to_string()
15717+        u2 = uri.WriteableSSKFileURI.init_from_string(cap)
15718+        self.failUnlessEqual([], u2.get_extension_params())
15719+
15720+    def test_extension_character_range(self):
15721+        # As written now, we shouldn't put things other than numbers in
15722+        # the extension fields.
15723+        writecap = uri.WritableMDMFFileURI(self.writekey, self.fingerprint).to_string()
15724+        readcap  = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint).to_string()
15725+        vcap     = uri.MDMFVerifierURI(self.storage_index, self.fingerprint).to_string()
15726+        self.failUnlessRaises(uri.BadURIError,
15727+                              uri.WritableMDMFFileURI.init_from_string,
15728+                              ("%s:invalid" % writecap))
15729+        self.failUnlessRaises(uri.BadURIError,
15730+                              uri.ReadonlyMDMFFileURI.init_from_string,
15731+                              ("%s:invalid" % readcap))
15732+        self.failUnlessRaises(uri.BadURIError,
15733+                              uri.MDMFVerifierURI.init_from_string,
15734+                              ("%s:invalid" % vcap))
15735+
15736+
15737+    def test_mdmf_valid_human_encoding(self):
15738+        # What's a human encoding? Well, it's of the form:
15739+        base = "https://127.0.0.1:3456/uri/"
15740+        # With a cap on the end. For each of the cap types, we need to
15741+        # test that a valid cap (with and without the traditional
15742+        # separators) is recognized and accepted by the classes.
15743+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15744+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15745+                                     ['131073', '3'])
15746+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15747+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15748+                                     ['131073', '3'])
15749+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15750+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15751+                                 ['131073', '3'])
15752+
15753+        # These will yield six different caps.
15754+        for o in (w1, w2, r1 , r2, v1, v2):
15755+            url = base + o.to_string()
15756+            o1 = o.__class__.init_from_human_encoding(url)
15757+            self.failUnlessReallyEqual(o1, o)
15758+
15759+            # Note that our cap will, by default, have : as separators.
15760+            # But it's expected that users from, e.g., the WUI, will
15761+            # have %3A as a separator. We need to make sure that the
15762+            # initialization routine handles that, too.
15763+            cap = o.to_string()
15764+            cap = re.sub(":", "%3A", cap)
15765+            url = base + cap
15766+            o2 = o.__class__.init_from_human_encoding(url)
15767+            self.failUnlessReallyEqual(o2, o)
15768+
15769+
15770+    def test_mdmf_human_encoding_invalid_base(self):
15771+        # What's a human encoding? Well, it's of the form:
15772+        base = "https://127.0.0.1:3456/foo/bar/bazuri/"
15773+        # With a cap on the end. For each of the cap types, we need to
15774+        # test that a valid cap (with and without the traditional
15775+        # separators) is recognized and accepted by the classes.
15776+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15777+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15778+                                     ['131073', '3'])
15779+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15780+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15781+                                     ['131073', '3'])
15782+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15783+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15784+                                 ['131073', '3'])
15785+
15786+        # These will yield six different caps.
15787+        for o in (w1, w2, r1 , r2, v1, v2):
15788+            url = base + o.to_string()
15789+            self.failUnlessRaises(uri.BadURIError,
15790+                                  o.__class__.init_from_human_encoding,
15791+                                  url)
15792+
15793+    def test_mdmf_human_encoding_invalid_cap(self):
15794+        base = "https://127.0.0.1:3456/uri/"
15795+        # With a cap on the end. For each of the cap types, we need to
15796+        # test that a valid cap (with and without the traditional
15797+        # separators) is recognized and accepted by the classes.
15798+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15799+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15800+                                     ['131073', '3'])
15801+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15802+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15803+                                     ['131073', '3'])
15804+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15805+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15806+                                 ['131073', '3'])
15807+
15808+        # These will yield six different caps.
15809+        for o in (w1, w2, r1 , r2, v1, v2):
15810+            # not exhaustive, obviously...
15811+            url = base + o.to_string() + "foobarbaz"
15812+            url2 = base + "foobarbaz" + o.to_string()
15813+            url3 = base + o.to_string()[:25] + "foo" + o.to_string()[:25]
15814+            for u in (url, url2, url3):
15815+                self.failUnlessRaises(uri.BadURIError,
15816+                                      o.__class__.init_from_human_encoding,
15817+                                      u)
15818+
15819+    def test_mdmf_from_string(self):
15820+        # Make sure that the from_string utility function works with
15821+        # MDMF caps.
15822+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15823+        cap = u1.to_string()
15824+        self.failUnless(uri.is_uri(cap))
15825+        u2 = uri.from_string(cap)
15826+        self.failUnlessReallyEqual(u1, u2)
15827+        u3 = uri.from_string_mutable_filenode(cap)
15828+        self.failUnlessEqual(u3, u1)
15829+
15830+        # XXX: We should refactor the extension field into setUp
15831+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15832+                                     ['131073', '3'])
15833+        cap = u1.to_string()
15834+        self.failUnless(uri.is_uri(cap))
15835+        u2 = uri.from_string(cap)
15836+        self.failUnlessReallyEqual(u1, u2)
15837+        u3 = uri.from_string_mutable_filenode(cap)
15838+        self.failUnlessEqual(u3, u1)
15839+
15840+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15841+        cap = u1.to_string()
15842+        self.failUnless(uri.is_uri(cap))
15843+        u2 = uri.from_string(cap)
15844+        self.failUnlessReallyEqual(u1, u2)
15845+        u3 = uri.from_string_mutable_filenode(cap)
15846+        self.failUnlessEqual(u3, u1)
15847+
15848+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15849+                                     ['131073', '3'])
15850+        cap = u1.to_string()
15851+        self.failUnless(uri.is_uri(cap))
15852+        u2 = uri.from_string(cap)
15853+        self.failUnlessReallyEqual(u1, u2)
15854+        u3 = uri.from_string_mutable_filenode(cap)
15855+        self.failUnlessEqual(u3, u1)
15856+
15857+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15858+        cap = u1.to_string()
15859+        self.failUnless(uri.is_uri(cap))
15860+        u2 = uri.from_string(cap)
15861+        self.failUnlessReallyEqual(u1, u2)
15862+        u3 = uri.from_string_verifier(cap)
15863+        self.failUnlessEqual(u3, u1)
15864+
15865+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15866+                                 ['131073', '3'])
15867+        cap = u1.to_string()
15868+        self.failUnless(uri.is_uri(cap))
15869+        u2 = uri.from_string(cap)
15870+        self.failUnlessReallyEqual(u1, u2)
15871+        u3 = uri.from_string_verifier(cap)
15872+        self.failUnlessEqual(u3, u1)
15873+
15874+
15875 class Dirnode(testutil.ReallyEqualMixin, unittest.TestCase):
15876     def test_pack(self):
15877         writekey = "\x01" * 16
15878hunk ./src/allmydata/uri.py 31
15879 SEP='(?::|%3A)'
15880 NUMBER='([0-9]+)'
15881 NUMBER_IGNORE='(?:[0-9]+)'
15882+OPTIONAL_EXTENSION_FIELD = '(' + SEP + '[0-9' + SEP + ']+|)'
15883 
15884 # "human-encoded" URIs are allowed to come with a leading
15885 # 'http://127.0.0.1:(8123|3456)/uri/' that will be ignored.
15886hunk ./src/allmydata/uri.py 297
15887     def get_verify_cap(self):
15888         return SSKVerifierURI(self.storage_index, self.fingerprint)
15889 
15890+    def get_extension_params(self):
15891+        return []
15892 
15893 class ReadonlySSKFileURI(_BaseURI):
15894     implements(IURI, IMutableFileURI)
15895hunk ./src/allmydata/uri.py 354
15896     def get_verify_cap(self):
15897         return SSKVerifierURI(self.storage_index, self.fingerprint)
15898 
15899+    def get_extension_params(self):
15900+        return []
15901 
15902 class SSKVerifierURI(_BaseURI):
15903     implements(IVerifierURI)
15904hunk ./src/allmydata/uri.py 401
15905     def get_verify_cap(self):
15906         return self
15907 
15908+    def get_extension_params(self):
15909+        return []
15910+
15911+class WritableMDMFFileURI(_BaseURI):
15912+    implements(IURI, IMutableFileURI)
15913+
15914+    BASE_STRING='URI:MDMF:'
15915+    STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15916+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15917+
15918+    def __init__(self, writekey, fingerprint, params=[]):
15919+        self.writekey = writekey
15920+        self.readkey = hashutil.ssk_readkey_hash(writekey)
15921+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15922+        assert len(self.storage_index) == 16
15923+        self.fingerprint = fingerprint
15924+        self.extension = params
15925+
15926+    @classmethod
15927+    def init_from_human_encoding(cls, uri):
15928+        mo = cls.HUMAN_RE.search(uri)
15929+        if not mo:
15930+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15931+        params = filter(lambda x: x != '', re.split(SEP, mo.group(3)))
15932+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15933+
15934+    @classmethod
15935+    def init_from_string(cls, uri):
15936+        mo = cls.STRING_RE.search(uri)
15937+        if not mo:
15938+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15939+        params = mo.group(3)
15940+        params = filter(lambda x: x != '', params.split(":"))
15941+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15942+
15943+    def to_string(self):
15944+        assert isinstance(self.writekey, str)
15945+        assert isinstance(self.fingerprint, str)
15946+        ret = 'URI:MDMF:%s:%s' % (base32.b2a(self.writekey),
15947+                                  base32.b2a(self.fingerprint))
15948+        if self.extension:
15949+            ret += ":"
15950+            ret += ":".join(self.extension)
15951+
15952+        return ret
15953+
15954+    def __repr__(self):
15955+        return "<%s %s>" % (self.__class__.__name__, self.abbrev())
15956+
15957+    def abbrev(self):
15958+        return base32.b2a(self.writekey[:5])
15959+
15960+    def abbrev_si(self):
15961+        return base32.b2a(self.storage_index)[:5]
15962+
15963+    def is_readonly(self):
15964+        return False
15965+
15966+    def is_mutable(self):
15967+        return True
15968+
15969+    def get_readonly(self):
15970+        return ReadonlyMDMFFileURI(self.readkey, self.fingerprint, self.extension)
15971+
15972+    def get_verify_cap(self):
15973+        return MDMFVerifierURI(self.storage_index, self.fingerprint, self.extension)
15974+
15975+    def get_extension_params(self):
15976+        return self.extension
15977+
15978+class ReadonlyMDMFFileURI(_BaseURI):
15979+    implements(IURI, IMutableFileURI)
15980+
15981+    BASE_STRING='URI:MDMF-RO:'
15982+    STRING_RE=re.compile('^' +BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15983+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15984+
15985+    def __init__(self, readkey, fingerprint, params=[]):
15986+        self.readkey = readkey
15987+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15988+        assert len(self.storage_index) == 16
15989+        self.fingerprint = fingerprint
15990+        self.extension = params
15991+
15992+    @classmethod
15993+    def init_from_human_encoding(cls, uri):
15994+        mo = cls.HUMAN_RE.search(uri)
15995+        if not mo:
15996+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15997+        params = mo.group(3)
15998+        params = filter(lambda x: x!= '', re.split(SEP, params))
15999+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16000+
16001+    @classmethod
16002+    def init_from_string(cls, uri):
16003+        mo = cls.STRING_RE.search(uri)
16004+        if not mo:
16005+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16006+
16007+        params = mo.group(3)
16008+        params = filter(lambda x: x != '', params.split(":"))
16009+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16010+
16011+    def to_string(self):
16012+        assert isinstance(self.readkey, str)
16013+        assert isinstance(self.fingerprint, str)
16014+        ret = 'URI:MDMF-RO:%s:%s' % (base32.b2a(self.readkey),
16015+                                     base32.b2a(self.fingerprint))
16016+        if self.extension:
16017+            ret += ":"
16018+            ret += ":".join(self.extension)
16019+
16020+        return ret
16021+
16022+    def __repr__(self):
16023+        return "<%s %s>" % (self.__class__.__name__, self.abbrev())
16024+
16025+    def abbrev(self):
16026+        return base32.b2a(self.readkey[:5])
16027+
16028+    def abbrev_si(self):
16029+        return base32.b2a(self.storage_index)[:5]
16030+
16031+    def is_readonly(self):
16032+        return True
16033+
16034+    def is_mutable(self):
16035+        return True
16036+
16037+    def get_readonly(self):
16038+        return self
16039+
16040+    def get_verify_cap(self):
16041+        return MDMFVerifierURI(self.storage_index, self.fingerprint, self.extension)
16042+
16043+    def get_extension_params(self):
16044+        return self.extension
16045+
16046+class MDMFVerifierURI(_BaseURI):
16047+    implements(IVerifierURI)
16048+
16049+    BASE_STRING='URI:MDMF-Verifier:'
16050+    STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16051+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16052+
16053+    def __init__(self, storage_index, fingerprint, params=[]):
16054+        assert len(storage_index) == 16
16055+        self.storage_index = storage_index
16056+        self.fingerprint = fingerprint
16057+        self.extension = params
16058+
16059+    @classmethod
16060+    def init_from_human_encoding(cls, uri):
16061+        mo = cls.HUMAN_RE.search(uri)
16062+        if not mo:
16063+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16064+        params = mo.group(3)
16065+        params = filter(lambda x: x != '', re.split(SEP, params))
16066+        return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16067+
16068+    @classmethod
16069+    def init_from_string(cls, uri):
16070+        mo = cls.STRING_RE.search(uri)
16071+        if not mo:
16072+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16073+        params = mo.group(3)
16074+        params = filter(lambda x: x != '', params.split(":"))
16075+        return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16076+
16077+    def to_string(self):
16078+        assert isinstance(self.storage_index, str)
16079+        assert isinstance(self.fingerprint, str)
16080+        ret = 'URI:MDMF-Verifier:%s:%s' % (si_b2a(self.storage_index),
16081+                                           base32.b2a(self.fingerprint))
16082+        if self.extension:
16083+            ret += ':'
16084+            ret += ":".join(self.extension)
16085+
16086+        return ret
16087+
16088+    def is_readonly(self):
16089+        return True
16090+
16091+    def is_mutable(self):
16092+        return False
16093+
16094+    def get_readonly(self):
16095+        return self
16096+
16097+    def get_verify_cap(self):
16098+        return self
16099+
16100+    def get_extension_params(self):
16101+        return self.extension
16102+
16103 class _DirectoryBaseURI(_BaseURI):
16104     implements(IURI, IDirnodeURI)
16105     def __init__(self, filenode_uri=None):
16106hunk ./src/allmydata/uri.py 831
16107             kind = "URI:SSK-RO readcap to a mutable file"
16108         elif s.startswith('URI:SSK-Verifier:'):
16109             return SSKVerifierURI.init_from_string(s)
16110+        elif s.startswith('URI:MDMF:'):
16111+            return WritableMDMFFileURI.init_from_string(s)
16112+        elif s.startswith('URI:MDMF-RO:'):
16113+            return ReadonlyMDMFFileURI.init_from_string(s)
16114+        elif s.startswith('URI:MDMF-Verifier:'):
16115+            return MDMFVerifierURI.init_from_string(s)
16116         elif s.startswith('URI:DIR2:'):
16117             if can_be_writeable:
16118                 return DirectoryURI.init_from_string(s)
16119}
16120[nodemaker, mutable/filenode: train nodemaker and filenode to handle MDMF caps
16121Kevan Carstensen <kevan@isnotajoke.com>**20110501224523
16122 Ignore-this: 1f3b4581eb583e7bb93d234182bda395
16123] {
16124hunk ./src/allmydata/mutable/filenode.py 12
16125      IMutableFileVersion, IWritable
16126 from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
16127 from allmydata.util.assertutil import precondition
16128-from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
16129+from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI, \
16130+                          WritableMDMFFileURI, ReadonlyMDMFFileURI
16131 from allmydata.monitor import Monitor
16132 from pycryptopp.cipher.aes import AES
16133 
16134hunk ./src/allmydata/mutable/filenode.py 75
16135         # set to this default value in case neither of those things happen,
16136         # or in case the servermap can't find any shares to tell us what
16137         # to publish as.
16138-        # TODO: Set this back to None, and find out why the tests fail
16139-        #       with it set to None.
16140+        # XXX: Version should come in via the constructor.
16141         self._protocol_version = None
16142 
16143         # all users of this MutableFileNode go through the serializer. This
16144hunk ./src/allmydata/mutable/filenode.py 95
16145         # verification key, nor things like 'k' or 'N'. If and when someone
16146         # wants to get our contents, we'll pull from shares and fill those
16147         # in.
16148-        assert isinstance(filecap, (ReadonlySSKFileURI, WriteableSSKFileURI))
16149+        if isinstance(filecap, (WritableMDMFFileURI, ReadonlyMDMFFileURI)):
16150+            self._protocol_version = MDMF_VERSION
16151+        elif isinstance(filecap, (ReadonlySSKFileURI, WriteableSSKFileURI)):
16152+            self._protocol_version = SDMF_VERSION
16153+
16154         self._uri = filecap
16155         self._writekey = None
16156hunk ./src/allmydata/mutable/filenode.py 102
16157-        if isinstance(filecap, WriteableSSKFileURI):
16158+
16159+        if not filecap.is_readonly() and filecap.is_mutable():
16160             self._writekey = self._uri.writekey
16161         self._readkey = self._uri.readkey
16162         self._storage_index = self._uri.storage_index
16163hunk ./src/allmydata/mutable/filenode.py 131
16164         self._writekey = hashutil.ssk_writekey_hash(privkey_s)
16165         self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
16166         self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
16167-        self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
16168+        if self._protocol_version == MDMF_VERSION:
16169+            self._uri = WritableMDMFFileURI(self._writekey, self._fingerprint)
16170+        else:
16171+            self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
16172         self._readkey = self._uri.readkey
16173         self._storage_index = self._uri.storage_index
16174         initial_contents = self._get_initial_contents(contents)
16175hunk ./src/allmydata/nodemaker.py 82
16176             return self._create_immutable(cap)
16177         if isinstance(cap, uri.CHKFileVerifierURI):
16178             return self._create_immutable_verifier(cap)
16179-        if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI)):
16180+        if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI,
16181+                            uri.WritableMDMFFileURI, uri.ReadonlyMDMFFileURI)):
16182             return self._create_mutable(cap)
16183         if isinstance(cap, (uri.DirectoryURI,
16184                             uri.ReadonlyDirectoryURI,
16185hunk ./src/allmydata/test/test_mutable.py 196
16186                     offset2 = 0
16187                 if offset1 == "pubkey" and IV:
16188                     real_offset = 107
16189-                elif offset1 == "share_data" and not IV:
16190-                    real_offset = 107
16191                 elif offset1 in o:
16192                     real_offset = o[offset1]
16193                 else:
16194hunk ./src/allmydata/test/test_mutable.py 270
16195         return d
16196 
16197 
16198+    def test_mdmf_filenode_cap(self):
16199+        # Test that an MDMF filenode, once created, returns an MDMF URI.
16200+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16201+        def _created(n):
16202+            self.failUnless(isinstance(n, MutableFileNode))
16203+            cap = n.get_cap()
16204+            self.failUnless(isinstance(cap, uri.WritableMDMFFileURI))
16205+            rcap = n.get_readcap()
16206+            self.failUnless(isinstance(rcap, uri.ReadonlyMDMFFileURI))
16207+            vcap = n.get_verify_cap()
16208+            self.failUnless(isinstance(vcap, uri.MDMFVerifierURI))
16209+        d.addCallback(_created)
16210+        return d
16211+
16212+
16213+    def test_create_from_mdmf_writecap(self):
16214+        # Test that the nodemaker is capable of creating an MDMF
16215+        # filenode given an MDMF cap.
16216+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16217+        def _created(n):
16218+            self.failUnless(isinstance(n, MutableFileNode))
16219+            s = n.get_uri()
16220+            self.failUnless(s.startswith("URI:MDMF"))
16221+            n2 = self.nodemaker.create_from_cap(s)
16222+            self.failUnless(isinstance(n2, MutableFileNode))
16223+            self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
16224+            self.failUnlessEqual(n.get_uri(), n2.get_uri())
16225+        d.addCallback(_created)
16226+        return d
16227+
16228+
16229+    def test_create_from_mdmf_writecap_with_extensions(self):
16230+        # Test that the nodemaker is capable of creating an MDMF
16231+        # filenode when given a writecap with extension parameters in
16232+        # them.
16233+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16234+        def _created(n):
16235+            self.failUnless(isinstance(n, MutableFileNode))
16236+            s = n.get_uri()
16237+            s2 = "%s:3:131073" % s
16238+            n2 = self.nodemaker.create_from_cap(s2)
16239+
16240+            self.failUnlessEqual(n2.get_storage_index(), n.get_storage_index())
16241+            self.failUnlessEqual(n.get_writekey(), n2.get_writekey())
16242+        d.addCallback(_created)
16243+        return d
16244+
16245+
16246+    def test_create_from_mdmf_readcap(self):
16247+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16248+        def _created(n):
16249+            self.failUnless(isinstance(n, MutableFileNode))
16250+            s = n.get_readonly_uri()
16251+            n2 = self.nodemaker.create_from_cap(s)
16252+            self.failUnless(isinstance(n2, MutableFileNode))
16253+
16254+            # Check that it's a readonly node
16255+            self.failUnless(n2.is_readonly())
16256+        d.addCallback(_created)
16257+        return d
16258+
16259+
16260+    def test_create_from_mdmf_readcap_with_extensions(self):
16261+        # We should be able to create an MDMF filenode with the
16262+        # extension parameters without it breaking.
16263+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16264+        def _created(n):
16265+            self.failUnless(isinstance(n, MutableFileNode))
16266+            s = n.get_readonly_uri()
16267+            s = "%s:3:131073" % s
16268+
16269+            n2 = self.nodemaker.create_from_cap(s)
16270+            self.failUnless(isinstance(n2, MutableFileNode))
16271+            self.failUnless(n2.is_readonly())
16272+            self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
16273+        d.addCallback(_created)
16274+        return d
16275+
16276+
16277+    def test_internal_version_from_cap(self):
16278+        # MutableFileNodes and MutableFileVersions have an internal
16279+        # switch that tells them whether they're dealing with an SDMF or
16280+        # MDMF mutable file when they start doing stuff. We want to make
16281+        # sure that this is set appropriately given an MDMF cap.
16282+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16283+        def _created(n):
16284+            self.uri = n.get_uri()
16285+            self.failUnlessEqual(n._protocol_version, MDMF_VERSION)
16286+
16287+            n2 = self.nodemaker.create_from_cap(self.uri)
16288+            self.failUnlessEqual(n2._protocol_version, MDMF_VERSION)
16289+        d.addCallback(_created)
16290+        return d
16291+
16292+
16293     def test_serialize(self):
16294         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
16295         calls = []
16296hunk ./src/allmydata/test/test_mutable.py 464
16297         return d
16298 
16299 
16300+    def test_download_from_mdmf_cap(self):
16301+        # We should be able to download an MDMF file given its cap
16302+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16303+        def _created(node):
16304+            self.uri = node.get_uri()
16305+
16306+            return node.overwrite(MutableData("contents1" * 100000))
16307+        def _then(ignored):
16308+            node = self.nodemaker.create_from_cap(self.uri)
16309+            return node.download_best_version()
16310+        def _downloaded(data):
16311+            self.failUnlessEqual(data, "contents1" * 100000)
16312+        d.addCallback(_created)
16313+        d.addCallback(_then)
16314+        d.addCallback(_downloaded)
16315+        return d
16316+
16317+
16318     def test_mdmf_write_count(self):
16319         # Publishing an MDMF file should only cause one write for each
16320         # share that is to be published. Otherwise, we introduce
16321hunk ./src/allmydata/test/test_mutable.py 1735
16322     def test_verify_mdmf_bad_encprivkey(self):
16323         d = self.publish_mdmf()
16324         d.addCallback(lambda ignored:
16325-            corrupt(None, self._storage, "enc_privkey", [1]))
16326+            corrupt(None, self._storage, "enc_privkey", [0]))
16327         d.addCallback(lambda ignored:
16328             self._fn.check(Monitor(), verify=True))
16329         d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
16330hunk ./src/allmydata/test/test_mutable.py 2843
16331         return d
16332 
16333 
16334+    def test_version_extension_api(self):
16335+        # We need to define an API by which an uploader can set the
16336+        # extension parameters, and by which a downloader can retrieve
16337+        # extensions.
16338+        self.failUnless(False)
16339+
16340+
16341+    def test_extensions_from_cap(self):
16342+        self.failUnless(False)
16343+
16344+
16345+    def test_extensions_from_upload(self):
16346+        self.failUnless(False)
16347+
16348+
16349+    def test_cap_after_upload(self):
16350+        self.failUnless(False)
16351+
16352+
16353     def test_get_writekey(self):
16354         d = self.mdmf_node.get_best_mutable_version()
16355         d.addCallback(lambda bv:
16356}
16357[mutable/retrieve: fix typo in paused check
16358Kevan Carstensen <kevan@isnotajoke.com>**20110515225946
16359 Ignore-this: a9c7f3bdbab2f8248f8b6a64f574e7c4
16360] hunk ./src/allmydata/mutable/retrieve.py 207
16361         """
16362         if self._paused:
16363             d = defer.Deferred()
16364-            self._pause_defered.addCallback(lambda ignored: d.callback(res))
16365+            self._pause_deferred.addCallback(lambda ignored: d.callback(res))
16366             return d
16367         return defer.succeed(res)
16368 
16369[scripts/tahoe_put.py: teach tahoe put about MDMF caps
16370Kevan Carstensen <kevan@isnotajoke.com>**20110515230008
16371 Ignore-this: 1522f434f651683c924e37251a3c1bfd
16372] hunk ./src/allmydata/scripts/tahoe_put.py 49
16373         #  DIRCAP:./subdir/foo : DIRCAP/subdir/foo
16374         #  MUTABLE-FILE-WRITECAP : filecap
16375 
16376-        # FIXME: this shouldn't rely on a particular prefix.
16377-        if to_file.startswith("URI:SSK:"):
16378+        # FIXME: don't hardcode cap format.
16379+        if to_file.startswith("URI:MDMF:") or to_file.startswith("URI:SSK:"):
16380             url = nodeurl + "uri/%s" % urllib.quote(to_file)
16381         else:
16382             try:
16383[test/common.py: fix some MDMF-related bugs in common test fixtures
16384Kevan Carstensen <kevan@isnotajoke.com>**20110515230038
16385 Ignore-this: ab5ffe4789bb5e6ed5f54b91b760bac9
16386] {
16387hunk ./src/allmydata/test/common.py 199
16388                  default_encoding_parameters, history):
16389         self.init_from_cap(make_mutable_file_cap())
16390     def create(self, contents, key_generator=None, keysize=None):
16391+        if self.file_types[self.storage_index] == MDMF_VERSION and \
16392+            isinstance(self.my_uri, (uri.ReadonlySSKFileURI,
16393+                                 uri.WriteableSSKFileURI)):
16394+            self.init_from_cap(make_mdmf_mutable_file_cap())
16395         initial_contents = self._get_initial_contents(contents)
16396         data = initial_contents.read(initial_contents.get_size())
16397         data = "".join(data)
16398hunk ./src/allmydata/test/common.py 220
16399         return contents(self)
16400     def init_from_cap(self, filecap):
16401         assert isinstance(filecap, (uri.WriteableSSKFileURI,
16402-                                    uri.ReadonlySSKFileURI))
16403+                                    uri.ReadonlySSKFileURI,
16404+                                    uri.WritableMDMFFileURI,
16405+                                    uri.ReadonlyMDMFFileURI))
16406         self.my_uri = filecap
16407         self.storage_index = self.my_uri.get_storage_index()
16408hunk ./src/allmydata/test/common.py 225
16409+        if isinstance(filecap, (uri.WritableMDMFFileURI,
16410+                                uri.ReadonlyMDMFFileURI)):
16411+            self.file_types[self.storage_index] = MDMF_VERSION
16412+
16413+        else:
16414+            self.file_types[self.storage_index] = SDMF_VERSION
16415+
16416         return self
16417     def get_cap(self):
16418         return self.my_uri
16419hunk ./src/allmydata/test/common.py 249
16420         return self.my_uri.get_readonly().to_string()
16421     def get_verify_cap(self):
16422         return self.my_uri.get_verify_cap()
16423+    def get_repair_cap(self):
16424+        if self.my_uri.is_readonly():
16425+            return None
16426+        return self.my_uri
16427     def is_readonly(self):
16428         return self.my_uri.is_readonly()
16429     def is_mutable(self):
16430hunk ./src/allmydata/test/common.py 406
16431 def make_mutable_file_cap():
16432     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
16433                                    fingerprint=os.urandom(32))
16434-def make_mutable_file_uri():
16435-    return make_mutable_file_cap().to_string()
16436+
16437+def make_mdmf_mutable_file_cap():
16438+    return uri.WritableMDMFFileURI(writekey=os.urandom(16),
16439+                                   fingerprint=os.urandom(32))
16440+
16441+def make_mutable_file_uri(mdmf=False):
16442+    if mdmf:
16443+        uri = make_mdmf_mutable_file_cap()
16444+    else:
16445+        uri = make_mutable_file_cap()
16446+
16447+    return uri.to_string()
16448 
16449 def make_verifier_uri():
16450     return uri.SSKVerifierURI(storage_index=os.urandom(16),
16451hunk ./src/allmydata/test/common.py 423
16452                               fingerprint=os.urandom(32)).to_string()
16453 
16454+def create_mutable_filenode(contents, mdmf=False):
16455+    # XXX: All of these arguments are kind of stupid.
16456+    if mdmf:
16457+        cap = make_mdmf_mutable_file_cap()
16458+    else:
16459+        cap = make_mutable_file_cap()
16460+
16461+    filenode = FakeMutableFileNode(None, None, None, None)
16462+    filenode.init_from_cap(cap)
16463+    FakeMutableFileNode.all_contents[filenode.storage_index] = contents
16464+    return filenode
16465+
16466+
16467 class FakeDirectoryNode(dirnode.DirectoryNode):
16468     """This offers IDirectoryNode, but uses a FakeMutableFileNode for the
16469     backing store, so it doesn't go to the grid. The child data is still
16470}
16471[test/test_cli: Alter existing MDMF tests to test for MDMF caps
16472Kevan Carstensen <kevan@isnotajoke.com>**20110515230054
16473 Ignore-this: a90d089e1afb0f261710083c2be6b2fa
16474] {
16475hunk ./src/allmydata/test/test_cli.py 13
16476 from allmydata.util import fileutil, hashutil, base32
16477 from allmydata import uri
16478 from allmydata.immutable import upload
16479+from allmydata.interfaces import MDMF_VERSION, SDMF_VERSION
16480 from allmydata.mutable.publish import MutableData
16481 from allmydata.dirnode import normalize
16482 
16483hunk ./src/allmydata/test/test_cli.py 33
16484 from allmydata.test.common_util import StallMixin, ReallyEqualMixin
16485 from allmydata.test.no_network import GridTestMixin
16486 from twisted.internet import threads # CLI tests use deferToThread
16487+from twisted.internet import defer # List uses a DeferredList in one place.
16488 from twisted.python import usage
16489 
16490 from allmydata.util.assertutil import precondition
16491hunk ./src/allmydata/test/test_cli.py 969
16492         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
16493         return d
16494 
16495+    def _check_mdmf_json(self, (rc, json, err)):
16496+         self.failUnlessEqual(rc, 0)
16497+         self.failUnlessEqual(err, "")
16498+         self.failUnlessIn('"mutable-type": "mdmf"', json)
16499+         # We also want a valid MDMF cap to be in the json.
16500+         self.failUnlessIn("URI:MDMF", json)
16501+         self.failUnlessIn("URI:MDMF-RO", json)
16502+         self.failUnlessIn("URI:MDMF-Verifier", json)
16503+
16504+    def _check_sdmf_json(self, (rc, json, err)):
16505+        self.failUnlessEqual(rc, 0)
16506+        self.failUnlessEqual(err, "")
16507+        self.failUnlessIn('"mutable-type": "sdmf"', json)
16508+        # We also want to see the appropriate SDMF caps.
16509+        self.failUnlessIn("URI:SSK", json)
16510+        self.failUnlessIn("URI:SSK-RO", json)
16511+        self.failUnlessIn("URI:SSK-Verifier", json)
16512+
16513     def test_mutable_type(self):
16514         self.basedir = "cli/Put/mutable_type"
16515         self.set_up_grid()
16516hunk ./src/allmydata/test/test_cli.py 999
16517                         fn1, "tahoe:uploaded.txt"))
16518         d.addCallback(lambda ignored:
16519             self.do_cli("ls", "--json", "tahoe:uploaded.txt"))
16520-        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
16521+        d.addCallback(self._check_mdmf_json)
16522         d.addCallback(lambda ignored:
16523             self.do_cli("put", "--mutable", "--mutable-type=sdmf",
16524                         fn1, "tahoe:uploaded2.txt"))
16525hunk ./src/allmydata/test/test_cli.py 1005
16526         d.addCallback(lambda ignored:
16527             self.do_cli("ls", "--json", "tahoe:uploaded2.txt"))
16528-        d.addCallback(lambda (rc, json, err):
16529-            self.failUnlessIn("sdmf", json))
16530+        d.addCallback(self._check_sdmf_json)
16531         return d
16532 
16533     def test_mutable_type_unlinked(self):
16534hunk ./src/allmydata/test/test_cli.py 1017
16535         d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
16536         d.addCallback(lambda (rc, cap, err):
16537             self.do_cli("ls", "--json", cap))
16538-        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
16539+        d.addCallback(self._check_mdmf_json)
16540         d.addCallback(lambda ignored:
16541             self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1))
16542         d.addCallback(lambda (rc, cap, err):
16543hunk ./src/allmydata/test/test_cli.py 1022
16544             self.do_cli("ls", "--json", cap))
16545-        d.addCallback(lambda (rc, json, err):
16546-            self.failUnlessIn("sdmf", json))
16547+        d.addCallback(self._check_sdmf_json)
16548         return d
16549 
16550hunk ./src/allmydata/test/test_cli.py 1025
16551+    def test_put_to_mdmf_cap(self):
16552+        self.basedir = "cli/Put/put_to_mdmf_cap"
16553+        self.set_up_grid()
16554+        data = "data" * 100000
16555+        fn1 = os.path.join(self.basedir, "data")
16556+        fileutil.write(fn1, data)
16557+        d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
16558+        def _got_cap((rc, out, err)):
16559+            self.failUnlessEqual(rc, 0)
16560+            self.cap = out
16561+        d.addCallback(_got_cap)
16562+        # Now try to write something to the cap using put.
16563+        data2 = "data2" * 100000
16564+        fn2 = os.path.join(self.basedir, "data2")
16565+        fileutil.write(fn2, data2)
16566+        d.addCallback(lambda ignored:
16567+            self.do_cli("put", fn2, self.cap))
16568+        def _got_put((rc, out, err)):
16569+            self.failUnlessEqual(rc, 0)
16570+            self.failUnlessIn(self.cap, out)
16571+        d.addCallback(_got_put)
16572+        # Now get the cap. We should see the data we just put there.
16573+        d.addCallback(lambda ignored:
16574+            self.do_cli("get", self.cap))
16575+        def _got_data((rc, out, err)):
16576+            self.failUnlessEqual(rc, 0)
16577+            self.failUnlessEqual(out, data2)
16578+        d.addCallback(_got_data)
16579+        return d
16580+
16581+    def test_put_to_sdmf_cap(self):
16582+        self.basedir = "cli/Put/put_to_sdmf_cap"
16583+        self.set_up_grid()
16584+        data = "data" * 100000
16585+        fn1 = os.path.join(self.basedir, "data")
16586+        fileutil.write(fn1, data)
16587+        d = self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1)
16588+        def _got_cap((rc, out, err)):
16589+            self.failUnlessEqual(rc, 0)
16590+            self.cap = out
16591+        d.addCallback(_got_cap)
16592+        # Now try to write something to the cap using put.
16593+        data2 = "data2" * 100000
16594+        fn2 = os.path.join(self.basedir, "data2")
16595+        fileutil.write(fn2, data2)
16596+        d.addCallback(lambda ignored:
16597+            self.do_cli("put", fn2, self.cap))
16598+        def _got_put((rc, out, err)):
16599+            self.failUnlessEqual(rc, 0)
16600+            self.failUnlessIn(self.cap, out)
16601+        d.addCallback(_got_put)
16602+        # Now get the cap. We should see the data we just put there.
16603+        d.addCallback(lambda ignored:
16604+            self.do_cli("get", self.cap))
16605+        def _got_data((rc, out, err)):
16606+            self.failUnlessEqual(rc, 0)
16607+            self.failUnlessEqual(out, data2)
16608+        d.addCallback(_got_data)
16609+        return d
16610+
16611     def test_mutable_type_invalid_format(self):
16612         o = cli.PutOptions()
16613         self.failUnlessRaises(usage.UsageError,
16614hunk ./src/allmydata/test/test_cli.py 1318
16615         d.addCallback(_check)
16616         return d
16617 
16618+    def _create_directory_structure(self):
16619+        # Create a simple directory structure that we can use for MDMF,
16620+        # SDMF, and immutable testing.
16621+        assert self.g
16622+
16623+        client = self.g.clients[0]
16624+        # Create a dirnode
16625+        d = client.create_dirnode()
16626+        def _got_rootnode(n):
16627+            # Add a few nodes.
16628+            self._dircap = n.get_uri()
16629+            nm = n._nodemaker
16630+            # The uploaders may run at the same time, so we need two
16631+            # MutableData instances or they'll fight over offsets &c and
16632+            # break.
16633+            mutable_data = MutableData("data" * 100000)
16634+            mutable_data2 = MutableData("data" * 100000)
16635+            # Add both kinds of mutable node.
16636+            d1 = nm.create_mutable_file(mutable_data,
16637+                                        version=MDMF_VERSION)
16638+            d2 = nm.create_mutable_file(mutable_data2,
16639+                                        version=SDMF_VERSION)
16640+            # Add an immutable node. We do this through the directory,
16641+            # with add_file.
16642+            immutable_data = upload.Data("immutable data" * 100000,
16643+                                         convergence="")
16644+            d3 = n.add_file(u"immutable", immutable_data)
16645+            ds = [d1, d2, d3]
16646+            dl = defer.DeferredList(ds)
16647+            def _made_files((r1, r2, r3)):
16648+                self.failUnless(r1[0])
16649+                self.failUnless(r2[0])
16650+                self.failUnless(r3[0])
16651+
16652+                # r1, r2, and r3 contain nodes.
16653+                mdmf_node = r1[1]
16654+                sdmf_node = r2[1]
16655+                imm_node = r3[1]
16656+
16657+                self._mdmf_uri = mdmf_node.get_uri()
16658+                self._mdmf_readonly_uri = mdmf_node.get_readonly_uri()
16659+                self._sdmf_uri = mdmf_node.get_uri()
16660+                self._sdmf_readonly_uri = sdmf_node.get_readonly_uri()
16661+                self._imm_uri = imm_node.get_uri()
16662+
16663+                d1 = n.set_node(u"mdmf", mdmf_node)
16664+                d2 = n.set_node(u"sdmf", sdmf_node)
16665+                return defer.DeferredList([d1, d2])
16666+            # We can now list the directory by listing self._dircap.
16667+            dl.addCallback(_made_files)
16668+            return dl
16669+        d.addCallback(_got_rootnode)
16670+        return d
16671+
16672+    def test_list_mdmf(self):
16673+        # 'tahoe ls' should include MDMF files.
16674+        self.basedir = "cli/List/list_mdmf"
16675+        self.set_up_grid()
16676+        d = self._create_directory_structure()
16677+        d.addCallback(lambda ignored:
16678+            self.do_cli("ls", self._dircap))
16679+        def _got_ls((rc, out, err)):
16680+            self.failUnlessEqual(rc, 0)
16681+            self.failUnlessEqual(err, "")
16682+            self.failUnlessIn("immutable", out)
16683+            self.failUnlessIn("mdmf", out)
16684+            self.failUnlessIn("sdmf", out)
16685+        d.addCallback(_got_ls)
16686+        return d
16687+
16688+    def test_list_mdmf_json(self):
16689+        # 'tahoe ls' should include MDMF caps when invoked with MDMF
16690+        # caps.
16691+        self.basedir = "cli/List/list_mdmf_json"
16692+        self.set_up_grid()
16693+        d = self._create_directory_structure()
16694+        d.addCallback(lambda ignored:
16695+            self.do_cli("ls", "--json", self._dircap))
16696+        def _got_json((rc, out, err)):
16697+            self.failUnlessEqual(rc, 0)
16698+            self.failUnlessEqual(err, "")
16699+            self.failUnlessIn(self._mdmf_uri, out)
16700+            self.failUnlessIn(self._mdmf_readonly_uri, out)
16701+            self.failUnlessIn(self._sdmf_uri, out)
16702+            self.failUnlessIn(self._sdmf_readonly_uri, out)
16703+            self.failUnlessIn(self._imm_uri, out)
16704+            self.failUnlessIn('"mutable-type": "sdmf"', out)
16705+            self.failUnlessIn('"mutable-type": "mdmf"', out)
16706+        d.addCallback(_got_json)
16707+        return d
16708+
16709 
16710 class Mv(GridTestMixin, CLITestMixin, unittest.TestCase):
16711     def test_mv_behavior(self):
16712}
16713[test/test_mutable.py: write a test for pausing during retrieval, write support structure for that test
16714Kevan Carstensen <kevan@isnotajoke.com>**20110515230207
16715 Ignore-this: 8884ef3ad5be59dbc870ed14002ac45
16716] {
16717hunk ./src/allmydata/test/test_mutable.py 6
16718 from cStringIO import StringIO
16719 from twisted.trial import unittest
16720 from twisted.internet import defer, reactor
16721+from twisted.internet.interfaces import IConsumer
16722+from zope.interface import implements
16723 from allmydata import uri, client
16724 from allmydata.nodemaker import NodeMaker
16725 from allmydata.util import base32, consumer
16726hunk ./src/allmydata/test/test_mutable.py 466
16727         return d
16728 
16729 
16730+    def test_retrieve_pause(self):
16731+        # We should make sure that the retriever is able to pause
16732+        # correctly.
16733+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16734+        def _created(node):
16735+            self.node = node
16736+
16737+            return node.overwrite(MutableData("contents1" * 100000))
16738+        d.addCallback(_created)
16739+        # Now we'll retrieve it into a pausing consumer.
16740+        d.addCallback(lambda ignored:
16741+            self.node.get_best_mutable_version())
16742+        def _got_version(version):
16743+            self.c = PausingConsumer()
16744+            return version.read(self.c)
16745+        d.addCallback(_got_version)
16746+        d.addCallback(lambda ignored:
16747+            self.failUnlessEqual(self.c.data, "contents1" * 100000))
16748+        return d
16749+    test_retrieve_pause.timeout = 25
16750+
16751+
16752     def test_download_from_mdmf_cap(self):
16753         # We should be able to download an MDMF file given its cap
16754         d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16755hunk ./src/allmydata/test/test_mutable.py 944
16756                     index = versionmap[shnum]
16757                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
16758 
16759+class PausingConsumer:
16760+    implements(IConsumer)
16761+    def __init__(self):
16762+        self.data = ""
16763+        self.already_paused = False
16764+
16765+    def registerProducer(self, producer, streaming):
16766+        self.producer = producer
16767+        self.producer.resumeProducing()
16768 
16769hunk ./src/allmydata/test/test_mutable.py 954
16770+    def unregisterProducer(self):
16771+        self.producer = None
16772+
16773+    def _unpause(self, ignored):
16774+        self.producer.resumeProducing()
16775+
16776+    def write(self, data):
16777+        self.data += data
16778+        if not self.already_paused:
16779+           self.producer.pauseProducing()
16780+           self.already_paused = True
16781+           reactor.callLater(15, self._unpause, None)
16782 
16783 
16784 class Servermap(unittest.TestCase, PublishMixin):
16785}
16786[test/test_mutable.py: implement cap type checking
16787Kevan Carstensen <kevan@isnotajoke.com>**20110515230326
16788 Ignore-this: 64cf51b809605061047c8a1b02f5e212
16789] hunk ./src/allmydata/test/test_mutable.py 2904
16790 
16791 
16792     def test_cap_after_upload(self):
16793-        self.failUnless(False)
16794+        # If we create a new mutable file and upload things to it, and
16795+        # it's an MDMF file, we should get an MDMF cap back from that
16796+        # file and should be able to use that.
16797+        # That's essentially what MDMF node is, so just check that.
16798+        mdmf_uri = self.mdmf_node.get_uri()
16799+        cap = uri.from_string(mdmf_uri)
16800+        self.failUnless(isinstance(cap, uri.WritableMDMFFileURI))
16801+        readonly_mdmf_uri = self.mdmf_node.get_readonly_uri()
16802+        cap = uri.from_string(readonly_mdmf_uri)
16803+        self.failUnless(isinstance(cap, uri.ReadonlyMDMFFileURI))
16804 
16805 
16806     def test_get_writekey(self):
16807[test/test_web: add MDMF cap tests
16808Kevan Carstensen <kevan@isnotajoke.com>**20110515230358
16809 Ignore-this: ace5af3bdc9b65c3f6964c8fe056816
16810] {
16811hunk ./src/allmydata/test/test_web.py 27
16812 from allmydata.util.netstring import split_netstring
16813 from allmydata.util.encodingutil import to_str
16814 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
16815-     create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
16816+     create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
16817+     make_mutable_file_uri, create_mutable_filenode
16818 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
16819 from allmydata.mutable import servermap, publish, retrieve
16820 import allmydata.test.common_util as testutil
16821hunk ./src/allmydata/test/test_web.py 203
16822             foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
16823             self._bar_txt_verifycap = n.get_verify_cap().to_string()
16824 
16825+            # sdmf
16826+            # XXX: Do we ever use this?
16827+            self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
16828+
16829+            foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
16830+
16831+            # mdmf
16832+            self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
16833+            assert self._quux_txt_uri.startswith("URI:MDMF")
16834+            foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
16835+
16836             foo.set_uri(u"empty", res[3][1].get_uri(),
16837                         res[3][1].get_readonly_uri())
16838             sub_uri = res[4][1].get_uri()
16839hunk ./src/allmydata/test/test_web.py 245
16840             # public/
16841             # public/foo/
16842             # public/foo/bar.txt
16843+            # public/foo/baz.txt
16844+            # public/foo/quux.txt
16845             # public/foo/blockingfile
16846             # public/foo/empty/
16847             # public/foo/sub/
16848hunk ./src/allmydata/test/test_web.py 267
16849         n = create_chk_filenode(contents)
16850         return contents, n, n.get_uri()
16851 
16852+    def makefile_mutable(self, number, mdmf=False):
16853+        contents = "contents of mutable file %s\n" % number
16854+        n = create_mutable_filenode(contents, mdmf)
16855+        return contents, n, n.get_uri(), n.get_readonly_uri()
16856+
16857     def tearDown(self):
16858         return self.s.stopService()
16859 
16860hunk ./src/allmydata/test/test_web.py 278
16861     def failUnlessIsBarDotTxt(self, res):
16862         self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
16863 
16864+    def failUnlessIsQuuxDotTxt(self, res):
16865+        self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
16866+
16867+    def failUnlessIsBazDotTxt(self, res):
16868+        self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
16869+
16870     def failUnlessIsBarJSON(self, res):
16871         data = simplejson.loads(res)
16872         self.failUnless(isinstance(data, list))
16873hunk ./src/allmydata/test/test_web.py 295
16874         self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
16875         self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
16876 
16877+    def failUnlessIsQuuxJSON(self, res):
16878+        data = simplejson.loads(res)
16879+        self.failUnless(isinstance(data, list))
16880+        self.failUnlessEqual(data[0], "filenode")
16881+        self.failUnless(isinstance(data[1], dict))
16882+        metadata = data[1]
16883+        return self.failUnlessIsQuuxDotTxtMetadata(metadata)
16884+
16885+    def failUnlessIsQuuxDotTxtMetadata(self, metadata):
16886+        self.failUnless(metadata['mutable'])
16887+        self.failUnless("rw_uri" in metadata)
16888+        self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
16889+        self.failUnless("ro_uri" in metadata)
16890+        self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
16891+        self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
16892+
16893     def failUnlessIsFooJSON(self, res):
16894         data = simplejson.loads(res)
16895         self.failUnless(isinstance(data, list))
16896hunk ./src/allmydata/test/test_web.py 324
16897 
16898         kidnames = sorted([unicode(n) for n in data[1]["children"]])
16899         self.failUnlessEqual(kidnames,
16900-                             [u"bar.txt", u"blockingfile", u"empty",
16901-                              u"n\u00fc.txt", u"sub"])
16902+                             [u"bar.txt", u"baz.txt", u"blockingfile",
16903+                              u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
16904         kids = dict( [(unicode(name),value)
16905                       for (name,value)
16906                       in data[1]["children"].iteritems()] )
16907hunk ./src/allmydata/test/test_web.py 346
16908                                    self._bar_txt_metadata["tahoe"]["linkcrtime"])
16909         self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
16910                                    self._bar_txt_uri)
16911+        self.failUnlessIn("quux.txt", kids)
16912+        self.failUnlessReallyEqual(kids[u"quux.txt"][1]["rw_uri"],
16913+                                   self._quux_txt_uri)
16914+        self.failUnlessReallyEqual(kids[u"quux.txt"][1]["ro_uri"],
16915+                                   self._quux_txt_readonly_uri)
16916 
16917     def GET(self, urlpath, followRedirect=False, return_response=False,
16918             **kwargs):
16919hunk ./src/allmydata/test/test_web.py 851
16920         d.addCallback(self.failUnlessIsBarDotTxt)
16921         return d
16922 
16923+    def test_GET_FILE_URI_mdmf(self):
16924+        base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
16925+        d = self.GET(base)
16926+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16927+        return d
16928+
16929+    def test_GET_FILE_URI_mdmf_extensions(self):
16930+        base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
16931+        d = self.GET(base)
16932+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16933+        return d
16934+
16935+    def test_GET_FILE_URI_mdmf_readonly(self):
16936+        base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
16937+        d = self.GET(base)
16938+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16939+        return d
16940+
16941     def test_GET_FILE_URI_badchild(self):
16942         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
16943         errmsg = "Files have no children, certainly not named 'boguschild'"
16944hunk ./src/allmydata/test/test_web.py 885
16945                              self.PUT, base, "")
16946         return d
16947 
16948+    def test_PUT_FILE_URI_mdmf(self):
16949+        base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
16950+        self._quux_new_contents = "new_contents"
16951+        d = self.GET(base)
16952+        d.addCallback(lambda res:
16953+            self.failUnlessIsQuuxDotTxt(res))
16954+        d.addCallback(lambda ignored:
16955+            self.PUT(base, self._quux_new_contents))
16956+        d.addCallback(lambda ignored:
16957+            self.GET(base))
16958+        d.addCallback(lambda res:
16959+            self.failUnlessReallyEqual(res, self._quux_new_contents))
16960+        return d
16961+
16962+    def test_PUT_FILE_URI_mdmf_extensions(self):
16963+        base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
16964+        self._quux_new_contents = "new_contents"
16965+        d = self.GET(base)
16966+        d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
16967+        d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
16968+        d.addCallback(lambda ignored: self.GET(base))
16969+        d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
16970+                                                       res))
16971+        return d
16972+
16973+    def test_PUT_FILE_URI_mdmf_readonly(self):
16974+        # We're not allowed to PUT things to a readonly cap.
16975+        base = "/uri/%s" % self._quux_txt_readonly_uri
16976+        d = self.GET(base)
16977+        d.addCallback(lambda res:
16978+            self.failUnlessIsQuuxDotTxt(res))
16979+        # What should we get here? We get a 500 error now; that's not right.
16980+        d.addCallback(lambda ignored:
16981+            self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
16982+                             "400 Bad Request", "read-only cap",
16983+                             self.PUT, base, "new data"))
16984+        return d
16985+
16986+    def test_PUT_FILE_URI_sdmf_readonly(self):
16987+        # We're not allowed to put things to a readonly cap.
16988+        base = "/uri/%s" % self._baz_txt_readonly_uri
16989+        d = self.GET(base)
16990+        d.addCallback(lambda res:
16991+            self.failUnlessIsBazDotTxt(res))
16992+        d.addCallback(lambda ignored:
16993+            self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
16994+                             "400 Bad Request", "read-only cap",
16995+                             self.PUT, base, "new_data"))
16996+        return d
16997+
16998     # TODO: version of this with a Unicode filename
16999     def test_GET_FILEURL_save(self):
17000         d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
17001hunk ./src/allmydata/test/test_web.py 951
17002         d.addBoth(self.should404, "test_GET_FILEURL_missing")
17003         return d
17004 
17005+    def test_GET_FILEURL_info_mdmf(self):
17006+        d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
17007+        def _got(res):
17008+            self.failUnlessIn("mutable file (mdmf)", res)
17009+            self.failUnlessIn(self._quux_txt_uri, res)
17010+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17011+        d.addCallback(_got)
17012+        return d
17013+
17014+    def test_GET_FILEURL_info_mdmf_readonly(self):
17015+        d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
17016+        def _got(res):
17017+            self.failUnlessIn("mutable file (mdmf)", res)
17018+            self.failIfIn(self._quux_txt_uri, res)
17019+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17020+        d.addCallback(_got)
17021+        return d
17022+
17023+    def test_GET_FILEURL_info_sdmf(self):
17024+        d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
17025+        def _got(res):
17026+            self.failUnlessIn("mutable file (sdmf)", res)
17027+            self.failUnlessIn(self._baz_txt_uri, res)
17028+        d.addCallback(_got)
17029+        return d
17030+
17031+    def test_GET_FILEURL_info_mdmf_extensions(self):
17032+        d = self.GET("/uri/%s:3:131073?t=info" % self._quux_txt_uri)
17033+        def _got(res):
17034+            self.failUnlessIn("mutable file (mdmf)", res)
17035+            self.failUnlessIn(self._quux_txt_uri, res)
17036+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17037+        d.addCallback(_got)
17038+        return d
17039+
17040     def test_PUT_overwrite_only_files(self):
17041         # create a directory, put a file in that directory.
17042         contents, n, filecap = self.makefile(8)
17043hunk ./src/allmydata/test/test_web.py 1033
17044         contents = self.NEWFILE_CONTENTS * 300000
17045         d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
17046                      contents)
17047+        def _got_filecap(filecap):
17048+            self.failUnless(filecap.startswith("URI:MDMF"))
17049+            return filecap
17050+        d.addCallback(_got_filecap)
17051         d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
17052         d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
17053         return d
17054hunk ./src/allmydata/test/test_web.py 1203
17055         d.addCallback(_got_json, "sdmf")
17056         return d
17057 
17058+    def test_GET_FILEURL_json_mdmf_extensions(self):
17059+        # A GET invoked against a URL that includes an MDMF cap with
17060+        # extensions should fetch the same JSON information as a GET
17061+        # invoked against a bare cap.
17062+        self._quux_txt_uri = "%s:3:131073" % self._quux_txt_uri
17063+        self._quux_txt_readonly_uri = "%s:3:131073" % self._quux_txt_readonly_uri
17064+        d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
17065+        d.addCallback(self.failUnlessIsQuuxJSON)
17066+        return d
17067+
17068+    def test_GET_FILEURL_json_mdmf(self):
17069+        d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
17070+        d.addCallback(self.failUnlessIsQuuxJSON)
17071+        return d
17072+
17073     def test_GET_FILEURL_json_missing(self):
17074         d = self.GET(self.public_url + "/foo/missing?json")
17075         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
17076hunk ./src/allmydata/test/test_web.py 1262
17077             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
17078             self.failUnlessIn("mutable-type-mdmf", res)
17079             self.failUnlessIn("mutable-type-sdmf", res)
17080+            self.failUnlessIn("quux", res)
17081         d.addCallback(_check)
17082         return d
17083 
17084hunk ./src/allmydata/test/test_web.py 1520
17085         d.addCallback(self.get_operation_results, "127", "json")
17086         def _got_json(stats):
17087             expected = {"count-immutable-files": 3,
17088-                        "count-mutable-files": 0,
17089+                        "count-mutable-files": 2,
17090                         "count-literal-files": 0,
17091hunk ./src/allmydata/test/test_web.py 1522
17092-                        "count-files": 3,
17093+                        "count-files": 5,
17094                         "count-directories": 3,
17095                         "size-immutable-files": 57,
17096                         "size-literal-files": 0,
17097hunk ./src/allmydata/test/test_web.py 1528
17098                         #"size-directories": 1912, # varies
17099                         #"largest-directory": 1590,
17100-                        "largest-directory-children": 5,
17101+                        "largest-directory-children": 7,
17102                         "largest-immutable-file": 19,
17103                         }
17104             for k,v in expected.iteritems():
17105hunk ./src/allmydata/test/test_web.py 1545
17106         def _check(res):
17107             self.failUnless(res.endswith("\n"))
17108             units = [simplejson.loads(t) for t in res[:-1].split("\n")]
17109-            self.failUnlessReallyEqual(len(units), 7)
17110+            self.failUnlessReallyEqual(len(units), 9)
17111             self.failUnlessEqual(units[-1]["type"], "stats")
17112             first = units[0]
17113             self.failUnlessEqual(first["path"], [])
17114hunk ./src/allmydata/test/test_web.py 1556
17115             self.failIfEqual(baz["storage-index"], None)
17116             self.failIfEqual(baz["verifycap"], None)
17117             self.failIfEqual(baz["repaircap"], None)
17118+            # XXX: Add quux and baz to this test.
17119             return
17120         d.addCallback(_check)
17121         return d
17122hunk ./src/allmydata/test/test_web.py 2002
17123         d.addCallback(lambda ignored:
17124             self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
17125                       file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
17126+        def _got_filecap(filecap):
17127+            self.failUnless(filecap.startswith("URI:MDMF"))
17128+            return filecap
17129+        d.addCallback(_got_filecap)
17130         d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
17131         d.addCallback(_got_json, "mdmf")
17132         return d
17133hunk ./src/allmydata/test/test_web.py 2019
17134             filenameu = unicode(filename)
17135             self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
17136             return self.GET(self.public_url + "/foo/%s?t=json" % filename)
17137+        def _got_mdmf_cap(filecap):
17138+            self.failUnless(filecap.startswith("URI:MDMF"))
17139+            return filecap
17140         d.addCallback(_got_cap, "sdmf.txt")
17141         def _got_json(json, version):
17142             data = simplejson.loads(json)
17143hunk ./src/allmydata/test/test_web.py 2034
17144             self.POST(self.public_url + \
17145                       "/foo?t=upload&mutable=true&mutable-type=mdmf",
17146                       file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
17147+        d.addCallback(_got_mdmf_cap)
17148         d.addCallback(_got_cap, "mdmf.txt")
17149         d.addCallback(_got_json, "mdmf")
17150         return d
17151hunk ./src/allmydata/test/test_web.py 2268
17152         # make sure that nothing was added
17153         d.addCallback(lambda res:
17154                       self.failUnlessNodeKeysAre(self._foo_node,
17155-                                                 [u"bar.txt", u"blockingfile",
17156-                                                  u"empty", u"n\u00fc.txt",
17157+                                                 [u"bar.txt", u"baz.txt", u"blockingfile",
17158+                                                  u"empty", u"n\u00fc.txt", u"quux.txt",
17159                                                   u"sub"]))
17160         return d
17161 
17162hunk ./src/allmydata/test/test_web.py 2391
17163         d.addCallback(_check3)
17164         return d
17165 
17166+    def test_POST_FILEURL_mdmf_check(self):
17167+        quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
17168+        d = self.POST(quux_url, t="check")
17169+        def _check(res):
17170+            self.failUnlessIn("Healthy", res)
17171+        d.addCallback(_check)
17172+        quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
17173+        d.addCallback(lambda ignored:
17174+            self.POST(quux_extension_url, t="check"))
17175+        d.addCallback(_check)
17176+        return d
17177+
17178+    def test_POST_FILEURL_mdmf_check_and_repair(self):
17179+        quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
17180+        d = self.POST(quux_url, t="check", repair="true")
17181+        def _check(res):
17182+            self.failUnlessIn("Healthy", res)
17183+        d.addCallback(_check)
17184+        quux_extension_url = "/uri/%s" %\
17185+            urllib.quote("%s:3:131073" % self._quux_txt_uri)
17186+        d.addCallback(lambda ignored:
17187+            self.POST(quux_extension_url, t="check", repair="true"))
17188+        d.addCallback(_check)
17189+        return d
17190+
17191     def wait_for_operation(self, ignored, ophandle):
17192         url = "/operations/" + ophandle
17193         url += "?t=status&output=JSON"
17194hunk ./src/allmydata/test/test_web.py 2461
17195         d.addCallback(self.wait_for_operation, "123")
17196         def _check_json(data):
17197             self.failUnlessReallyEqual(data["finished"], True)
17198-            self.failUnlessReallyEqual(data["count-objects-checked"], 8)
17199-            self.failUnlessReallyEqual(data["count-objects-healthy"], 8)
17200+            self.failUnlessReallyEqual(data["count-objects-checked"], 10)
17201+            self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
17202         d.addCallback(_check_json)
17203         d.addCallback(self.get_operation_results, "123", "html")
17204         def _check_html(res):
17205hunk ./src/allmydata/test/test_web.py 2466
17206-            self.failUnless("Objects Checked: <span>8</span>" in res)
17207-            self.failUnless("Objects Healthy: <span>8</span>" in res)
17208+            self.failUnless("Objects Checked: <span>10</span>" in res)
17209+            self.failUnless("Objects Healthy: <span>10</span>" in res)
17210         d.addCallback(_check_html)
17211 
17212         d.addCallback(lambda res:
17213hunk ./src/allmydata/test/test_web.py 2496
17214         d.addCallback(self.wait_for_operation, "124")
17215         def _check_json(data):
17216             self.failUnlessReallyEqual(data["finished"], True)
17217-            self.failUnlessReallyEqual(data["count-objects-checked"], 8)
17218-            self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 8)
17219+            self.failUnlessReallyEqual(data["count-objects-checked"], 10)
17220+            self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
17221             self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
17222             self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
17223             self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
17224hunk ./src/allmydata/test/test_web.py 2503
17225             self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
17226             self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
17227-            self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 8)
17228+            self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
17229             self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
17230             self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
17231         d.addCallback(_check_json)
17232hunk ./src/allmydata/test/test_web.py 2509
17233         d.addCallback(self.get_operation_results, "124", "html")
17234         def _check_html(res):
17235-            self.failUnless("Objects Checked: <span>8</span>" in res)
17236+            self.failUnless("Objects Checked: <span>10</span>" in res)
17237 
17238hunk ./src/allmydata/test/test_web.py 2511
17239-            self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
17240+            self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
17241             self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
17242             self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
17243 
17244hunk ./src/allmydata/test/test_web.py 2519
17245             self.failUnless("Repairs Successful: <span>0</span>" in res)
17246             self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
17247 
17248-            self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
17249+            self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
17250             self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
17251             self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
17252         d.addCallback(_check_html)
17253hunk ./src/allmydata/test/test_web.py 2649
17254         filecap3 = node3.get_readonly_uri()
17255         node4 = self.s.create_node_from_uri(make_mutable_file_uri())
17256         dircap = DirectoryNode(node4, None, None).get_uri()
17257+        mdmfcap = make_mutable_file_uri(mdmf=True)
17258         litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
17259         emptydircap = "URI:DIR2-LIT:"
17260         newkids = {u"child-imm":        ["filenode", {"rw_uri": filecap1,
17261hunk ./src/allmydata/test/test_web.py 2666
17262                                                       "ro_uri": self._make_readonly(dircap)}],
17263                    u"dirchild-lit":     ["dirnode",  {"ro_uri": litdircap}],
17264                    u"dirchild-empty":   ["dirnode",  {"ro_uri": emptydircap}],
17265+                   u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
17266+                                                        "ro_uri": self._make_readonly(mdmfcap)}],
17267                    }
17268         return newkids, {'filecap1': filecap1,
17269                          'filecap2': filecap2,
17270hunk ./src/allmydata/test/test_web.py 2677
17271                          'unknown_immcap': unknown_immcap,
17272                          'dircap': dircap,
17273                          'litdircap': litdircap,
17274-                         'emptydircap': emptydircap}
17275+                         'emptydircap': emptydircap,
17276+                         'mdmfcap': mdmfcap}
17277 
17278     def _create_immutable_children(self):
17279         contents, n, filecap1 = self.makefile(12)
17280hunk ./src/allmydata/test/test_web.py 3224
17281             data = data[1]
17282             self.failUnlessIn("mutable-type", data)
17283             self.failUnlessEqual(data['mutable-type'], "mdmf")
17284+            self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
17285+            self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
17286         d.addCallback(_got_json)
17287         return d
17288 
17289}
17290[web/filenode.py: complain if a PUT is requested with a readonly cap
17291Kevan Carstensen <kevan@isnotajoke.com>**20110515230421
17292 Ignore-this: e2f05201f3b008e157062ed187eacbb9
17293] hunk ./src/allmydata/web/filenode.py 229
17294                 raise ExistingChildError()
17295 
17296             if self.node.is_mutable():
17297+                # Are we a readonly filenode? We shouldn't allow callers
17298+                # to try to replace us if we are.
17299+                if self.node.is_readonly():
17300+                    raise WebError("PUT to a mutable file: replace or update"
17301+                                   " requested with read-only cap")
17302                 if offset is None:
17303                     return self.replace_my_contents(req)
17304 
17305[web/info.py: Display mutable type information when describing a mutable file
17306Kevan Carstensen <kevan@isnotajoke.com>**20110515230444
17307 Ignore-this: ce5ad22b494effe6c15e49471fae0d99
17308] {
17309hunk ./src/allmydata/web/info.py 8
17310 from nevow.inevow import IRequest
17311 
17312 from allmydata.util import base32
17313-from allmydata.interfaces import IDirectoryNode, IFileNode
17314+from allmydata.interfaces import IDirectoryNode, IFileNode, MDMF_VERSION, SDMF_VERSION
17315 from allmydata.web.common import getxmlfile
17316 from allmydata.mutable.common import UnrecoverableFileError # TODO: move
17317 
17318hunk ./src/allmydata/web/info.py 31
17319             si = node.get_storage_index()
17320             if si:
17321                 if node.is_mutable():
17322-                    return "mutable file"
17323+                    ret = "mutable file"
17324+                    if node.get_version() == MDMF_VERSION:
17325+                        ret += " (mdmf)"
17326+                    else:
17327+                        ret += " (sdmf)"
17328+                    return ret
17329                 return "immutable file"
17330             return "immutable LIT file"
17331         return "unknown"
17332}
17333[uri: teach mutable URI objects how to allow other objects to give them extension parameters
17334Kevan Carstensen <kevan@isnotajoke.com>**20110531012036
17335 Ignore-this: 96c06cee1efe5a92a5ed8d87ca09a7dd
17336] {
17337hunk ./src/allmydata/uri.py 300
17338     def get_extension_params(self):
17339         return []
17340 
17341+    def set_extension_params(self, params):
17342+        pass
17343+
17344 class ReadonlySSKFileURI(_BaseURI):
17345     implements(IURI, IMutableFileURI)
17346 
17347hunk ./src/allmydata/uri.py 360
17348     def get_extension_params(self):
17349         return []
17350 
17351+    def set_extension_params(self, params):
17352+        pass
17353+
17354 class SSKVerifierURI(_BaseURI):
17355     implements(IVerifierURI)
17356 
17357hunk ./src/allmydata/uri.py 410
17358     def get_extension_params(self):
17359         return []
17360 
17361+    def set_extension_params(self, params):
17362+        pass
17363+
17364 class WritableMDMFFileURI(_BaseURI):
17365     implements(IURI, IMutableFileURI)
17366 
17367hunk ./src/allmydata/uri.py 480
17368     def get_extension_params(self):
17369         return self.extension
17370 
17371+    def set_extension_params(self, params):
17372+        params = map(str, params)
17373+        self.extension = params
17374+
17375 class ReadonlyMDMFFileURI(_BaseURI):
17376     implements(IURI, IMutableFileURI)
17377 
17378hunk ./src/allmydata/uri.py 552
17379     def get_extension_params(self):
17380         return self.extension
17381 
17382+    def set_extension_params(self, params):
17383+        params = map(str, params)
17384+        self.extension = params
17385+
17386 class MDMFVerifierURI(_BaseURI):
17387     implements(IVerifierURI)
17388 
17389}
17390[interfaces: working update to interfaces.py for extension handling
17391Kevan Carstensen <kevan@isnotajoke.com>**20110531012201
17392 Ignore-this: 559c43cbf14eec7ac163ebd00c0b7a36
17393] {
17394hunk ./src/allmydata/interfaces.py 549
17395     def get_extension_params():
17396         """Return the extension parameters in the URI"""
17397 
17398+    def set_extension_params():
17399+        """Set the extension parameters that should be in the URI"""
17400+
17401 class IDirectoryURI(Interface):
17402     pass
17403 
17404hunk ./src/allmydata/interfaces.py 1049
17405         writer-visible data using this writekey.
17406         """
17407 
17408-    def set_version(version):
17409-        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
17410-        we upload in SDMF for reasons of compatibility. If you want to
17411-        change this, set_version will let you do that.
17412-
17413-        To say that this file should be uploaded in SDMF, pass in a 0. To
17414-        say that the file should be uploaded as MDMF, pass in a 1.
17415-        """
17416-
17417     def get_version():
17418         """Returns the mutable file protocol version."""
17419 
17420}
17421[mutable/publish: tell filenodes about encoding parameters so they can be put in the cap
17422Kevan Carstensen <kevan@isnotajoke.com>**20110531012447
17423 Ignore-this: cf19f07a6913208a327604457466f2f2
17424] hunk ./src/allmydata/mutable/publish.py 1146
17425         self.log("Publish done, success")
17426         self._status.set_status("Finished")
17427         self._status.set_progress(1.0)
17428+        # Get k and segsize, then give them to the caller.
17429+        hints = {}
17430+        hints['segsize'] = self.segment_size
17431+        hints['k'] = self.required_shares
17432+        self._node.set_downloader_hints(hints)
17433         eventually(self.done_deferred.callback, res)
17434 
17435     def _failure(self):
17436[mutable/servermap: caps imply protocol version, so the servermap doesn't need to tell the filenode what it is anymore.
17437Kevan Carstensen <kevan@isnotajoke.com>**20110531012557
17438 Ignore-this: 9925f5dde5452db92cdbc4a7d6adf1c1
17439] hunk ./src/allmydata/mutable/servermap.py 877
17440         # and the versionmap
17441         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
17442 
17443-        # It's our job to set the protocol version of our parent
17444-        # filenode if it isn't already set.
17445-        if not self._node.get_version():
17446-            # The first byte of the prefix is the version.
17447-            v = struct.unpack(">B", prefix[:1])[0]
17448-            self.log("got version %d" % v)
17449-            self._node.set_version(v)
17450-
17451         return verinfo
17452 
17453 
17454[mutable/filenode: pass downloader hints between publisher, MutableFileNode, and MutableFileVersion as convenient
17455Kevan Carstensen <kevan@isnotajoke.com>**20110531012641
17456 Ignore-this: 672c586891abfa38397bcdf90b64ca72
17457 
17458 We still need to work on making this more thorough; i.e., passing hints
17459 when other operations change encoding parameters.
17460] {
17461hunk ./src/allmydata/mutable/filenode.py 75
17462         # set to this default value in case neither of those things happen,
17463         # or in case the servermap can't find any shares to tell us what
17464         # to publish as.
17465-        # XXX: Version should come in via the constructor.
17466         self._protocol_version = None
17467 
17468         # all users of this MutableFileNode go through the serializer. This
17469hunk ./src/allmydata/mutable/filenode.py 83
17470         # forever without consuming more and more memory.
17471         self._serializer = defer.succeed(None)
17472 
17473+        # Starting with MDMF, we can get these from caps if they're
17474+        # there. Leave them alone for now; they'll be filled in by my
17475+        # init_from_cap method if necessary.
17476+        self._downloader_hints = {}
17477+
17478     def __repr__(self):
17479         if hasattr(self, '_uri'):
17480             return "<%s %x %s %s>" % (self.__class__.__name__, id(self), self.is_readonly() and 'RO' or 'RW', self._uri.abbrev())
17481hunk ./src/allmydata/mutable/filenode.py 120
17482         # if possible, otherwise by the first peer that Publish talks to.
17483         self._privkey = None
17484         self._encprivkey = None
17485+
17486+        # Starting with MDMF caps, we allowed arbitrary extensions in
17487+        # caps. If we were initialized with a cap that had extensions,
17488+        # we want to remember them so we can tell MutableFileVersions
17489+        # about them.
17490+        extensions = self._uri.get_extension_params()
17491+        if extensions:
17492+            extensions = map(int, extensions)
17493+            suspected_k, suspected_segsize = extensions
17494+            self._downloader_hints['k'] = suspected_k
17495+            self._downloader_hints['segsize'] = suspected_segsize
17496+
17497         return self
17498 
17499hunk ./src/allmydata/mutable/filenode.py 134
17500-    def create_with_keys(self, (pubkey, privkey), contents):
17501+    def create_with_keys(self, (pubkey, privkey), contents,
17502+                         version=SDMF_VERSION):
17503         """Call this to create a brand-new mutable file. It will create the
17504         shares, find homes for them, and upload the initial contents (created
17505         with the same rules as IClient.create_mutable_file() ). Returns a
17506hunk ./src/allmydata/mutable/filenode.py 148
17507         self._writekey = hashutil.ssk_writekey_hash(privkey_s)
17508         self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
17509         self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
17510-        if self._protocol_version == MDMF_VERSION:
17511+        if version == MDMF_VERSION:
17512             self._uri = WritableMDMFFileURI(self._writekey, self._fingerprint)
17513hunk ./src/allmydata/mutable/filenode.py 150
17514-        else:
17515+            self._protocol_version = version
17516+        elif version == SDMF_VERSION:
17517             self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
17518hunk ./src/allmydata/mutable/filenode.py 153
17519+            self._protocol_version = version
17520         self._readkey = self._uri.readkey
17521         self._storage_index = self._uri.storage_index
17522         initial_contents = self._get_initial_contents(contents)
17523hunk ./src/allmydata/mutable/filenode.py 365
17524                                      self._readkey,
17525                                      history=self._history)
17526             assert mfv.is_readonly()
17527+            mfv.set_downloader_hints(self._downloader_hints)
17528             # our caller can use this to download the contents of the
17529             # mutable file.
17530             return mfv
17531hunk ./src/allmydata/mutable/filenode.py 520
17532                                      self._secret_holder,
17533                                      history=self._history)
17534             assert not mfv.is_readonly()
17535+            mfv.set_downloader_hints(self._downloader_hints)
17536             return mfv
17537 
17538         return d.addCallback(_build_version)
17539hunk ./src/allmydata/mutable/filenode.py 549
17540         new_contents as an argument. I return a Deferred that eventually
17541         fires with the results of my replacement process.
17542         """
17543+        # TODO: Update downloader hints.
17544         return self._do_serialized(self._overwrite, new_contents)
17545 
17546 
17547hunk ./src/allmydata/mutable/filenode.py 563
17548         return d
17549 
17550 
17551-
17552     def upload(self, new_contents, servermap):
17553         """
17554         I overwrite the contents of the best recoverable version of this
17555hunk ./src/allmydata/mutable/filenode.py 570
17556         creating/updating our own servermap. I return a Deferred that
17557         fires with the results of my upload.
17558         """
17559+        # TODO: Update downloader hints
17560         return self._do_serialized(self._upload, new_contents, servermap)
17561 
17562 
17563hunk ./src/allmydata/mutable/filenode.py 582
17564         Deferred that eventually fires with an UploadResults instance
17565         describing this process.
17566         """
17567+        # TODO: Update downloader hints.
17568         return self._do_serialized(self._modify, modifier, backoffer)
17569 
17570 
17571hunk ./src/allmydata/mutable/filenode.py 650
17572         return u.update()
17573 
17574 
17575-    def set_version(self, version):
17576+    #def set_version(self, version):
17577         # I can be set in two ways:
17578         #  1. When the node is created.
17579         #  2. (for an existing share) when the Servermap is updated
17580hunk ./src/allmydata/mutable/filenode.py 655
17581         #     before I am read.
17582-        assert version in (MDMF_VERSION, SDMF_VERSION)
17583-        self._protocol_version = version
17584+    #    assert version in (MDMF_VERSION, SDMF_VERSION)
17585+    #    self._protocol_version = version
17586 
17587 
17588     def get_version(self):
17589hunk ./src/allmydata/mutable/filenode.py 691
17590         """
17591         assert self._pubkey, "update_servermap must be called before publish"
17592 
17593+        # Define IPublishInvoker with a set_downloader_hints method?
17594+        # Then have the publisher call that method when it's done publishing?
17595         p = Publish(self, self._storage_broker, servermap)
17596         if self._history:
17597             self._history.notify_publish(p.get_status(),
17598hunk ./src/allmydata/mutable/filenode.py 702
17599         return d
17600 
17601 
17602+    def set_downloader_hints(self, hints):
17603+        self._downloader_hints = hints
17604+        extensions = hints.values()
17605+        self._uri.set_extension_params(extensions)
17606+
17607+
17608     def _did_upload(self, res, size):
17609         self._most_recent_size = size
17610         return res
17611hunk ./src/allmydata/mutable/filenode.py 769
17612         return self._writekey
17613 
17614 
17615+    def set_downloader_hints(self, hints):
17616+        """
17617+        I set the downloader hints.
17618+        """
17619+        assert isinstance(hints, dict)
17620+
17621+        self._downloader_hints = hints
17622+
17623+
17624+    def get_downloader_hints(self):
17625+        """
17626+        I return the downloader hints.
17627+        """
17628+        return self._downloader_hints
17629+
17630+
17631     def overwrite(self, new_contents):
17632         """
17633         I overwrite the contents of this mutable file version with the
17634hunk ./src/allmydata/nodemaker.py 97
17635                             version=SDMF_VERSION):
17636         n = MutableFileNode(self.storage_broker, self.secret_holder,
17637                             self.default_encoding_parameters, self.history)
17638-        n.set_version(version)
17639         d = self.key_generator.generate(keysize)
17640hunk ./src/allmydata/nodemaker.py 98
17641-        d.addCallback(n.create_with_keys, contents)
17642+        d.addCallback(n.create_with_keys, contents, version=version)
17643         d.addCallback(lambda res: n)
17644         return d
17645 
17646}
17647[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
17648Kevan Carstensen <kevan@isnotajoke.com>**20110531012739
17649 Ignore-this: 30ebf79b5f6c17f40fa4385de12070a0
17650] {
17651hunk ./src/allmydata/test/common.py 198
17652     def __init__(self, storage_broker, secret_holder,
17653                  default_encoding_parameters, history):
17654         self.init_from_cap(make_mutable_file_cap())
17655-    def create(self, contents, key_generator=None, keysize=None):
17656-        if self.file_types[self.storage_index] == MDMF_VERSION and \
17657+        self._k = default_encoding_parameters['k']
17658+        self._segsize = default_encoding_parameters['max_segment_size']
17659+    def create(self, contents, key_generator=None, keysize=None,
17660+               version=SDMF_VERSION):
17661+        if version == MDMF_VERSION and \
17662             isinstance(self.my_uri, (uri.ReadonlySSKFileURI,
17663                                  uri.WriteableSSKFileURI)):
17664             self.init_from_cap(make_mdmf_mutable_file_cap())
17665hunk ./src/allmydata/test/common.py 206
17666+        self.file_types[self.storage_index] = version
17667         initial_contents = self._get_initial_contents(contents)
17668         data = initial_contents.read(initial_contents.get_size())
17669         data = "".join(data)
17670hunk ./src/allmydata/test/common.py 211
17671         self.all_contents[self.storage_index] = data
17672+        self.my_uri.set_extension_params([self._k, self._segsize])
17673         return defer.succeed(self)
17674     def _get_initial_contents(self, contents):
17675         if contents is None:
17676hunk ./src/allmydata/test/common.py 283
17677     def get_servermap(self, mode):
17678         return defer.succeed(None)
17679 
17680-    def set_version(self, version):
17681-        assert version in (SDMF_VERSION, MDMF_VERSION)
17682-        self.file_types[self.storage_index] = version
17683-
17684     def get_version(self):
17685         assert self.storage_index in self.file_types
17686         return self.file_types[self.storage_index]
17687hunk ./src/allmydata/test/common.py 361
17688         new_data = new_contents.read(new_contents.get_size())
17689         new_data = "".join(new_data)
17690         self.all_contents[self.storage_index] = new_data
17691+        self.my_uri.set_extension_params([self._k, self._segsize])
17692         return defer.succeed(None)
17693     def modify(self, modifier):
17694         # this does not implement FileTooLargeError, but the real one does
17695hunk ./src/allmydata/test/common.py 371
17696         old_contents = self.all_contents[self.storage_index]
17697         new_data = modifier(old_contents, None, True)
17698         self.all_contents[self.storage_index] = new_data
17699+        self.my_uri.set_extension_params([self._k, self._segsize])
17700         return None
17701 
17702     # As actually implemented, MutableFilenode and MutableFileVersion
17703hunk ./src/allmydata/test/common.py 433
17704     else:
17705         cap = make_mutable_file_cap()
17706 
17707-    filenode = FakeMutableFileNode(None, None, None, None)
17708+    encoding_params = {}
17709+    encoding_params['k'] = 3
17710+    encoding_params['max_segment_size'] = 128*1024
17711+
17712+    filenode = FakeMutableFileNode(None, None, encoding_params, None)
17713     filenode.init_from_cap(cap)
17714hunk ./src/allmydata/test/common.py 439
17715-    FakeMutableFileNode.all_contents[filenode.storage_index] = contents
17716+    if mdmf:
17717+        filenode.create(MutableData(contents), version=MDMF_VERSION)
17718+    else:
17719+        filenode.create(MutableData(contents), version=SDMF_VERSION)
17720     return filenode
17721 
17722 
17723hunk ./src/allmydata/test/test_cli.py 1053
17724             self.failUnlessEqual(rc, 0)
17725             self.failUnlessEqual(out, data2)
17726         d.addCallback(_got_data)
17727+        # Now strip the extension information off of the cap and try
17728+        # to put something to it.
17729+        def _make_bare_cap(ignored):
17730+            cap = self.cap.split(":")
17731+            cap = ":".join(cap[:len(cap) - 2])
17732+            self.cap = cap
17733+        d.addCallback(_make_bare_cap)
17734+        data3 = "data3" * 100000
17735+        fn3 = os.path.join(self.basedir, "data3")
17736+        fileutil.write(fn3, data3)
17737+        d.addCallback(lambda ignored:
17738+            self.do_cli("put", fn3, self.cap))
17739+        d.addCallback(lambda ignored:
17740+            self.do_cli("get", self.cap))
17741+        def _got_data3((rc, out, err)):
17742+            self.failUnlessEqual(rc, 0)
17743+            self.failUnlessEqual(out, data3)
17744+        d.addCallback(_got_data3)
17745         return d
17746 
17747     def test_put_to_sdmf_cap(self):
17748hunk ./src/allmydata/test/test_mutable.py 311
17749         def _created(n):
17750             self.failUnless(isinstance(n, MutableFileNode))
17751             s = n.get_uri()
17752-            s2 = "%s:3:131073" % s
17753-            n2 = self.nodemaker.create_from_cap(s2)
17754+            # We need to cheat a little and delete the nodemaker's
17755+            # cache, otherwise we'll get the same node instance back.
17756+            self.failUnlessIn(":3:131073", s)
17757+            n2 = self.nodemaker.create_from_cap(s)
17758 
17759             self.failUnlessEqual(n2.get_storage_index(), n.get_storage_index())
17760             self.failUnlessEqual(n.get_writekey(), n2.get_writekey())
17761hunk ./src/allmydata/test/test_mutable.py 318
17762+            hints = n2._downloader_hints
17763+            self.failUnlessEqual(hints['k'], 3)
17764+            self.failUnlessEqual(hints['segsize'], 131073)
17765         d.addCallback(_created)
17766         return d
17767 
17768hunk ./src/allmydata/test/test_mutable.py 346
17769         def _created(n):
17770             self.failUnless(isinstance(n, MutableFileNode))
17771             s = n.get_readonly_uri()
17772-            s = "%s:3:131073" % s
17773+            self.failUnlessIn(":3:131073", s)
17774 
17775             n2 = self.nodemaker.create_from_cap(s)
17776             self.failUnless(isinstance(n2, MutableFileNode))
17777hunk ./src/allmydata/test/test_mutable.py 352
17778             self.failUnless(n2.is_readonly())
17779             self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
17780+            hints = n2._downloader_hints
17781+            self.failUnlessEqual(hints["k"], 3)
17782+            self.failUnlessEqual(hints["segsize"], 131073)
17783         d.addCallback(_created)
17784         return d
17785 
17786hunk ./src/allmydata/test/test_mutable.py 514
17787         return d
17788 
17789 
17790+    def test_create_and_download_from_bare_mdmf_cap(self):
17791+        # MDMF caps have extension parameters on them by default. We
17792+        # need to make sure that they work without extension parameters.
17793+        contents = MutableData("contents" * 100000)
17794+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION,
17795+                                               contents=contents)
17796+        def _created(node):
17797+            uri = node.get_uri()
17798+            self._created = node
17799+            self.failUnlessIn(":3:131073", uri)
17800+            # Now strip that off the end of the uri, then try creating
17801+            # and downloading the node again.
17802+            bare_uri = uri.replace(":3:131073", "")
17803+            assert ":3:131073" not in bare_uri
17804+
17805+            return self.nodemaker.create_from_cap(bare_uri)
17806+        d.addCallback(_created)
17807+        def _created_bare(node):
17808+            self.failUnlessEqual(node.get_writekey(),
17809+                                 self._created.get_writekey())
17810+            self.failUnlessEqual(node.get_readkey(),
17811+                                 self._created.get_readkey())
17812+            self.failUnlessEqual(node.get_storage_index(),
17813+                                 self._created.get_storage_index())
17814+            return node.download_best_version()
17815+        d.addCallback(_created_bare)
17816+        d.addCallback(lambda data:
17817+            self.failUnlessEqual(data, "contents" * 100000))
17818+        return d
17819+
17820+
17821     def test_mdmf_write_count(self):
17822         # Publishing an MDMF file should only cause one write for each
17823         # share that is to be published. Otherwise, we introduce
17824hunk ./src/allmydata/test/test_mutable.py 2155
17825         # and set the encoding parameters to something completely different
17826         fn2._required_shares = k
17827         fn2._total_shares = n
17828-        # Normally a servermap update would occur before a publish.
17829-        # Here, it doesn't, so we have to do it ourselves.
17830-        fn2.set_version(version)
17831 
17832         s = self._storage
17833         s._peers = {} # clear existing storage
17834hunk ./src/allmydata/test/test_mutable.py 2928
17835         # We need to define an API by which an uploader can set the
17836         # extension parameters, and by which a downloader can retrieve
17837         # extensions.
17838-        self.failUnless(False)
17839+        d = self.mdmf_node.get_best_mutable_version()
17840+        def _got_version(version):
17841+            hints = version.get_downloader_hints()
17842+            # Should be empty at this point.
17843+            self.failUnlessIn("k", hints)
17844+            self.failUnlessEqual(hints['k'], 3)
17845+            self.failUnlessIn('segsize', hints)
17846+            self.failUnlessEqual(hints['segsize'], 131073)
17847+        d.addCallback(_got_version)
17848+        return d
17849 
17850 
17851     def test_extensions_from_cap(self):
17852hunk ./src/allmydata/test/test_mutable.py 2941
17853-        self.failUnless(False)
17854+        # If we initialize a mutable file with a cap that has extension
17855+        # parameters in it and then grab the extension parameters using
17856+        # our API, we should see that they're set correctly.
17857+        mdmf_uri = self.mdmf_node.get_uri()
17858+        new_node = self.nm.create_from_cap(mdmf_uri)
17859+        d = new_node.get_best_mutable_version()
17860+        def _got_version(version):
17861+            hints = version.get_downloader_hints()
17862+            self.failUnlessIn("k", hints)
17863+            self.failUnlessEqual(hints["k"], 3)
17864+            self.failUnlessIn("segsize", hints)
17865+            self.failUnlessEqual(hints["segsize"], 131073)
17866+        d.addCallback(_got_version)
17867+        return d
17868 
17869 
17870     def test_extensions_from_upload(self):
17871hunk ./src/allmydata/test/test_mutable.py 2958
17872-        self.failUnless(False)
17873+        # If we create a new mutable file with some contents, we should
17874+        # get back an MDMF cap with the right hints in place.
17875+        contents = "foo bar baz" * 100000
17876+        d = self.nm.create_mutable_file(contents, version=MDMF_VERSION)
17877+        def _got_mutable_file(n):
17878+            rw_uri = n.get_uri()
17879+            expected_k = str(self.c.DEFAULT_ENCODING_PARAMETERS['k'])
17880+            self.failUnlessIn(expected_k, rw_uri)
17881+            # XXX: Get this more intelligently.
17882+            self.failUnlessIn("131073", rw_uri)
17883+
17884+            ro_uri = n.get_readonly_uri()
17885+            self.failUnlessIn(expected_k, ro_uri)
17886+            self.failUnlessIn("131073", ro_uri)
17887+        d.addCallback(_got_mutable_file)
17888+        return d
17889 
17890 
17891     def test_cap_after_upload(self):
17892hunk ./src/allmydata/test/test_web.py 52
17893         return stats
17894 
17895 class FakeNodeMaker(NodeMaker):
17896+    encoding_params = {
17897+        'k': 3,
17898+        'n': 10,
17899+        'happy': 7,
17900+        'max_segment_size':128*1024 # 1024=KiB
17901+    }
17902     def _create_lit(self, cap):
17903         return FakeCHKFileNode(cap)
17904     def _create_immutable(self, cap):
17905hunk ./src/allmydata/test/test_web.py 63
17906         return FakeCHKFileNode(cap)
17907     def _create_mutable(self, cap):
17908-        return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
17909+        return FakeMutableFileNode(None,
17910+                                   None,
17911+                                   self.encoding_params, None).init_from_cap(cap)
17912     def create_mutable_file(self, contents="", keysize=None,
17913                             version=SDMF_VERSION):
17914hunk ./src/allmydata/test/test_web.py 68
17915-        n = FakeMutableFileNode(None, None, None, None)
17916-        n.set_version(version)
17917-        return n.create(contents)
17918+        n = FakeMutableFileNode(None, None, self.encoding_params, None)
17919+        return n.create(contents, version=version)
17920 
17921 class FakeUploader(service.Service):
17922     name = "uploader"
17923hunk ./src/allmydata/test/test_web.py 302
17924         self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
17925         self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
17926 
17927-    def failUnlessIsQuuxJSON(self, res):
17928+    def failUnlessIsQuuxJSON(self, res, readonly=False):
17929         data = simplejson.loads(res)
17930         self.failUnless(isinstance(data, list))
17931         self.failUnlessEqual(data[0], "filenode")
17932hunk ./src/allmydata/test/test_web.py 308
17933         self.failUnless(isinstance(data[1], dict))
17934         metadata = data[1]
17935-        return self.failUnlessIsQuuxDotTxtMetadata(metadata)
17936+        return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
17937 
17938hunk ./src/allmydata/test/test_web.py 310
17939-    def failUnlessIsQuuxDotTxtMetadata(self, metadata):
17940+    def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
17941         self.failUnless(metadata['mutable'])
17942hunk ./src/allmydata/test/test_web.py 312
17943-        self.failUnless("rw_uri" in metadata)
17944-        self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
17945+        if readonly:
17946+            self.failIf("rw_uri" in metadata)
17947+        else:
17948+            self.failUnless("rw_uri" in metadata)
17949+            self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
17950         self.failUnless("ro_uri" in metadata)
17951         self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
17952         self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
17953hunk ./src/allmydata/test/test_web.py 873
17954         d.addCallback(self.failUnlessIsQuuxDotTxt)
17955         return d
17956 
17957+    def test_GET_FILE_URI_mdmf_bare_cap(self):
17958+        cap_elements = self._quux_txt_uri.split(":")
17959+        # 6 == expected cap length with two extensions.
17960+        self.failUnlessEqual(len(cap_elements), 6)
17961+
17962+        # Now lop off the extension parameters and stitch everything
17963+        # back together
17964+        quux_uri = ":".join(cap_elements[:len(cap_elements) - 2])
17965+
17966+        # Now GET that. We should get back quux.
17967+        base = "/uri/%s" % urllib.quote(quux_uri)
17968+        d = self.GET(base)
17969+        d.addCallback(self.failUnlessIsQuuxDotTxt)
17970+        return d
17971+
17972     def test_GET_FILE_URI_mdmf_readonly(self):
17973         base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
17974         d = self.GET(base)
17975hunk ./src/allmydata/test/test_web.py 935
17976                                                        res))
17977         return d
17978 
17979+    def test_PUT_FILE_URI_mdmf_bare_cap(self):
17980+        elements = self._quux_txt_uri.split(":")
17981+        self.failUnlessEqual(len(elements), 6)
17982+
17983+        quux_uri = ":".join(elements[:len(elements) - 2])
17984+        base = "/uri/%s" % urllib.quote(quux_uri)
17985+        self._quux_new_contents = "new_contents" * 50000
17986+
17987+        d = self.GET(base)
17988+        d.addCallback(self.failUnlessIsQuuxDotTxt)
17989+        d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
17990+        d.addCallback(lambda ignored: self.GET(base))
17991+        d.addCallback(lambda res:
17992+            self.failUnlessEqual(res, self._quux_new_contents))
17993+        return d
17994+
17995     def test_PUT_FILE_URI_mdmf_readonly(self):
17996         # We're not allowed to PUT things to a readonly cap.
17997         base = "/uri/%s" % self._quux_txt_readonly_uri
17998hunk ./src/allmydata/test/test_web.py 1027
17999         d.addCallback(_got)
18000         return d
18001 
18002+    def test_GET_FILEURL_info_mdmf_bare_cap(self):
18003+        elements = self._quux_txt_uri.split(":")
18004+        self.failUnlessEqual(len(elements), 6)
18005+
18006+        quux_uri = ":".join(elements[:len(elements) - 2])
18007+        base = "/uri/%s?t=info" % urllib.quote(quux_uri)
18008+        d = self.GET(base)
18009+        def _got(res):
18010+            self.failUnlessIn("mutable file (mdmf)", res)
18011+            self.failUnlessIn(quux_uri, res)
18012+        d.addCallback(_got)
18013+        return d
18014+
18015     def test_PUT_overwrite_only_files(self):
18016         # create a directory, put a file in that directory.
18017         contents, n, filecap = self.makefile(8)
18018hunk ./src/allmydata/test/test_web.py 1267
18019         d.addCallback(self.failUnlessIsQuuxJSON)
18020         return d
18021 
18022+    def test_GET_FILEURL_json_mdmf_bare_cap(self):
18023+        elements = self._quux_txt_uri.split(":")
18024+        self.failUnlessEqual(len(elements), 6)
18025+
18026+        quux_uri = ":".join(elements[:len(elements) - 2])
18027+        # so failUnlessIsQuuxJSON will work.
18028+        self._quux_txt_uri = quux_uri
18029+
18030+        # we need to alter the readonly URI in the same way, again so
18031+        # failUnlessIsQuuxJSON will work
18032+        elements = self._quux_txt_readonly_uri.split(":")
18033+        self.failUnlessEqual(len(elements), 6)
18034+        quux_ro_uri = ":".join(elements[:len(elements) - 2])
18035+        self._quux_txt_readonly_uri = quux_ro_uri
18036+
18037+        base = "/uri/%s?t=json" % urllib.quote(quux_uri)
18038+        d = self.GET(base)
18039+        d.addCallback(self.failUnlessIsQuuxJSON)
18040+        return d
18041+
18042+    def test_GET_FILEURL_json_mdmf_bare_readonly_cap(self):
18043+        elements = self._quux_txt_readonly_uri.split(":")
18044+        self.failUnlessEqual(len(elements), 6)
18045+
18046+        quux_readonly_uri = ":".join(elements[:len(elements) - 2])
18047+        # so failUnlessIsQuuxJSON will work
18048+        self._quux_txt_readonly_uri = quux_readonly_uri
18049+        base = "/uri/%s?t=json" % quux_readonly_uri
18050+        d = self.GET(base)
18051+        # XXX: We may need to make a method that knows how to check for
18052+        # readonly JSON, or else alter that one so that it knows how to
18053+        # do that.
18054+        d.addCallback(self.failUnlessIsQuuxJSON, readonly=True)
18055+        return d
18056+
18057     def test_GET_FILEURL_json_mdmf(self):
18058         d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
18059         d.addCallback(self.failUnlessIsQuuxJSON)
18060}
18061[Add MDMF dirnodes
18062Kevan Carstensen <kevan@isnotajoke.com>**20110617175808
18063 Ignore-this: e7d184ece57b272be0e5a3917cc7642a
18064] {
18065hunk ./src/allmydata/client.py 493
18066         # may get an opaque node if there were any problems.
18067         return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name)
18068 
18069-    def create_dirnode(self, initial_children={}):
18070-        d = self.nodemaker.create_new_mutable_directory(initial_children)
18071+    def create_dirnode(self, initial_children={}, version=SDMF_VERSION):
18072+        d = self.nodemaker.create_new_mutable_directory(initial_children, version=version)
18073         return d
18074 
18075     def create_immutable_dirnode(self, children, convergence=None):
18076hunk ./src/allmydata/dirnode.py 14
18077 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
18078      IImmutableFileNode, IMutableFileNode, \
18079      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
18080-     MustBeDeepImmutableError, CapConstraintError, ChildOfWrongTypeError
18081+     MustBeDeepImmutableError, CapConstraintError, ChildOfWrongTypeError, \
18082+     SDMF_VERSION, MDMF_VERSION
18083 from allmydata.check_results import DeepCheckResults, \
18084      DeepCheckAndRepairResults
18085 from allmydata.monitor import Monitor
18086hunk ./src/allmydata/dirnode.py 617
18087         d.addCallback(lambda res: deleter.old_child)
18088         return d
18089 
18090+    # XXX: Too many arguments? Worthwhile to break into mutable/immutable?
18091     def create_subdirectory(self, namex, initial_children={}, overwrite=True,
18092hunk ./src/allmydata/dirnode.py 619
18093-                            mutable=True, metadata=None):
18094+                            mutable=True, mutable_version=None, metadata=None):
18095         name = normalize(namex)
18096         if self.is_readonly():
18097             return defer.fail(NotWriteableError())
18098hunk ./src/allmydata/dirnode.py 624
18099         if mutable:
18100-            d = self._nodemaker.create_new_mutable_directory(initial_children)
18101+            if mutable_version:
18102+                d = self._nodemaker.create_new_mutable_directory(initial_children,
18103+                                                                 version=mutable_version)
18104+            else:
18105+                d = self._nodemaker.create_new_mutable_directory(initial_children)
18106         else:
18107hunk ./src/allmydata/dirnode.py 630
18108+            # mutable version doesn't make sense for immmutable directories.
18109+            assert mutable_version is None
18110             d = self._nodemaker.create_immutable_directory(initial_children)
18111         def _created(child):
18112             entries = {name: (child, metadata)}
18113hunk ./src/allmydata/nodemaker.py 88
18114         if isinstance(cap, (uri.DirectoryURI,
18115                             uri.ReadonlyDirectoryURI,
18116                             uri.ImmutableDirectoryURI,
18117-                            uri.LiteralDirectoryURI)):
18118+                            uri.LiteralDirectoryURI,
18119+                            uri.MDMFDirectoryURI,
18120+                            uri.ReadonlyMDMFDirectoryURI)):
18121             filenode = self._create_from_single_cap(cap.get_filenode_cap())
18122             return self._create_dirnode(filenode)
18123         return None
18124hunk ./src/allmydata/nodemaker.py 104
18125         d.addCallback(lambda res: n)
18126         return d
18127 
18128-    def create_new_mutable_directory(self, initial_children={}):
18129-        # mutable directories will always be SDMF for now, to help
18130-        # compatibility with older clients.
18131-        version = SDMF_VERSION
18132+    def create_new_mutable_directory(self, initial_children={},
18133+                                     version=SDMF_VERSION):
18134         # initial_children must have metadata (i.e. {} instead of None)
18135         for (name, (node, metadata)) in initial_children.iteritems():
18136             precondition(isinstance(metadata, dict),
18137hunk ./src/allmydata/uri.py 750
18138         return None
18139 
18140 
18141+class MDMFDirectoryURI(_DirectoryBaseURI):
18142+    implements(IDirectoryURI)
18143+
18144+    BASE_STRING='URI:DIR2-MDMF:'
18145+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
18146+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF'+SEP)
18147+    INNER_URI_CLASS=WritableMDMFFileURI
18148+
18149+    def __init__(self, filenode_uri=None):
18150+        if filenode_uri:
18151+            assert not filenode_uri.is_readonly()
18152+        _DirectoryBaseURI.__init__(self, filenode_uri)
18153+
18154+    def is_readonly(self):
18155+        return False
18156+
18157+    def get_readonly(self):
18158+        return ReadonlyMDMFDirectoryURI(self._filenode_uri.get_readonly())
18159+
18160+    def get_verify_cap(self):
18161+        return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
18162+
18163+
18164+class ReadonlyMDMFDirectoryURI(_DirectoryBaseURI):
18165+    implements(IReadonlyDirectoryURI)
18166+
18167+    BASE_STRING='URI:DIR2-MDMF-RO:'
18168+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
18169+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-RO'+SEP)
18170+    INNER_URI_CLASS=ReadonlyMDMFFileURI
18171+
18172+    def __init__(self, filenode_uri=None):
18173+        if filenode_uri:
18174+            assert filenode_uri.is_readonly()
18175+        _DirectoryBaseURI.__init__(self, filenode_uri)
18176+
18177+    def is_readonly(self):
18178+        return True
18179+
18180+    def get_readonly(self):
18181+        return self
18182+
18183+    def get_verify_cap(self):
18184+        return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
18185+
18186 def wrap_dirnode_cap(filecap):
18187     if isinstance(filecap, WriteableSSKFileURI):
18188         return DirectoryURI(filecap)
18189hunk ./src/allmydata/uri.py 804
18190         return ImmutableDirectoryURI(filecap)
18191     if isinstance(filecap, LiteralFileURI):
18192         return LiteralDirectoryURI(filecap)
18193+    if isinstance(filecap, WritableMDMFFileURI):
18194+        return MDMFDirectoryURI(filecap)
18195+    if isinstance(filecap, ReadonlyMDMFFileURI):
18196+        return ReadonlyMDMFDirectoryURI(filecap)
18197     assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
18198 
18199hunk ./src/allmydata/uri.py 810
18200+class MDMFDirectoryURIVerifier(_DirectoryBaseURI):
18201+    implements(IVerifierURI)
18202+
18203+    BASE_STRING='URI:DIR2-MDMF-Verifier:'
18204+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
18205+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-Verifier'+SEP)
18206+    INNER_URI_CLASS=MDMFVerifierURI
18207+
18208+    def __init__(self, filenode_uri=None):
18209+        if filenode_uri:
18210+            assert IVerifierURI.providedBy(filenode_uri)
18211+        self._filenode_uri = filenode_uri
18212+
18213+    def get_filenode_cap(self):
18214+        return self._filenode_uri
18215+
18216+    def is_mutable(self):
18217+        return False
18218 
18219 class DirectoryURIVerifier(_DirectoryBaseURI):
18220     implements(IVerifierURI)
18221hunk ./src/allmydata/uri.py 935
18222             return ImmutableDirectoryURI.init_from_string(s)
18223         elif s.startswith('URI:DIR2-LIT:'):
18224             return LiteralDirectoryURI.init_from_string(s)
18225+        elif s.startswith('URI:DIR2-MDMF:'):
18226+            if can_be_writeable:
18227+                return MDMFDirectoryURI.init_from_string(s)
18228+            kind = "URI:DIR2-MDMF directory writecap"
18229+        elif s.startswith('URI:DIR2-MDMF-RO:'):
18230+            if can_be_mutable:
18231+                return ReadonlyMDMFDirectoryURI.init_from_string(s)
18232+            kind = "URI:DIR2-MDMF-RO readcap to a mutable directory"
18233         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
18234             # For testing how future writeable caps would behave in read-only contexts.
18235             kind = "x-tahoe-future-test-writeable: testing cap"
18236}
18237[Add tests for MDMF directories
18238Kevan Carstensen <kevan@isnotajoke.com>**20110617175950
18239 Ignore-this: 27882fd4cf827030d7574bd4b2b8cb77
18240] {
18241hunk ./src/allmydata/test/test_dirnode.py 14
18242 from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
18243      ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \
18244      MustBeDeepImmutableError, MustBeReadonlyError, \
18245-     IDeepCheckResults, IDeepCheckAndRepairResults
18246+     IDeepCheckResults, IDeepCheckAndRepairResults, \
18247+     MDMF_VERSION, SDMF_VERSION
18248 from allmydata.mutable.filenode import MutableFileNode
18249 from allmydata.mutable.common import UncoordinatedWriteError
18250 from allmydata.util import hashutil, base32
18251hunk ./src/allmydata/test/test_dirnode.py 61
18252               testutil.ReallyEqualMixin, testutil.ShouldFailMixin, testutil.StallMixin, ErrorMixin):
18253     timeout = 480 # It occasionally takes longer than 240 seconds on Francois's arm box.
18254 
18255-    def test_basic(self):
18256-        self.basedir = "dirnode/Dirnode/test_basic"
18257-        self.set_up_grid()
18258+    def _do_create_test(self, mdmf=False):
18259         c = self.g.clients[0]
18260hunk ./src/allmydata/test/test_dirnode.py 63
18261-        d = c.create_dirnode()
18262-        def _done(res):
18263-            self.failUnless(isinstance(res, dirnode.DirectoryNode))
18264-            self.failUnless(res.is_mutable())
18265-            self.failIf(res.is_readonly())
18266-            self.failIf(res.is_unknown())
18267-            self.failIf(res.is_allowed_in_immutable_directory())
18268-            res.raise_error()
18269-            rep = str(res)
18270-            self.failUnless("RW-MUT" in rep)
18271-        d.addCallback(_done)
18272+
18273+        self.expected_manifest = []
18274+        self.expected_verifycaps = set()
18275+        self.expected_storage_indexes = set()
18276+
18277+        d = None
18278+        if mdmf:
18279+            d = c.create_dirnode(version=MDMF_VERSION)
18280+        else:
18281+            d = c.create_dirnode()
18282+        def _then(n):
18283+            # /
18284+            self.rootnode = n
18285+            backing_node = n._node
18286+            if mdmf:
18287+                self.failUnlessEqual(backing_node.get_version(),
18288+                                     MDMF_VERSION)
18289+            else:
18290+                self.failUnlessEqual(backing_node.get_version(),
18291+                                     SDMF_VERSION)
18292+            self.failUnless(n.is_mutable())
18293+            u = n.get_uri()
18294+            self.failUnless(u)
18295+            cap_formats = []
18296+            if mdmf:
18297+                cap_formats = ["URI:DIR2-MDMF:",
18298+                               "URI:DIR2-MDMF-RO:",
18299+                               "URI:DIR2-MDMF-Verifier:"]
18300+            else:
18301+                cap_formats = ["URI:DIR2:",
18302+                               "URI:DIR2-RO",
18303+                               "URI:DIR2-Verifier:"]
18304+            rw, ro, v = cap_formats
18305+            self.failUnless(u.startswith(rw), u)
18306+            u_ro = n.get_readonly_uri()
18307+            self.failUnless(u_ro.startswith(ro), u_ro)
18308+            u_v = n.get_verify_cap().to_string()
18309+            self.failUnless(u_v.startswith(v), u_v)
18310+            u_r = n.get_repair_cap().to_string()
18311+            self.failUnlessReallyEqual(u_r, u)
18312+            self.expected_manifest.append( ((), u) )
18313+            self.expected_verifycaps.add(u_v)
18314+            si = n.get_storage_index()
18315+            self.expected_storage_indexes.add(base32.b2a(si))
18316+            expected_si = n._uri.get_storage_index()
18317+            self.failUnlessReallyEqual(si, expected_si)
18318+
18319+            d = n.list()
18320+            d.addCallback(lambda res: self.failUnlessEqual(res, {}))
18321+            d.addCallback(lambda res: n.has_child(u"missing"))
18322+            d.addCallback(lambda res: self.failIf(res))
18323+
18324+            fake_file_uri = make_mutable_file_uri()
18325+            other_file_uri = make_mutable_file_uri()
18326+            m = c.nodemaker.create_from_cap(fake_file_uri)
18327+            ffu_v = m.get_verify_cap().to_string()
18328+            self.expected_manifest.append( ((u"child",) , m.get_uri()) )
18329+            self.expected_verifycaps.add(ffu_v)
18330+            self.expected_storage_indexes.add(base32.b2a(m.get_storage_index()))
18331+            d.addCallback(lambda res: n.set_uri(u"child",
18332+                                                fake_file_uri, fake_file_uri))
18333+            d.addCallback(lambda res:
18334+                          self.shouldFail(ExistingChildError, "set_uri-no",
18335+                                          "child 'child' already exists",
18336+                                          n.set_uri, u"child",
18337+                                          other_file_uri, other_file_uri,
18338+                                          overwrite=False))
18339+            # /
18340+            # /child = mutable
18341+
18342+            d.addCallback(lambda res: n.create_subdirectory(u"subdir"))
18343+
18344+            # /
18345+            # /child = mutable
18346+            # /subdir = directory
18347+            def _created(subdir):
18348+                self.failUnless(isinstance(subdir, dirnode.DirectoryNode))
18349+                self.subdir = subdir
18350+                new_v = subdir.get_verify_cap().to_string()
18351+                assert isinstance(new_v, str)
18352+                self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
18353+                self.expected_verifycaps.add(new_v)
18354+                si = subdir.get_storage_index()
18355+                self.expected_storage_indexes.add(base32.b2a(si))
18356+            d.addCallback(_created)
18357+
18358+            d.addCallback(lambda res:
18359+                          self.shouldFail(ExistingChildError, "mkdir-no",
18360+                                          "child 'subdir' already exists",
18361+                                          n.create_subdirectory, u"subdir",
18362+                                          overwrite=False))
18363+
18364+            d.addCallback(lambda res: n.list())
18365+            d.addCallback(lambda children:
18366+                          self.failUnlessReallyEqual(set(children.keys()),
18367+                                                     set([u"child", u"subdir"])))
18368+
18369+            d.addCallback(lambda res: n.start_deep_stats().when_done())
18370+            def _check_deepstats(stats):
18371+                self.failUnless(isinstance(stats, dict))
18372+                expected = {"count-immutable-files": 0,
18373+                            "count-mutable-files": 1,
18374+                            "count-literal-files": 0,
18375+                            "count-files": 1,
18376+                            "count-directories": 2,
18377+                            "size-immutable-files": 0,
18378+                            "size-literal-files": 0,
18379+                            #"size-directories": 616, # varies
18380+                            #"largest-directory": 616,
18381+                            "largest-directory-children": 2,
18382+                            "largest-immutable-file": 0,
18383+                            }
18384+                for k,v in expected.iteritems():
18385+                    self.failUnlessReallyEqual(stats[k], v,
18386+                                               "stats[%s] was %s, not %s" %
18387+                                               (k, stats[k], v))
18388+                self.failUnless(stats["size-directories"] > 500,
18389+                                stats["size-directories"])
18390+                self.failUnless(stats["largest-directory"] > 500,
18391+                                stats["largest-directory"])
18392+                self.failUnlessReallyEqual(stats["size-files-histogram"], [])
18393+            d.addCallback(_check_deepstats)
18394+
18395+            d.addCallback(lambda res: n.build_manifest().when_done())
18396+            def _check_manifest(res):
18397+                manifest = res["manifest"]
18398+                self.failUnlessReallyEqual(sorted(manifest),
18399+                                           sorted(self.expected_manifest))
18400+                stats = res["stats"]
18401+                _check_deepstats(stats)
18402+                self.failUnlessReallyEqual(self.expected_verifycaps,
18403+                                           res["verifycaps"])
18404+                self.failUnlessReallyEqual(self.expected_storage_indexes,
18405+                                           res["storage-index"])
18406+            d.addCallback(_check_manifest)
18407+
18408+            def _add_subsubdir(res):
18409+                return self.subdir.create_subdirectory(u"subsubdir")
18410+            d.addCallback(_add_subsubdir)
18411+            # /
18412+            # /child = mutable
18413+            # /subdir = directory
18414+            # /subdir/subsubdir = directory
18415+            d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
18416+            d.addCallback(lambda subsubdir:
18417+                          self.failUnless(isinstance(subsubdir,
18418+                                                     dirnode.DirectoryNode)))
18419+            d.addCallback(lambda res: n.get_child_at_path(u""))
18420+            d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(),
18421+                                                                 n.get_uri()))
18422+
18423+            d.addCallback(lambda res: n.get_metadata_for(u"child"))
18424+            d.addCallback(lambda metadata:
18425+                          self.failUnlessEqual(set(metadata.keys()),
18426+                                               set(["tahoe"])))
18427+
18428+            d.addCallback(lambda res:
18429+                          self.shouldFail(NoSuchChildError, "gcamap-no",
18430+                                          "nope",
18431+                                          n.get_child_and_metadata_at_path,
18432+                                          u"subdir/nope"))
18433+            d.addCallback(lambda res:
18434+                          n.get_child_and_metadata_at_path(u""))
18435+            def _check_child_and_metadata1(res):
18436+                child, metadata = res
18437+                self.failUnless(isinstance(child, dirnode.DirectoryNode))
18438+                # edge-metadata needs at least one path segment
18439+                self.failUnlessEqual(set(metadata.keys()), set([]))
18440+            d.addCallback(_check_child_and_metadata1)
18441+            d.addCallback(lambda res:
18442+                          n.get_child_and_metadata_at_path(u"child"))
18443+
18444+            def _check_child_and_metadata2(res):
18445+                child, metadata = res
18446+                self.failUnlessReallyEqual(child.get_uri(),
18447+                                           fake_file_uri)
18448+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18449+            d.addCallback(_check_child_and_metadata2)
18450+
18451+            d.addCallback(lambda res:
18452+                          n.get_child_and_metadata_at_path(u"subdir/subsubdir"))
18453+            def _check_child_and_metadata3(res):
18454+                child, metadata = res
18455+                self.failUnless(isinstance(child, dirnode.DirectoryNode))
18456+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18457+            d.addCallback(_check_child_and_metadata3)
18458+
18459+            # set_uri + metadata
18460+            # it should be possible to add a child without any metadata
18461+            d.addCallback(lambda res: n.set_uri(u"c2",
18462+                                                fake_file_uri, fake_file_uri,
18463+                                                {}))
18464+            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
18465+            d.addCallback(lambda metadata:
18466+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18467+
18468+            # You can't override the link timestamps.
18469+            d.addCallback(lambda res: n.set_uri(u"c2",
18470+                                                fake_file_uri, fake_file_uri,
18471+                                                { 'tahoe': {'linkcrtime': "bogus"}}))
18472+            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
18473+            def _has_good_linkcrtime(metadata):
18474+                self.failUnless(metadata.has_key('tahoe'))
18475+                self.failUnless(metadata['tahoe'].has_key('linkcrtime'))
18476+                self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus')
18477+            d.addCallback(_has_good_linkcrtime)
18478+
18479+            # if we don't set any defaults, the child should get timestamps
18480+            d.addCallback(lambda res: n.set_uri(u"c3",
18481+                                                fake_file_uri, fake_file_uri))
18482+            d.addCallback(lambda res: n.get_metadata_for(u"c3"))
18483+            d.addCallback(lambda metadata:
18484+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18485+
18486+            # we can also add specific metadata at set_uri() time
18487+            d.addCallback(lambda res: n.set_uri(u"c4",
18488+                                                fake_file_uri, fake_file_uri,
18489+                                                {"key": "value"}))
18490+            d.addCallback(lambda res: n.get_metadata_for(u"c4"))
18491+            d.addCallback(lambda metadata:
18492+                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18493+                                              (metadata['key'] == "value"), metadata))
18494+
18495+            d.addCallback(lambda res: n.delete(u"c2"))
18496+            d.addCallback(lambda res: n.delete(u"c3"))
18497+            d.addCallback(lambda res: n.delete(u"c4"))
18498+
18499+            # set_node + metadata
18500+            # it should be possible to add a child without any metadata except for timestamps
18501+            d.addCallback(lambda res: n.set_node(u"d2", n, {}))
18502+            d.addCallback(lambda res: c.create_dirnode())
18503+            d.addCallback(lambda n2:
18504+                          self.shouldFail(ExistingChildError, "set_node-no",
18505+                                          "child 'd2' already exists",
18506+                                          n.set_node, u"d2", n2,
18507+                                          overwrite=False))
18508+            d.addCallback(lambda res: n.get_metadata_for(u"d2"))
18509+            d.addCallback(lambda metadata:
18510+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18511+
18512+            # if we don't set any defaults, the child should get timestamps
18513+            d.addCallback(lambda res: n.set_node(u"d3", n))
18514+            d.addCallback(lambda res: n.get_metadata_for(u"d3"))
18515+            d.addCallback(lambda metadata:
18516+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18517+
18518+            # we can also add specific metadata at set_node() time
18519+            d.addCallback(lambda res: n.set_node(u"d4", n,
18520+                                                {"key": "value"}))
18521+            d.addCallback(lambda res: n.get_metadata_for(u"d4"))
18522+            d.addCallback(lambda metadata:
18523+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18524+                                          (metadata["key"] == "value"), metadata))
18525+
18526+            d.addCallback(lambda res: n.delete(u"d2"))
18527+            d.addCallback(lambda res: n.delete(u"d3"))
18528+            d.addCallback(lambda res: n.delete(u"d4"))
18529+
18530+            # metadata through set_children()
18531+            d.addCallback(lambda res:
18532+                          n.set_children({
18533+                              u"e1": (fake_file_uri, fake_file_uri),
18534+                              u"e2": (fake_file_uri, fake_file_uri, {}),
18535+                              u"e3": (fake_file_uri, fake_file_uri,
18536+                                      {"key": "value"}),
18537+                              }))
18538+            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
18539+            d.addCallback(lambda res:
18540+                          self.shouldFail(ExistingChildError, "set_children-no",
18541+                                          "child 'e1' already exists",
18542+                                          n.set_children,
18543+                                          { u"e1": (other_file_uri,
18544+                                                    other_file_uri),
18545+                                            u"new": (other_file_uri,
18546+                                                     other_file_uri),
18547+                                            },
18548+                                          overwrite=False))
18549+            # and 'new' should not have been created
18550+            d.addCallback(lambda res: n.list())
18551+            d.addCallback(lambda children: self.failIf(u"new" in children))
18552+            d.addCallback(lambda res: n.get_metadata_for(u"e1"))
18553+            d.addCallback(lambda metadata:
18554+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18555+            d.addCallback(lambda res: n.get_metadata_for(u"e2"))
18556+            d.addCallback(lambda metadata:
18557+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18558+            d.addCallback(lambda res: n.get_metadata_for(u"e3"))
18559+            d.addCallback(lambda metadata:
18560+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18561+                                          (metadata["key"] == "value"), metadata))
18562+
18563+            d.addCallback(lambda res: n.delete(u"e1"))
18564+            d.addCallback(lambda res: n.delete(u"e2"))
18565+            d.addCallback(lambda res: n.delete(u"e3"))
18566+
18567+            # metadata through set_nodes()
18568+            d.addCallback(lambda res:
18569+                          n.set_nodes({ u"f1": (n, None),
18570+                                        u"f2": (n, {}),
18571+                                        u"f3": (n, {"key": "value"}),
18572+                                        }))
18573+            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
18574+            d.addCallback(lambda res:
18575+                          self.shouldFail(ExistingChildError, "set_nodes-no",
18576+                                          "child 'f1' already exists",
18577+                                          n.set_nodes, { u"f1": (n, None),
18578+                                                         u"new": (n, None), },
18579+                                          overwrite=False))
18580+            # and 'new' should not have been created
18581+            d.addCallback(lambda res: n.list())
18582+            d.addCallback(lambda children: self.failIf(u"new" in children))
18583+            d.addCallback(lambda res: n.get_metadata_for(u"f1"))
18584+            d.addCallback(lambda metadata:
18585+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18586+            d.addCallback(lambda res: n.get_metadata_for(u"f2"))
18587+            d.addCallback(lambda metadata:
18588+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18589+            d.addCallback(lambda res: n.get_metadata_for(u"f3"))
18590+            d.addCallback(lambda metadata:
18591+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18592+                                          (metadata["key"] == "value"), metadata))
18593+
18594+            d.addCallback(lambda res: n.delete(u"f1"))
18595+            d.addCallback(lambda res: n.delete(u"f2"))
18596+            d.addCallback(lambda res: n.delete(u"f3"))
18597+
18598+
18599+            d.addCallback(lambda res:
18600+                          n.set_metadata_for(u"child",
18601+                                             {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
18602+            d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
18603+            d.addCallback(lambda metadata:
18604+                          self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and
18605+                                          metadata["tags"] == ["web2.0-compatible"] and
18606+                                          "bad" not in metadata["tahoe"], metadata))
18607+
18608+            d.addCallback(lambda res:
18609+                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
18610+                                          n.set_metadata_for, u"nosuch", {}))
18611+
18612+
18613+            def _start(res):
18614+                self._start_timestamp = time.time()
18615+            d.addCallback(_start)
18616+            # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
18617+            # floats to hundredeths (it uses str(num) instead of repr(num)).
18618+            # simplejson-1.7.3 does not have this bug. To prevent this bug
18619+            # from causing the test to fail, stall for more than a few
18620+            # hundrededths of a second.
18621+            d.addCallback(self.stall, 0.1)
18622+            d.addCallback(lambda res: n.add_file(u"timestamps",
18623+                                                 upload.Data("stamp me", convergence="some convergence string")))
18624+            d.addCallback(self.stall, 0.1)
18625+            def _stop(res):
18626+                self._stop_timestamp = time.time()
18627+            d.addCallback(_stop)
18628+
18629+            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
18630+            def _check_timestamp1(metadata):
18631+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18632+                tahoe_md = metadata["tahoe"]
18633+                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
18634+
18635+                self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"],
18636+                                                  self._start_timestamp)
18637+                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
18638+                                                  tahoe_md["linkcrtime"])
18639+                self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"],
18640+                                                  self._start_timestamp)
18641+                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
18642+                                                  tahoe_md["linkmotime"])
18643+                # Our current timestamp rules say that replacing an existing
18644+                # child should preserve the 'linkcrtime' but update the
18645+                # 'linkmotime'
18646+                self._old_linkcrtime = tahoe_md["linkcrtime"]
18647+                self._old_linkmotime = tahoe_md["linkmotime"]
18648+            d.addCallback(_check_timestamp1)
18649+            d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
18650+            d.addCallback(lambda res: n.set_node(u"timestamps", n))
18651+            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
18652+            def _check_timestamp2(metadata):
18653+                self.failUnlessIn("tahoe", metadata)
18654+                tahoe_md = metadata["tahoe"]
18655+                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
18656+
18657+                self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime)
18658+                self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime)
18659+                return n.delete(u"timestamps")
18660+            d.addCallback(_check_timestamp2)
18661+
18662+            d.addCallback(lambda res: n.delete(u"subdir"))
18663+            d.addCallback(lambda old_child:
18664+                          self.failUnlessReallyEqual(old_child.get_uri(),
18665+                                                     self.subdir.get_uri()))
18666+
18667+            d.addCallback(lambda res: n.list())
18668+            d.addCallback(lambda children:
18669+                          self.failUnlessReallyEqual(set(children.keys()),
18670+                                                     set([u"child"])))
18671+
18672+            uploadable1 = upload.Data("some data", convergence="converge")
18673+            d.addCallback(lambda res: n.add_file(u"newfile", uploadable1))
18674+            d.addCallback(lambda newnode:
18675+                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
18676+            uploadable2 = upload.Data("some data", convergence="stuff")
18677+            d.addCallback(lambda res:
18678+                          self.shouldFail(ExistingChildError, "add_file-no",
18679+                                          "child 'newfile' already exists",
18680+                                          n.add_file, u"newfile",
18681+                                          uploadable2,
18682+                                          overwrite=False))
18683+            d.addCallback(lambda res: n.list())
18684+            d.addCallback(lambda children:
18685+                          self.failUnlessReallyEqual(set(children.keys()),
18686+                                                     set([u"child", u"newfile"])))
18687+            d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
18688+            d.addCallback(lambda metadata:
18689+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18690+
18691+            uploadable3 = upload.Data("some data", convergence="converge")
18692+            d.addCallback(lambda res: n.add_file(u"newfile-metadata",
18693+                                                 uploadable3,
18694+                                                 {"key": "value"}))
18695+            d.addCallback(lambda newnode:
18696+                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
18697+            d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
18698+            d.addCallback(lambda metadata:
18699+                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18700+                                              (metadata['key'] == "value"), metadata))
18701+            d.addCallback(lambda res: n.delete(u"newfile-metadata"))
18702+
18703+            d.addCallback(lambda res: n.create_subdirectory(u"subdir2"))
18704+            def _created2(subdir2):
18705+                self.subdir2 = subdir2
18706+                # put something in the way, to make sure it gets overwritten
18707+                return subdir2.add_file(u"child", upload.Data("overwrite me",
18708+                                                              "converge"))
18709+            d.addCallback(_created2)
18710+
18711+            d.addCallback(lambda res:
18712+                          n.move_child_to(u"child", self.subdir2))
18713+            d.addCallback(lambda res: n.list())
18714+            d.addCallback(lambda children:
18715+                          self.failUnlessReallyEqual(set(children.keys()),
18716+                                                     set([u"newfile", u"subdir2"])))
18717+            d.addCallback(lambda res: self.subdir2.list())
18718+            d.addCallback(lambda children:
18719+                          self.failUnlessReallyEqual(set(children.keys()),
18720+                                                     set([u"child"])))
18721+            d.addCallback(lambda res: self.subdir2.get(u"child"))
18722+            d.addCallback(lambda child:
18723+                          self.failUnlessReallyEqual(child.get_uri(),
18724+                                                     fake_file_uri))
18725+
18726+            # move it back, using new_child_name=
18727+            d.addCallback(lambda res:
18728+                          self.subdir2.move_child_to(u"child", n, u"newchild"))
18729+            d.addCallback(lambda res: n.list())
18730+            d.addCallback(lambda children:
18731+                          self.failUnlessReallyEqual(set(children.keys()),
18732+                                                     set([u"newchild", u"newfile",
18733+                                                          u"subdir2"])))
18734+            d.addCallback(lambda res: self.subdir2.list())
18735+            d.addCallback(lambda children:
18736+                          self.failUnlessReallyEqual(set(children.keys()), set([])))
18737+
18738+            # now make sure that we honor overwrite=False
18739+            d.addCallback(lambda res:
18740+                          self.subdir2.set_uri(u"newchild",
18741+                                               other_file_uri, other_file_uri))
18742+
18743+            d.addCallback(lambda res:
18744+                          self.shouldFail(ExistingChildError, "move_child_to-no",
18745+                                          "child 'newchild' already exists",
18746+                                          n.move_child_to, u"newchild",
18747+                                          self.subdir2,
18748+                                          overwrite=False))
18749+            d.addCallback(lambda res: self.subdir2.get(u"newchild"))
18750+            d.addCallback(lambda child:
18751+                          self.failUnlessReallyEqual(child.get_uri(),
18752+                                                     other_file_uri))
18753+
18754+
18755+            # Setting the no-write field should diminish a mutable cap to read-only
18756+            # (for both files and directories).
18757+
18758+            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
18759+            d.addCallback(lambda ign: n.get(u"mutable"))
18760+            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
18761+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
18762+            d.addCallback(lambda ign: n.get(u"mutable"))
18763+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
18764+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
18765+            d.addCallback(lambda ign: n.get(u"mutable"))
18766+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
18767+
18768+            d.addCallback(lambda ign: n.get(u"subdir2"))
18769+            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
18770+            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
18771+            d.addCallback(lambda ign: n.get(u"subdir2"))
18772+            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
18773+
18774+            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
18775+                                                metadata={"no-write": True}))
18776+            d.addCallback(lambda ign: n.get(u"mutable_ro"))
18777+            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
18778+
18779+            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
18780+            d.addCallback(lambda ign: n.get(u"subdir_ro"))
18781+            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
18782+
18783+            return d
18784+
18785+        d.addCallback(_then)
18786+
18787+        d.addErrback(self.explain_error)
18788         return d
18789 
18790hunk ./src/allmydata/test/test_dirnode.py 581
18791-    def test_initial_children(self):
18792-        self.basedir = "dirnode/Dirnode/test_initial_children"
18793-        self.set_up_grid()
18794+
18795+    def _do_initial_children_test(self, mdmf=False):
18796         c = self.g.clients[0]
18797         nm = c.nodemaker
18798 
18799hunk ./src/allmydata/test/test_dirnode.py 597
18800                 u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}),
18801                 u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}),
18802                 }
18803-        d = c.create_dirnode(kids)
18804-       
18805+        d = None
18806+        if mdmf:
18807+            d = c.create_dirnode(kids, version=MDMF_VERSION)
18808+        else:
18809+            d = c.create_dirnode(kids)
18810         def _created(dn):
18811             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
18812hunk ./src/allmydata/test/test_dirnode.py 604
18813+            backing_node = dn._node
18814+            if mdmf:
18815+                self.failUnlessEqual(backing_node.get_version(),
18816+                                     MDMF_VERSION)
18817+            else:
18818+                self.failUnlessEqual(backing_node.get_version(),
18819+                                     SDMF_VERSION)
18820             self.failUnless(dn.is_mutable())
18821             self.failIf(dn.is_readonly())
18822             self.failIf(dn.is_unknown())
18823hunk ./src/allmydata/test/test_dirnode.py 619
18824             rep = str(dn)
18825             self.failUnless("RW-MUT" in rep)
18826             return dn.list()
18827-        d.addCallback(_created)
18828-       
18829+
18830         def _check_kids(children):
18831             self.failUnlessReallyEqual(set(children.keys()),
18832                                        set([one_nfc, u"two", u"mut", u"fut", u"fro",
18833hunk ./src/allmydata/test/test_dirnode.py 623
18834-                                            u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"]))
18835+                                        u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"]))
18836             one_node, one_metadata = children[one_nfc]
18837             two_node, two_metadata = children[u"two"]
18838             mut_node, mut_metadata = children[u"mut"]
18839hunk ./src/allmydata/test/test_dirnode.py 683
18840             d2.addCallback(lambda children: children[u"short"][0].read(MemAccum()))
18841             d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end."))
18842             return d2
18843-
18844+        d.addCallback(_created)
18845         d.addCallback(_check_kids)
18846 
18847         d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
18848hunk ./src/allmydata/test/test_dirnode.py 707
18849                                       bad_kids2))
18850         return d
18851 
18852+    def _do_basic_test(self, mdmf=False):
18853+        c = self.g.clients[0]
18854+        d = None
18855+        if mdmf:
18856+            d = c.create_dirnode(version=MDMF_VERSION)
18857+        else:
18858+            d = c.create_dirnode()
18859+        def _done(res):
18860+            self.failUnless(isinstance(res, dirnode.DirectoryNode))
18861+            self.failUnless(res.is_mutable())
18862+            self.failIf(res.is_readonly())
18863+            self.failIf(res.is_unknown())
18864+            self.failIf(res.is_allowed_in_immutable_directory())
18865+            res.raise_error()
18866+            rep = str(res)
18867+            self.failUnless("RW-MUT" in rep)
18868+        d.addCallback(_done)
18869+        return d
18870+
18871+    def test_basic(self):
18872+        self.basedir = "dirnode/Dirnode/test_basic"
18873+        self.set_up_grid()
18874+        return self._do_basic_test()
18875+
18876+    def test_basic_mdmf(self):
18877+        self.basedir = "dirnode/Dirnode/test_basic_mdmf"
18878+        self.set_up_grid()
18879+        return self._do_basic_test(mdmf=True)
18880+
18881+    def test_initial_children(self):
18882+        self.basedir = "dirnode/Dirnode/test_initial_children"
18883+        self.set_up_grid()
18884+        return self._do_initial_children_test()
18885+
18886     def test_immutable(self):
18887         self.basedir = "dirnode/Dirnode/test_immutable"
18888         self.set_up_grid()
18889hunk ./src/allmydata/test/test_dirnode.py 1025
18890         d.addCallback(_done)
18891         return d
18892 
18893-    def _test_deepcheck_create(self):
18894+    def _test_deepcheck_create(self, version=SDMF_VERSION):
18895         # create a small tree with a loop, and some non-directories
18896         #  root/
18897         #  root/subdir/
18898hunk ./src/allmydata/test/test_dirnode.py 1033
18899         #  root/subdir/link -> root
18900         #  root/rodir
18901         c = self.g.clients[0]
18902-        d = c.create_dirnode()
18903+        d = c.create_dirnode(version=version)
18904         def _created_root(rootnode):
18905             self._rootnode = rootnode
18906hunk ./src/allmydata/test/test_dirnode.py 1036
18907+            self.failUnlessEqual(rootnode._node.get_version(), version)
18908             return rootnode.create_subdirectory(u"subdir")
18909         d.addCallback(_created_root)
18910         def _created_subdir(subdir):
18911hunk ./src/allmydata/test/test_dirnode.py 1075
18912         d.addCallback(_check_results)
18913         return d
18914 
18915+    def test_deepcheck_mdmf(self):
18916+        self.basedir = "dirnode/Dirnode/test_deepcheck_mdmf"
18917+        self.set_up_grid()
18918+        d = self._test_deepcheck_create(MDMF_VERSION)
18919+        d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done())
18920+        def _check_results(r):
18921+            self.failUnless(IDeepCheckResults.providedBy(r))
18922+            c = r.get_counters()
18923+            self.failUnlessReallyEqual(c,
18924+                                       {"count-objects-checked": 4,
18925+                                        "count-objects-healthy": 4,
18926+                                        "count-objects-unhealthy": 0,
18927+                                        "count-objects-unrecoverable": 0,
18928+                                        "count-corrupt-shares": 0,
18929+                                        })
18930+            self.failIf(r.get_corrupt_shares())
18931+            self.failUnlessReallyEqual(len(r.get_all_results()), 4)
18932+        d.addCallback(_check_results)
18933+        return d
18934+
18935     def test_deepcheck_and_repair(self):
18936         self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair"
18937         self.set_up_grid()
18938hunk ./src/allmydata/test/test_dirnode.py 1124
18939         d.addCallback(_check_results)
18940         return d
18941 
18942+    def test_deepcheck_and_repair_mdmf(self):
18943+        self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair_mdmf"
18944+        self.set_up_grid()
18945+        d = self._test_deepcheck_create(version=MDMF_VERSION)
18946+        d.addCallback(lambda rootnode:
18947+                      rootnode.start_deep_check_and_repair().when_done())
18948+        def _check_results(r):
18949+            self.failUnless(IDeepCheckAndRepairResults.providedBy(r))
18950+            c = r.get_counters()
18951+            self.failUnlessReallyEqual(c,
18952+                                       {"count-objects-checked": 4,
18953+                                        "count-objects-healthy-pre-repair": 4,
18954+                                        "count-objects-unhealthy-pre-repair": 0,
18955+                                        "count-objects-unrecoverable-pre-repair": 0,
18956+                                        "count-corrupt-shares-pre-repair": 0,
18957+                                        "count-objects-healthy-post-repair": 4,
18958+                                        "count-objects-unhealthy-post-repair": 0,
18959+                                        "count-objects-unrecoverable-post-repair": 0,
18960+                                        "count-corrupt-shares-post-repair": 0,
18961+                                        "count-repairs-attempted": 0,
18962+                                        "count-repairs-successful": 0,
18963+                                        "count-repairs-unsuccessful": 0,
18964+                                        })
18965+            self.failIf(r.get_corrupt_shares())
18966+            self.failIf(r.get_remaining_corrupt_shares())
18967+            self.failUnlessReallyEqual(len(r.get_all_results()), 4)
18968+        d.addCallback(_check_results)
18969+        return d
18970+
18971     def _mark_file_bad(self, rootnode):
18972         self.delete_shares_numbered(rootnode.get_uri(), [0])
18973         return rootnode
18974hunk ./src/allmydata/test/test_dirnode.py 1176
18975         d.addCallback(_check_results)
18976         return d
18977 
18978-    def test_readonly(self):
18979-        self.basedir = "dirnode/Dirnode/test_readonly"
18980+    def test_deepcheck_problems_mdmf(self):
18981+        self.basedir = "dirnode/Dirnode/test_deepcheck_problems_mdmf"
18982         self.set_up_grid()
18983hunk ./src/allmydata/test/test_dirnode.py 1179
18984+        d = self._test_deepcheck_create(version=MDMF_VERSION)
18985+        d.addCallback(lambda rootnode: self._mark_file_bad(rootnode))
18986+        d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done())
18987+        def _check_results(r):
18988+            c = r.get_counters()
18989+            self.failUnlessReallyEqual(c,
18990+                                       {"count-objects-checked": 4,
18991+                                        "count-objects-healthy": 3,
18992+                                        "count-objects-unhealthy": 1,
18993+                                        "count-objects-unrecoverable": 0,
18994+                                        "count-corrupt-shares": 0,
18995+                                        })
18996+            #self.failUnlessReallyEqual(len(r.get_problems()), 1) # TODO
18997+        d.addCallback(_check_results)
18998+        return d
18999+
19000+    def _do_readonly_test(self, version=SDMF_VERSION):
19001         c = self.g.clients[0]
19002         nm = c.nodemaker
19003         filecap = make_chk_file_uri(1234)
19004hunk ./src/allmydata/test/test_dirnode.py 1202
19005         filenode = nm.create_from_cap(filecap)
19006         uploadable = upload.Data("some data", convergence="some convergence string")
19007 
19008-        d = c.create_dirnode()
19009+        d = c.create_dirnode(version=version)
19010         def _created(rw_dn):
19011hunk ./src/allmydata/test/test_dirnode.py 1204
19012+            backing_node = rw_dn._node
19013+            self.failUnlessEqual(backing_node.get_version(), version)
19014             d2 = rw_dn.set_uri(u"child", filecap, filecap)
19015             d2.addCallback(lambda res: rw_dn)
19016             return d2
19017hunk ./src/allmydata/test/test_dirnode.py 1245
19018         d.addCallback(_listed)
19019         return d
19020 
19021+    def test_readonly(self):
19022+        self.basedir = "dirnode/Dirnode/test_readonly"
19023+        self.set_up_grid()
19024+        return self._do_readonly_test()
19025+
19026+    def test_readonly_mdmf(self):
19027+        self.basedir = "dirnode/Dirnode/test_readonly_mdmf"
19028+        self.set_up_grid()
19029+        return self._do_readonly_test(version=MDMF_VERSION)
19030+
19031     def failUnlessGreaterThan(self, a, b):
19032         self.failUnless(a > b, "%r should be > %r" % (a, b))
19033 
19034hunk ./src/allmydata/test/test_dirnode.py 1264
19035     def test_create(self):
19036         self.basedir = "dirnode/Dirnode/test_create"
19037         self.set_up_grid()
19038-        c = self.g.clients[0]
19039-
19040-        self.expected_manifest = []
19041-        self.expected_verifycaps = set()
19042-        self.expected_storage_indexes = set()
19043-
19044-        d = c.create_dirnode()
19045-        def _then(n):
19046-            # /
19047-            self.rootnode = n
19048-            self.failUnless(n.is_mutable())
19049-            u = n.get_uri()
19050-            self.failUnless(u)
19051-            self.failUnless(u.startswith("URI:DIR2:"), u)
19052-            u_ro = n.get_readonly_uri()
19053-            self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
19054-            u_v = n.get_verify_cap().to_string()
19055-            self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
19056-            u_r = n.get_repair_cap().to_string()
19057-            self.failUnlessReallyEqual(u_r, u)
19058-            self.expected_manifest.append( ((), u) )
19059-            self.expected_verifycaps.add(u_v)
19060-            si = n.get_storage_index()
19061-            self.expected_storage_indexes.add(base32.b2a(si))
19062-            expected_si = n._uri.get_storage_index()
19063-            self.failUnlessReallyEqual(si, expected_si)
19064-
19065-            d = n.list()
19066-            d.addCallback(lambda res: self.failUnlessEqual(res, {}))
19067-            d.addCallback(lambda res: n.has_child(u"missing"))
19068-            d.addCallback(lambda res: self.failIf(res))
19069-
19070-            fake_file_uri = make_mutable_file_uri()
19071-            other_file_uri = make_mutable_file_uri()
19072-            m = c.nodemaker.create_from_cap(fake_file_uri)
19073-            ffu_v = m.get_verify_cap().to_string()
19074-            self.expected_manifest.append( ((u"child",) , m.get_uri()) )
19075-            self.expected_verifycaps.add(ffu_v)
19076-            self.expected_storage_indexes.add(base32.b2a(m.get_storage_index()))
19077-            d.addCallback(lambda res: n.set_uri(u"child",
19078-                                                fake_file_uri, fake_file_uri))
19079-            d.addCallback(lambda res:
19080-                          self.shouldFail(ExistingChildError, "set_uri-no",
19081-                                          "child 'child' already exists",
19082-                                          n.set_uri, u"child",
19083-                                          other_file_uri, other_file_uri,
19084-                                          overwrite=False))
19085-            # /
19086-            # /child = mutable
19087-
19088-            d.addCallback(lambda res: n.create_subdirectory(u"subdir"))
19089-
19090-            # /
19091-            # /child = mutable
19092-            # /subdir = directory
19093-            def _created(subdir):
19094-                self.failUnless(isinstance(subdir, dirnode.DirectoryNode))
19095-                self.subdir = subdir
19096-                new_v = subdir.get_verify_cap().to_string()
19097-                assert isinstance(new_v, str)
19098-                self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
19099-                self.expected_verifycaps.add(new_v)
19100-                si = subdir.get_storage_index()
19101-                self.expected_storage_indexes.add(base32.b2a(si))
19102-            d.addCallback(_created)
19103-
19104-            d.addCallback(lambda res:
19105-                          self.shouldFail(ExistingChildError, "mkdir-no",
19106-                                          "child 'subdir' already exists",
19107-                                          n.create_subdirectory, u"subdir",
19108-                                          overwrite=False))
19109-
19110-            d.addCallback(lambda res: n.list())
19111-            d.addCallback(lambda children:
19112-                          self.failUnlessReallyEqual(set(children.keys()),
19113-                                                     set([u"child", u"subdir"])))
19114-
19115-            d.addCallback(lambda res: n.start_deep_stats().when_done())
19116-            def _check_deepstats(stats):
19117-                self.failUnless(isinstance(stats, dict))
19118-                expected = {"count-immutable-files": 0,
19119-                            "count-mutable-files": 1,
19120-                            "count-literal-files": 0,
19121-                            "count-files": 1,
19122-                            "count-directories": 2,
19123-                            "size-immutable-files": 0,
19124-                            "size-literal-files": 0,
19125-                            #"size-directories": 616, # varies
19126-                            #"largest-directory": 616,
19127-                            "largest-directory-children": 2,
19128-                            "largest-immutable-file": 0,
19129-                            }
19130-                for k,v in expected.iteritems():
19131-                    self.failUnlessReallyEqual(stats[k], v,
19132-                                               "stats[%s] was %s, not %s" %
19133-                                               (k, stats[k], v))
19134-                self.failUnless(stats["size-directories"] > 500,
19135-                                stats["size-directories"])
19136-                self.failUnless(stats["largest-directory"] > 500,
19137-                                stats["largest-directory"])
19138-                self.failUnlessReallyEqual(stats["size-files-histogram"], [])
19139-            d.addCallback(_check_deepstats)
19140-
19141-            d.addCallback(lambda res: n.build_manifest().when_done())
19142-            def _check_manifest(res):
19143-                manifest = res["manifest"]
19144-                self.failUnlessReallyEqual(sorted(manifest),
19145-                                           sorted(self.expected_manifest))
19146-                stats = res["stats"]
19147-                _check_deepstats(stats)
19148-                self.failUnlessReallyEqual(self.expected_verifycaps,
19149-                                           res["verifycaps"])
19150-                self.failUnlessReallyEqual(self.expected_storage_indexes,
19151-                                           res["storage-index"])
19152-            d.addCallback(_check_manifest)
19153-
19154-            def _add_subsubdir(res):
19155-                return self.subdir.create_subdirectory(u"subsubdir")
19156-            d.addCallback(_add_subsubdir)
19157-            # /
19158-            # /child = mutable
19159-            # /subdir = directory
19160-            # /subdir/subsubdir = directory
19161-            d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
19162-            d.addCallback(lambda subsubdir:
19163-                          self.failUnless(isinstance(subsubdir,
19164-                                                     dirnode.DirectoryNode)))
19165-            d.addCallback(lambda res: n.get_child_at_path(u""))
19166-            d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(),
19167-                                                                 n.get_uri()))
19168-
19169-            d.addCallback(lambda res: n.get_metadata_for(u"child"))
19170-            d.addCallback(lambda metadata:
19171-                          self.failUnlessEqual(set(metadata.keys()),
19172-                                               set(["tahoe"])))
19173-
19174-            d.addCallback(lambda res:
19175-                          self.shouldFail(NoSuchChildError, "gcamap-no",
19176-                                          "nope",
19177-                                          n.get_child_and_metadata_at_path,
19178-                                          u"subdir/nope"))
19179-            d.addCallback(lambda res:
19180-                          n.get_child_and_metadata_at_path(u""))
19181-            def _check_child_and_metadata1(res):
19182-                child, metadata = res
19183-                self.failUnless(isinstance(child, dirnode.DirectoryNode))
19184-                # edge-metadata needs at least one path segment
19185-                self.failUnlessEqual(set(metadata.keys()), set([]))
19186-            d.addCallback(_check_child_and_metadata1)
19187-            d.addCallback(lambda res:
19188-                          n.get_child_and_metadata_at_path(u"child"))
19189-
19190-            def _check_child_and_metadata2(res):
19191-                child, metadata = res
19192-                self.failUnlessReallyEqual(child.get_uri(),
19193-                                           fake_file_uri)
19194-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
19195-            d.addCallback(_check_child_and_metadata2)
19196-
19197-            d.addCallback(lambda res:
19198-                          n.get_child_and_metadata_at_path(u"subdir/subsubdir"))
19199-            def _check_child_and_metadata3(res):
19200-                child, metadata = res
19201-                self.failUnless(isinstance(child, dirnode.DirectoryNode))
19202-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
19203-            d.addCallback(_check_child_and_metadata3)
19204-
19205-            # set_uri + metadata
19206-            # it should be possible to add a child without any metadata
19207-            d.addCallback(lambda res: n.set_uri(u"c2",
19208-                                                fake_file_uri, fake_file_uri,
19209-                                                {}))
19210-            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
19211-            d.addCallback(lambda metadata:
19212-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19213-
19214-            # You can't override the link timestamps.
19215-            d.addCallback(lambda res: n.set_uri(u"c2",
19216-                                                fake_file_uri, fake_file_uri,
19217-                                                { 'tahoe': {'linkcrtime': "bogus"}}))
19218-            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
19219-            def _has_good_linkcrtime(metadata):
19220-                self.failUnless(metadata.has_key('tahoe'))
19221-                self.failUnless(metadata['tahoe'].has_key('linkcrtime'))
19222-                self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus')
19223-            d.addCallback(_has_good_linkcrtime)
19224-
19225-            # if we don't set any defaults, the child should get timestamps
19226-            d.addCallback(lambda res: n.set_uri(u"c3",
19227-                                                fake_file_uri, fake_file_uri))
19228-            d.addCallback(lambda res: n.get_metadata_for(u"c3"))
19229-            d.addCallback(lambda metadata:
19230-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19231-
19232-            # we can also add specific metadata at set_uri() time
19233-            d.addCallback(lambda res: n.set_uri(u"c4",
19234-                                                fake_file_uri, fake_file_uri,
19235-                                                {"key": "value"}))
19236-            d.addCallback(lambda res: n.get_metadata_for(u"c4"))
19237-            d.addCallback(lambda metadata:
19238-                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19239-                                              (metadata['key'] == "value"), metadata))
19240-
19241-            d.addCallback(lambda res: n.delete(u"c2"))
19242-            d.addCallback(lambda res: n.delete(u"c3"))
19243-            d.addCallback(lambda res: n.delete(u"c4"))
19244-
19245-            # set_node + metadata
19246-            # it should be possible to add a child without any metadata except for timestamps
19247-            d.addCallback(lambda res: n.set_node(u"d2", n, {}))
19248-            d.addCallback(lambda res: c.create_dirnode())
19249-            d.addCallback(lambda n2:
19250-                          self.shouldFail(ExistingChildError, "set_node-no",
19251-                                          "child 'd2' already exists",
19252-                                          n.set_node, u"d2", n2,
19253-                                          overwrite=False))
19254-            d.addCallback(lambda res: n.get_metadata_for(u"d2"))
19255-            d.addCallback(lambda metadata:
19256-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19257-
19258-            # if we don't set any defaults, the child should get timestamps
19259-            d.addCallback(lambda res: n.set_node(u"d3", n))
19260-            d.addCallback(lambda res: n.get_metadata_for(u"d3"))
19261-            d.addCallback(lambda metadata:
19262-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19263-
19264-            # we can also add specific metadata at set_node() time
19265-            d.addCallback(lambda res: n.set_node(u"d4", n,
19266-                                                {"key": "value"}))
19267-            d.addCallback(lambda res: n.get_metadata_for(u"d4"))
19268-            d.addCallback(lambda metadata:
19269-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19270-                                          (metadata["key"] == "value"), metadata))
19271-
19272-            d.addCallback(lambda res: n.delete(u"d2"))
19273-            d.addCallback(lambda res: n.delete(u"d3"))
19274-            d.addCallback(lambda res: n.delete(u"d4"))
19275-
19276-            # metadata through set_children()
19277-            d.addCallback(lambda res:
19278-                          n.set_children({
19279-                              u"e1": (fake_file_uri, fake_file_uri),
19280-                              u"e2": (fake_file_uri, fake_file_uri, {}),
19281-                              u"e3": (fake_file_uri, fake_file_uri,
19282-                                      {"key": "value"}),
19283-                              }))
19284-            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
19285-            d.addCallback(lambda res:
19286-                          self.shouldFail(ExistingChildError, "set_children-no",
19287-                                          "child 'e1' already exists",
19288-                                          n.set_children,
19289-                                          { u"e1": (other_file_uri,
19290-                                                    other_file_uri),
19291-                                            u"new": (other_file_uri,
19292-                                                     other_file_uri),
19293-                                            },
19294-                                          overwrite=False))
19295-            # and 'new' should not have been created
19296-            d.addCallback(lambda res: n.list())
19297-            d.addCallback(lambda children: self.failIf(u"new" in children))
19298-            d.addCallback(lambda res: n.get_metadata_for(u"e1"))
19299-            d.addCallback(lambda metadata:
19300-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19301-            d.addCallback(lambda res: n.get_metadata_for(u"e2"))
19302-            d.addCallback(lambda metadata:
19303-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19304-            d.addCallback(lambda res: n.get_metadata_for(u"e3"))
19305-            d.addCallback(lambda metadata:
19306-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19307-                                          (metadata["key"] == "value"), metadata))
19308-
19309-            d.addCallback(lambda res: n.delete(u"e1"))
19310-            d.addCallback(lambda res: n.delete(u"e2"))
19311-            d.addCallback(lambda res: n.delete(u"e3"))
19312-
19313-            # metadata through set_nodes()
19314-            d.addCallback(lambda res:
19315-                          n.set_nodes({ u"f1": (n, None),
19316-                                        u"f2": (n, {}),
19317-                                        u"f3": (n, {"key": "value"}),
19318-                                        }))
19319-            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
19320-            d.addCallback(lambda res:
19321-                          self.shouldFail(ExistingChildError, "set_nodes-no",
19322-                                          "child 'f1' already exists",
19323-                                          n.set_nodes, { u"f1": (n, None),
19324-                                                         u"new": (n, None), },
19325-                                          overwrite=False))
19326-            # and 'new' should not have been created
19327-            d.addCallback(lambda res: n.list())
19328-            d.addCallback(lambda children: self.failIf(u"new" in children))
19329-            d.addCallback(lambda res: n.get_metadata_for(u"f1"))
19330-            d.addCallback(lambda metadata:
19331-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19332-            d.addCallback(lambda res: n.get_metadata_for(u"f2"))
19333-            d.addCallback(lambda metadata:
19334-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19335-            d.addCallback(lambda res: n.get_metadata_for(u"f3"))
19336-            d.addCallback(lambda metadata:
19337-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19338-                                          (metadata["key"] == "value"), metadata))
19339-
19340-            d.addCallback(lambda res: n.delete(u"f1"))
19341-            d.addCallback(lambda res: n.delete(u"f2"))
19342-            d.addCallback(lambda res: n.delete(u"f3"))
19343-
19344-
19345-            d.addCallback(lambda res:
19346-                          n.set_metadata_for(u"child",
19347-                                             {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
19348-            d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
19349-            d.addCallback(lambda metadata:
19350-                          self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and
19351-                                          metadata["tags"] == ["web2.0-compatible"] and
19352-                                          "bad" not in metadata["tahoe"], metadata))
19353-
19354-            d.addCallback(lambda res:
19355-                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
19356-                                          n.set_metadata_for, u"nosuch", {}))
19357-
19358-
19359-            def _start(res):
19360-                self._start_timestamp = time.time()
19361-            d.addCallback(_start)
19362-            # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
19363-            # floats to hundredeths (it uses str(num) instead of repr(num)).
19364-            # simplejson-1.7.3 does not have this bug. To prevent this bug
19365-            # from causing the test to fail, stall for more than a few
19366-            # hundrededths of a second.
19367-            d.addCallback(self.stall, 0.1)
19368-            d.addCallback(lambda res: n.add_file(u"timestamps",
19369-                                                 upload.Data("stamp me", convergence="some convergence string")))
19370-            d.addCallback(self.stall, 0.1)
19371-            def _stop(res):
19372-                self._stop_timestamp = time.time()
19373-            d.addCallback(_stop)
19374-
19375-            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
19376-            def _check_timestamp1(metadata):
19377-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
19378-                tahoe_md = metadata["tahoe"]
19379-                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
19380-
19381-                self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"],
19382-                                                  self._start_timestamp)
19383-                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
19384-                                                  tahoe_md["linkcrtime"])
19385-                self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"],
19386-                                                  self._start_timestamp)
19387-                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
19388-                                                  tahoe_md["linkmotime"])
19389-                # Our current timestamp rules say that replacing an existing
19390-                # child should preserve the 'linkcrtime' but update the
19391-                # 'linkmotime'
19392-                self._old_linkcrtime = tahoe_md["linkcrtime"]
19393-                self._old_linkmotime = tahoe_md["linkmotime"]
19394-            d.addCallback(_check_timestamp1)
19395-            d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
19396-            d.addCallback(lambda res: n.set_node(u"timestamps", n))
19397-            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
19398-            def _check_timestamp2(metadata):
19399-                self.failUnlessIn("tahoe", metadata)
19400-                tahoe_md = metadata["tahoe"]
19401-                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
19402-
19403-                self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime)
19404-                self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime)
19405-                return n.delete(u"timestamps")
19406-            d.addCallback(_check_timestamp2)
19407-
19408-            d.addCallback(lambda res: n.delete(u"subdir"))
19409-            d.addCallback(lambda old_child:
19410-                          self.failUnlessReallyEqual(old_child.get_uri(),
19411-                                                     self.subdir.get_uri()))
19412-
19413-            d.addCallback(lambda res: n.list())
19414-            d.addCallback(lambda children:
19415-                          self.failUnlessReallyEqual(set(children.keys()),
19416-                                                     set([u"child"])))
19417-
19418-            uploadable1 = upload.Data("some data", convergence="converge")
19419-            d.addCallback(lambda res: n.add_file(u"newfile", uploadable1))
19420-            d.addCallback(lambda newnode:
19421-                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
19422-            uploadable2 = upload.Data("some data", convergence="stuff")
19423-            d.addCallback(lambda res:
19424-                          self.shouldFail(ExistingChildError, "add_file-no",
19425-                                          "child 'newfile' already exists",
19426-                                          n.add_file, u"newfile",
19427-                                          uploadable2,
19428-                                          overwrite=False))
19429-            d.addCallback(lambda res: n.list())
19430-            d.addCallback(lambda children:
19431-                          self.failUnlessReallyEqual(set(children.keys()),
19432-                                                     set([u"child", u"newfile"])))
19433-            d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
19434-            d.addCallback(lambda metadata:
19435-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
19436-
19437-            uploadable3 = upload.Data("some data", convergence="converge")
19438-            d.addCallback(lambda res: n.add_file(u"newfile-metadata",
19439-                                                 uploadable3,
19440-                                                 {"key": "value"}))
19441-            d.addCallback(lambda newnode:
19442-                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
19443-            d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
19444-            d.addCallback(lambda metadata:
19445-                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
19446-                                              (metadata['key'] == "value"), metadata))
19447-            d.addCallback(lambda res: n.delete(u"newfile-metadata"))
19448-
19449-            d.addCallback(lambda res: n.create_subdirectory(u"subdir2"))
19450-            def _created2(subdir2):
19451-                self.subdir2 = subdir2
19452-                # put something in the way, to make sure it gets overwritten
19453-                return subdir2.add_file(u"child", upload.Data("overwrite me",
19454-                                                              "converge"))
19455-            d.addCallback(_created2)
19456-
19457-            d.addCallback(lambda res:
19458-                          n.move_child_to(u"child", self.subdir2))
19459-            d.addCallback(lambda res: n.list())
19460-            d.addCallback(lambda children:
19461-                          self.failUnlessReallyEqual(set(children.keys()),
19462-                                                     set([u"newfile", u"subdir2"])))
19463-            d.addCallback(lambda res: self.subdir2.list())
19464-            d.addCallback(lambda children:
19465-                          self.failUnlessReallyEqual(set(children.keys()),
19466-                                                     set([u"child"])))
19467-            d.addCallback(lambda res: self.subdir2.get(u"child"))
19468-            d.addCallback(lambda child:
19469-                          self.failUnlessReallyEqual(child.get_uri(),
19470-                                                     fake_file_uri))
19471-
19472-            # move it back, using new_child_name=
19473-            d.addCallback(lambda res:
19474-                          self.subdir2.move_child_to(u"child", n, u"newchild"))
19475-            d.addCallback(lambda res: n.list())
19476-            d.addCallback(lambda children:
19477-                          self.failUnlessReallyEqual(set(children.keys()),
19478-                                                     set([u"newchild", u"newfile",
19479-                                                          u"subdir2"])))
19480-            d.addCallback(lambda res: self.subdir2.list())
19481-            d.addCallback(lambda children:
19482-                          self.failUnlessReallyEqual(set(children.keys()), set([])))
19483-
19484-            # now make sure that we honor overwrite=False
19485-            d.addCallback(lambda res:
19486-                          self.subdir2.set_uri(u"newchild",
19487-                                               other_file_uri, other_file_uri))
19488-
19489-            d.addCallback(lambda res:
19490-                          self.shouldFail(ExistingChildError, "move_child_to-no",
19491-                                          "child 'newchild' already exists",
19492-                                          n.move_child_to, u"newchild",
19493-                                          self.subdir2,
19494-                                          overwrite=False))
19495-            d.addCallback(lambda res: self.subdir2.get(u"newchild"))
19496-            d.addCallback(lambda child:
19497-                          self.failUnlessReallyEqual(child.get_uri(),
19498-                                                     other_file_uri))
19499-
19500-
19501-            # Setting the no-write field should diminish a mutable cap to read-only
19502-            # (for both files and directories).
19503-
19504-            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
19505-            d.addCallback(lambda ign: n.get(u"mutable"))
19506-            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
19507-            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
19508-            d.addCallback(lambda ign: n.get(u"mutable"))
19509-            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
19510-            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
19511-            d.addCallback(lambda ign: n.get(u"mutable"))
19512-            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
19513-
19514-            d.addCallback(lambda ign: n.get(u"subdir2"))
19515-            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
19516-            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
19517-            d.addCallback(lambda ign: n.get(u"subdir2"))
19518-            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
19519-
19520-            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
19521-                                                metadata={"no-write": True}))
19522-            d.addCallback(lambda ign: n.get(u"mutable_ro"))
19523-            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
19524-
19525-            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
19526-            d.addCallback(lambda ign: n.get(u"subdir_ro"))
19527-            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
19528-
19529-            return d
19530-
19531-        d.addCallback(_then)
19532-
19533-        d.addErrback(self.explain_error)
19534-        return d
19535+        return self._do_create_test()
19536 
19537     def test_update_metadata(self):
19538         (t1, t2, t3) = (626644800.0, 634745640.0, 892226160.0)
19539hunk ./src/allmydata/test/test_dirnode.py 1283
19540         self.failUnlessEqual(md4, {"bool": True, "number": 42,
19541                                    "tahoe":{"linkcrtime": t1, "linkmotime": t1}})
19542 
19543-    def test_create_subdirectory(self):
19544-        self.basedir = "dirnode/Dirnode/test_create_subdirectory"
19545-        self.set_up_grid()
19546+    def _do_create_subdirectory_test(self, version=SDMF_VERSION):
19547         c = self.g.clients[0]
19548         nm = c.nodemaker
19549 
19550hunk ./src/allmydata/test/test_dirnode.py 1287
19551-        d = c.create_dirnode()
19552+        d = c.create_dirnode(version=version)
19553         def _then(n):
19554             # /
19555             self.rootnode = n
19556hunk ./src/allmydata/test/test_dirnode.py 1297
19557             kids = {u"kid1": (nm.create_from_cap(fake_file_uri), {}),
19558                     u"kid2": (nm.create_from_cap(other_file_uri), md),
19559                     }
19560-            d = n.create_subdirectory(u"subdir", kids)
19561+            d = n.create_subdirectory(u"subdir", kids,
19562+                                      mutable_version=version)
19563             def _check(sub):
19564                 d = n.get_child_at_path(u"subdir")
19565                 d.addCallback(lambda sub2: self.failUnlessReallyEqual(sub2.get_uri(),
19566hunk ./src/allmydata/test/test_dirnode.py 1314
19567         d.addCallback(_then)
19568         return d
19569 
19570+    def test_create_subdirectory(self):
19571+        self.basedir = "dirnode/Dirnode/test_create_subdirectory"
19572+        self.set_up_grid()
19573+        return self._do_create_subdirectory_test()
19574+
19575+    def test_create_subdirectory_mdmf(self):
19576+        self.basedir = "dirnode/Dirnode/test_create_subdirectory_mdmf"
19577+        self.set_up_grid()
19578+        return self._do_create_subdirectory_test(version=MDMF_VERSION)
19579+
19580+    def test_create_mdmf(self):
19581+        self.basedir = "dirnode/Dirnode/test_mdmf"
19582+        self.set_up_grid()
19583+        return self._do_create_test(mdmf=True)
19584+
19585+    def test_mdmf_initial_children(self):
19586+        self.basedir = "dirnode/Dirnode/test_mdmf"
19587+        self.set_up_grid()
19588+        return self._do_initial_children_test(mdmf=True)
19589+
19590 class MinimalFakeMutableFile:
19591     def get_writekey(self):
19592         return "writekey"
19593hunk ./src/allmydata/test/test_dirnode.py 1706
19594             self.failUnless(n.get_readonly_uri().startswith("imm."), i)
19595 
19596 
19597+
19598 class DeepStats(testutil.ReallyEqualMixin, unittest.TestCase):
19599     timeout = 240 # It takes longer than 120 seconds on Francois's arm box.
19600     def test_stats(self):
19601hunk ./src/allmydata/test/test_uri.py 795
19602         self.failUnlessReallyEqual(u1.get_storage_index(), None)
19603         self.failUnlessReallyEqual(u1.abbrev_si(), "<LIT>")
19604 
19605+    def test_mdmf(self):
19606+        writekey = "\x01" * 16
19607+        fingerprint = "\x02" * 32
19608+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19609+        d1 = uri.MDMFDirectoryURI(uri1)
19610+        self.failIf(d1.is_readonly())
19611+        self.failUnless(d1.is_mutable())
19612+        self.failUnless(IURI.providedBy(d1))
19613+        self.failUnless(IDirnodeURI.providedBy(d1))
19614+        d1_uri = d1.to_string()
19615+
19616+        d2 = uri.from_string(d1_uri)
19617+        self.failUnlessIsInstance(d2, uri.MDMFDirectoryURI)
19618+        self.failIf(d2.is_readonly())
19619+        self.failUnless(d2.is_mutable())
19620+        self.failUnless(IURI.providedBy(d2))
19621+        self.failUnless(IDirnodeURI.providedBy(d2))
19622+
19623+        # It doesn't make sense to ask for a deep immutable URI for a
19624+        # mutable directory, and we should get back a result to that
19625+        # effect.
19626+        d3 = uri.from_string(d2.to_string(), deep_immutable=True)
19627+        self.failUnlessIsInstance(d3, uri.UnknownURI)
19628+
19629+    def test_mdmf_with_extensions(self):
19630+        writekey = "\x01" * 16
19631+        fingerprint = "\x02" * 32
19632+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19633+        d1 = uri.MDMFDirectoryURI(uri1)
19634+        d1_uri = d1.to_string()
19635+        # Add some extensions, verify that the URI is interpreted
19636+        # correctly.
19637+        d1_uri += ":3:131073"
19638+        uri2 = uri.from_string(d1_uri)
19639+        self.failUnlessIsInstance(uri2, uri.MDMFDirectoryURI)
19640+        self.failUnless(IURI.providedBy(uri2))
19641+        self.failUnless(IDirnodeURI.providedBy(uri2))
19642+        self.failUnless(uri1.is_mutable())
19643+        self.failIf(uri1.is_readonly())
19644+
19645+        d2_uri = uri2.to_string()
19646+        self.failUnlessIn(":3:131073", d2_uri)
19647+
19648+        # Now attenuate, verify that the extensions persist
19649+        ro_uri = uri2.get_readonly()
19650+        self.failUnlessIsInstance(ro_uri, uri.ReadonlyMDMFDirectoryURI)
19651+        self.failUnless(ro_uri.is_mutable())
19652+        self.failUnless(ro_uri.is_readonly())
19653+        self.failUnless(IURI.providedBy(ro_uri))
19654+        self.failUnless(IDirnodeURI.providedBy(ro_uri))
19655+        ro_uri_str = ro_uri.to_string()
19656+        self.failUnlessIn(":3:131073", ro_uri_str)
19657+
19658+    def test_mdmf_attenuation(self):
19659+        writekey = "\x01" * 16
19660+        fingerprint = "\x02" * 32
19661+
19662+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19663+        d1 = uri.MDMFDirectoryURI(uri1)
19664+        self.failUnless(d1.is_mutable())
19665+        self.failIf(d1.is_readonly())
19666+        self.failUnless(IURI.providedBy(d1))
19667+        self.failUnless(IDirnodeURI.providedBy(d1))
19668+
19669+        d1_uri = d1.to_string()
19670+        d1_uri_from_fn = uri.MDMFDirectoryURI(d1.get_filenode_cap()).to_string()
19671+        self.failUnlessEqual(d1_uri_from_fn, d1_uri)
19672+
19673+        uri2 = uri.from_string(d1_uri)
19674+        self.failUnlessIsInstance(uri2, uri.MDMFDirectoryURI)
19675+        self.failUnless(IURI.providedBy(uri2))
19676+        self.failUnless(IDirnodeURI.providedBy(uri2))
19677+        self.failUnless(uri2.is_mutable())
19678+        self.failIf(uri2.is_readonly())
19679+
19680+        ro = uri2.get_readonly()
19681+        self.failUnlessIsInstance(ro, uri.ReadonlyMDMFDirectoryURI)
19682+        self.failUnless(ro.is_mutable())
19683+        self.failUnless(ro.is_readonly())
19684+        self.failUnless(IURI.providedBy(ro))
19685+        self.failUnless(IDirnodeURI.providedBy(ro))
19686+
19687+        ro_uri = ro.to_string()
19688+        n = uri.from_string(ro_uri, deep_immutable=True)
19689+        self.failUnlessIsInstance(n, uri.UnknownURI)
19690+
19691+        fn_cap = ro.get_filenode_cap()
19692+        fn_ro_cap = fn_cap.get_readonly()
19693+        d3 = uri.ReadonlyMDMFDirectoryURI(fn_ro_cap)
19694+        self.failUnlessEqual(ro.to_string(), d3.to_string())
19695+        self.failUnless(ro.is_mutable())
19696+        self.failUnless(ro.is_readonly())
19697+
19698+    def test_mdmf_verifier(self):
19699+        # I'm not sure what I want to write here yet.
19700+        writekey = "\x01" * 16
19701+        fingerprint = "\x02" * 32
19702+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
19703+        d1 = uri.MDMFDirectoryURI(uri1)
19704+        v1 = d1.get_verify_cap()
19705+        self.failUnlessIsInstance(v1, uri.MDMFDirectoryURIVerifier)
19706+        self.failIf(v1.is_mutable())
19707+
19708+        d2 = uri.from_string(d1.to_string())
19709+        v2 = d2.get_verify_cap()
19710+        self.failUnlessIsInstance(v2, uri.MDMFDirectoryURIVerifier)
19711+        self.failIf(v2.is_mutable())
19712+        self.failUnlessEqual(v2.to_string(), v1.to_string())
19713+
19714+        # Now attenuate and make sure that works correctly.
19715+        r3 = d2.get_readonly()
19716+        v3 = r3.get_verify_cap()
19717+        self.failUnlessIsInstance(v3, uri.MDMFDirectoryURIVerifier)
19718+        self.failIf(v3.is_mutable())
19719+        self.failUnlessEqual(v3.to_string(), v1.to_string())
19720+        r4 = uri.from_string(r3.to_string())
19721+        v4 = r4.get_verify_cap()
19722+        self.failUnlessIsInstance(v4, uri.MDMFDirectoryURIVerifier)
19723+        self.failIf(v4.is_mutable())
19724+        self.failUnlessEqual(v4.to_string(), v3.to_string())
19725+
19726}
19727[web: teach WUI and webapi to create MDMF directories
19728Kevan Carstensen <kevan@isnotajoke.com>**20110617180019
19729 Ignore-this: 956a60542a26c2d5118085ab9e3c470e
19730] {
19731hunk ./src/allmydata/web/common.py 41
19732         return None # interpreted by the caller as "let the nodemaker decide"
19733 
19734     arg = arg.lower()
19735-    assert arg in ("mdmf", "sdmf")
19736-
19737     if arg == "mdmf":
19738         return MDMF_VERSION
19739hunk ./src/allmydata/web/common.py 43
19740+    elif arg == "sdmf":
19741+        return SDMF_VERSION
19742 
19743hunk ./src/allmydata/web/common.py 46
19744-    return SDMF_VERSION
19745+    return "invalid"
19746 
19747 
19748 def parse_offset_arg(offset):
19749hunk ./src/allmydata/web/directory.py 26
19750      IOpHandleTable, NeedOperationHandleError, \
19751      boolean_of_arg, get_arg, get_root, parse_replace_arg, \
19752      should_create_intermediate_directories, \
19753-     getxmlfile, RenderMixin, humanize_failure, convert_children_json
19754+     getxmlfile, RenderMixin, humanize_failure, convert_children_json, \
19755+     parse_mutable_type_arg
19756 from allmydata.web.filenode import ReplaceMeMixin, \
19757      FileNodeHandler, PlaceHolderNodeHandler
19758 from allmydata.web.check_results import CheckResults, \
19759hunk ./src/allmydata/web/directory.py 112
19760                     mutable = True
19761                     if t == "mkdir-immutable":
19762                         mutable = False
19763+
19764+                    mt = None
19765+                    if mutable:
19766+                        arg = get_arg(req, "mutable-type", None)
19767+                        mt = parse_mutable_type_arg(arg)
19768+                        if mt is "invalid":
19769+                            raise WebError("Unknown type: %s" % arg,
19770+                                           http.BAD_REQUEST)
19771                     d = self.node.create_subdirectory(name, kids,
19772hunk ./src/allmydata/web/directory.py 121
19773-                                                      mutable=mutable)
19774+                                                      mutable=mutable,
19775+                                                      mutable_version=mt)
19776                     d.addCallback(make_handler_for,
19777                                   self.client, self.node, name)
19778                     return d
19779hunk ./src/allmydata/web/directory.py 253
19780         name = name.decode("utf-8")
19781         replace = boolean_of_arg(get_arg(req, "replace", "true"))
19782         kids = {}
19783-        d = self.node.create_subdirectory(name, kids, overwrite=replace)
19784+        arg = get_arg(req, "mutable-type", None)
19785+        mt = parse_mutable_type_arg(arg)
19786+        if mt is not None and mt is not "invalid":
19787+            d = self.node.create_subdirectory(name, kids, overwrite=replace,
19788+                                          mutable_version=mt)
19789+        elif mt is "invalid":
19790+            raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19791+        else:
19792+            d = self.node.create_subdirectory(name, kids, overwrite=replace)
19793         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
19794         return d
19795 
19796hunk ./src/allmydata/web/directory.py 277
19797         req.content.seek(0)
19798         kids_json = req.content.read()
19799         kids = convert_children_json(self.client.nodemaker, kids_json)
19800-        d = self.node.create_subdirectory(name, kids, overwrite=False)
19801+        arg = get_arg(req, "mutable-type", None)
19802+        mt = parse_mutable_type_arg(arg)
19803+        if mt is not None and mt is not "invalid":
19804+            d = self.node.create_subdirectory(name, kids, overwrite=False,
19805+                                              mutable_version=mt)
19806+        elif mt is "invalid":
19807+            raise WebError("Unknown type: %s" % arg)
19808+        else:
19809+            d = self.node.create_subdirectory(name, kids, overwrite=False)
19810         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
19811         return d
19812 
19813hunk ./src/allmydata/web/directory.py 786
19814 
19815         return ctx.tag
19816 
19817+    # XXX: Duplicated from root.py.
19818     def render_forms(self, ctx, data):
19819         forms = []
19820 
19821hunk ./src/allmydata/web/directory.py 795
19822         if self.dirnode_children is None:
19823             return T.div["No upload forms: directory is unreadable"]
19824 
19825+        mdmf_directory_input = T.input(type='radio', name='mutable-type',
19826+                                       id='mutable-directory-mdmf',
19827+                                       value='mdmf')
19828+        sdmf_directory_input = T.input(type='radio', name='mutable-type',
19829+                                       id='mutable-directory-sdmf',
19830+                                       value='sdmf', checked='checked')
19831         mkdir = T.form(action=".", method="post",
19832                        enctype="multipart/form-data")[
19833             T.fieldset[
19834hunk ./src/allmydata/web/directory.py 809
19835             T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
19836             "New directory name: ",
19837             T.input(type="text", name="name"), " ",
19838+            T.label(for_='mutable-directory-sdmf')["SDMF"],
19839+            sdmf_directory_input,
19840+            T.label(for_='mutable-directory-mdmf')["MDMF"],
19841+            mdmf_directory_input,
19842             T.input(type="submit", value="Create"),
19843             ]]
19844         forms.append(T.div(class_="freeform-form")[mkdir])
19845hunk ./src/allmydata/web/filenode.py 29
19846         # a new file is being uploaded in our place.
19847         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
19848         if mutable:
19849-            mutable_type = parse_mutable_type_arg(get_arg(req,
19850-                                                          "mutable-type",
19851-                                                          None))
19852+            arg = get_arg(req, "mutable-type", None)
19853+            mutable_type = parse_mutable_type_arg(arg)
19854+            if mutable_type is "invalid":
19855+                raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19856+
19857             data = MutableFileHandle(req.content)
19858             d = client.create_mutable_file(data, version=mutable_type)
19859             def _uploaded(newnode):
19860hunk ./src/allmydata/web/filenode.py 76
19861         # create an immutable file
19862         contents = req.fields["file"]
19863         if mutable:
19864-            mutable_type = parse_mutable_type_arg(get_arg(req, "mutable-type",
19865-                                                          None))
19866+            arg = get_arg(req, "mutable-type", None)
19867+            mutable_type = parse_mutable_type_arg(arg)
19868+            if mutable_type is "invalid":
19869+                raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19870             uploadable = MutableFileHandle(contents.file)
19871             d = client.create_mutable_file(uploadable, version=mutable_type)
19872             def _uploaded(newnode):
19873hunk ./src/allmydata/web/root.py 50
19874         if t == "":
19875             mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
19876             if mutable:
19877-                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
19878-                                                 None))
19879+                arg = get_arg(req, "mutable-type", None)
19880+                version = parse_mutable_type_arg(arg)
19881+                if version == "invalid":
19882+                    errmsg = "Unknown type: %s" % arg
19883+                    raise WebError(errmsg, http.BAD_REQUEST)
19884+
19885                 return unlinked.PUTUnlinkedSSK(req, self.client, version)
19886             else:
19887                 return unlinked.PUTUnlinkedCHK(req, self.client)
19888hunk ./src/allmydata/web/root.py 74
19889         if t in ("", "upload"):
19890             mutable = bool(get_arg(req, "mutable", "").strip())
19891             if mutable:
19892-                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
19893-                                                         None))
19894+                arg = get_arg(req, "mutable-type", None)
19895+                version = parse_mutable_type_arg(arg)
19896+                if version is "invalid":
19897+                    raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
19898                 return unlinked.POSTUnlinkedSSK(req, self.client, version)
19899             else:
19900                 return unlinked.POSTUnlinkedCHK(req, self.client)
19901hunk ./src/allmydata/web/root.py 371
19902 
19903     def render_mkdir_form(self, ctx, data):
19904         # this is a form where users can create new directories
19905+        mdmf_input = T.input(type='radio', name='mutable-type',
19906+                             value='mdmf', id='mutable-directory-mdmf')
19907+        sdmf_input = T.input(type='radio', name='mutable-type',
19908+                             value='sdmf', id='mutable-directory-sdmf',
19909+                             checked='checked')
19910         form = T.form(action="uri", method="post",
19911                       enctype="multipart/form-data")[
19912             T.fieldset[
19913hunk ./src/allmydata/web/root.py 380
19914             T.legend(class_="freeform-form-label")["Create a directory"],
19915+            T.label(for_='mutable-directory-sdmf')["SDMF"],
19916+            sdmf_input,
19917+            T.label(for_='mutable-directory-mdmf')["MDMF"],
19918+            mdmf_input,
19919             T.input(type="hidden", name="t", value="mkdir"),
19920             T.input(type="hidden", name="redirect_to_result", value="true"),
19921             T.input(type="submit", value="Create a directory"),
19922hunk ./src/allmydata/web/unlinked.py 9
19923 from allmydata.immutable.upload import FileHandle
19924 from allmydata.mutable.publish import MutableFileHandle
19925 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
19926-     convert_children_json, WebError
19927+     convert_children_json, WebError, parse_mutable_type_arg
19928 from allmydata.web import status
19929 
19930 def PUTUnlinkedCHK(req, client):
19931hunk ./src/allmydata/web/unlinked.py 30
19932 
19933 def PUTUnlinkedCreateDirectory(req, client):
19934     # "PUT /uri?t=mkdir", to create an unlinked directory.
19935-    d = client.create_dirnode()
19936+    arg = get_arg(req, "mutable-type", None)
19937+    mt = parse_mutable_type_arg(arg)
19938+    if mt is not None and mt is not "invalid":
19939+        d = client.create_dirnode(version=mt)
19940+    elif mt is "invalid":
19941+        msg = "Unknown type: %s" % arg
19942+        raise WebError(msg, http.BAD_REQUEST)
19943+    else:
19944+        d = client.create_dirnode()
19945     d.addCallback(lambda dirnode: dirnode.get_uri())
19946     # XXX add redirect_to_result
19947     return d
19948hunk ./src/allmydata/web/unlinked.py 115
19949             raise WebError("t=mkdir does not accept children=, "
19950                            "try t=mkdir-with-children instead",
19951                            http.BAD_REQUEST)
19952-    d = client.create_dirnode()
19953+    arg = get_arg(req, "mutable-type", None)
19954+    mt = parse_mutable_type_arg(arg)
19955+    if mt is not None and mt is not "invalid":
19956+        d = client.create_dirnode(version=mt)
19957+    elif mt is "invalid":
19958+        msg = "Unknown type: %s" % arg
19959+        raise WebError(msg, http.BAD_REQUEST)
19960+    else:
19961+        d = client.create_dirnode()
19962     redirect = get_arg(req, "redirect_to_result", "false")
19963     if boolean_of_arg(redirect):
19964         def _then_redir(res):
19965}
19966[test/test_web: test webapi and WUI for MDMF directory handling
19967Kevan Carstensen <kevan@isnotajoke.com>**20110617180100
19968 Ignore-this: 63ed7832872fd35eb7319cf6a6f251b
19969] {
19970hunk ./src/allmydata/test/test_web.py 1103
19971         d.addCallback(lambda json: self.failUnlessIn("sdmf", json))
19972         return d
19973 
19974+    def test_PUT_NEWFILEURL_unlinked_bad_mutable_type(self):
19975+        contents = self.NEWFILE_CONTENTS * 300000
19976+        return self.shouldHTTPError("test bad mutable type",
19977+                                    400, "Bad Request", "Unknown type: foo",
19978+                                    self.PUT, "/uri?mutable=true&mutable-type=foo",
19979+                                    contents)
19980+
19981     def test_PUT_NEWFILEURL_range_bad(self):
19982         headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
19983         target = self.public_url + "/foo/new.txt"
19984hunk ./src/allmydata/test/test_web.py 1346
19985             self.failUnless(CSS_STYLE.search(res), res)
19986         d.addCallback(_check)
19987         return d
19988-   
19989+
19990     def test_GET_FILEURL_uri_missing(self):
19991         d = self.GET(self.public_url + "/foo/missing?t=uri")
19992         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
19993hunk ./src/allmydata/test/test_web.py 1356
19994         d = self.GET(self.public_url + "/foo", followRedirect=True)
19995         def _check(res):
19996             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
19997+            # These are radio buttons that allow a user to toggle
19998+            # whether a particular mutable file is SDMF or MDMF.
19999             self.failUnlessIn("mutable-type-mdmf", res)
20000             self.failUnlessIn("mutable-type-sdmf", res)
20001hunk ./src/allmydata/test/test_web.py 1360
20002+            # Similarly, these toggle whether a particular directory
20003+            # should be MDMF or SDMF.
20004+            self.failUnlessIn("mutable-directory-mdmf", res)
20005+            self.failUnlessIn("mutable-directory-sdmf", res)
20006             self.failUnlessIn("quux", res)
20007         d.addCallback(_check)
20008         return d
20009hunk ./src/allmydata/test/test_web.py 1377
20010             # whether a particular mutable file is MDMF or SDMF.
20011             self.failUnlessIn("mutable-type-mdmf", html)
20012             self.failUnlessIn("mutable-type-sdmf", html)
20013+            # We should also have the ability to create a mutable directory.
20014+            self.failUnlessIn("mkdir", html)
20015+            # ...and we should have the ability to say whether that's an
20016+            # MDMF or SDMF directory
20017+            self.failUnlessIn("mutable-directory-mdmf", html)
20018+            self.failUnlessIn("mutable-directory-sdmf", html)
20019         d.addCallback(_got_html)
20020         return d
20021 
20022hunk ./src/allmydata/test/test_web.py 1691
20023         d.addCallback(self.failUnlessNodeKeysAre, [])
20024         return d
20025 
20026+    def test_PUT_NEWDIRURL_mdmf(self):
20027+        d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&mutable-type=mdmf", "")
20028+        d.addCallback(lambda res:
20029+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20030+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20031+        d.addCallback(lambda node:
20032+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20033+        return d
20034+
20035+    def test_PUT_NEWDIRURL_sdmf(self):
20036+        d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&mutable-type=sdmf",
20037+                     "")
20038+        d.addCallback(lambda res:
20039+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20040+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20041+        d.addCallback(lambda node:
20042+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20043+        return d
20044+
20045+    def test_PUT_NEWDIRURL_bad_mutable_type(self):
20046+        return self.shouldHTTPError("test bad mutable type",
20047+                             400, "Bad Request", "Unknown type: foo",
20048+                             self.PUT, self.public_url + \
20049+                             "/foo/newdir=?t=mkdir&mutable-type=foo", "")
20050+
20051     def test_POST_NEWDIRURL(self):
20052         d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
20053         d.addCallback(lambda res:
20054hunk ./src/allmydata/test/test_web.py 1724
20055         d.addCallback(self.failUnlessNodeKeysAre, [])
20056         return d
20057 
20058+    def test_POST_NEWDIRURL_mdmf(self):
20059+        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&mutable-type=mdmf", "")
20060+        d.addCallback(lambda res:
20061+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20062+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20063+        d.addCallback(lambda node:
20064+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20065+        return d
20066+
20067+    def test_POST_NEWDIRURL_sdmf(self):
20068+        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&mutable-type=sdmf", "")
20069+        d.addCallback(lambda res:
20070+            self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20071+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20072+        d.addCallback(lambda node:
20073+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20074+        return d
20075+
20076+    def test_POST_NEWDIRURL_bad_mutable_type(self):
20077+        return self.shouldHTTPError("test bad mutable type",
20078+                                    400, "Bad Request", "Unknown type: foo",
20079+                                    self.POST2, self.public_url + \
20080+                                    "/foo/newdir?t=mkdir&mutable-type=foo", "")
20081+
20082     def test_POST_NEWDIRURL_emptyname(self):
20083         # an empty pathname component (i.e. a double-slash) is disallowed
20084         d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
20085hunk ./src/allmydata/test/test_web.py 1756
20086                              self.POST, self.public_url + "//?t=mkdir")
20087         return d
20088 
20089-    def test_POST_NEWDIRURL_initial_children(self):
20090+    def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
20091         (newkids, caps) = self._create_initial_children()
20092hunk ./src/allmydata/test/test_web.py 1758
20093-        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
20094+        query = "/foo/newdir?t=mkdir-with-children"
20095+        if version == MDMF_VERSION:
20096+            query += "&mutable-type=mdmf"
20097+        elif version == SDMF_VERSION:
20098+            query += "&mutable-type=sdmf"
20099+        else:
20100+            version = SDMF_VERSION # for later
20101+        d = self.POST2(self.public_url + query,
20102                        simplejson.dumps(newkids))
20103         def _check(uri):
20104             n = self.s.create_node_from_uri(uri.strip())
20105hunk ./src/allmydata/test/test_web.py 1770
20106             d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
20107+            self.failUnlessEqual(n._node.get_version(), version)
20108             d2.addCallback(lambda ign:
20109                            self.failUnlessROChildURIIs(n, u"child-imm",
20110                                                        caps['filecap1']))
20111hunk ./src/allmydata/test/test_web.py 1808
20112         d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
20113         return d
20114 
20115+    def test_POST_NEWDIRURL_initial_children(self):
20116+        return self._do_POST_NEWDIRURL_initial_children_test()
20117+
20118+    def test_POST_NEWDIRURL_initial_children_mdmf(self):
20119+        return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
20120+
20121+    def test_POST_NEWDIRURL_initial_children_sdmf(self):
20122+        return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
20123+
20124+    def test_POST_NEWDIRURL_initial_children_bad_mutable_type(self):
20125+        (newkids, caps) = self._create_initial_children()
20126+        return self.shouldHTTPError("test bad mutable type",
20127+                                    400, "Bad Request", "Unknown type: foo",
20128+                                    self.POST2, self.public_url + \
20129+                                    "/foo/newdir?t=mkdir-with-children&mutable-type=foo",
20130+                                    simplejson.dumps(newkids))
20131+
20132     def test_POST_NEWDIRURL_immutable(self):
20133         (newkids, caps) = self._create_immutable_children()
20134         d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
20135hunk ./src/allmydata/test/test_web.py 1925
20136         d.addCallback(self.failUnlessNodeKeysAre, [])
20137         return d
20138 
20139+    def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
20140+        d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&mutable-type=mdmf", "")
20141+        d.addCallback(lambda ignored:
20142+            self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
20143+        d.addCallback(lambda ignored:
20144+            self.failIfNodeHasChild(self._foo_node, u"newdir"))
20145+        d.addCallback(lambda ignored:
20146+            self._foo_node.get_child_at_path(u"subdir"))
20147+        def _got_subdir(subdir):
20148+            # XXX: What we want?
20149+            #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
20150+            self.failUnlessNodeHasChild(subdir, u"newdir")
20151+            return subdir.get_child_at_path(u"newdir")
20152+        d.addCallback(_got_subdir)
20153+        d.addCallback(lambda newdir:
20154+            self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
20155+        return d
20156+
20157+    def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
20158+        d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&mutable-type=sdmf", "")
20159+        d.addCallback(lambda ignored:
20160+            self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
20161+        d.addCallback(lambda ignored:
20162+            self.failIfNodeHasChild(self._foo_node, u"newdir"))
20163+        d.addCallback(lambda ignored:
20164+            self._foo_node.get_child_at_path(u"subdir"))
20165+        def _got_subdir(subdir):
20166+            # XXX: What we want?
20167+            #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
20168+            self.failUnlessNodeHasChild(subdir, u"newdir")
20169+            return subdir.get_child_at_path(u"newdir")
20170+        d.addCallback(_got_subdir)
20171+        d.addCallback(lambda newdir:
20172+            self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
20173+        return d
20174+
20175+    def test_PUT_NEWDIRURL_mkdirs_bad_mutable_type(self):
20176+        return self.shouldHTTPError("test bad mutable type",
20177+                                    400, "Bad Request", "Unknown type: foo",
20178+                                    self.PUT, self.public_url + \
20179+                                    "/foo/subdir/newdir?t=mkdir&mutable-type=foo",
20180+                                    "")
20181+
20182     def test_DELETE_DIRURL(self):
20183         d = self.DELETE(self.public_url + "/foo")
20184         d.addCallback(lambda res:
20185hunk ./src/allmydata/test/test_web.py 2235
20186         d.addCallback(_got_json, "mdmf")
20187         return d
20188 
20189+    def test_POST_upload_mutable_type_unlinked_bad_mutable_type(self):
20190+        return self.shouldHTTPError("test bad mutable type",
20191+                                    400, "Bad Request", "Unknown type: foo",
20192+                                    self.POST,
20193+                                    "/uri?5=upload&mutable=true&mutable-type=foo",
20194+                                    file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
20195+
20196     def test_POST_upload_mutable_type(self):
20197         d = self.POST(self.public_url + \
20198                       "/foo?t=upload&mutable=true&mutable-type=sdmf",
20199hunk ./src/allmydata/test/test_web.py 2271
20200         d.addCallback(_got_json, "mdmf")
20201         return d
20202 
20203+    def test_POST_upload_bad_mutable_type(self):
20204+        return self.shouldHTTPError("test bad mutable type",
20205+                                    400, "Bad Request", "Unknown type: foo",
20206+                                    self.POST, self.public_url + \
20207+                                    "/foo?t=upload&mutable=true&mutable-type=foo",
20208+                                    file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
20209+
20210     def test_POST_upload_mutable(self):
20211         # this creates a mutable file
20212         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
20213hunk ./src/allmydata/test/test_web.py 2777
20214         d.addCallback(self.failUnlessNodeKeysAre, [])
20215         return d
20216 
20217+    def test_POST_mkdir_mdmf(self):
20218+        d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=mdmf")
20219+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20220+        d.addCallback(lambda node:
20221+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20222+        return d
20223+
20224+    def test_POST_mkdir_sdmf(self):
20225+        d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=sdmf")
20226+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20227+        d.addCallback(lambda node:
20228+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20229+        return d
20230+
20231+    def test_POST_mkdir_bad_mutable_type(self):
20232+        return self.shouldHTTPError("test bad mutable type",
20233+                                    400, "Bad Request", "Unknown type: foo",
20234+                                    self.POST, self.public_url + \
20235+                                    "/foo?t=mkdir&name=newdir&mutable-type=foo")
20236+
20237     def test_POST_mkdir_initial_children(self):
20238         (newkids, caps) = self._create_initial_children()
20239         d = self.POST2(self.public_url +
20240hunk ./src/allmydata/test/test_web.py 2810
20241         d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
20242         return d
20243 
20244+    def test_POST_mkdir_initial_children_mdmf(self):
20245+        (newkids, caps) = self._create_initial_children()
20246+        d = self.POST2(self.public_url +
20247+                       "/foo?t=mkdir-with-children&name=newdir&mutable-type=mdmf",
20248+                       simplejson.dumps(newkids))
20249+        d.addCallback(lambda res:
20250+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20251+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20252+        d.addCallback(lambda node:
20253+            self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
20254+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20255+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
20256+                       caps['filecap1'])
20257+        return d
20258+
20259+    # XXX: Duplication.
20260+    def test_POST_mkdir_initial_children_sdmf(self):
20261+        (newkids, caps) = self._create_initial_children()
20262+        d = self.POST2(self.public_url +
20263+                       "/foo?t=mkdir-with-children&name=newdir&mutable-type=sdmf",
20264+                       simplejson.dumps(newkids))
20265+        d.addCallback(lambda res:
20266+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
20267+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20268+        d.addCallback(lambda node:
20269+            self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
20270+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
20271+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
20272+                       caps['filecap1'])
20273+        return d
20274+
20275+    def test_POST_mkdir_initial_children_bad_mutable_type(self):
20276+        (newkids, caps) = self._create_initial_children()
20277+        return self.shouldHTTPError("test bad mutable type",
20278+                                    400, "Bad Request", "Unknown type: foo",
20279+                                    self.POST, self.public_url + \
20280+                                    "/foo?t=mkdir-with-children&name=newdir&mutable-type=foo",
20281+                                    simplejson.dumps(newkids))
20282+
20283     def test_POST_mkdir_immutable(self):
20284         (newkids, caps) = self._create_immutable_children()
20285         d = self.POST2(self.public_url +
20286hunk ./src/allmydata/test/test_web.py 2905
20287         d.addCallback(_after_mkdir)
20288         return d
20289 
20290+    def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
20291+        d = self.POST("/uri?t=mkdir&mutable-type=mdmf")
20292+        def _after_mkdir(res):
20293+            u = uri.from_string(res)
20294+            # Check that this is an MDMF writecap
20295+            self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
20296+        d.addCallback(_after_mkdir)
20297+        return d
20298+
20299+    def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
20300+        d = self.POST("/uri?t=mkdir&mutable-type=sdmf")
20301+        def _after_mkdir(res):
20302+            u = uri.from_string(res)
20303+            self.failUnlessIsInstance(u, uri.DirectoryURI)
20304+        d.addCallback(_after_mkdir)
20305+        return d
20306+
20307+    def test_POST_mkdir_no_parentdir_noredirect_bad_mutable_type(self):
20308+        return self.shouldHTTPError("test bad mutable type",
20309+                                    400, "Bad Request", "Unknown type: foo",
20310+                                    self.POST, self.public_url + \
20311+                                    "/uri?t=mkdir&mutable-type=foo")
20312+
20313     def test_POST_mkdir_no_parentdir_noredirect2(self):
20314         # make sure form-based arguments (as on the welcome page) still work
20315         d = self.POST("/uri", t="mkdir")
20316hunk ./src/allmydata/test/test_web.py 3565
20317         d.addCallback(_got_json)
20318         return d
20319 
20320+    def test_PUT_NEWFILEURL_bad_mutable_type(self):
20321+       new_contents = self.NEWFILE_CONTENTS * 300000
20322+       return self.shouldHTTPError("test bad mutable type",
20323+                                   400, "Bad Request", "Unknown type: foo",
20324+                                   self.PUT, self.public_url + \
20325+                                   "/foo/foo.txt?mutable=true&mutable-type=foo",
20326+                                   new_contents)
20327+
20328     def test_PUT_NEWFILEURL_uri_replace(self):
20329         contents, n, new_uri = self.makefile(8)
20330         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
20331hunk ./src/allmydata/test/test_web.py 3682
20332         d.addCallback(self.failUnlessIsEmptyJSON)
20333         return d
20334 
20335+    def test_PUT_mkdir_mdmf(self):
20336+        d = self.PUT("/uri?t=mkdir&mutable-type=mdmf", "")
20337+        def _got(res):
20338+            u = uri.from_string(res)
20339+            # Check that this is an MDMF writecap
20340+            self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
20341+        d.addCallback(_got)
20342+        return d
20343+
20344+    def test_PUT_mkdir_sdmf(self):
20345+        d = self.PUT("/uri?t=mkdir&mutable-type=sdmf", "")
20346+        def _got(res):
20347+            u = uri.from_string(res)
20348+            self.failUnlessIsInstance(u, uri.DirectoryURI)
20349+        d.addCallback(_got)
20350+        return d
20351+
20352+    def test_PUT_mkdir_bad_mutable_type(self):
20353+        return self.shouldHTTPError("bad mutable type",
20354+                                    400, "Bad Request", "Unknown type: foo",
20355+                                    self.PUT, "/uri?t=mkdir&mutable-type=foo",
20356+                                    "")
20357+
20358     def test_POST_check(self):
20359         d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
20360         def _done(res):
20361}
20362[scripts: teach CLI to make MDMF directories
20363Kevan Carstensen <kevan@isnotajoke.com>**20110617180137
20364 Ignore-this: 5dc968bd22278033b534a561f230a4f
20365] {
20366hunk ./src/allmydata/scripts/cli.py 53
20367 
20368 
20369 class MakeDirectoryOptions(VDriveOptions):
20370+    optParameters = [
20371+        ("mutable-type", None, False, "Create a mutable file in the given format. Valid formats are 'sdmf' for SDMF and 'mdmf' for MDMF"),
20372+        ]
20373+
20374     def parseArgs(self, where=""):
20375         self.where = argv_to_unicode(where)
20376hunk ./src/allmydata/scripts/cli.py 59
20377+        if self['mutable-type'] and self['mutable-type'] not in ("sdmf", "mdmf"):
20378+            raise usage.UsageError("%s is an invalid format" % self['mutable-type'])
20379     longdesc = """Create a new directory, either unlinked or as a subdirectory."""
20380 
20381 class AddAliasOptions(VDriveOptions):
20382hunk ./src/allmydata/scripts/tahoe_mkdir.py 25
20383     if not where or not path:
20384         # create a new unlinked directory
20385         url = nodeurl + "uri?t=mkdir"
20386+        if options["mutable-type"]:
20387+            url += "&mutable-type=%s" % urllib.quote(options['mutable-type'])
20388         resp = do_http("POST", url)
20389         rc = check_http_error(resp, stderr)
20390         if rc:
20391hunk ./src/allmydata/scripts/tahoe_mkdir.py 42
20392     # path must be "/".join([s.encode("utf-8") for s in segments])
20393     url = nodeurl + "uri/%s/%s?t=mkdir" % (urllib.quote(rootcap),
20394                                            urllib.quote(path))
20395+    if options['mutable-type']:
20396+        url += "&mutable-type=%s" % urllib.quote(options['mutable-type'])
20397+
20398     resp = do_http("POST", url)
20399     check_http_error(resp, stderr)
20400     new_uri = resp.read().strip()
20401}
20402[test/test_cli: test CLI's MDMF creation powers
20403Kevan Carstensen <kevan@isnotajoke.com>**20110617180209
20404 Ignore-this: d4b493b266446b2be3ce3c5f2505577d
20405] hunk ./src/allmydata/test/test_cli.py 2760
20406 
20407         return d
20408 
20409+    def test_mkdir_mutable_type(self):
20410+        self.basedir = os.path.dirname(self.mktemp())
20411+        self.set_up_grid()
20412+        d = self.do_cli("create-alias", "tahoe")
20413+        d.addCallback(lambda ignored:
20414+            self.do_cli("mkdir", "--mutable-type=sdmf", "tahoe:foo"))
20415+        def _check((rc, out, err), st):
20416+            self.failUnlessReallyEqual(rc, 0)
20417+            self.failUnlessReallyEqual(err, "")
20418+            self.failUnlessIn(st, out)
20419+            return out
20420+        def _stash_dircap(cap):
20421+            self._dircap = cap
20422+            u = uri.from_string(cap)
20423+            fn_uri = u.get_filenode_cap()
20424+            self._filecap = fn_uri.to_string()
20425+        d.addCallback(_check, "URI:DIR2")
20426+        d.addCallback(_stash_dircap)
20427+        d.addCallback(lambda ignored:
20428+            self.do_cli("ls", "--json", "tahoe:foo"))
20429+        d.addCallback(_check, "URI:DIR2")
20430+        d.addCallback(lambda ignored:
20431+            self.do_cli("ls", "--json", self._filecap))
20432+        d.addCallback(_check, '"mutable-type": "sdmf"')
20433+        d.addCallback(lambda ignored:
20434+            self.do_cli("mkdir", "--mutable-type=mdmf", "tahoe:bar"))
20435+        d.addCallback(_check, "URI:DIR2-MDMF")
20436+        d.addCallback(_stash_dircap)
20437+        d.addCallback(lambda ignored:
20438+            self.do_cli("ls", "--json", "tahoe:bar"))
20439+        d.addCallback(_check, "URI:DIR2-MDMF")
20440+        d.addCallback(lambda ignored:
20441+            self.do_cli("ls", "--json", self._filecap))
20442+        d.addCallback(_check, '"mutable-type": "mdmf"')
20443+        return d
20444+
20445+    def test_mkdir_mutable_type_unlinked(self):
20446+        self.basedir = os.path.dirname(self.mktemp())
20447+        self.set_up_grid()
20448+        d = self.do_cli("mkdir", "--mutable-type=sdmf")
20449+        def _check((rc, out, err), st):
20450+            self.failUnlessReallyEqual(rc, 0)
20451+            self.failUnlessReallyEqual(err, "")
20452+            self.failUnlessIn(st, out)
20453+            return out
20454+        d.addCallback(_check, "URI:DIR2")
20455+        def _stash_dircap(cap):
20456+            self._dircap = cap
20457+            # Now we're going to feed the cap into uri.from_string...
20458+            u = uri.from_string(cap)
20459+            # ...grab the underlying filenode uri.
20460+            fn_uri = u.get_filenode_cap()
20461+            # ...and stash that.
20462+            self._filecap = fn_uri.to_string()
20463+        d.addCallback(_stash_dircap)
20464+        d.addCallback(lambda res: self.do_cli("ls", "--json",
20465+                                              self._filecap))
20466+        d.addCallback(_check, '"mutable-type": "sdmf"')
20467+        d.addCallback(lambda res: self.do_cli("mkdir", "--mutable-type=mdmf"))
20468+        d.addCallback(_check, "URI:DIR2-MDMF")
20469+        d.addCallback(_stash_dircap)
20470+        d.addCallback(lambda res: self.do_cli("ls", "--json",
20471+                                              self._filecap))
20472+        d.addCallback(_check, '"mutable-type": "mdmf"')
20473+        return d
20474+
20475+    def test_mkdir_bad_mutable_type(self):
20476+        o = cli.MakeDirectoryOptions()
20477+        self.failUnlessRaises(usage.UsageError,
20478+                              o.parseOptions,
20479+                              ["--mutable", "--mutable-type=ldmf"])
20480+
20481     def test_mkdir_unicode(self):
20482         self.basedir = os.path.dirname(self.mktemp())
20483         self.set_up_grid()
20484
20485Context:
20486
20487[tests: fix tests to accomodate [20110611153758-92b7f-0ba5e4726fb6318dac28fb762a6512a003f4c430]
20488zooko@zooko.com**20110611163741
20489 Ignore-this: 64073a5f39e7937e8e5e1314c1a302d1
20490 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.
20491 fixes #1412
20492] 
20493[wui: right-align the size column in the WUI
20494zooko@zooko.com**20110611153758
20495 Ignore-this: 492bdaf4373c96f59f90581c7daf7cd7
20496 Thanks to Ted "stercor" Rolle Jr. and Terrell Russell.
20497 fixes #1412
20498] 
20499[docs: three minor fixes
20500zooko@zooko.com**20110610121656
20501 Ignore-this: fec96579eb95aceb2ad5fc01a814c8a2
20502 CREDITS for arc for stats tweak
20503 fix link to .zip file in quickstart.rst (thanks to ChosenOne for noticing)
20504 English usage tweak
20505] 
20506[docs/running.rst: fix stray HTML (not .rst) link noticed by ChosenOne.
20507david-sarah@jacaranda.org**20110609223719
20508 Ignore-this: fc50ac9c94792dcac6f1067df8ac0d4a
20509] 
20510[server.py:  get_latencies now reports percentiles _only_ if there are sufficient observations for the interpretation of the percentile to be unambiguous.
20511wilcoxjg@gmail.com**20110527120135
20512 Ignore-this: 2e7029764bffc60e26f471d7c2b6611e
20513 interfaces.py:  modified the return type of RIStatsProvider.get_stats to allow for None as a return value
20514 NEWS.rst, stats.py: documentation of change to get_latencies
20515 stats.rst: now documents percentile modification in get_latencies
20516 test_storage.py:  test_latencies now expects None in output categories that contain too few samples for the associated percentile to be unambiguously reported.
20517 fixes #1392
20518] 
20519[docs: revert link in relnotes.txt from NEWS.rst to NEWS, since the former did not exist at revision 5000.
20520david-sarah@jacaranda.org**20110517011214
20521 Ignore-this: 6a5be6e70241e3ec0575641f64343df7
20522] 
20523[docs: convert NEWS to NEWS.rst and change all references to it.
20524david-sarah@jacaranda.org**20110517010255
20525 Ignore-this: a820b93ea10577c77e9c8206dbfe770d
20526] 
20527[docs: remove out-of-date docs/testgrid/introducer.furl and containing directory. fixes #1404
20528david-sarah@jacaranda.org**20110512140559
20529 Ignore-this: 784548fc5367fac5450df1c46890876d
20530] 
20531[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
20532david-sarah@jacaranda.org**20110130164923
20533 Ignore-this: a271e77ce81d84bb4c43645b891d92eb
20534] 
20535[setup: don't catch all Exception from check_requirement(), but only PackagingError and ImportError
20536zooko@zooko.com**20110128142006
20537 Ignore-this: 57d4bc9298b711e4bc9dc832c75295de
20538 I noticed this because I had accidentally inserted a bug which caused AssertionError to be raised from check_requirement().
20539] 
20540[M-x whitespace-cleanup
20541zooko@zooko.com**20110510193653
20542 Ignore-this: dea02f831298c0f65ad096960e7df5c7
20543] 
20544[docs: fix typo in running.rst, thanks to arch_o_median
20545zooko@zooko.com**20110510193633
20546 Ignore-this: ca06de166a46abbc61140513918e79e8
20547] 
20548[relnotes.txt: don't claim to work on Cygwin (which has been untested for some time). refs #1342
20549david-sarah@jacaranda.org**20110204204902
20550 Ignore-this: 85ef118a48453d93fa4cddc32d65b25b
20551] 
20552[relnotes.txt: forseeable -> foreseeable. refs #1342
20553david-sarah@jacaranda.org**20110204204116
20554 Ignore-this: 746debc4d82f4031ebf75ab4031b3a9
20555] 
20556[replace remaining .html docs with .rst docs
20557zooko@zooko.com**20110510191650
20558 Ignore-this: d557d960a986d4ac8216d1677d236399
20559 Remove install.html (long since deprecated).
20560 Also replace some obsolete references to install.html with references to quickstart.rst.
20561 Fix some broken internal references within docs/historical/historical_known_issues.txt.
20562 Thanks to Ravi Pinjala and Patrick McDonald.
20563 refs #1227
20564] 
20565[docs: FTP-and-SFTP.rst: fix a minor error and update the information about which version of Twisted fixes #1297
20566zooko@zooko.com**20110428055232
20567 Ignore-this: b63cfb4ebdbe32fb3b5f885255db4d39
20568] 
20569[munin tahoe_files plugin: fix incorrect file count
20570francois@ctrlaltdel.ch**20110428055312
20571 Ignore-this: 334ba49a0bbd93b4a7b06a25697aba34
20572 fixes #1391
20573] 
20574[corrected "k must never be smaller than N" to "k must never be greater than N"
20575secorp@allmydata.org**20110425010308
20576 Ignore-this: 233129505d6c70860087f22541805eac
20577] 
20578[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
20579david-sarah@jacaranda.org**20110411190738
20580 Ignore-this: 7847d26bc117c328c679f08a7baee519
20581] 
20582[tests: add test for including the ImportError message and traceback entry in the summary of errors from importing dependencies. refs #1389
20583david-sarah@jacaranda.org**20110410155844
20584 Ignore-this: fbecdbeb0d06a0f875fe8d4030aabafa
20585] 
20586[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
20587david-sarah@jacaranda.org**20110410155705
20588 Ignore-this: 2f87b8b327906cf8bfca9440a0904900
20589] 
20590[remove unused variable detected by pyflakes
20591zooko@zooko.com**20110407172231
20592 Ignore-this: 7344652d5e0720af822070d91f03daf9
20593] 
20594[allmydata/__init__.py: Nicer reporting of unparseable version numbers in dependencies. fixes #1388
20595david-sarah@jacaranda.org**20110401202750
20596 Ignore-this: 9c6bd599259d2405e1caadbb3e0d8c7f
20597] 
20598[update FTP-and-SFTP.rst: the necessary patch is included in Twisted-10.1
20599Brian Warner <warner@lothar.com>**20110325232511
20600 Ignore-this: d5307faa6900f143193bfbe14e0f01a
20601] 
20602[control.py: remove all uses of s.get_serverid()
20603warner@lothar.com**20110227011203
20604 Ignore-this: f80a787953bd7fa3d40e828bde00e855
20605] 
20606[web: remove some uses of s.get_serverid(), not all
20607warner@lothar.com**20110227011159
20608 Ignore-this: a9347d9cf6436537a47edc6efde9f8be
20609] 
20610[immutable/downloader/fetcher.py: remove all get_serverid() calls
20611warner@lothar.com**20110227011156
20612 Ignore-this: fb5ef018ade1749348b546ec24f7f09a
20613] 
20614[immutable/downloader/fetcher.py: fix diversity bug in server-response handling
20615warner@lothar.com**20110227011153
20616 Ignore-this: bcd62232c9159371ae8a16ff63d22c1b
20617 
20618 When blocks terminate (either COMPLETE or CORRUPT/DEAD/BADSEGNUM), the
20619 _shares_from_server dict was being popped incorrectly (using shnum as the
20620 index instead of serverid). I'm still thinking through the consequences of
20621 this bug. It was probably benign and really hard to detect. I think it would
20622 cause us to incorrectly believe that we're pulling too many shares from a
20623 server, and thus prefer a different server rather than asking for a second
20624 share from the first server. The diversity code is intended to spread out the
20625 number of shares simultaneously being requested from each server, but with
20626 this bug, it might be spreading out the total number of shares requested at
20627 all, not just simultaneously. (note that SegmentFetcher is scoped to a single
20628 segment, so the effect doesn't last very long).
20629] 
20630[immutable/downloader/share.py: reduce get_serverid(), one left, update ext deps
20631warner@lothar.com**20110227011150
20632 Ignore-this: d8d56dd8e7b280792b40105e13664554
20633 
20634 test_download.py: create+check MyShare instances better, make sure they share
20635 Server objects, now that finder.py cares
20636] 
20637[immutable/downloader/finder.py: reduce use of get_serverid(), one left
20638warner@lothar.com**20110227011146
20639 Ignore-this: 5785be173b491ae8a78faf5142892020
20640] 
20641[immutable/offloaded.py: reduce use of get_serverid() a bit more
20642warner@lothar.com**20110227011142
20643 Ignore-this: b48acc1b2ae1b311da7f3ba4ffba38f
20644] 
20645[immutable/upload.py: reduce use of get_serverid()
20646warner@lothar.com**20110227011138
20647 Ignore-this: ffdd7ff32bca890782119a6e9f1495f6
20648] 
20649[immutable/checker.py: remove some uses of s.get_serverid(), not all
20650warner@lothar.com**20110227011134
20651 Ignore-this: e480a37efa9e94e8016d826c492f626e
20652] 
20653[add remaining get_* methods to storage_client.Server, NoNetworkServer, and
20654warner@lothar.com**20110227011132
20655 Ignore-this: 6078279ddf42b179996a4b53bee8c421
20656 MockIServer stubs
20657] 
20658[upload.py: rearrange _make_trackers a bit, no behavior changes
20659warner@lothar.com**20110227011128
20660 Ignore-this: 296d4819e2af452b107177aef6ebb40f
20661] 
20662[happinessutil.py: finally rename merge_peers to merge_servers
20663warner@lothar.com**20110227011124
20664 Ignore-this: c8cd381fea1dd888899cb71e4f86de6e
20665] 
20666[test_upload.py: factor out FakeServerTracker
20667warner@lothar.com**20110227011120
20668 Ignore-this: 6c182cba90e908221099472cc159325b
20669] 
20670[test_upload.py: server-vs-tracker cleanup
20671warner@lothar.com**20110227011115
20672 Ignore-this: 2915133be1a3ba456e8603885437e03
20673] 
20674[happinessutil.py: server-vs-tracker cleanup
20675warner@lothar.com**20110227011111
20676 Ignore-this: b856c84033562d7d718cae7cb01085a9
20677] 
20678[upload.py: more tracker-vs-server cleanup
20679warner@lothar.com**20110227011107
20680 Ignore-this: bb75ed2afef55e47c085b35def2de315
20681] 
20682[upload.py: fix var names to avoid confusion between 'trackers' and 'servers'
20683warner@lothar.com**20110227011103
20684 Ignore-this: 5d5e3415b7d2732d92f42413c25d205d
20685] 
20686[refactor: s/peer/server/ in immutable/upload, happinessutil.py, test_upload
20687warner@lothar.com**20110227011100
20688 Ignore-this: 7ea858755cbe5896ac212a925840fe68
20689 
20690 No behavioral changes, just updating variable/method names and log messages.
20691 The effects outside these three files should be minimal: some exception
20692 messages changed (to say "server" instead of "peer"), and some internal class
20693 names were changed. A few things still use "peer" to minimize external
20694 changes, like UploadResults.timings["peer_selection"] and
20695 happinessutil.merge_peers, which can be changed later.
20696] 
20697[storage_client.py: clean up test_add_server/test_add_descriptor, remove .test_servers
20698warner@lothar.com**20110227011056
20699 Ignore-this: efad933e78179d3d5fdcd6d1ef2b19cc
20700] 
20701[test_client.py, upload.py:: remove KiB/MiB/etc constants, and other dead code
20702warner@lothar.com**20110227011051
20703 Ignore-this: dc83c5794c2afc4f81e592f689c0dc2d
20704] 
20705[test: increase timeout on a network test because Francois's ARM machine hit that timeout
20706zooko@zooko.com**20110317165909
20707 Ignore-this: 380c345cdcbd196268ca5b65664ac85b
20708 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.
20709] 
20710[docs/configuration.rst: add a "Frontend Configuration" section
20711Brian Warner <warner@lothar.com>**20110222014323
20712 Ignore-this: 657018aa501fe4f0efef9851628444ca
20713 
20714 this points to docs/frontends/*.rst, which were previously underlinked
20715] 
20716[web/filenode.py: avoid calling req.finish() on closed HTTP connections. Closes #1366
20717"Brian Warner <warner@lothar.com>"**20110221061544
20718 Ignore-this: 799d4de19933f2309b3c0c19a63bb888
20719] 
20720[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.
20721david-sarah@jacaranda.org**20110221015817
20722 Ignore-this: 51d181698f8c20d3aca58b057e9c475a
20723] 
20724[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.
20725david-sarah@jacaranda.org**20110221020125
20726 Ignore-this: b0744ed58f161bf188e037bad077fc48
20727] 
20728[Refactor StorageFarmBroker handling of servers
20729Brian Warner <warner@lothar.com>**20110221015804
20730 Ignore-this: 842144ed92f5717699b8f580eab32a51
20731 
20732 Pass around IServer instance instead of (peerid, rref) tuple. Replace
20733 "descriptor" with "server". Other replacements:
20734 
20735  get_all_servers -> get_connected_servers/get_known_servers
20736  get_servers_for_index -> get_servers_for_psi (now returns IServers)
20737 
20738 This change still needs to be pushed further down: lots of code is now
20739 getting the IServer and then distributing (peerid, rref) internally.
20740 Instead, it ought to distribute the IServer internally and delay
20741 extracting a serverid or rref until the last moment.
20742 
20743 no_network.py was updated to retain parallelism.
20744] 
20745[TAG allmydata-tahoe-1.8.2
20746warner@lothar.com**20110131020101] 
20747Patch bundle hash:
20748302dd409c9b7f075a3e0fe0402e8468c1b909301