1 | diff -rN -u old-tahoe/contrib/fuse/impl_c/blackmatch.py new-tahoe/contrib/fuse/impl_c/blackmatch.py |
---|
2 | --- old-tahoe/contrib/fuse/impl_c/blackmatch.py 2010-01-25 11:54:53.023000000 +0000 |
---|
3 | +++ new-tahoe/contrib/fuse/impl_c/blackmatch.py 2010-01-25 11:54:58.114000000 +0000 |
---|
4 | @@ -1,7 +1,7 @@ |
---|
5 | #!/usr/bin/env python |
---|
6 | |
---|
7 | #----------------------------------------------------------------------------------------------- |
---|
8 | -from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI |
---|
9 | +from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI, is_literal_file_uri |
---|
10 | from allmydata.scripts.common_http import do_http as do_http_req |
---|
11 | from allmydata.util.hashutil import tagged_hash |
---|
12 | from allmydata.util.assertutil import precondition |
---|
13 | @@ -335,7 +335,7 @@ |
---|
14 | self.fname = self.tfs.cache.tmp_file(os.urandom(20)) |
---|
15 | if self.fnode is None: |
---|
16 | log('TFF: [%s] open() for write: no file node, creating new File %s' % (self.name, self.fname, )) |
---|
17 | - self.fnode = File(0, 'URI:LIT:') |
---|
18 | + self.fnode = File(0, LiteralFileURI.BASE_STRING) |
---|
19 | self.fnode.tmp_fname = self.fname # XXX kill this |
---|
20 | self.parent.add_child(self.name, self.fnode, {}) |
---|
21 | elif hasattr(self.fnode, 'tmp_fname'): |
---|
22 | @@ -362,7 +362,7 @@ |
---|
23 | self.fname = self.fnode.tmp_fname |
---|
24 | log('TFF: reopening(%s) for reading' % self.fname) |
---|
25 | else: |
---|
26 | - if uri.startswith("URI:LIT") or not self.tfs.async: |
---|
27 | + if is_literal_file_uri(uri) or not self.tfs.async: |
---|
28 | log('TFF: synchronously fetching file from cache for reading') |
---|
29 | self.fname = self.tfs.cache.get_file(uri) |
---|
30 | else: |
---|
31 | @@ -906,7 +906,7 @@ |
---|
32 | |
---|
33 | class TStat(fuse.Stat): |
---|
34 | # in fuse 0.2, these are set by fuse.Stat.__init__ |
---|
35 | - # in fuse 0.2-pre3 (hardy) they are not. badness unsues if they're missing |
---|
36 | + # in fuse 0.2-pre3 (hardy) they are not. badness ensues if they're missing |
---|
37 | st_mode = None |
---|
38 | st_ino = 0 |
---|
39 | st_dev = 0 |
---|
40 | @@ -1237,7 +1237,7 @@ |
---|
41 | |
---|
42 | def get_file(self, uri): |
---|
43 | self.log('get_file(%s)' % (uri,)) |
---|
44 | - if uri.startswith("URI:LIT"): |
---|
45 | + if is_literal_file_uri(uri): |
---|
46 | return self.get_literal(uri) |
---|
47 | else: |
---|
48 | return self.get_chk(uri, async=False) |
---|
49 | diff -rN -u old-tahoe/docs/frontends/webapi.txt new-tahoe/docs/frontends/webapi.txt |
---|
50 | --- old-tahoe/docs/frontends/webapi.txt 2010-01-25 11:54:53.204000000 +0000 |
---|
51 | +++ new-tahoe/docs/frontends/webapi.txt 2010-01-25 11:54:58.321000000 +0000 |
---|
52 | @@ -70,20 +70,20 @@ |
---|
53 | these tasks. In general, everything that can be done with a PUT or DELETE can |
---|
54 | also be done with a POST. |
---|
55 | |
---|
56 | -Tahoe's web API is designed for two different consumers. The first is a |
---|
57 | -program that needs to manipulate the virtual file system. Such programs are |
---|
58 | +Tahoe's web API is designed for two different kinds of consumer. The first is |
---|
59 | +a program that needs to manipulate the virtual file system. Such programs are |
---|
60 | expected to use the RESTful interface described above. The second is a human |
---|
61 | using a standard web browser to work with the filesystem. This user is given |
---|
62 | a series of HTML pages with links to download files, and forms that use POST |
---|
63 | actions to upload, rename, and delete files. |
---|
64 | |
---|
65 | When an error occurs, the HTTP response code will be set to an appropriate |
---|
66 | -400-series code (like 404 for an unknown childname, or 400 Gone when a file |
---|
67 | -is unrecoverable due to insufficient shares), and the HTTP response body will |
---|
68 | -usually contain a few lines of explanation as to the cause of the error and |
---|
69 | -possible responses. Unusual exceptions may result in a 500 Internal Server |
---|
70 | -Error as a catch-all, with a default response body will contain a |
---|
71 | -Nevow-generated HTML-ized representation of the Python exception stack trace |
---|
72 | +400-series code (like 404 Not Found for an unknown childname, or 400 Bad Request |
---|
73 | +when the parameters to a webapi operation are invalid), and the HTTP response |
---|
74 | +body will usually contain a few lines of explanation as to the cause of the |
---|
75 | +error and possible responses. Unusual exceptions may result in a |
---|
76 | +500 Internal Server Error as a catch-all, with a default response body containing |
---|
77 | +a Nevow-generated HTML-ized representation of the Python exception stack trace |
---|
78 | that caused the problem. CLI programs which want to copy the response body to |
---|
79 | stderr should provide an "Accept: text/plain" header to their requests to get |
---|
80 | a plain text stack trace instead. If the Accept header contains */*, or |
---|
81 | @@ -108,9 +108,9 @@ |
---|
82 | read- and write- caps, which start with "URI:SSK", and give access to mutable |
---|
83 | files. |
---|
84 | |
---|
85 | -(later versions of Tahoe will make these strings shorter, and will remove the |
---|
86 | +(Later versions of Tahoe will make these strings shorter, and will remove the |
---|
87 | unfortunate colons, which must be escaped when these caps are embedded in |
---|
88 | -URLs). |
---|
89 | +URLs.) |
---|
90 | |
---|
91 | To refer to any Tahoe object through the web API, you simply need to combine |
---|
92 | a prefix (which indicates the HTTP server to use) with the cap (which |
---|
93 | @@ -150,8 +150,12 @@ |
---|
94 | |
---|
95 | === Child Lookup === |
---|
96 | |
---|
97 | -Tahoe directories contain named children, just like directories in a regular |
---|
98 | -local filesystem. These children can be either files or subdirectories. |
---|
99 | +Tahoe directories contain named child entries, just like directories in a regular |
---|
100 | +local filesystem. These child entries, called "dirnodes", consist of a name, |
---|
101 | +metadata, a write slot, and a read slot. The write and read slots normally contain |
---|
102 | +a writecap and readcap referring to the same object, which can be either a file |
---|
103 | +or a subdirectory. The write slot may be empty (actually, both may be empty, |
---|
104 | +but that is unusual). |
---|
105 | |
---|
106 | If you have a Tahoe URL that refers to a directory, and want to reference a |
---|
107 | named child inside it, just append the child name to the URL. For example, if |
---|
108 | @@ -195,9 +199,9 @@ |
---|
109 | representable as such. |
---|
110 | |
---|
111 | All Tahoe operations that refer to existing files or directories must include |
---|
112 | -a suitable read- or write- cap in the URL: the wapi server won't add one |
---|
113 | +a suitable read- or write- cap in the URL: the webapi server won't add one |
---|
114 | for you. If you don't know the cap, you can't access the file. This allows |
---|
115 | -the security properties of Tahoe caps to be extended across the wapi |
---|
116 | +the security properties of Tahoe caps to be extended across the webapi |
---|
117 | interface. |
---|
118 | |
---|
119 | == Slow Operations, Progress, and Cancelling == |
---|
120 | @@ -271,7 +275,7 @@ |
---|
121 | since the operation completed) will remain valid for ten minutes. |
---|
122 | |
---|
123 | Many "slow" operations can begin to use unacceptable amounts of memory when |
---|
124 | -operation on large directory structures. The memory usage increases when the |
---|
125 | +operating on large directory structures. The memory usage increases when the |
---|
126 | ophandle is polled, as the results must be copied into a JSON string, sent |
---|
127 | over the wire, then parsed by a client. So, as an alternative, many "slow" |
---|
128 | operations have streaming equivalents. These equivalents do not use operation |
---|
129 | @@ -314,7 +318,7 @@ |
---|
130 | To use the /uri/$FILECAP form, $FILECAP be a write-cap for a mutable file. |
---|
131 | |
---|
132 | In the /uri/$DIRCAP/[SUBDIRS../]FILENAME form, if the target file is a |
---|
133 | - writable mutable file, that files contents will be overwritten in-place. If |
---|
134 | + writable mutable file, that file's contents will be overwritten in-place. If |
---|
135 | it is a read-cap for a mutable file, an error will occur. If it is an |
---|
136 | immutable file, the old file will be discarded, and a new one will be put in |
---|
137 | its place. |
---|
138 | @@ -333,7 +337,7 @@ |
---|
139 | PUT /uri |
---|
140 | |
---|
141 | This uploads a file, and produces a file-cap for the contents, but does not |
---|
142 | - attach the file into the virtual drive. No directories will be modified by |
---|
143 | + attach the file into the filesystem. No directories will be modified by |
---|
144 | this operation. The file-cap is returned as the body of the HTTP response. |
---|
145 | |
---|
146 | If "mutable=true" is in the query arguments, the operation will create a |
---|
147 | @@ -347,7 +351,7 @@ |
---|
148 | |
---|
149 | Create a new empty directory and return its write-cap as the HTTP response |
---|
150 | body. This does not make the newly created directory visible from the |
---|
151 | - virtual drive. The "PUT" operation is provided for backwards compatibility: |
---|
152 | + filesystem. The "PUT" operation is provided for backwards compatibility: |
---|
153 | new code should use POST. |
---|
154 | |
---|
155 | POST /uri?t=mkdir-with-children |
---|
156 | @@ -388,8 +392,29 @@ |
---|
157 | "linkcrtime": 1202777696.7564139, |
---|
158 | "linkmotime": 1202777696.7564139, |
---|
159 | } } } ] |
---|
160 | - } |
---|
161 | + } |
---|
162 | |
---|
163 | + For forward-compatibility, a mutable directory can also contain caps in |
---|
164 | + a format that is unknown to the webapi server. When such caps are retrieved |
---|
165 | + from a mutable directory in a "ro_uri" field, they will be prefixed with |
---|
166 | + the string "ro.", indicating that they must not be decoded without |
---|
167 | + checking that they are read-only. The "ro." prefix must not be stripped |
---|
168 | + off without performing this check. (Future versions of the webapi server |
---|
169 | + will perform it where necessary.) |
---|
170 | + |
---|
171 | + If both the "rw_uri" and "ro_uri" fields are present in a given PROPDICT, |
---|
172 | + and the webapi server recognizes the rw_uri as a write cap, then it will |
---|
173 | + reset the ro_uri to the corresponding read cap and discard the original |
---|
174 | + contents of ro_uri (in order to ensure that the two caps correspond to the |
---|
175 | + same object and that the ro_uri is in fact read-only). However this may not |
---|
176 | + happen for caps in a format unknown to the webapi server. Therefore, when |
---|
177 | + writing a directory the webapi client should ensure that the contents |
---|
178 | + of "rw_uri" and "ro_uri" for a given PROPDICT are a consistent |
---|
179 | + (write cap, read cap) pair if possible. If the webapi client only has |
---|
180 | + one cap and does not know whether it is a write cap or read cap, then |
---|
181 | + it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The |
---|
182 | + client must not put a write cap into a "ro_uri" field. |
---|
183 | + |
---|
184 | Note that the webapi-using client application must not provide the |
---|
185 | "Content-Type: multipart/form-data" header that usually accompanies HTML |
---|
186 | form submissions, since the body is not formatted this way. Doing so will |
---|
187 | @@ -404,59 +429,95 @@ |
---|
188 | |
---|
189 | Like t=mkdir-with-children above, but the new directory will be |
---|
190 | deep-immutable. This means that the directory itself is immutable, and that |
---|
191 | - it can only contain deep-immutable objects, like immutable files, literal |
---|
192 | - files, and deep-immutable directories. A non-empty request body is |
---|
193 | - mandatory, since after the directory is created, it will not be possible to |
---|
194 | - add more children to it. |
---|
195 | + it can only contain objects that are treated as being deep-immutable, like |
---|
196 | + immutable files, literal files, and deep-immutable directories. |
---|
197 | + |
---|
198 | + For forward-compatibility, a deep-immutable directory can also contain caps |
---|
199 | + in a format that is unknown to the webapi server. When such caps are retrieved |
---|
200 | + from a deep-immutable directory in a "ro_uri" field, they will be prefixed |
---|
201 | + with the string "imm.", indicating that they must not be decoded without |
---|
202 | + checking that they are immutable. The "imm." prefix must not be stripped |
---|
203 | + off without performing this check. (Future versions of the webapi server |
---|
204 | + will perform it where necessary.) |
---|
205 | + |
---|
206 | + The cap for each child may be given either in the "rw_uri" or "ro_uri" |
---|
207 | + field of the PROPDICT (not both). If a cap is given in the "rw_uri" field, |
---|
208 | + then the webapi server will check that it is an immutable readcap of a |
---|
209 | + *known* format, and give an error if it is not. If a cap is given in the |
---|
210 | + "ro_uri" field, then the webapi server will still check whether known |
---|
211 | + caps are immutable, but for unknown caps it will simply assume that the |
---|
212 | + cap can be stored, as described above. Note that an attacker would be |
---|
213 | + able to store any cap in an immutable directory, so this check when |
---|
214 | + creating the directory is only to help non-malicious clients to avoid |
---|
215 | + accidentally giving away more authority than intended. |
---|
216 | + |
---|
217 | + A non-empty request body is mandatory, since after the directory is created, |
---|
218 | + it will not be possible to add more children to it. |
---|
219 | |
---|
220 | POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir |
---|
221 | PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir |
---|
222 | |
---|
223 | Create new directories as necessary to make sure that the named target |
---|
224 | ($DIRCAP/SUBDIRS../SUBDIR) is a directory. This will create additional |
---|
225 | - intermediate directories as necessary. If the named target directory already |
---|
226 | - exists, this will make no changes to it. |
---|
227 | + intermediate mutable directories as necessary. If the named target directory |
---|
228 | + already exists, this will make no changes to it. |
---|
229 | |
---|
230 | If the final directory is created, it will be empty. |
---|
231 | |
---|
232 | - This will return an error if a blocking file is present at any of the parent |
---|
233 | - names, preventing the server from creating the necessary parent directory. |
---|
234 | + This operation will return an error if a blocking file is present at any of |
---|
235 | + the parent names, preventing the server from creating the necessary parent |
---|
236 | + directory, or if it would require changing an immutable directory. |
---|
237 | |
---|
238 | The write-cap of the new directory will be returned as the HTTP response |
---|
239 | body. |
---|
240 | |
---|
241 | POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children |
---|
242 | |
---|
243 | - Like above, but if the final directory is created, it will be populated with |
---|
244 | - initial children from the POST request body, as described above in the |
---|
245 | - /uri?t=mkdir-with-children operation. |
---|
246 | + Like /uri?t=mkdir-with-children, but the final directory is created as a |
---|
247 | + child of an existing mutable directory. This will create additional |
---|
248 | + intermediate mutable directories as necessary. If the final directory is |
---|
249 | + created, it will be populated with initial children from the POST request |
---|
250 | + body, as described above. |
---|
251 | |
---|
252 | POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-immutable |
---|
253 | |
---|
254 | - Like above, but the final directory will be deep-immutable, with the |
---|
255 | - children specified as a JSON dictionary in the POST request body. |
---|
256 | + Like /uri?t=mkdir-immutable, but the final directory is created as a child |
---|
257 | + of an existing mutable directory. The final directory will be deep-immutable, |
---|
258 | + and will be populated with the children specified as a JSON dictionary in |
---|
259 | + the POST request body. |
---|
260 | + |
---|
261 | + In Tahoe 1.6 this operation creates intermediate mutable directories if |
---|
262 | + necessary, but that behaviour should not be relied on; see ticket #920. |
---|
263 | |
---|
264 | POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME |
---|
265 | |
---|
266 | - Create a new empty directory and attach it to the given existing directory. |
---|
267 | - This will create additional intermediate directories as necessary. |
---|
268 | + Create a new empty mutable directory and attach it to the given existing |
---|
269 | + directory. This will create additional intermediate directories as necessary. |
---|
270 | |
---|
271 | - The URL of this form points to the parent of the bottom-most new directory, |
---|
272 | - whereas the previous form has a URL that points directly to the bottom-most |
---|
273 | - new directory. |
---|
274 | + This operation will return an error if a blocking file is present at any of |
---|
275 | + the parent names, preventing the server from creating the necessary parent |
---|
276 | + directory, or if it would require changing any immutable directory. |
---|
277 | + |
---|
278 | + The URL of this operation points to the parent of the bottommost new directory, |
---|
279 | + whereas the /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir operation above has a URL |
---|
280 | + that points directly to the bottommost new directory. |
---|
281 | |
---|
282 | POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME |
---|
283 | |
---|
284 | - As above, but the new directory will be populated with initial children via |
---|
285 | - the POST request body, as described in /uri?t=mkdir-with-children above. |
---|
286 | + Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME, but the new directory will |
---|
287 | + be populated with initial children via the POST request body. |
---|
288 | Note that the name= argument must be passed as a queryarg, because the POST |
---|
289 | request body is used for the initial children JSON. |
---|
290 | |
---|
291 | POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME |
---|
292 | |
---|
293 | - As above, but the new directory will be deep-immutable, with the children |
---|
294 | - specified as a JSON dictionary in the POST request body. Again, the name= |
---|
295 | - argument must be passed as a queryarg. |
---|
296 | + Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME, but the |
---|
297 | + final directory will be deep-immutable. The children are specified as a |
---|
298 | + JSON dictionary in the POST request body. Again, the name= argument must be |
---|
299 | + passed as a queryarg. |
---|
300 | + |
---|
301 | + In Tahoe 1.6 this operation creates intermediate mutable directories if |
---|
302 | + necessary, but that behaviour should not be relied on; see ticket #920. |
---|
303 | |
---|
304 | === Get Information About A File Or Directory (as JSON) === |
---|
305 | |
---|
306 | @@ -546,7 +607,7 @@ |
---|
307 | |
---|
308 | Then the rw_uri field will be present in the information about a directory |
---|
309 | if and only if you have read-write access to that directory. The verify_uri |
---|
310 | - field will be presend if and only if the object has a verify-cap |
---|
311 | + field will be present if and only if the object has a verify-cap |
---|
312 | (non-distributed LIT files do not have verify-caps). |
---|
313 | |
---|
314 | ==== About the metadata ==== |
---|
315 | @@ -622,11 +683,11 @@ |
---|
316 | link points. |
---|
317 | |
---|
318 | 4. Also, quite apart from Tahoe, you might be confused about the meaning of |
---|
319 | - the 'ctime' in unix local filesystems, which people sometimes think means |
---|
320 | - file creation time, but which actually means, in unix local filesystems, the |
---|
321 | + the 'ctime' in UNIX local filesystems, which people sometimes think means |
---|
322 | + file creation time, but which actually means, in UNIX local filesystems, the |
---|
323 | most recent time that the file contents or the file metadata (such as owner, |
---|
324 | permission bits, extended attributes, etc.) has changed. Note that although |
---|
325 | - 'ctime' does not mean file creation time in Unix, it does mean link creation |
---|
326 | + 'ctime' does not mean file creation time in UNIX, it does mean link creation |
---|
327 | time in Tahoe, unless the "tahoe backup" command has been used on that link, |
---|
328 | in which case it means something about the local filesystem file which |
---|
329 | corresponds to the Tahoe file which is pointed at by the link. It means |
---|
330 | @@ -634,7 +695,6 @@ |
---|
331 | Windows) or file-contents-or-metadata-update-time of the local file (if |
---|
332 | "tahoe backup" was run on a different operating system). |
---|
333 | |
---|
334 | - |
---|
335 | === Attaching an existing File or Directory by its read- or write- cap === |
---|
336 | |
---|
337 | PUT /uri/$DIRCAP/[SUBDIRS../]CHILDNAME?t=uri |
---|
338 | @@ -658,10 +718,15 @@ |
---|
339 | if there is already an object at the given location, rather than |
---|
340 | overwriting the existing object. To allow the operation to overwrite a |
---|
341 | file, but return an error when trying to overwrite a directory, use |
---|
342 | - "replace=only-files" (this behavior is closer to the traditional unix "mv" |
---|
343 | + "replace=only-files" (this behavior is closer to the traditional UNIX "mv" |
---|
344 | command). Note that "true", "t", and "1" are all synonyms for "True", and |
---|
345 | "false", "f", and "0" are synonyms for "False", and the parameter is |
---|
346 | case-insensitive. |
---|
347 | + |
---|
348 | + Note that this operation does not take its child cap in the form of |
---|
349 | + separate "rw_uri" and "ro_uri" fields. Therefore, it cannot accept a |
---|
350 | + child cap in a format unknown to the webapi server, because the server |
---|
351 | + is not able to attenuate an unknown write cap to a read cap. |
---|
352 | |
---|
353 | === Adding multiple files or directories to a parent directory at once === |
---|
354 | |
---|
355 | @@ -679,7 +744,9 @@ |
---|
356 | "childinfo" is a dictionary that contains "rw_uri", "ro_uri", and |
---|
357 | "metadata" keys. You can take the output of "GET /uri/$DIRCAP1?t=json" and |
---|
358 | use it as the input to "POST /uri/$DIRCAP2?t=set_children" to make DIR2 |
---|
359 | - look very much like DIR1. |
---|
360 | + look very much like DIR1 (except for any existing children of DIR2 that |
---|
361 | + were not overwritten, and any existing "tahoe" metadata keys as described |
---|
362 | + below). |
---|
363 | |
---|
364 | When the set_children request contains a child name that already exists in |
---|
365 | the target directory, this command defaults to overwriting that child with |
---|
366 | @@ -721,7 +788,7 @@ |
---|
367 | The object will only become completely unreachable once 1: there are no |
---|
368 | reachable directories that reference it, and 2: nobody is holding a read- |
---|
369 | or write- cap to the object. (This behavior is very similar to the way |
---|
370 | - hardlinks and anonymous files work in traditional unix filesystems). |
---|
371 | + hardlinks and anonymous files work in traditional UNIX filesystems). |
---|
372 | |
---|
373 | This operation will not modify more than a single directory. Intermediate |
---|
374 | directories which were implicitly created by PUT or POST methods will *not* |
---|
375 | @@ -850,7 +917,7 @@ |
---|
376 | POST /uri?t=upload |
---|
377 | |
---|
378 | This uploads a file, and produces a file-cap for the contents, but does not |
---|
379 | - attach the file into the virtual drive. No directories will be modified by |
---|
380 | + attach the file into the filesystem. No directories will be modified by |
---|
381 | this operation. |
---|
382 | |
---|
383 | The file must be provided as the "file" field of an HTML encoded form body, |
---|
384 | @@ -880,9 +947,9 @@ |
---|
385 | |
---|
386 | POST /uri/$DIRCAP/[SUBDIRS../]?t=upload |
---|
387 | |
---|
388 | - This uploads a file, and attaches it as a new child of the given directory. |
---|
389 | - The file must be provided as the "file" field of an HTML encoded form body, |
---|
390 | - produced in response to an HTML form like this: |
---|
391 | + This uploads a file, and attaches it as a new child of the given directory, |
---|
392 | + which must be mutable. The file must be provided as the "file" field of an |
---|
393 | + HTML-encoded form body, produced in response to an HTML form like this: |
---|
394 | <form action="." method="POST" enctype="multipart/form-data"> |
---|
395 | <input type="hidden" name="t" value="upload" /> |
---|
396 | <input type="file" name="file" /> |
---|
397 | @@ -925,9 +992,10 @@ |
---|
398 | POST /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=upload |
---|
399 | |
---|
400 | This also uploads a file and attaches it as a new child of the given |
---|
401 | - directory. It is a slight variant of the previous operation, as the URL |
---|
402 | - refers to the target file rather than the parent directory. It is otherwise |
---|
403 | - identical: this accepts mutable= and when_done= arguments too. |
---|
404 | + directory, which must be mutable. It is a slight variant of the previous |
---|
405 | + operation, as the URL refers to the target file rather than the parent |
---|
406 | + directory. It is otherwise identical: this accepts mutable= and when_done= |
---|
407 | + arguments too. |
---|
408 | |
---|
409 | POST /uri/$FILECAP?t=upload |
---|
410 | |
---|
411 | @@ -955,20 +1023,21 @@ |
---|
412 | |
---|
413 | POST /uri/$DIRCAP/[SUBDIRS../]?t=delete&name=CHILDNAME |
---|
414 | |
---|
415 | - This instructs the node to delete a child object (file or subdirectory) from |
---|
416 | - the given directory. Note that the entire subtree is removed. This is |
---|
417 | - somewhat like "rm -rf" (from the point of view of the parent), but other |
---|
418 | - references into the subtree will see that the child subdirectories are not |
---|
419 | - modified by this operation. Only the link from the given directory to its |
---|
420 | - child is severed. |
---|
421 | + This instructs the node to remove a child object (file or subdirectory) from |
---|
422 | + the given directory, which must be mutable. Note that the entire subtree is |
---|
423 | + unlinked from the parent. Unlike deleting a subdirectory in a UNIX local |
---|
424 | + filesystem, the subtree need not be empty; if it isn't, then other references |
---|
425 | + into the subtree will see that the child subdirectories are not modified by |
---|
426 | + this operation. Only the link from the given directory to its child is severed. |
---|
427 | |
---|
428 | === Renaming A Child === |
---|
429 | |
---|
430 | POST /uri/$DIRCAP/[SUBDIRS../]?t=rename&from_name=OLD&to_name=NEW |
---|
431 | |
---|
432 | - This instructs the node to rename a child of the given directory. This is |
---|
433 | - exactly the same as removing the child, then adding the same child-cap under |
---|
434 | - the new name. This operation cannot move the child to a different directory. |
---|
435 | + This instructs the node to rename a child of the given directory, which must |
---|
436 | + be mutable. This has a similar effect to removing the child, then adding the |
---|
437 | + same child-cap under the new name, except that it preserves metadata. This |
---|
438 | + operation cannot move the child to a different directory. |
---|
439 | |
---|
440 | This operation will replace any existing child of the new name, making it |
---|
441 | behave like the UNIX "mv -f" command. |
---|
442 | @@ -1590,7 +1659,7 @@ |
---|
443 | |
---|
444 | == Static Files in /public_html == |
---|
445 | |
---|
446 | -The wapi server will take any request for a URL that starts with /static |
---|
447 | +The webapi server will take any request for a URL that starts with /static |
---|
448 | and serve it from a configurable directory which defaults to |
---|
449 | $BASEDIR/public_html . This is configured by setting the "[node]web.static" |
---|
450 | value in $BASEDIR/tahoe.cfg . If this is left at the default value of |
---|
451 | @@ -1598,10 +1667,10 @@ |
---|
452 | served with the contents of the file $BASEDIR/public_html/subdir/foo.html . |
---|
453 | |
---|
454 | This can be useful to serve a javascript application which provides a |
---|
455 | -prettier front-end to the rest of the Tahoe wapi. |
---|
456 | +prettier front-end to the rest of the Tahoe webapi. |
---|
457 | |
---|
458 | |
---|
459 | -== safety and security issues -- names vs. URIs == |
---|
460 | +== Safety and security issues -- names vs. URIs == |
---|
461 | |
---|
462 | Summary: use explicit file- and dir- caps whenever possible, to reduce the |
---|
463 | potential for surprises when the filesystem structure is changed. |
---|
464 | @@ -1641,6 +1710,17 @@ |
---|
465 | child's name and the child's URI are included in the results of listing the |
---|
466 | parent directory, so it isn't any harder to use the URI for this purpose. |
---|
467 | |
---|
468 | +The read and write caps in a given directory node are separate URIs, and |
---|
469 | +can't be assumed to point to the same object even if they were retrieved in |
---|
470 | +the same operation (although the webapi server attempts to ensure this |
---|
471 | +in most cases). If you need to rely on that property, you should explicitly |
---|
472 | +verify it. More generally, you should not make assumptions about the |
---|
473 | +internal consistency of the contents of mutable directories. As a result |
---|
474 | +of the signatures on mutable object versions, it is guaranteed that a given |
---|
475 | +version was written in a single update, but -- as in the case of a file -- |
---|
476 | +the contents may have been chosen by a malicious writer in a way that is |
---|
477 | +designed to confuse applications that rely on their consistency. |
---|
478 | + |
---|
479 | In general, use names if you want "whatever object (whether file or |
---|
480 | directory) is found by following this name (or sequence of names) when my |
---|
481 | request reaches the server". Use URIs if you want "this particular object". |
---|
482 | @@ -1676,7 +1756,7 @@ |
---|
483 | |
---|
484 | Tahoe nodes implement internal serialization to make sure that a single Tahoe |
---|
485 | node cannot conflict with itself. For example, it is safe to issue two |
---|
486 | -directory modification requests to a single tahoe node's wapi server at the |
---|
487 | +directory modification requests to a single tahoe node's webapi server at the |
---|
488 | same time, because the Tahoe node will internally delay one of them until |
---|
489 | after the other has finished being applied. (This feature was introduced in |
---|
490 | Tahoe-1.1; back with Tahoe-1.0 the web client was responsible for serializing |
---|
491 | diff -rN -u old-tahoe/relnotes.txt new-tahoe/relnotes.txt |
---|
492 | --- old-tahoe/relnotes.txt 2010-01-25 11:54:55.260000000 +0000 |
---|
493 | +++ new-tahoe/relnotes.txt 2010-01-25 11:55:00.443000000 +0000 |
---|
494 | @@ -1,7 +1,7 @@ |
---|
495 | -ANNOUNCING Tahoe, the Lofty-Atmospheric Filesystem, v1.5 |
---|
496 | +ANNOUNCING Tahoe, the Lofty-Atmospheric Filesystem, v1.6 |
---|
497 | |
---|
498 | The Tahoe-LAFS team is pleased to announce the immediate |
---|
499 | -availability of version 1.5 of Tahoe, the Lofty Atmospheric |
---|
500 | +availability of version 1.6 of Tahoe, the Lofty Atmospheric |
---|
501 | File System. |
---|
502 | |
---|
503 | Tahoe-LAFS is the first cloud storage technology which offers |
---|
504 | @@ -29,15 +29,20 @@ |
---|
505 | |
---|
506 | COMPATIBILITY |
---|
507 | |
---|
508 | -Version 1.5 is fully compatible with the version 1 series of |
---|
509 | -Tahoe-LAFS. Files written by v1.5 clients can be read by |
---|
510 | -clients of all versions back to v1.0. v1.5 clients can read |
---|
511 | -files produced by clients of all versions since v1.0. v1.5 |
---|
512 | -servers can serve clients of all versions back to v1.0 and v1.5 |
---|
513 | +Version 1.6 is fully compatible with the version 1 series of |
---|
514 | +Tahoe-LAFS. Files written by v1.6 clients can be read by |
---|
515 | +clients of all versions back to v1.0. v1.6 clients can read |
---|
516 | +files produced by clients of all versions since v1.0. v1.6 |
---|
517 | +servers can serve clients of all versions back to v1.0 and v1.6 |
---|
518 | clients can use servers of all versions back to v1.0. |
---|
519 | |
---|
520 | -This is the sixth release in the version 1 series. The version |
---|
521 | -1 series of Tahoe-LAFS will be actively supported and |
---|
522 | +In addition, version 1.6 improves forward-compatibility with |
---|
523 | +planned future cap formats, allowing updates to a directory |
---|
524 | +containing both current and future caps, without loss of |
---|
525 | +information. |
---|
526 | + |
---|
527 | +This is the seventh major release in the version 1 series. The |
---|
528 | +version 1 series of Tahoe-LAFS will be actively supported and |
---|
529 | maintained for the forseeable future, and future versions of |
---|
530 | Tahoe-LAFS will retain the ability to read and write files |
---|
531 | compatible with Tahoe-LAFS v1. |
---|
532 | diff -rN -u old-tahoe/src/allmydata/client.py new-tahoe/src/allmydata/client.py |
---|
533 | --- old-tahoe/src/allmydata/client.py 2010-01-25 11:54:55.348000000 +0000 |
---|
534 | +++ new-tahoe/src/allmydata/client.py 2010-01-25 11:55:00.506000000 +0000 |
---|
535 | @@ -471,13 +471,16 @@ |
---|
536 | # dirnodes. The first takes a URI and produces a filenode or (new-style) |
---|
537 | # dirnode. The other three create brand-new filenodes/dirnodes. |
---|
538 | |
---|
539 | - def create_node_from_uri(self, writecap, readcap=None): |
---|
540 | - # this returns synchronously. |
---|
541 | - return self.nodemaker.create_from_cap(writecap, readcap) |
---|
542 | + def create_node_from_uri(self, write_uri, read_uri=None, deep_immutable=False, name="<unknown name>"): |
---|
543 | + # This returns synchronously. |
---|
544 | + # Note that it does *not* validate the write_uri and read_uri; instead we |
---|
545 | + # may get an opaque node if there were any problems. |
---|
546 | + return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name) |
---|
547 | |
---|
548 | def create_dirnode(self, initial_children={}): |
---|
549 | d = self.nodemaker.create_new_mutable_directory(initial_children) |
---|
550 | return d |
---|
551 | + |
---|
552 | def create_immutable_dirnode(self, children, convergence=None): |
---|
553 | return self.nodemaker.create_immutable_directory(children, convergence) |
---|
554 | |
---|
555 | diff -rN -u old-tahoe/src/allmydata/control.py new-tahoe/src/allmydata/control.py |
---|
556 | --- old-tahoe/src/allmydata/control.py 2010-01-25 11:54:55.368000000 +0000 |
---|
557 | +++ new-tahoe/src/allmydata/control.py 2010-01-25 11:55:00.523000000 +0000 |
---|
558 | @@ -5,7 +5,7 @@ |
---|
559 | from twisted.internet import defer |
---|
560 | from twisted.internet.interfaces import IConsumer |
---|
561 | from foolscap.api import Referenceable |
---|
562 | -from allmydata.interfaces import RIControlClient |
---|
563 | +from allmydata.interfaces import RIControlClient, IFileNode |
---|
564 | from allmydata.util import fileutil, mathutil |
---|
565 | from allmydata.immutable import upload |
---|
566 | from twisted.python import log |
---|
567 | @@ -67,7 +67,9 @@ |
---|
568 | return d |
---|
569 | |
---|
570 | def remote_download_from_uri_to_file(self, uri, filename): |
---|
571 | - filenode = self.parent.create_node_from_uri(uri) |
---|
572 | + filenode = self.parent.create_node_from_uri(uri, name=filename) |
---|
573 | + if not IFileNode.providedBy(filenode): |
---|
574 | + raise AssertionError("The URI does not reference a file.") |
---|
575 | c = FileWritingConsumer(filename) |
---|
576 | d = filenode.read(c) |
---|
577 | d.addCallback(lambda res: filename) |
---|
578 | @@ -199,6 +201,8 @@ |
---|
579 | if i >= self.count: |
---|
580 | return |
---|
581 | n = self.parent.create_node_from_uri(self.uris[i]) |
---|
582 | + if not IFileNode.providedBy(n): |
---|
583 | + raise AssertionError("The URI does not reference a file.") |
---|
584 | if n.is_mutable(): |
---|
585 | d1 = n.download_best_version() |
---|
586 | else: |
---|
587 | diff -rN -u old-tahoe/src/allmydata/dirnode.py new-tahoe/src/allmydata/dirnode.py |
---|
588 | --- old-tahoe/src/allmydata/dirnode.py 2010-01-25 11:54:55.383000000 +0000 |
---|
589 | +++ new-tahoe/src/allmydata/dirnode.py 2010-01-25 11:55:00.535000000 +0000 |
---|
590 | @@ -5,13 +5,13 @@ |
---|
591 | from twisted.internet import defer |
---|
592 | from foolscap.api import fireEventually |
---|
593 | import simplejson |
---|
594 | -from allmydata.mutable.common import NotMutableError |
---|
595 | +from allmydata.mutable.common import NotWriteableError |
---|
596 | from allmydata.mutable.filenode import MutableFileNode |
---|
597 | -from allmydata.unknown import UnknownNode |
---|
598 | +from allmydata.unknown import UnknownNode, strip_prefix_for_ro |
---|
599 | from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \ |
---|
600 | IImmutableFileNode, IMutableFileNode, \ |
---|
601 | ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \ |
---|
602 | - CannotPackUnknownNodeError |
---|
603 | + MustBeDeepImmutableError, CapConstraintError |
---|
604 | from allmydata.check_results import DeepCheckResults, \ |
---|
605 | DeepCheckAndRepairResults |
---|
606 | from allmydata.monitor import Monitor |
---|
607 | @@ -23,6 +23,11 @@ |
---|
608 | from pycryptopp.cipher.aes import AES |
---|
609 | from allmydata.util.dictutil import AuxValueDict |
---|
610 | |
---|
611 | + |
---|
612 | +# TODO: {Deleter,MetadataSetter,Adder}.modify all start by unpacking the |
---|
613 | +# contents and end by repacking them. It might be better to apply them to |
---|
614 | +# the unpacked contents. |
---|
615 | + |
---|
616 | class Deleter: |
---|
617 | def __init__(self, node, name, must_exist=True): |
---|
618 | self.node = node |
---|
619 | @@ -40,6 +45,7 @@ |
---|
620 | new_contents = self.node._pack_contents(children) |
---|
621 | return new_contents |
---|
622 | |
---|
623 | + |
---|
624 | class MetadataSetter: |
---|
625 | def __init__(self, node, name, metadata): |
---|
626 | self.node = node |
---|
627 | @@ -75,6 +81,11 @@ |
---|
628 | for (name, (child, new_metadata)) in self.entries.iteritems(): |
---|
629 | precondition(isinstance(name, unicode), name) |
---|
630 | precondition(IFilesystemNode.providedBy(child), child) |
---|
631 | + |
---|
632 | + # Strictly speaking this is redundant because we would raise the |
---|
633 | + # error again in pack_children. |
---|
634 | + child.raise_error() |
---|
635 | + |
---|
636 | if name in children: |
---|
637 | if not self.overwrite: |
---|
638 | raise ExistingChildError("child '%s' already exists" % name) |
---|
639 | @@ -123,25 +134,21 @@ |
---|
640 | new_contents = self.node._pack_contents(children) |
---|
641 | return new_contents |
---|
642 | |
---|
643 | -def _encrypt_rwcap(filenode, rwcap): |
---|
644 | - assert isinstance(rwcap, str) |
---|
645 | +def _encrypt_rw_uri(filenode, rw_uri): |
---|
646 | + assert isinstance(rw_uri, str) |
---|
647 | writekey = filenode.get_writekey() |
---|
648 | if not writekey: |
---|
649 | return "" |
---|
650 | - salt = hashutil.mutable_rwcap_salt_hash(rwcap) |
---|
651 | + salt = hashutil.mutable_rwcap_salt_hash(rw_uri) |
---|
652 | key = hashutil.mutable_rwcap_key_hash(salt, writekey) |
---|
653 | cryptor = AES(key) |
---|
654 | - crypttext = cryptor.process(rwcap) |
---|
655 | + crypttext = cryptor.process(rw_uri) |
---|
656 | mac = hashutil.hmac(key, salt + crypttext) |
---|
657 | assert len(mac) == 32 |
---|
658 | return salt + crypttext + mac |
---|
659 | # The MAC is not checked by readers in Tahoe >= 1.3.0, but we still |
---|
660 | # produce it for the sake of older readers. |
---|
661 | |
---|
662 | -class MustBeDeepImmutable(Exception): |
---|
663 | - """You tried to add a non-deep-immutable node to a deep-immutable |
---|
664 | - directory.""" |
---|
665 | - |
---|
666 | def pack_children(filenode, children, deep_immutable=False): |
---|
667 | """Take a dict that maps: |
---|
668 | children[unicode_name] = (IFileSystemNode, metadata_dict) |
---|
669 | @@ -152,7 +159,7 @@ |
---|
670 | time. |
---|
671 | |
---|
672 | If deep_immutable is True, I will require that all my children are deeply |
---|
673 | - immutable, and will raise a MustBeDeepImmutable exception if not. |
---|
674 | + immutable, and will raise a MustBeDeepImmutableError if not. |
---|
675 | """ |
---|
676 | |
---|
677 | has_aux = isinstance(children, AuxValueDict) |
---|
678 | @@ -161,25 +168,29 @@ |
---|
679 | assert isinstance(name, unicode) |
---|
680 | entry = None |
---|
681 | (child, metadata) = children[name] |
---|
682 | - if deep_immutable and child.is_mutable(): |
---|
683 | - # TODO: consider adding IFileSystemNode.is_deep_immutable() |
---|
684 | - raise MustBeDeepImmutable("child '%s' is mutable" % (name,)) |
---|
685 | + child.raise_error() |
---|
686 | + if deep_immutable and not child.is_allowed_in_immutable_directory(): |
---|
687 | + raise MustBeDeepImmutableError("child '%s' is not allowed in an immutable directory" % (name,), name) |
---|
688 | if has_aux: |
---|
689 | entry = children.get_aux(name) |
---|
690 | if not entry: |
---|
691 | assert IFilesystemNode.providedBy(child), (name,child) |
---|
692 | assert isinstance(metadata, dict) |
---|
693 | - rwcap = child.get_uri() # might be RO if the child is not writeable |
---|
694 | - if rwcap is None: |
---|
695 | - rwcap = "" |
---|
696 | - assert isinstance(rwcap, str), rwcap |
---|
697 | - rocap = child.get_readonly_uri() |
---|
698 | - if rocap is None: |
---|
699 | - rocap = "" |
---|
700 | - assert isinstance(rocap, str), rocap |
---|
701 | + rw_uri = child.get_write_uri() |
---|
702 | + if rw_uri is None: |
---|
703 | + rw_uri = "" |
---|
704 | + assert isinstance(rw_uri, str), rw_uri |
---|
705 | + |
---|
706 | + # should be prevented by MustBeDeepImmutableError check above |
---|
707 | + assert not (rw_uri and deep_immutable) |
---|
708 | + |
---|
709 | + ro_uri = child.get_readonly_uri() |
---|
710 | + if ro_uri is None: |
---|
711 | + ro_uri = "" |
---|
712 | + assert isinstance(ro_uri, str), ro_uri |
---|
713 | entry = "".join([netstring(name.encode("utf-8")), |
---|
714 | - netstring(rocap), |
---|
715 | - netstring(_encrypt_rwcap(filenode, rwcap)), |
---|
716 | + netstring(strip_prefix_for_ro(ro_uri, deep_immutable)), |
---|
717 | + netstring(_encrypt_rw_uri(filenode, rw_uri)), |
---|
718 | netstring(simplejson.dumps(metadata))]) |
---|
719 | entries.append(netstring(entry)) |
---|
720 | return "".join(entries) |
---|
721 | @@ -230,38 +241,68 @@ |
---|
722 | plaintext = cryptor.process(crypttext) |
---|
723 | return plaintext |
---|
724 | |
---|
725 | - def _create_node(self, rwcap, rocap): |
---|
726 | - return self._nodemaker.create_from_cap(rwcap, rocap) |
---|
727 | + def _create_and_validate_node(self, rw_uri, ro_uri, name): |
---|
728 | + #print "mutable? %r\n" % self.is_mutable() |
---|
729 | + #print "_create_and_validate_node(rw_uri=%r, ro_uri=%r, name=%r)\n" % (rw_uri, ro_uri, name) |
---|
730 | + node = self._nodemaker.create_from_cap(rw_uri, ro_uri, |
---|
731 | + deep_immutable=not self.is_mutable(), |
---|
732 | + name=name) |
---|
733 | + node.raise_error() |
---|
734 | + return node |
---|
735 | |
---|
736 | def _unpack_contents(self, data): |
---|
737 | # the directory is serialized as a list of netstrings, one per child. |
---|
738 | - # Each child is serialized as a list of four netstrings: (name, |
---|
739 | - # rocap, rwcap, metadata), in which the name,rocap,metadata are in |
---|
740 | - # cleartext. The 'name' is UTF-8 encoded. The rwcap is formatted as: |
---|
741 | - # pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac) |
---|
742 | + # Each child is serialized as a list of four netstrings: (name, ro_uri, |
---|
743 | + # rwcapdata, metadata), in which the name, ro_uri, metadata are in |
---|
744 | + # cleartext. The 'name' is UTF-8 encoded. The rwcapdata is formatted as: |
---|
745 | + # pack("16ss32s", iv, AES(H(writekey+iv), plaintext_rw_uri), mac) |
---|
746 | assert isinstance(data, str), (repr(data), type(data)) |
---|
747 | # an empty directory is serialized as an empty string |
---|
748 | if data == "": |
---|
749 | return AuxValueDict() |
---|
750 | writeable = not self.is_readonly() |
---|
751 | + mutable = self.is_mutable() |
---|
752 | children = AuxValueDict() |
---|
753 | position = 0 |
---|
754 | while position < len(data): |
---|
755 | entries, position = split_netstring(data, 1, position) |
---|
756 | entry = entries[0] |
---|
757 | - (name, rocap, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) |
---|
758 | + (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) |
---|
759 | + if not mutable and len(rwcapdata) > 0: |
---|
760 | + raise ValueError("the rwcapdata field of a dirnode in an immutable directory was not empty") |
---|
761 | name = name.decode("utf-8") |
---|
762 | - rwcap = None |
---|
763 | + rw_uri = "" |
---|
764 | if writeable: |
---|
765 | - rwcap = self._decrypt_rwcapdata(rwcapdata) |
---|
766 | - if not rwcap: |
---|
767 | - rwcap = None # rwcap is None or a non-empty string |
---|
768 | - if not rocap: |
---|
769 | - rocap = None # rocap is None or a non-empty string |
---|
770 | - child = self._create_node(rwcap, rocap) |
---|
771 | - metadata = simplejson.loads(metadata_s) |
---|
772 | - assert isinstance(metadata, dict) |
---|
773 | - children.set_with_aux(name, (child, metadata), auxilliary=entry) |
---|
774 | + rw_uri = self._decrypt_rwcapdata(rwcapdata) |
---|
775 | + #print "mutable=%r, writeable=%r, rw_uri=%r, ro_uri=%r, name=%r" % (mutable, writeable, rw_uri, ro_uri, name) |
---|
776 | + |
---|
777 | + # Since the encryption uses CTR mode, it currently leaks the length of the |
---|
778 | + # plaintext rw_uri -- and therefore whether it is present, i.e. whether the |
---|
779 | + # dirnode is writable (ticket #925). By stripping spaces in Tahoe >= 1.6.0, |
---|
780 | + # we may make it easier for future versions to plug this leak. |
---|
781 | + rw_uri = rw_uri.strip(' ') |
---|
782 | + if not rw_uri: |
---|
783 | + rw_uri = None # rw_uri is None or a non-empty string |
---|
784 | + |
---|
785 | + # Treat ro_uri in the same way for consistency. |
---|
786 | + ro_uri = ro_uri.strip(' ') |
---|
787 | + if not ro_uri: |
---|
788 | + ro_uri = None # ro_uri is None or a non-empty string |
---|
789 | + |
---|
790 | + try: |
---|
791 | + child = self._create_and_validate_node(rw_uri, ro_uri, name) |
---|
792 | + #print "%r.is_allowed_in_immutable_directory() = %r" % (child, child.is_allowed_in_immutable_directory()) |
---|
793 | + if mutable or child.is_allowed_in_immutable_directory(): |
---|
794 | + metadata = simplejson.loads(metadata_s) |
---|
795 | + assert isinstance(metadata, dict) |
---|
796 | + children[name] = (child, metadata) |
---|
797 | + children.set_with_aux(name, (child, metadata), auxilliary=entry) |
---|
798 | + except CapConstraintError, e: |
---|
799 | + #print "unmet constraint: (%s, %s)" % (e.args[0], e.args[1].encode("utf-8")) |
---|
800 | + log.msg(format="unmet constraint on cap for child '%(name)s' unpacked from a directory:\n" |
---|
801 | + "%(message)s", message=e.args[0], name=e.args[1].encode("utf-8"), |
---|
802 | + facility="tahoe.webish", level=log.UNUSUAL) |
---|
803 | + |
---|
804 | return children |
---|
805 | |
---|
806 | def _pack_contents(self, children): |
---|
807 | @@ -270,21 +311,39 @@ |
---|
808 | |
---|
809 | def is_readonly(self): |
---|
810 | return self._node.is_readonly() |
---|
811 | + |
---|
812 | def is_mutable(self): |
---|
813 | return self._node.is_mutable() |
---|
814 | |
---|
815 | + def is_unknown(self): |
---|
816 | + return False |
---|
817 | + |
---|
818 | + def is_allowed_in_immutable_directory(self): |
---|
819 | + return not self._node.is_mutable() |
---|
820 | + |
---|
821 | + def raise_error(self): |
---|
822 | + pass |
---|
823 | + |
---|
824 | def get_uri(self): |
---|
825 | return self._uri.to_string() |
---|
826 | |
---|
827 | + def get_write_uri(self): |
---|
828 | + if self.is_readonly(): |
---|
829 | + return None |
---|
830 | + return self._uri.to_string() |
---|
831 | + |
---|
832 | def get_readonly_uri(self): |
---|
833 | return self._uri.get_readonly().to_string() |
---|
834 | |
---|
835 | def get_cap(self): |
---|
836 | return self._uri |
---|
837 | + |
---|
838 | def get_readcap(self): |
---|
839 | return self._uri.get_readonly() |
---|
840 | + |
---|
841 | def get_verify_cap(self): |
---|
842 | return self._uri.get_verify_cap() |
---|
843 | + |
---|
844 | def get_repair_cap(self): |
---|
845 | if self._node.is_readonly(): |
---|
846 | return None # readonly (mutable) dirnodes are not yet repairable |
---|
847 | @@ -350,7 +409,7 @@ |
---|
848 | def set_metadata_for(self, name, metadata): |
---|
849 | assert isinstance(name, unicode) |
---|
850 | if self.is_readonly(): |
---|
851 | - return defer.fail(NotMutableError()) |
---|
852 | + return defer.fail(NotWriteableError()) |
---|
853 | assert isinstance(metadata, dict) |
---|
854 | s = MetadataSetter(self, name, metadata) |
---|
855 | d = self._node.modify(s.modify) |
---|
856 | @@ -398,14 +457,10 @@ |
---|
857 | precondition(isinstance(name, unicode), name) |
---|
858 | precondition(isinstance(writecap, (str,type(None))), writecap) |
---|
859 | precondition(isinstance(readcap, (str,type(None))), readcap) |
---|
860 | - child_node = self._create_node(writecap, readcap) |
---|
861 | - if isinstance(child_node, UnknownNode): |
---|
862 | - # don't be willing to pack unknown nodes: we might accidentally |
---|
863 | - # put some write-authority into the rocap slot because we don't |
---|
864 | - # know how to diminish the URI they gave us. We don't even know |
---|
865 | - # if they gave us a readcap or a writecap. |
---|
866 | - msg = "cannot pack unknown node as child %s" % str(name) |
---|
867 | - raise CannotPackUnknownNodeError(msg) |
---|
868 | + |
---|
869 | + # We now allow packing unknown nodes, provided they are valid |
---|
870 | + # for this type of directory. |
---|
871 | + child_node = self._create_and_validate_node(writecap, readcap, name) |
---|
872 | d = self.set_node(name, child_node, metadata, overwrite) |
---|
873 | d.addCallback(lambda res: child_node) |
---|
874 | return d |
---|
875 | @@ -423,10 +478,10 @@ |
---|
876 | writecap, readcap, metadata = e |
---|
877 | precondition(isinstance(writecap, (str,type(None))), writecap) |
---|
878 | precondition(isinstance(readcap, (str,type(None))), readcap) |
---|
879 | - child_node = self._create_node(writecap, readcap) |
---|
880 | - if isinstance(child_node, UnknownNode): |
---|
881 | - msg = "cannot pack unknown node as child %s" % str(name) |
---|
882 | - raise CannotPackUnknownNodeError(msg) |
---|
883 | + |
---|
884 | + # We now allow packing unknown nodes, provided they are valid |
---|
885 | + # for this type of directory. |
---|
886 | + child_node = self._create_and_validate_node(writecap, readcap, name) |
---|
887 | a.set_node(name, child_node, metadata) |
---|
888 | d = self._node.modify(a.modify) |
---|
889 | d.addCallback(lambda ign: self) |
---|
890 | @@ -439,12 +494,12 @@ |
---|
891 | same name. |
---|
892 | |
---|
893 | If this directory node is read-only, the Deferred will errback with a |
---|
894 | - NotMutableError.""" |
---|
895 | + NotWriteableError.""" |
---|
896 | |
---|
897 | precondition(IFilesystemNode.providedBy(child), child) |
---|
898 | |
---|
899 | if self.is_readonly(): |
---|
900 | - return defer.fail(NotMutableError()) |
---|
901 | + return defer.fail(NotWriteableError()) |
---|
902 | assert isinstance(name, unicode) |
---|
903 | assert IFilesystemNode.providedBy(child), child |
---|
904 | a = Adder(self, overwrite=overwrite) |
---|
905 | @@ -456,7 +511,7 @@ |
---|
906 | def set_nodes(self, entries, overwrite=True): |
---|
907 | precondition(isinstance(entries, dict), entries) |
---|
908 | if self.is_readonly(): |
---|
909 | - return defer.fail(NotMutableError()) |
---|
910 | + return defer.fail(NotWriteableError()) |
---|
911 | a = Adder(self, entries, overwrite=overwrite) |
---|
912 | d = self._node.modify(a.modify) |
---|
913 | d.addCallback(lambda res: self) |
---|
914 | @@ -470,10 +525,10 @@ |
---|
915 | the operation completes.""" |
---|
916 | assert isinstance(name, unicode) |
---|
917 | if self.is_readonly(): |
---|
918 | - return defer.fail(NotMutableError()) |
---|
919 | + return defer.fail(NotWriteableError()) |
---|
920 | d = self._uploader.upload(uploadable) |
---|
921 | - d.addCallback(lambda results: results.uri) |
---|
922 | - d.addCallback(self._nodemaker.create_from_cap) |
---|
923 | + d.addCallback(lambda results: |
---|
924 | + self._create_and_validate_node(results.uri, None, name)) |
---|
925 | d.addCallback(lambda node: |
---|
926 | self.set_node(name, node, metadata, overwrite)) |
---|
927 | return d |
---|
928 | @@ -483,7 +538,7 @@ |
---|
929 | fires (with the node just removed) when the operation finishes.""" |
---|
930 | assert isinstance(name, unicode) |
---|
931 | if self.is_readonly(): |
---|
932 | - return defer.fail(NotMutableError()) |
---|
933 | + return defer.fail(NotWriteableError()) |
---|
934 | deleter = Deleter(self, name) |
---|
935 | d = self._node.modify(deleter.modify) |
---|
936 | d.addCallback(lambda res: deleter.old_child) |
---|
937 | @@ -493,7 +548,7 @@ |
---|
938 | mutable=True): |
---|
939 | assert isinstance(name, unicode) |
---|
940 | if self.is_readonly(): |
---|
941 | - return defer.fail(NotMutableError()) |
---|
942 | + return defer.fail(NotWriteableError()) |
---|
943 | if mutable: |
---|
944 | d = self._nodemaker.create_new_mutable_directory(initial_children) |
---|
945 | else: |
---|
946 | @@ -515,7 +570,7 @@ |
---|
947 | Deferred that fires when the operation finishes.""" |
---|
948 | assert isinstance(current_child_name, unicode) |
---|
949 | if self.is_readonly() or new_parent.is_readonly(): |
---|
950 | - return defer.fail(NotMutableError()) |
---|
951 | + return defer.fail(NotWriteableError()) |
---|
952 | if new_child_name is None: |
---|
953 | new_child_name = current_child_name |
---|
954 | assert isinstance(new_child_name, unicode) |
---|
955 | diff -rN -u old-tahoe/src/allmydata/immutable/filenode.py new-tahoe/src/allmydata/immutable/filenode.py |
---|
956 | --- old-tahoe/src/allmydata/immutable/filenode.py 2010-01-25 11:54:55.626000000 +0000 |
---|
957 | +++ new-tahoe/src/allmydata/immutable/filenode.py 2010-01-25 11:55:00.764000000 +0000 |
---|
958 | @@ -17,6 +17,9 @@ |
---|
959 | class _ImmutableFileNodeBase(object): |
---|
960 | implements(IImmutableFileNode, ICheckable) |
---|
961 | |
---|
962 | + def get_write_uri(self): |
---|
963 | + return None |
---|
964 | + |
---|
965 | def get_readonly_uri(self): |
---|
966 | return self.get_uri() |
---|
967 | |
---|
968 | @@ -26,6 +29,15 @@ |
---|
969 | def is_readonly(self): |
---|
970 | return True |
---|
971 | |
---|
972 | + def is_unknown(self): |
---|
973 | + return False |
---|
974 | + |
---|
975 | + def is_allowed_in_immutable_directory(self): |
---|
976 | + return True |
---|
977 | + |
---|
978 | + def raise_error(self): |
---|
979 | + pass |
---|
980 | + |
---|
981 | def __hash__(self): |
---|
982 | return self.u.__hash__() |
---|
983 | def __eq__(self, other): |
---|
984 | diff -rN -u old-tahoe/src/allmydata/interfaces.py new-tahoe/src/allmydata/interfaces.py |
---|
985 | --- old-tahoe/src/allmydata/interfaces.py 2010-01-25 11:54:55.672000000 +0000 |
---|
986 | +++ new-tahoe/src/allmydata/interfaces.py 2010-01-25 11:55:00.785000000 +0000 |
---|
987 | @@ -426,6 +426,7 @@ |
---|
988 | """Return True if the data can be modified by *somebody* (perhaps |
---|
989 | someone who has a more powerful URI than this one).""" |
---|
990 | |
---|
991 | + # TODO: rename to get_read_cap() |
---|
992 | def get_readonly(): |
---|
993 | """Return another IURI instance, which represents a read-only form of |
---|
994 | this one. If is_readonly() is True, this returns self.""" |
---|
995 | @@ -456,7 +457,6 @@ |
---|
996 | class IDirnodeURI(Interface): |
---|
997 | """I am a URI which represents a dirnode.""" |
---|
998 | |
---|
999 | - |
---|
1000 | class IFileURI(Interface): |
---|
1001 | """I am a URI which represents a filenode.""" |
---|
1002 | def get_size(): |
---|
1003 | @@ -467,21 +467,28 @@ |
---|
1004 | |
---|
1005 | class IMutableFileURI(Interface): |
---|
1006 | """I am a URI which represents a mutable filenode.""" |
---|
1007 | + |
---|
1008 | class IDirectoryURI(Interface): |
---|
1009 | pass |
---|
1010 | + |
---|
1011 | class IReadonlyDirectoryURI(Interface): |
---|
1012 | pass |
---|
1013 | |
---|
1014 | -class CannotPackUnknownNodeError(Exception): |
---|
1015 | - """UnknownNodes (using filecaps from the future that we don't understand) |
---|
1016 | - cannot yet be copied safely, so I refuse to copy them.""" |
---|
1017 | - |
---|
1018 | -class UnhandledCapTypeError(Exception): |
---|
1019 | - """I recognize the cap/URI, but I cannot create an IFilesystemNode for |
---|
1020 | - it.""" |
---|
1021 | +class CapConstraintError(Exception): |
---|
1022 | + """A constraint on a cap was violated.""" |
---|
1023 | |
---|
1024 | -class NotDeepImmutableError(Exception): |
---|
1025 | - """Deep-immutable directories can only contain deep-immutable children""" |
---|
1026 | +class MustBeDeepImmutableError(CapConstraintError): |
---|
1027 | + """Mutable children cannot be added to an immutable directory. |
---|
1028 | + Also, caps obtained from an immutable directory can trigger this error |
---|
1029 | + if they are later found to refer to a mutable object and then used.""" |
---|
1030 | + |
---|
1031 | +class MustBeReadonlyError(CapConstraintError): |
---|
1032 | + """Known write caps cannot be specified in a ro_uri field. Also, |
---|
1033 | + caps obtained from a ro_uri field can trigger this error if they |
---|
1034 | + are later found to be write caps and then used.""" |
---|
1035 | + |
---|
1036 | +class MustNotBeUnknownRWError(CapConstraintError): |
---|
1037 | + """Cannot add an unknown child cap specified in a rw_uri field.""" |
---|
1038 | |
---|
1039 | # The hierarchy looks like this: |
---|
1040 | # IFilesystemNode |
---|
1041 | @@ -518,9 +525,8 @@ |
---|
1042 | """ |
---|
1043 | |
---|
1044 | def get_uri(): |
---|
1045 | - """ |
---|
1046 | - Return the URI string that can be used by others to get access to |
---|
1047 | - this node. If this node is read-only, the URI will only offer |
---|
1048 | + """Return the URI string corresponding to the strongest cap associated |
---|
1049 | + with this node. If this node is read-only, the URI will only offer |
---|
1050 | read-only access. If this node is read-write, the URI will offer |
---|
1051 | read-write access. |
---|
1052 | |
---|
1053 | @@ -528,6 +534,11 @@ |
---|
1054 | read-only access with others, use get_readonly_uri(). |
---|
1055 | """ |
---|
1056 | |
---|
1057 | + def get_write_uri(n): |
---|
1058 | + """Return the URI string that can be used by others to get write |
---|
1059 | + access to this node, if it is writeable. If this is a read-only node, |
---|
1060 | + return None.""" |
---|
1061 | + |
---|
1062 | def get_readonly_uri(): |
---|
1063 | """Return the URI string that can be used by others to get read-only |
---|
1064 | access to this node. The result is a read-only URI, regardless of |
---|
1065 | @@ -557,6 +568,18 @@ |
---|
1066 | file. |
---|
1067 | """ |
---|
1068 | |
---|
1069 | + def is_unknown(): |
---|
1070 | + """Return True if this is an unknown node.""" |
---|
1071 | + |
---|
1072 | + def is_allowed_in_immutable_directory(): |
---|
1073 | + """Return True if this node is allowed as a child of a deep-immutable |
---|
1074 | + directory. This is true if either the node is of a known-immutable type, |
---|
1075 | + or it is unknown and read-only. |
---|
1076 | + """ |
---|
1077 | + |
---|
1078 | + def raise_error(): |
---|
1079 | + """Raise any error associated with this node.""" |
---|
1080 | + |
---|
1081 | def get_size(): |
---|
1082 | """Return the length (in bytes) of the data this node represents. For |
---|
1083 | directory nodes, I return the size of the backing store. I return |
---|
1084 | @@ -902,7 +925,7 @@ |
---|
1085 | ctime/mtime semantics of traditional filesystems. |
---|
1086 | |
---|
1087 | If this directory node is read-only, the Deferred will errback with a |
---|
1088 | - NotMutableError.""" |
---|
1089 | + NotWriteableError.""" |
---|
1090 | |
---|
1091 | def set_children(entries, overwrite=True): |
---|
1092 | """Add multiple children (by writecap+readcap) to a directory node. |
---|
1093 | @@ -928,7 +951,7 @@ |
---|
1094 | ctime/mtime semantics of traditional filesystems. |
---|
1095 | |
---|
1096 | If this directory node is read-only, the Deferred will errback with a |
---|
1097 | - NotMutableError.""" |
---|
1098 | + NotWriteableError.""" |
---|
1099 | |
---|
1100 | def set_nodes(entries, overwrite=True): |
---|
1101 | """Add multiple children to a directory node. Takes a dict mapping |
---|
1102 | @@ -2074,7 +2097,7 @@ |
---|
1103 | Tahoe process will typically have a single NodeMaker, but unit tests may |
---|
1104 | create simplified/mocked forms for testing purposes. |
---|
1105 | """ |
---|
1106 | - def create_from_cap(writecap, readcap=None): |
---|
1107 | + def create_from_cap(writecap, readcap=None, **kwargs): |
---|
1108 | """I create an IFilesystemNode from the given writecap/readcap. I can |
---|
1109 | only provide nodes for existing file/directory objects: use my other |
---|
1110 | methods to create new objects. I return synchronously.""" |
---|
1111 | diff -rN -u old-tahoe/src/allmydata/mutable/common.py new-tahoe/src/allmydata/mutable/common.py |
---|
1112 | --- old-tahoe/src/allmydata/mutable/common.py 2010-01-25 11:54:55.746000000 +0000 |
---|
1113 | +++ new-tahoe/src/allmydata/mutable/common.py 2010-01-25 11:55:00.832000000 +0000 |
---|
1114 | @@ -8,7 +8,7 @@ |
---|
1115 | # creation |
---|
1116 | MODE_READ = "MODE_READ" |
---|
1117 | |
---|
1118 | -class NotMutableError(Exception): |
---|
1119 | +class NotWriteableError(Exception): |
---|
1120 | pass |
---|
1121 | |
---|
1122 | class NeedMoreDataError(Exception): |
---|
1123 | diff -rN -u old-tahoe/src/allmydata/mutable/filenode.py new-tahoe/src/allmydata/mutable/filenode.py |
---|
1124 | --- old-tahoe/src/allmydata/mutable/filenode.py 2010-01-25 11:54:55.753000000 +0000 |
---|
1125 | +++ new-tahoe/src/allmydata/mutable/filenode.py 2010-01-25 11:55:00.837000000 +0000 |
---|
1126 | @@ -214,6 +214,12 @@ |
---|
1127 | |
---|
1128 | def get_uri(self): |
---|
1129 | return self._uri.to_string() |
---|
1130 | + |
---|
1131 | + def get_write_uri(self): |
---|
1132 | + if self.is_readonly(): |
---|
1133 | + return None |
---|
1134 | + return self._uri.to_string() |
---|
1135 | + |
---|
1136 | def get_readonly_uri(self): |
---|
1137 | return self._uri.get_readonly().to_string() |
---|
1138 | |
---|
1139 | @@ -227,9 +233,19 @@ |
---|
1140 | |
---|
1141 | def is_mutable(self): |
---|
1142 | return self._uri.is_mutable() |
---|
1143 | + |
---|
1144 | def is_readonly(self): |
---|
1145 | return self._uri.is_readonly() |
---|
1146 | |
---|
1147 | + def is_unknown(self): |
---|
1148 | + return False |
---|
1149 | + |
---|
1150 | + def is_allowed_in_immutable_directory(self): |
---|
1151 | + return not self._uri.is_mutable() |
---|
1152 | + |
---|
1153 | + def raise_error(self): |
---|
1154 | + pass |
---|
1155 | + |
---|
1156 | def __hash__(self): |
---|
1157 | return hash((self.__class__, self._uri)) |
---|
1158 | def __cmp__(self, them): |
---|
1159 | diff -rN -u old-tahoe/src/allmydata/nodemaker.py new-tahoe/src/allmydata/nodemaker.py |
---|
1160 | --- old-tahoe/src/allmydata/nodemaker.py 2010-01-25 11:54:55.808000000 +0000 |
---|
1161 | +++ new-tahoe/src/allmydata/nodemaker.py 2010-01-25 11:55:00.872000000 +0000 |
---|
1162 | @@ -1,7 +1,7 @@ |
---|
1163 | import weakref |
---|
1164 | from zope.interface import implements |
---|
1165 | from allmydata.util.assertutil import precondition |
---|
1166 | -from allmydata.interfaces import INodeMaker, NotDeepImmutableError |
---|
1167 | +from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError |
---|
1168 | from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode |
---|
1169 | from allmydata.immutable.upload import Data |
---|
1170 | from allmydata.mutable.filenode import MutableFileNode |
---|
1171 | @@ -44,28 +44,36 @@ |
---|
1172 | def _create_dirnode(self, filenode): |
---|
1173 | return DirectoryNode(filenode, self, self.uploader) |
---|
1174 | |
---|
1175 | - def create_from_cap(self, writecap, readcap=None): |
---|
1176 | + def create_from_cap(self, writecap, readcap=None, deep_immutable=False, name=u"<unknown name>"): |
---|
1177 | # this returns synchronously. It starts with a "cap string". |
---|
1178 | assert isinstance(writecap, (str, type(None))), type(writecap) |
---|
1179 | assert isinstance(readcap, (str, type(None))), type(readcap) |
---|
1180 | + #import traceback |
---|
1181 | + #traceback.print_stack() |
---|
1182 | + #print '%r.create_from_cap(%r, %r, %r)' % (self, writecap, readcap, kwargs) |
---|
1183 | + |
---|
1184 | bigcap = writecap or readcap |
---|
1185 | if not bigcap: |
---|
1186 | # maybe the writecap was hidden because we're in a readonly |
---|
1187 | # directory, and the future cap format doesn't have a readcap, or |
---|
1188 | # something. |
---|
1189 | - return UnknownNode(writecap, readcap) |
---|
1190 | - if bigcap in self._node_cache: |
---|
1191 | - return self._node_cache[bigcap] |
---|
1192 | - cap = uri.from_string(bigcap) |
---|
1193 | - node = self._create_from_cap(cap) |
---|
1194 | + return UnknownNode(None, None) # deep_immutable and name not needed |
---|
1195 | + |
---|
1196 | + # The name doesn't matter for caching since it's only used in the error |
---|
1197 | + # attribute of an UnknownNode, and we don't cache those. |
---|
1198 | + memokey = ("I" if deep_immutable else "M") + bigcap |
---|
1199 | + if memokey in self._node_cache: |
---|
1200 | + return self._node_cache[memokey] |
---|
1201 | + cap = uri.from_string(bigcap, deep_immutable=deep_immutable, name=name) |
---|
1202 | + node = self._create_from_single_cap(cap) |
---|
1203 | if node: |
---|
1204 | - self._node_cache[bigcap] = node # note: WeakValueDictionary |
---|
1205 | + self._node_cache[memokey] = node # note: WeakValueDictionary |
---|
1206 | else: |
---|
1207 | - node = UnknownNode(writecap, readcap) # don't cache UnknownNode |
---|
1208 | + # don't cache UnknownNode |
---|
1209 | + node = UnknownNode(writecap, readcap, deep_immutable=deep_immutable, name=name) |
---|
1210 | return node |
---|
1211 | |
---|
1212 | - def _create_from_cap(self, cap): |
---|
1213 | - # This starts with a "cap instance" |
---|
1214 | + def _create_from_single_cap(self, cap): |
---|
1215 | if isinstance(cap, uri.LiteralFileURI): |
---|
1216 | return self._create_lit(cap) |
---|
1217 | if isinstance(cap, uri.CHKFileURI): |
---|
1218 | @@ -76,7 +84,7 @@ |
---|
1219 | uri.ReadonlyDirectoryURI, |
---|
1220 | uri.ImmutableDirectoryURI, |
---|
1221 | uri.LiteralDirectoryURI)): |
---|
1222 | - filenode = self._create_from_cap(cap.get_filenode_cap()) |
---|
1223 | + filenode = self._create_from_single_cap(cap.get_filenode_cap()) |
---|
1224 | return self._create_dirnode(filenode) |
---|
1225 | return None |
---|
1226 | |
---|
1227 | @@ -89,13 +97,11 @@ |
---|
1228 | return d |
---|
1229 | |
---|
1230 | def create_new_mutable_directory(self, initial_children={}): |
---|
1231 | - # initial_children must have metadata (i.e. {} instead of None), and |
---|
1232 | - # should not contain UnknownNodes |
---|
1233 | + # initial_children must have metadata (i.e. {} instead of None) |
---|
1234 | for (name, (node, metadata)) in initial_children.iteritems(): |
---|
1235 | - precondition(not isinstance(node, UnknownNode), |
---|
1236 | - "create_new_mutable_directory does not accept UnknownNode", node) |
---|
1237 | precondition(isinstance(metadata, dict), |
---|
1238 | "create_new_mutable_directory requires metadata to be a dict, not None", metadata) |
---|
1239 | + node.raise_error() |
---|
1240 | d = self.create_mutable_file(lambda n: |
---|
1241 | pack_children(n, initial_children)) |
---|
1242 | d.addCallback(self._create_dirnode) |
---|
1243 | @@ -105,19 +111,15 @@ |
---|
1244 | if convergence is None: |
---|
1245 | convergence = self.secret_holder.get_convergence_secret() |
---|
1246 | for (name, (node, metadata)) in children.iteritems(): |
---|
1247 | - precondition(not isinstance(node, UnknownNode), |
---|
1248 | - "create_immutable_directory does not accept UnknownNode", node) |
---|
1249 | precondition(isinstance(metadata, dict), |
---|
1250 | "create_immutable_directory requires metadata to be a dict, not None", metadata) |
---|
1251 | - if node.is_mutable(): |
---|
1252 | - raise NotDeepImmutableError("%s is not immutable" % (node,)) |
---|
1253 | + node.raise_error() |
---|
1254 | + if not node.is_allowed_in_immutable_directory(): |
---|
1255 | + raise MustBeDeepImmutableError("%s is not immutable" % (node,), name) |
---|
1256 | n = DummyImmutableFileNode() # writekey=None |
---|
1257 | packed = pack_children(n, children) |
---|
1258 | uploadable = Data(packed, convergence) |
---|
1259 | d = self.uploader.upload(uploadable, history=self.history) |
---|
1260 | - def _uploaded(results): |
---|
1261 | - filecap = self.create_from_cap(results.uri) |
---|
1262 | - return filecap |
---|
1263 | - d.addCallback(_uploaded) |
---|
1264 | + d.addCallback(lambda results: self.create_from_cap(None, results.uri)) |
---|
1265 | d.addCallback(self._create_dirnode) |
---|
1266 | return d |
---|
1267 | diff -rN -u old-tahoe/src/allmydata/scripts/common.py new-tahoe/src/allmydata/scripts/common.py |
---|
1268 | --- old-tahoe/src/allmydata/scripts/common.py 2010-01-25 11:54:55.874000000 +0000 |
---|
1269 | +++ new-tahoe/src/allmydata/scripts/common.py 2010-01-25 11:55:00.920000000 +0000 |
---|
1270 | @@ -128,12 +128,14 @@ |
---|
1271 | pass |
---|
1272 | |
---|
1273 | def get_alias(aliases, path, default): |
---|
1274 | + from allmydata import uri |
---|
1275 | # transform "work:path/filename" into (aliases["work"], "path/filename"). |
---|
1276 | # If default=None, then an empty alias is indicated by returning |
---|
1277 | - # DefaultAliasMarker. We special-case "URI:" to make it easy to access |
---|
1278 | - # specific files/directories by their read-cap. |
---|
1279 | + # DefaultAliasMarker. We special-case strings with a recognized cap URI |
---|
1280 | + # prefix, to make it easy to access specific files/directories by their |
---|
1281 | + # caps. |
---|
1282 | path = path.strip() |
---|
1283 | - if path.startswith("URI:"): |
---|
1284 | + if uri.has_uri_prefix(path): |
---|
1285 | # The only way to get a sub-path is to use URI:blah:./foo, and we |
---|
1286 | # strip out the :./ sequence. |
---|
1287 | sep = path.find(":./") |
---|
1288 | diff -rN -u old-tahoe/src/allmydata/scripts/tahoe_cp.py new-tahoe/src/allmydata/scripts/tahoe_cp.py |
---|
1289 | --- old-tahoe/src/allmydata/scripts/tahoe_cp.py 2010-01-25 11:54:55.971000000 +0000 |
---|
1290 | +++ new-tahoe/src/allmydata/scripts/tahoe_cp.py 2010-01-25 11:55:01.030000000 +0000 |
---|
1291 | @@ -258,8 +258,7 @@ |
---|
1292 | readcap = ascii_or_none(data[1].get("ro_uri")) |
---|
1293 | self.children[name] = TahoeFileSource(self.nodeurl, mutable, |
---|
1294 | writecap, readcap) |
---|
1295 | - else: |
---|
1296 | - assert data[0] == "dirnode" |
---|
1297 | + elif data[0] == "dirnode": |
---|
1298 | writecap = ascii_or_none(data[1].get("rw_uri")) |
---|
1299 | readcap = ascii_or_none(data[1].get("ro_uri")) |
---|
1300 | if writecap and writecap in self.cache: |
---|
1301 | @@ -277,6 +276,11 @@ |
---|
1302 | if recurse: |
---|
1303 | child.populate(True) |
---|
1304 | self.children[name] = child |
---|
1305 | + else: |
---|
1306 | + # TODO: there should be an option to skip unknown nodes. |
---|
1307 | + raise TahoeError("Cannot copy unknown nodes (ticket #839). " |
---|
1308 | + "You probably need to use a later version of " |
---|
1309 | + "Tahoe-LAFS to copy this directory.") |
---|
1310 | |
---|
1311 | class TahoeMissingTarget: |
---|
1312 | def __init__(self, url): |
---|
1313 | @@ -353,8 +357,7 @@ |
---|
1314 | urllib.quote(name.encode('utf-8'))]) |
---|
1315 | self.children[name] = TahoeFileTarget(self.nodeurl, mutable, |
---|
1316 | writecap, readcap, url) |
---|
1317 | - else: |
---|
1318 | - assert data[0] == "dirnode" |
---|
1319 | + elif data[0] == "dirnode": |
---|
1320 | writecap = ascii_or_none(data[1].get("rw_uri")) |
---|
1321 | readcap = ascii_or_none(data[1].get("ro_uri")) |
---|
1322 | if writecap and writecap in self.cache: |
---|
1323 | @@ -372,6 +375,11 @@ |
---|
1324 | if recurse: |
---|
1325 | child.populate(True) |
---|
1326 | self.children[name] = child |
---|
1327 | + else: |
---|
1328 | + # TODO: there should be an option to skip unknown nodes. |
---|
1329 | + raise TahoeError("Cannot copy unknown nodes (ticket #839). " |
---|
1330 | + "You probably need to use a later version of " |
---|
1331 | + "Tahoe-LAFS to copy this directory.") |
---|
1332 | |
---|
1333 | def get_child_target(self, name): |
---|
1334 | # return a new target for a named subdirectory of this dir |
---|
1335 | @@ -407,9 +415,11 @@ |
---|
1336 | set_data = {} |
---|
1337 | for (name, filecap) in self.new_children.items(): |
---|
1338 | # it just so happens that ?t=set_children will accept both file |
---|
1339 | - # read-caps and write-caps as ['rw_uri'], and will handle eithe |
---|
1340 | + # read-caps and write-caps as ['rw_uri'], and will handle either |
---|
1341 | # correctly. So don't bother trying to figure out whether the one |
---|
1342 | # we have is read-only or read-write. |
---|
1343 | + # TODO: think about how this affects forward-compatibility for |
---|
1344 | + # unknown caps |
---|
1345 | set_data[name] = ["filenode", {"rw_uri": filecap}] |
---|
1346 | body = simplejson.dumps(set_data) |
---|
1347 | POST(url, body) |
---|
1348 | @@ -770,6 +780,7 @@ |
---|
1349 | # local-file-in-the-way |
---|
1350 | # touch proposed |
---|
1351 | # tahoe cp -r my:docs/proposed/denver.txt proposed/denver.txt |
---|
1352 | +# handling of unknown nodes |
---|
1353 | |
---|
1354 | # things that maybe should be errors but aren't |
---|
1355 | # local-dir-in-the-way |
---|
1356 | diff -rN -u old-tahoe/src/allmydata/scripts/tahoe_put.py new-tahoe/src/allmydata/scripts/tahoe_put.py |
---|
1357 | --- old-tahoe/src/allmydata/scripts/tahoe_put.py 2010-01-25 11:54:56.014000000 +0000 |
---|
1358 | +++ new-tahoe/src/allmydata/scripts/tahoe_put.py 2010-01-25 11:55:01.059000000 +0000 |
---|
1359 | @@ -40,6 +40,7 @@ |
---|
1360 | # DIRCAP:./subdir/foo : DIRCAP/subdir/foo |
---|
1361 | # MUTABLE-FILE-WRITECAP : filecap |
---|
1362 | |
---|
1363 | + # FIXME: this shouldn't rely on a particular prefix. |
---|
1364 | if to_file.startswith("URI:SSK:"): |
---|
1365 | url = nodeurl + "uri/%s" % urllib.quote(to_file) |
---|
1366 | else: |
---|
1367 | diff -rN -u old-tahoe/src/allmydata/test/common.py new-tahoe/src/allmydata/test/common.py |
---|
1368 | --- old-tahoe/src/allmydata/test/common.py 2010-01-25 11:54:56.294000000 +0000 |
---|
1369 | +++ new-tahoe/src/allmydata/test/common.py 2010-01-25 11:55:01.529000000 +0000 |
---|
1370 | @@ -51,6 +51,8 @@ |
---|
1371 | |
---|
1372 | def get_uri(self): |
---|
1373 | return self.my_uri.to_string() |
---|
1374 | + def get_write_uri(self): |
---|
1375 | + return None |
---|
1376 | def get_readonly_uri(self): |
---|
1377 | return self.my_uri.to_string() |
---|
1378 | def get_cap(self): |
---|
1379 | @@ -103,6 +105,12 @@ |
---|
1380 | return False |
---|
1381 | def is_readonly(self): |
---|
1382 | return True |
---|
1383 | + def is_unknown(self): |
---|
1384 | + return False |
---|
1385 | + def is_allowed_in_immutable_directory(self): |
---|
1386 | + return True |
---|
1387 | + def raise_error(self): |
---|
1388 | + pass |
---|
1389 | |
---|
1390 | def get_size(self): |
---|
1391 | try: |
---|
1392 | @@ -190,6 +198,10 @@ |
---|
1393 | return self.my_uri.get_readonly() |
---|
1394 | def get_uri(self): |
---|
1395 | return self.my_uri.to_string() |
---|
1396 | + def get_write_uri(self): |
---|
1397 | + if self.is_readonly(): |
---|
1398 | + return None |
---|
1399 | + return self.my_uri.to_string() |
---|
1400 | def get_readonly(self): |
---|
1401 | return self.my_uri.get_readonly() |
---|
1402 | def get_readonly_uri(self): |
---|
1403 | @@ -200,6 +212,12 @@ |
---|
1404 | return self.my_uri.is_readonly() |
---|
1405 | def is_mutable(self): |
---|
1406 | return self.my_uri.is_mutable() |
---|
1407 | + def is_unknown(self): |
---|
1408 | + return False |
---|
1409 | + def is_allowed_in_immutable_directory(self): |
---|
1410 | + return not self.my_uri.is_mutable() |
---|
1411 | + def raise_error(self): |
---|
1412 | + pass |
---|
1413 | def get_writekey(self): |
---|
1414 | return "\x00"*16 |
---|
1415 | def get_size(self): |
---|
1416 | diff -rN -u old-tahoe/src/allmydata/test/test_client.py new-tahoe/src/allmydata/test/test_client.py |
---|
1417 | --- old-tahoe/src/allmydata/test/test_client.py 2010-01-25 11:54:56.512000000 +0000 |
---|
1418 | +++ new-tahoe/src/allmydata/test/test_client.py 2010-01-25 11:55:01.810000000 +0000 |
---|
1419 | @@ -288,11 +288,15 @@ |
---|
1420 | self.failUnless(n.is_readonly()) |
---|
1421 | self.failUnless(n.is_mutable()) |
---|
1422 | |
---|
1423 | - future = "x-tahoe-crazy://future_cap_format." |
---|
1424 | - n = c.create_node_from_uri(future) |
---|
1425 | + unknown_rw = "lafs://from_the_future" |
---|
1426 | + unknown_ro = "lafs://readonly_from_the_future" |
---|
1427 | + n = c.create_node_from_uri(unknown_rw, unknown_ro) |
---|
1428 | self.failUnless(IFilesystemNode.providedBy(n)) |
---|
1429 | self.failIf(IFileNode.providedBy(n)) |
---|
1430 | self.failIf(IImmutableFileNode.providedBy(n)) |
---|
1431 | self.failIf(IMutableFileNode.providedBy(n)) |
---|
1432 | self.failIf(IDirectoryNode.providedBy(n)) |
---|
1433 | - self.failUnlessEqual(n.get_uri(), future) |
---|
1434 | + self.failUnless(n.is_unknown()) |
---|
1435 | + self.failUnlessEqual(n.get_uri(), unknown_rw) |
---|
1436 | + self.failUnlessEqual(n.get_write_uri(), unknown_rw) |
---|
1437 | + self.failUnlessEqual(n.get_readonly_uri(), "ro." + unknown_ro) |
---|
1438 | diff -rN -u old-tahoe/src/allmydata/test/test_dirnode.py new-tahoe/src/allmydata/test/test_dirnode.py |
---|
1439 | --- old-tahoe/src/allmydata/test/test_dirnode.py 2010-01-25 11:54:56.560000000 +0000 |
---|
1440 | +++ new-tahoe/src/allmydata/test/test_dirnode.py 2010-01-25 11:55:01.887000000 +0000 |
---|
1441 | @@ -7,8 +7,9 @@ |
---|
1442 | from allmydata.client import Client |
---|
1443 | from allmydata.immutable import upload |
---|
1444 | from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \ |
---|
1445 | - ExistingChildError, NoSuchChildError, NotDeepImmutableError, \ |
---|
1446 | - IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError |
---|
1447 | + ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \ |
---|
1448 | + MustBeDeepImmutableError, MustBeReadonlyError, \ |
---|
1449 | + IDeepCheckResults, IDeepCheckAndRepairResults |
---|
1450 | from allmydata.mutable.filenode import MutableFileNode |
---|
1451 | from allmydata.mutable.common import UncoordinatedWriteError |
---|
1452 | from allmydata.util import hashutil, base32 |
---|
1453 | @@ -16,7 +17,7 @@ |
---|
1454 | from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \ |
---|
1455 | ErrorMixin |
---|
1456 | from allmydata.test.no_network import GridTestMixin |
---|
1457 | -from allmydata.unknown import UnknownNode |
---|
1458 | +from allmydata.unknown import UnknownNode, strip_prefix_for_ro |
---|
1459 | from allmydata.nodemaker import NodeMaker |
---|
1460 | from base64 import b32decode |
---|
1461 | import common_util as testutil |
---|
1462 | @@ -32,6 +33,11 @@ |
---|
1463 | d = c.create_dirnode() |
---|
1464 | def _done(res): |
---|
1465 | self.failUnless(isinstance(res, dirnode.DirectoryNode)) |
---|
1466 | + self.failUnless(res.is_mutable()) |
---|
1467 | + self.failIf(res.is_readonly()) |
---|
1468 | + self.failIf(res.is_unknown()) |
---|
1469 | + self.failIf(res.is_allowed_in_immutable_directory()) |
---|
1470 | + res.raise_error() |
---|
1471 | rep = str(res) |
---|
1472 | self.failUnless("RW-MUT" in rep) |
---|
1473 | d.addCallback(_done) |
---|
1474 | @@ -44,36 +50,74 @@ |
---|
1475 | nm = c.nodemaker |
---|
1476 | setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861" |
---|
1477 | one_uri = "URI:LIT:n5xgk" # LIT for "one" |
---|
1478 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1479 | + mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1480 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
1481 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1482 | kids = {u"one": (nm.create_from_cap(one_uri), {}), |
---|
1483 | u"two": (nm.create_from_cap(setup_py_uri), |
---|
1484 | {"metakey": "metavalue"}), |
---|
1485 | + u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}), |
---|
1486 | + u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}), |
---|
1487 | + u"fro": (nm.create_from_cap(None, future_read_uri), {}), |
---|
1488 | } |
---|
1489 | d = c.create_dirnode(kids) |
---|
1490 | + |
---|
1491 | def _created(dn): |
---|
1492 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1493 | + self.failUnless(dn.is_mutable()) |
---|
1494 | + self.failIf(dn.is_readonly()) |
---|
1495 | + self.failIf(dn.is_unknown()) |
---|
1496 | + self.failIf(dn.is_allowed_in_immutable_directory()) |
---|
1497 | + dn.raise_error() |
---|
1498 | rep = str(dn) |
---|
1499 | self.failUnless("RW-MUT" in rep) |
---|
1500 | return dn.list() |
---|
1501 | d.addCallback(_created) |
---|
1502 | + |
---|
1503 | def _check_kids(children): |
---|
1504 | - self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"]) |
---|
1505 | + self.failUnlessEqual(sorted(children.keys()), |
---|
1506 | + [u"fro", u"fut", u"mut", u"one", u"two"]) |
---|
1507 | one_node, one_metadata = children[u"one"] |
---|
1508 | two_node, two_metadata = children[u"two"] |
---|
1509 | + mut_node, mut_metadata = children[u"mut"] |
---|
1510 | + fut_node, fut_metadata = children[u"fut"] |
---|
1511 | + fro_node, fro_metadata = children[u"fro"] |
---|
1512 | + |
---|
1513 | self.failUnlessEqual(one_node.get_size(), 3) |
---|
1514 | - self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1515 | + self.failUnlessEqual(one_node.get_uri(), one_uri) |
---|
1516 | + self.failUnlessEqual(one_node.get_readonly_uri(), one_uri) |
---|
1517 | self.failUnless(isinstance(one_metadata, dict), one_metadata) |
---|
1518 | + |
---|
1519 | + self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1520 | + self.failUnlessEqual(two_node.get_uri(), setup_py_uri) |
---|
1521 | + self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri) |
---|
1522 | self.failUnlessEqual(two_metadata["metakey"], "metavalue") |
---|
1523 | + |
---|
1524 | + self.failUnlessEqual(mut_node.get_uri(), mut_write_uri) |
---|
1525 | + self.failUnlessEqual(mut_node.get_readonly_uri(), mut_read_uri) |
---|
1526 | + self.failUnless(isinstance(mut_metadata, dict), mut_metadata) |
---|
1527 | + |
---|
1528 | + self.failUnless(fut_node.is_unknown()) |
---|
1529 | + self.failUnlessEqual(fut_node.get_uri(), future_write_uri) |
---|
1530 | + self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) |
---|
1531 | + self.failUnless(isinstance(fut_metadata, dict), fut_metadata) |
---|
1532 | + |
---|
1533 | + self.failUnless(fro_node.is_unknown()) |
---|
1534 | + self.failUnlessEqual(fro_node.get_uri(), "ro." + future_read_uri) |
---|
1535 | + self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) |
---|
1536 | + self.failUnless(isinstance(fro_metadata, dict), fro_metadata) |
---|
1537 | d.addCallback(_check_kids) |
---|
1538 | + |
---|
1539 | d.addCallback(lambda ign: nm.create_new_mutable_directory(kids)) |
---|
1540 | d.addCallback(lambda dn: dn.list()) |
---|
1541 | d.addCallback(_check_kids) |
---|
1542 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
1543 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1544 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
1545 | - bad_kids1 = {u"one": (future_node, {})} |
---|
1546 | + |
---|
1547 | + bad_future_node = UnknownNode(future_write_uri, None) |
---|
1548 | + bad_kids1 = {u"one": (bad_future_node, {})} |
---|
1549 | d.addCallback(lambda ign: |
---|
1550 | - self.shouldFail(AssertionError, "bad_kids1", |
---|
1551 | - "does not accept UnknownNode", |
---|
1552 | + self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", |
---|
1553 | + "cannot attach unknown", |
---|
1554 | nm.create_new_mutable_directory, |
---|
1555 | bad_kids1)) |
---|
1556 | bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)} |
---|
1557 | @@ -91,17 +135,24 @@ |
---|
1558 | nm = c.nodemaker |
---|
1559 | setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861" |
---|
1560 | one_uri = "URI:LIT:n5xgk" # LIT for "one" |
---|
1561 | - mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1562 | - mut_writecap = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1563 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1564 | + mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1565 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
1566 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1567 | kids = {u"one": (nm.create_from_cap(one_uri), {}), |
---|
1568 | u"two": (nm.create_from_cap(setup_py_uri), |
---|
1569 | {"metakey": "metavalue"}), |
---|
1570 | + u"fut": (nm.create_from_cap(None, future_read_uri), {}), |
---|
1571 | } |
---|
1572 | d = c.create_immutable_dirnode(kids) |
---|
1573 | + |
---|
1574 | def _created(dn): |
---|
1575 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1576 | self.failIf(dn.is_mutable()) |
---|
1577 | self.failUnless(dn.is_readonly()) |
---|
1578 | + self.failIf(dn.is_unknown()) |
---|
1579 | + self.failUnless(dn.is_allowed_in_immutable_directory()) |
---|
1580 | + dn.raise_error() |
---|
1581 | rep = str(dn) |
---|
1582 | self.failUnless("RO-IMM" in rep) |
---|
1583 | cap = dn.get_cap() |
---|
1584 | @@ -109,50 +160,73 @@ |
---|
1585 | self.cap = cap |
---|
1586 | return dn.list() |
---|
1587 | d.addCallback(_created) |
---|
1588 | + |
---|
1589 | def _check_kids(children): |
---|
1590 | - self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"]) |
---|
1591 | + self.failUnlessEqual(sorted(children.keys()), [u"fut", u"one", u"two"]) |
---|
1592 | one_node, one_metadata = children[u"one"] |
---|
1593 | two_node, two_metadata = children[u"two"] |
---|
1594 | + fut_node, fut_metadata = children[u"fut"] |
---|
1595 | + |
---|
1596 | self.failUnlessEqual(one_node.get_size(), 3) |
---|
1597 | - self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1598 | + self.failUnlessEqual(one_node.get_uri(), one_uri) |
---|
1599 | + self.failUnlessEqual(one_node.get_readonly_uri(), one_uri) |
---|
1600 | self.failUnless(isinstance(one_metadata, dict), one_metadata) |
---|
1601 | + |
---|
1602 | + self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1603 | + self.failUnlessEqual(two_node.get_uri(), setup_py_uri) |
---|
1604 | + self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri) |
---|
1605 | self.failUnlessEqual(two_metadata["metakey"], "metavalue") |
---|
1606 | + |
---|
1607 | + self.failUnless(fut_node.is_unknown()) |
---|
1608 | + self.failUnlessEqual(fut_node.get_uri(), "imm." + future_read_uri) |
---|
1609 | + self.failUnlessEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri) |
---|
1610 | + self.failUnless(isinstance(fut_metadata, dict), fut_metadata) |
---|
1611 | d.addCallback(_check_kids) |
---|
1612 | + |
---|
1613 | d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) |
---|
1614 | d.addCallback(lambda dn: dn.list()) |
---|
1615 | d.addCallback(_check_kids) |
---|
1616 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
1617 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1618 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
1619 | - bad_kids1 = {u"one": (future_node, {})} |
---|
1620 | + |
---|
1621 | + bad_future_node1 = UnknownNode(future_write_uri, None) |
---|
1622 | + bad_kids1 = {u"one": (bad_future_node1, {})} |
---|
1623 | d.addCallback(lambda ign: |
---|
1624 | - self.shouldFail(AssertionError, "bad_kids1", |
---|
1625 | - "does not accept UnknownNode", |
---|
1626 | + self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", |
---|
1627 | + "cannot attach unknown", |
---|
1628 | c.create_immutable_dirnode, |
---|
1629 | bad_kids1)) |
---|
1630 | - bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)} |
---|
1631 | + bad_future_node2 = UnknownNode(future_write_uri, future_read_uri) |
---|
1632 | + bad_kids2 = {u"one": (bad_future_node2, {})} |
---|
1633 | d.addCallback(lambda ign: |
---|
1634 | - self.shouldFail(AssertionError, "bad_kids2", |
---|
1635 | - "requires metadata to be a dict", |
---|
1636 | + self.shouldFail(MustBeDeepImmutableError, "bad_kids2", |
---|
1637 | + "is not immutable", |
---|
1638 | c.create_immutable_dirnode, |
---|
1639 | bad_kids2)) |
---|
1640 | - bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})} |
---|
1641 | + bad_kids3 = {u"one": (nm.create_from_cap(one_uri), None)} |
---|
1642 | d.addCallback(lambda ign: |
---|
1643 | - self.shouldFail(NotDeepImmutableError, "bad_kids3", |
---|
1644 | - "is not immutable", |
---|
1645 | + self.shouldFail(AssertionError, "bad_kids3", |
---|
1646 | + "requires metadata to be a dict", |
---|
1647 | c.create_immutable_dirnode, |
---|
1648 | bad_kids3)) |
---|
1649 | - bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})} |
---|
1650 | + bad_kids4 = {u"one": (nm.create_from_cap(mut_write_uri), {})} |
---|
1651 | d.addCallback(lambda ign: |
---|
1652 | - self.shouldFail(NotDeepImmutableError, "bad_kids4", |
---|
1653 | + self.shouldFail(MustBeDeepImmutableError, "bad_kids4", |
---|
1654 | "is not immutable", |
---|
1655 | c.create_immutable_dirnode, |
---|
1656 | bad_kids4)) |
---|
1657 | + bad_kids5 = {u"one": (nm.create_from_cap(mut_read_uri), {})} |
---|
1658 | + d.addCallback(lambda ign: |
---|
1659 | + self.shouldFail(MustBeDeepImmutableError, "bad_kids5", |
---|
1660 | + "is not immutable", |
---|
1661 | + c.create_immutable_dirnode, |
---|
1662 | + bad_kids5)) |
---|
1663 | d.addCallback(lambda ign: c.create_immutable_dirnode({})) |
---|
1664 | def _created_empty(dn): |
---|
1665 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1666 | self.failIf(dn.is_mutable()) |
---|
1667 | self.failUnless(dn.is_readonly()) |
---|
1668 | + self.failIf(dn.is_unknown()) |
---|
1669 | + self.failUnless(dn.is_allowed_in_immutable_directory()) |
---|
1670 | + dn.raise_error() |
---|
1671 | rep = str(dn) |
---|
1672 | self.failUnless("RO-IMM" in rep) |
---|
1673 | cap = dn.get_cap() |
---|
1674 | @@ -168,6 +242,9 @@ |
---|
1675 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1676 | self.failIf(dn.is_mutable()) |
---|
1677 | self.failUnless(dn.is_readonly()) |
---|
1678 | + self.failIf(dn.is_unknown()) |
---|
1679 | + self.failUnless(dn.is_allowed_in_immutable_directory()) |
---|
1680 | + dn.raise_error() |
---|
1681 | rep = str(dn) |
---|
1682 | self.failUnless("RO-IMM" in rep) |
---|
1683 | cap = dn.get_cap() |
---|
1684 | @@ -193,9 +270,9 @@ |
---|
1685 | d.addCallback(_check_kids) |
---|
1686 | d.addCallback(lambda ign: n.get(u"subdir")) |
---|
1687 | d.addCallback(lambda sd: self.failIf(sd.is_mutable())) |
---|
1688 | - bad_kids = {u"one": (nm.create_from_cap(mut_writecap), {})} |
---|
1689 | + bad_kids = {u"one": (nm.create_from_cap(mut_write_uri), {})} |
---|
1690 | d.addCallback(lambda ign: |
---|
1691 | - self.shouldFail(NotDeepImmutableError, "YZ", |
---|
1692 | + self.shouldFail(MustBeDeepImmutableError, "YZ", |
---|
1693 | "is not immutable", |
---|
1694 | n.create_subdirectory, |
---|
1695 | u"sub2", bad_kids, mutable=False)) |
---|
1696 | @@ -203,7 +280,6 @@ |
---|
1697 | d.addCallback(_made_parent) |
---|
1698 | return d |
---|
1699 | |
---|
1700 | - |
---|
1701 | def test_check(self): |
---|
1702 | self.basedir = "dirnode/Dirnode/test_check" |
---|
1703 | self.set_up_grid() |
---|
1704 | @@ -337,24 +413,27 @@ |
---|
1705 | ro_dn = c.create_node_from_uri(ro_uri) |
---|
1706 | self.failUnless(ro_dn.is_readonly()) |
---|
1707 | self.failUnless(ro_dn.is_mutable()) |
---|
1708 | + self.failIf(ro_dn.is_unknown()) |
---|
1709 | + self.failIf(ro_dn.is_allowed_in_immutable_directory()) |
---|
1710 | + ro_dn.raise_error() |
---|
1711 | |
---|
1712 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1713 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1714 | ro_dn.set_uri, u"newchild", filecap, filecap) |
---|
1715 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1716 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1717 | ro_dn.set_node, u"newchild", filenode) |
---|
1718 | - self.shouldFail(dirnode.NotMutableError, "set_nodes ro", None, |
---|
1719 | + self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None, |
---|
1720 | ro_dn.set_nodes, { u"newchild": (filenode, None) }) |
---|
1721 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1722 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1723 | ro_dn.add_file, u"newchild", uploadable) |
---|
1724 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1725 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1726 | ro_dn.delete, u"child") |
---|
1727 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1728 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1729 | ro_dn.create_subdirectory, u"newchild") |
---|
1730 | - self.shouldFail(dirnode.NotMutableError, "set_metadata_for ro", None, |
---|
1731 | + self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None, |
---|
1732 | ro_dn.set_metadata_for, u"child", {}) |
---|
1733 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1734 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1735 | ro_dn.move_child_to, u"child", rw_dn) |
---|
1736 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1737 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1738 | rw_dn.move_child_to, u"child", ro_dn) |
---|
1739 | return ro_dn.list() |
---|
1740 | d.addCallback(_ready) |
---|
1741 | @@ -901,8 +980,8 @@ |
---|
1742 | nodemaker = NodeMaker(None, None, None, |
---|
1743 | None, None, None, |
---|
1744 | {"k": 3, "n": 10}, None) |
---|
1745 | - writecap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1746 | - filenode = nodemaker.create_from_cap(writecap) |
---|
1747 | + write_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1748 | + filenode = nodemaker.create_from_cap(write_uri) |
---|
1749 | node = dirnode.DirectoryNode(filenode, nodemaker, None) |
---|
1750 | children = node._unpack_contents(known_tree) |
---|
1751 | self._check_children(children) |
---|
1752 | @@ -975,23 +1054,23 @@ |
---|
1753 | self.failUnlessIn("lit", packed) |
---|
1754 | |
---|
1755 | kids = self._make_kids(nm, ["imm", "lit", "write"]) |
---|
1756 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1757 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1758 | dirnode.pack_children, |
---|
1759 | fn, kids, deep_immutable=True) |
---|
1760 | |
---|
1761 | # read-only is not enough: all children must be immutable |
---|
1762 | kids = self._make_kids(nm, ["imm", "lit", "read"]) |
---|
1763 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1764 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1765 | dirnode.pack_children, |
---|
1766 | fn, kids, deep_immutable=True) |
---|
1767 | |
---|
1768 | kids = self._make_kids(nm, ["imm", "lit", "dirwrite"]) |
---|
1769 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1770 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1771 | dirnode.pack_children, |
---|
1772 | fn, kids, deep_immutable=True) |
---|
1773 | |
---|
1774 | kids = self._make_kids(nm, ["imm", "lit", "dirread"]) |
---|
1775 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1776 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1777 | dirnode.pack_children, |
---|
1778 | fn, kids, deep_immutable=True) |
---|
1779 | |
---|
1780 | @@ -1017,16 +1096,31 @@ |
---|
1781 | |
---|
1782 | def get_cap(self): |
---|
1783 | return self.uri |
---|
1784 | + |
---|
1785 | def get_uri(self): |
---|
1786 | return self.uri.to_string() |
---|
1787 | + |
---|
1788 | + def get_write_uri(self): |
---|
1789 | + return self.uri.to_string() |
---|
1790 | + |
---|
1791 | def download_best_version(self): |
---|
1792 | return defer.succeed(self.data) |
---|
1793 | + |
---|
1794 | def get_writekey(self): |
---|
1795 | return "writekey" |
---|
1796 | + |
---|
1797 | def is_readonly(self): |
---|
1798 | return False |
---|
1799 | + |
---|
1800 | def is_mutable(self): |
---|
1801 | return True |
---|
1802 | + |
---|
1803 | + def is_unknown(self): |
---|
1804 | + return False |
---|
1805 | + |
---|
1806 | + def is_allowed_in_immutable_directory(self): |
---|
1807 | + return False |
---|
1808 | + |
---|
1809 | def modify(self, modifier): |
---|
1810 | self.data = modifier(self.data, None, True) |
---|
1811 | return defer.succeed(None) |
---|
1812 | @@ -1049,50 +1143,186 @@ |
---|
1813 | self.nodemaker = client.nodemaker |
---|
1814 | |
---|
1815 | def test_from_future(self): |
---|
1816 | - # create a dirnode that contains unknown URI types, and make sure we |
---|
1817 | - # tolerate them properly. Since dirnodes aren't allowed to add |
---|
1818 | - # unknown node types, we have to be tricky. |
---|
1819 | + # Create a mutable directory that contains unknown URI types, and make sure |
---|
1820 | + # we tolerate them properly. |
---|
1821 | d = self.nodemaker.create_new_mutable_directory() |
---|
1822 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
1823 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1824 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
1825 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
1826 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1827 | + future_imm_uri = "x-tahoe-crazy-immutable://I_am_from_the_future." |
---|
1828 | + future_node = UnknownNode(future_write_uri, future_read_uri) |
---|
1829 | def _then(n): |
---|
1830 | self._node = n |
---|
1831 | return n.set_node(u"future", future_node) |
---|
1832 | d.addCallback(_then) |
---|
1833 | |
---|
1834 | - # we should be prohibited from adding an unknown URI to a directory, |
---|
1835 | - # since we don't know how to diminish the cap to a readcap (for the |
---|
1836 | - # dirnode's rocap slot), and we don't want to accidentally grant |
---|
1837 | - # write access to a holder of the dirnode's readcap. |
---|
1838 | + # We should be prohibited from adding an unknown URI to a directory |
---|
1839 | + # just in the rw_uri slot, since we don't know how to diminish the cap |
---|
1840 | + # to a readcap (for the ro_uri slot). |
---|
1841 | d.addCallback(lambda ign: |
---|
1842 | - self.shouldFail(CannotPackUnknownNodeError, |
---|
1843 | + self.shouldFail(MustNotBeUnknownRWError, |
---|
1844 | "copy unknown", |
---|
1845 | - "cannot pack unknown node as child add", |
---|
1846 | + "cannot attach unknown rw cap as child", |
---|
1847 | self._node.set_uri, u"add", |
---|
1848 | - future_writecap, future_readcap)) |
---|
1849 | + future_write_uri, None)) |
---|
1850 | + |
---|
1851 | + # However, we should be able to add both rw_uri and ro_uri as a pair of |
---|
1852 | + # unknown URIs. |
---|
1853 | + d.addCallback(lambda ign: self._node.set_uri(u"add-pair", |
---|
1854 | + future_write_uri, future_read_uri)) |
---|
1855 | + |
---|
1856 | + # and to add an URI prefixed with "ro." or "imm." when it is given in a |
---|
1857 | + # write slot (or URL parameter). |
---|
1858 | + d.addCallback(lambda ign: self._node.set_uri(u"add-ro", |
---|
1859 | + "ro." + future_read_uri, None)) |
---|
1860 | + d.addCallback(lambda ign: self._node.set_uri(u"add-imm", |
---|
1861 | + "imm." + future_imm_uri, None)) |
---|
1862 | + |
---|
1863 | d.addCallback(lambda ign: self._node.list()) |
---|
1864 | def _check(children): |
---|
1865 | - self.failUnlessEqual(len(children), 1) |
---|
1866 | + self.failUnlessEqual(len(children), 4) |
---|
1867 | (fn, metadata) = children[u"future"] |
---|
1868 | self.failUnless(isinstance(fn, UnknownNode), fn) |
---|
1869 | - self.failUnlessEqual(fn.get_uri(), future_writecap) |
---|
1870 | - self.failUnlessEqual(fn.get_readonly_uri(), future_readcap) |
---|
1871 | - # but we *should* be allowed to copy this node, because the |
---|
1872 | - # UnknownNode contains all the information that was in the |
---|
1873 | - # original directory (readcap and writecap), so we're preserving |
---|
1874 | - # everything. |
---|
1875 | + self.failUnlessEqual(fn.get_uri(), future_write_uri) |
---|
1876 | + self.failUnlessEqual(fn.get_write_uri(), future_write_uri) |
---|
1877 | + self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri) |
---|
1878 | + |
---|
1879 | + (fn2, metadata2) = children[u"add-pair"] |
---|
1880 | + self.failUnless(isinstance(fn2, UnknownNode), fn2) |
---|
1881 | + self.failUnlessEqual(fn2.get_uri(), future_write_uri) |
---|
1882 | + self.failUnlessEqual(fn2.get_write_uri(), future_write_uri) |
---|
1883 | + self.failUnlessEqual(fn2.get_readonly_uri(), "ro." + future_read_uri) |
---|
1884 | + |
---|
1885 | + (fn3, metadata3) = children[u"add-ro"] |
---|
1886 | + self.failUnless(isinstance(fn3, UnknownNode), fn3) |
---|
1887 | + self.failUnlessEqual(fn3.get_uri(), "ro." + future_read_uri) |
---|
1888 | + self.failUnlessEqual(fn3.get_write_uri(), None) |
---|
1889 | + self.failUnlessEqual(fn3.get_readonly_uri(), "ro." + future_read_uri) |
---|
1890 | + |
---|
1891 | + (fn4, metadata4) = children[u"add-imm"] |
---|
1892 | + self.failUnless(isinstance(fn4, UnknownNode), fn4) |
---|
1893 | + self.failUnlessEqual(fn4.get_uri(), "imm." + future_imm_uri) |
---|
1894 | + self.failUnlessEqual(fn4.get_write_uri(), None) |
---|
1895 | + self.failUnlessEqual(fn4.get_readonly_uri(), "imm." + future_imm_uri) |
---|
1896 | + |
---|
1897 | + # We should also be allowed to copy the "future" UnknownNode, because |
---|
1898 | + # it contains all the information that was in the original directory |
---|
1899 | + # (readcap and writecap), so we're preserving everything. |
---|
1900 | return self._node.set_node(u"copy", fn) |
---|
1901 | d.addCallback(_check) |
---|
1902 | + |
---|
1903 | d.addCallback(lambda ign: self._node.list()) |
---|
1904 | def _check2(children): |
---|
1905 | - self.failUnlessEqual(len(children), 2) |
---|
1906 | + self.failUnlessEqual(len(children), 5) |
---|
1907 | (fn, metadata) = children[u"copy"] |
---|
1908 | self.failUnless(isinstance(fn, UnknownNode), fn) |
---|
1909 | - self.failUnlessEqual(fn.get_uri(), future_writecap) |
---|
1910 | - self.failUnlessEqual(fn.get_readonly_uri(), future_readcap) |
---|
1911 | + self.failUnlessEqual(fn.get_uri(), future_write_uri) |
---|
1912 | + self.failUnlessEqual(fn.get_write_uri(), future_write_uri) |
---|
1913 | + self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri) |
---|
1914 | + d.addCallback(_check2) |
---|
1915 | return d |
---|
1916 | |
---|
1917 | + def test_unknown_strip_prefix_for_ro(self): |
---|
1918 | + self.failUnlessEqual(strip_prefix_for_ro("foo", False), "foo") |
---|
1919 | + self.failUnlessEqual(strip_prefix_for_ro("ro.foo", False), "foo") |
---|
1920 | + self.failUnlessEqual(strip_prefix_for_ro("imm.foo", False), "imm.foo") |
---|
1921 | + self.failUnlessEqual(strip_prefix_for_ro("foo", True), "foo") |
---|
1922 | + self.failUnlessEqual(strip_prefix_for_ro("ro.foo", True), "foo") |
---|
1923 | + self.failUnlessEqual(strip_prefix_for_ro("imm.foo", True), "foo") |
---|
1924 | + |
---|
1925 | + def test_unknownnode(self): |
---|
1926 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1927 | + mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1928 | + lit_uri = "URI:LIT:n5xgk" |
---|
1929 | + |
---|
1930 | + # This does not attempt to be exhaustive. |
---|
1931 | + no_no = [# Opaque node, but not an error. |
---|
1932 | + ( 0, UnknownNode(None, None)), |
---|
1933 | + ( 1, UnknownNode(None, None, deep_immutable=True)), |
---|
1934 | + ] |
---|
1935 | + unknown_rw = [# These are errors because we're only given a rw_uri, and we can't |
---|
1936 | + # diminish it. |
---|
1937 | + ( 2, UnknownNode("foo", None)), |
---|
1938 | + ( 3, UnknownNode("foo", None, deep_immutable=True)), |
---|
1939 | + ( 4, UnknownNode("ro.foo", None, deep_immutable=True)), |
---|
1940 | + ( 5, UnknownNode("ro." + mut_read_uri, None, deep_immutable=True)), |
---|
1941 | + ( 6, UnknownNode("URI:SSK-RO:foo", None, deep_immutable=True)), |
---|
1942 | + ( 7, UnknownNode("URI:SSK:foo", None)), |
---|
1943 | + ] |
---|
1944 | + must_be_ro = [# These are errors because a readonly constraint is not met. |
---|
1945 | + ( 8, UnknownNode("ro." + mut_write_uri, None)), |
---|
1946 | + ( 9, UnknownNode(None, "ro." + mut_write_uri)), |
---|
1947 | + ] |
---|
1948 | + must_be_imm = [# These are errors because an immutable constraint is not met. |
---|
1949 | + (10, UnknownNode(None, "ro.URI:SSK-RO:foo", deep_immutable=True)), |
---|
1950 | + (11, UnknownNode(None, "imm.URI:SSK:foo")), |
---|
1951 | + (12, UnknownNode(None, "imm.URI:SSK-RO:foo")), |
---|
1952 | + (13, UnknownNode("bar", "ro.foo", deep_immutable=True)), |
---|
1953 | + (14, UnknownNode("bar", "imm.foo", deep_immutable=True)), |
---|
1954 | + (15, UnknownNode("bar", "imm." + lit_uri, deep_immutable=True)), |
---|
1955 | + (16, UnknownNode("imm." + mut_write_uri, None)), |
---|
1956 | + (17, UnknownNode("imm." + mut_read_uri, None)), |
---|
1957 | + (18, UnknownNode("bar", "imm.foo")), |
---|
1958 | + ] |
---|
1959 | + bad_uri = [# These are errors because the URI is bad once we've stripped the prefix. |
---|
1960 | + (19, UnknownNode("ro.URI:SSK-RO:foo", None)), |
---|
1961 | + (20, UnknownNode("imm.URI:CHK:foo", None, deep_immutable=True)), |
---|
1962 | + ] |
---|
1963 | + ro_prefixed = [# These are valid, and the readcap should end up with a ro. prefix. |
---|
1964 | + (21, UnknownNode(None, "foo")), |
---|
1965 | + (22, UnknownNode(None, "ro.foo")), |
---|
1966 | + (32, UnknownNode(None, "ro." + lit_uri)), |
---|
1967 | + (23, UnknownNode("bar", "foo")), |
---|
1968 | + (24, UnknownNode("bar", "ro.foo")), |
---|
1969 | + (32, UnknownNode("bar", "ro." + lit_uri)), |
---|
1970 | + (25, UnknownNode("ro.foo", None)), |
---|
1971 | + (30, UnknownNode("ro." + lit_uri, None)), |
---|
1972 | + ] |
---|
1973 | + imm_prefixed = [# These are valid, and the readcap should end up with an imm. prefix. |
---|
1974 | + (26, UnknownNode(None, "foo", deep_immutable=True)), |
---|
1975 | + (27, UnknownNode(None, "ro.foo", deep_immutable=True)), |
---|
1976 | + (28, UnknownNode(None, "imm.foo")), |
---|
1977 | + (29, UnknownNode(None, "imm.foo", deep_immutable=True)), |
---|
1978 | + (31, UnknownNode("imm." + lit_uri, None)), |
---|
1979 | + (31, UnknownNode("imm." + lit_uri, None, deep_immutable=True)), |
---|
1980 | + (33, UnknownNode(None, "imm." + lit_uri)), |
---|
1981 | + (33, UnknownNode(None, "imm." + lit_uri, deep_immutable=True)), |
---|
1982 | + ] |
---|
1983 | + error = unknown_rw + must_be_ro + must_be_imm + bad_uri |
---|
1984 | + ok = ro_prefixed + imm_prefixed |
---|
1985 | + |
---|
1986 | + for (i, n) in no_no + error + ok: |
---|
1987 | + self.failUnless(n.is_unknown(), i) |
---|
1988 | + |
---|
1989 | + for (i, n) in no_no + error: |
---|
1990 | + self.failUnless(n.get_uri() is None, i) |
---|
1991 | + self.failUnless(n.get_write_uri() is None, i) |
---|
1992 | + self.failUnless(n.get_readonly_uri() is None, i) |
---|
1993 | + |
---|
1994 | + for (i, n) in no_no + ok: |
---|
1995 | + n.raise_error() |
---|
1996 | + |
---|
1997 | + for (i, n) in unknown_rw: |
---|
1998 | + self.failUnlessRaises(MustNotBeUnknownRWError, lambda: n.raise_error()) |
---|
1999 | + |
---|
2000 | + for (i, n) in must_be_ro: |
---|
2001 | + self.failUnlessRaises(MustBeReadonlyError, lambda: n.raise_error()) |
---|
2002 | + |
---|
2003 | + for (i, n) in must_be_imm: |
---|
2004 | + self.failUnlessRaises(MustBeDeepImmutableError, lambda: n.raise_error()) |
---|
2005 | + |
---|
2006 | + for (i, n) in bad_uri: |
---|
2007 | + self.failUnlessRaises(uri.BadURIError, lambda: n.raise_error()) |
---|
2008 | + |
---|
2009 | + for (i, n) in ok: |
---|
2010 | + self.failIf(n.get_readonly_uri() is None, i) |
---|
2011 | + |
---|
2012 | + for (i, n) in ro_prefixed: |
---|
2013 | + self.failUnless(n.get_readonly_uri().startswith("ro."), i) |
---|
2014 | + |
---|
2015 | + for (i, n) in imm_prefixed: |
---|
2016 | + self.failUnless(n.get_readonly_uri().startswith("imm."), i) |
---|
2017 | + |
---|
2018 | + |
---|
2019 | class DeepStats(unittest.TestCase): |
---|
2020 | timeout = 240 # It takes longer than 120 seconds on Francois's arm box. |
---|
2021 | def test_stats(self): |
---|
2022 | diff -rN -u old-tahoe/src/allmydata/test/test_filenode.py new-tahoe/src/allmydata/test/test_filenode.py |
---|
2023 | --- old-tahoe/src/allmydata/test/test_filenode.py 2010-01-25 11:54:56.584000000 +0000 |
---|
2024 | +++ new-tahoe/src/allmydata/test/test_filenode.py 2010-01-25 11:55:01.908000000 +0000 |
---|
2025 | @@ -41,14 +41,21 @@ |
---|
2026 | self.failUnlessEqual(fn1.get_readcap(), u) |
---|
2027 | self.failUnlessEqual(fn1.is_readonly(), True) |
---|
2028 | self.failUnlessEqual(fn1.is_mutable(), False) |
---|
2029 | + self.failUnlessEqual(fn1.is_unknown(), False) |
---|
2030 | + self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True) |
---|
2031 | + self.failUnlessEqual(fn1.get_write_uri(), None) |
---|
2032 | self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string()) |
---|
2033 | self.failUnlessEqual(fn1.get_size(), 1000) |
---|
2034 | self.failUnlessEqual(fn1.get_storage_index(), u.storage_index) |
---|
2035 | + fn1.raise_error() |
---|
2036 | + fn2.raise_error() |
---|
2037 | d = {} |
---|
2038 | d[fn1] = 1 # exercise __hash__ |
---|
2039 | v = fn1.get_verify_cap() |
---|
2040 | self.failUnless(isinstance(v, uri.CHKFileVerifierURI)) |
---|
2041 | self.failUnlessEqual(fn1.get_repair_cap(), v) |
---|
2042 | + self.failUnlessEqual(v.is_readonly(), True) |
---|
2043 | + self.failUnlessEqual(v.is_mutable(), False) |
---|
2044 | |
---|
2045 | |
---|
2046 | def test_literal_filenode(self): |
---|
2047 | @@ -64,9 +71,14 @@ |
---|
2048 | self.failUnlessEqual(fn1.get_readcap(), u) |
---|
2049 | self.failUnlessEqual(fn1.is_readonly(), True) |
---|
2050 | self.failUnlessEqual(fn1.is_mutable(), False) |
---|
2051 | + self.failUnlessEqual(fn1.is_unknown(), False) |
---|
2052 | + self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True) |
---|
2053 | + self.failUnlessEqual(fn1.get_write_uri(), None) |
---|
2054 | self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string()) |
---|
2055 | self.failUnlessEqual(fn1.get_size(), len(DATA)) |
---|
2056 | self.failUnlessEqual(fn1.get_storage_index(), None) |
---|
2057 | + fn1.raise_error() |
---|
2058 | + fn2.raise_error() |
---|
2059 | d = {} |
---|
2060 | d[fn1] = 1 # exercise __hash__ |
---|
2061 | |
---|
2062 | @@ -99,24 +111,29 @@ |
---|
2063 | self.failUnlessEqual(n.get_writekey(), wk) |
---|
2064 | self.failUnlessEqual(n.get_readkey(), rk) |
---|
2065 | self.failUnlessEqual(n.get_storage_index(), si) |
---|
2066 | - # these itmes are populated on first read (or create), so until that |
---|
2067 | + # these items are populated on first read (or create), so until that |
---|
2068 | # happens they'll be None |
---|
2069 | self.failUnlessEqual(n.get_privkey(), None) |
---|
2070 | self.failUnlessEqual(n.get_encprivkey(), None) |
---|
2071 | self.failUnlessEqual(n.get_pubkey(), None) |
---|
2072 | |
---|
2073 | self.failUnlessEqual(n.get_uri(), u.to_string()) |
---|
2074 | + self.failUnlessEqual(n.get_write_uri(), u.to_string()) |
---|
2075 | self.failUnlessEqual(n.get_readonly_uri(), u.get_readonly().to_string()) |
---|
2076 | self.failUnlessEqual(n.get_cap(), u) |
---|
2077 | self.failUnlessEqual(n.get_readcap(), u.get_readonly()) |
---|
2078 | self.failUnlessEqual(n.is_mutable(), True) |
---|
2079 | self.failUnlessEqual(n.is_readonly(), False) |
---|
2080 | + self.failUnlessEqual(n.is_unknown(), False) |
---|
2081 | + self.failUnlessEqual(n.is_allowed_in_immutable_directory(), False) |
---|
2082 | + n.raise_error() |
---|
2083 | |
---|
2084 | n2 = MutableFileNode(None, None, client.get_encoding_parameters(), |
---|
2085 | None).init_from_cap(u) |
---|
2086 | self.failUnlessEqual(n, n2) |
---|
2087 | self.failIfEqual(n, "not even the right type") |
---|
2088 | self.failIfEqual(n, u) # not the right class |
---|
2089 | + n.raise_error() |
---|
2090 | d = {n: "can these be used as dictionary keys?"} |
---|
2091 | d[n2] = "replace the old one" |
---|
2092 | self.failUnlessEqual(len(d), 1) |
---|
2093 | @@ -127,12 +144,16 @@ |
---|
2094 | self.failUnlessEqual(nro.get_readonly(), nro) |
---|
2095 | self.failUnlessEqual(nro.get_cap(), u.get_readonly()) |
---|
2096 | self.failUnlessEqual(nro.get_readcap(), u.get_readonly()) |
---|
2097 | + self.failUnlessEqual(nro.is_mutable(), True) |
---|
2098 | + self.failUnlessEqual(nro.is_readonly(), True) |
---|
2099 | + self.failUnlessEqual(nro.is_unknown(), False) |
---|
2100 | + self.failUnlessEqual(nro.is_allowed_in_immutable_directory(), False) |
---|
2101 | nro_u = nro.get_uri() |
---|
2102 | self.failUnlessEqual(nro_u, nro.get_readonly_uri()) |
---|
2103 | self.failUnlessEqual(nro_u, u.get_readonly().to_string()) |
---|
2104 | - self.failUnlessEqual(nro.is_mutable(), True) |
---|
2105 | - self.failUnlessEqual(nro.is_readonly(), True) |
---|
2106 | + self.failUnlessEqual(nro.get_write_uri(), None) |
---|
2107 | self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap |
---|
2108 | + nro.raise_error() |
---|
2109 | |
---|
2110 | v = n.get_verify_cap() |
---|
2111 | self.failUnless(isinstance(v, uri.SSKVerifierURI)) |
---|
2112 | diff -rN -u old-tahoe/src/allmydata/test/test_system.py new-tahoe/src/allmydata/test/test_system.py |
---|
2113 | --- old-tahoe/src/allmydata/test/test_system.py 2010-01-25 11:54:56.895000000 +0000 |
---|
2114 | +++ new-tahoe/src/allmydata/test/test_system.py 2010-01-25 11:55:02.199000000 +0000 |
---|
2115 | @@ -17,7 +17,7 @@ |
---|
2116 | from allmydata.interfaces import IDirectoryNode, IFileNode, \ |
---|
2117 | NoSuchChildError, NoSharesError |
---|
2118 | from allmydata.monitor import Monitor |
---|
2119 | -from allmydata.mutable.common import NotMutableError |
---|
2120 | +from allmydata.mutable.common import NotWriteableError |
---|
2121 | from allmydata.mutable import layout as mutable_layout |
---|
2122 | from foolscap.api import DeadReferenceError |
---|
2123 | from twisted.python.failure import Failure |
---|
2124 | @@ -890,11 +890,11 @@ |
---|
2125 | d1.addCallback(lambda res: dirnode.list()) |
---|
2126 | d1.addCallback(self.log, "dirnode.list") |
---|
2127 | |
---|
2128 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope")) |
---|
2129 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope")) |
---|
2130 | |
---|
2131 | d1.addCallback(self.log, "doing add_file(ro)") |
---|
2132 | ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence="99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)") |
---|
2133 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut)) |
---|
2134 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut)) |
---|
2135 | |
---|
2136 | d1.addCallback(self.log, "doing get(ro)") |
---|
2137 | d1.addCallback(lambda res: dirnode.get(u"mydata992")) |
---|
2138 | @@ -902,17 +902,17 @@ |
---|
2139 | self.failUnless(IFileNode.providedBy(filenode))) |
---|
2140 | |
---|
2141 | d1.addCallback(self.log, "doing delete(ro)") |
---|
2142 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, u"mydata992")) |
---|
2143 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "delete(nope)", None, dirnode.delete, u"mydata992")) |
---|
2144 | |
---|
2145 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri)) |
---|
2146 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri)) |
---|
2147 | |
---|
2148 | d1.addCallback(lambda res: self.shouldFail2(NoSuchChildError, "get(missing)", "missing", dirnode.get, u"missing")) |
---|
2149 | |
---|
2150 | personal = self._personal_node |
---|
2151 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope")) |
---|
2152 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope")) |
---|
2153 | |
---|
2154 | d1.addCallback(self.log, "doing move_child_to(ro)2") |
---|
2155 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope")) |
---|
2156 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope")) |
---|
2157 | |
---|
2158 | d1.addCallback(self.log, "finished with _got_s2ro") |
---|
2159 | return d1 |
---|
2160 | diff -rN -u old-tahoe/src/allmydata/test/test_uri.py new-tahoe/src/allmydata/test/test_uri.py |
---|
2161 | --- old-tahoe/src/allmydata/test/test_uri.py 2010-01-25 11:54:56.910000000 +0000 |
---|
2162 | +++ new-tahoe/src/allmydata/test/test_uri.py 2010-01-25 11:55:02.216000000 +0000 |
---|
2163 | @@ -3,7 +3,7 @@ |
---|
2164 | from allmydata import uri |
---|
2165 | from allmydata.util import hashutil, base32 |
---|
2166 | from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \ |
---|
2167 | - IVerifierURI |
---|
2168 | + IVerifierURI, CapConstraintError |
---|
2169 | |
---|
2170 | class Literal(unittest.TestCase): |
---|
2171 | def _help_test(self, data): |
---|
2172 | @@ -22,8 +22,16 @@ |
---|
2173 | self.failIf(IDirnodeURI.providedBy(u2)) |
---|
2174 | self.failUnlessEqual(u2.data, data) |
---|
2175 | self.failUnlessEqual(u2.get_size(), len(data)) |
---|
2176 | - self.failUnless(u.is_readonly()) |
---|
2177 | - self.failIf(u.is_mutable()) |
---|
2178 | + self.failUnless(u2.is_readonly()) |
---|
2179 | + self.failIf(u2.is_mutable()) |
---|
2180 | + |
---|
2181 | + u2i = uri.from_string(u.to_string(), deep_immutable=True) |
---|
2182 | + self.failUnless(IFileURI.providedBy(u2i)) |
---|
2183 | + self.failIf(IDirnodeURI.providedBy(u2i)) |
---|
2184 | + self.failUnlessEqual(u2i.data, data) |
---|
2185 | + self.failUnlessEqual(u2i.get_size(), len(data)) |
---|
2186 | + self.failUnless(u2i.is_readonly()) |
---|
2187 | + self.failIf(u2i.is_mutable()) |
---|
2188 | |
---|
2189 | u3 = u.get_readonly() |
---|
2190 | self.failUnlessIdentical(u, u3) |
---|
2191 | @@ -51,18 +59,36 @@ |
---|
2192 | fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834' |
---|
2193 | chk1 = uri.CHKFileURI.init_from_string(fileURI) |
---|
2194 | chk2 = uri.CHKFileURI.init_from_string(fileURI) |
---|
2195 | + unk = uri.UnknownURI("lafs://from_the_future") |
---|
2196 | self.failIfEqual(lit1, chk1) |
---|
2197 | self.failUnlessEqual(chk1, chk2) |
---|
2198 | self.failIfEqual(chk1, "not actually a URI") |
---|
2199 | # these should be hashable too |
---|
2200 | - s = set([lit1, chk1, chk2]) |
---|
2201 | - self.failUnlessEqual(len(s), 2) # since chk1==chk2 |
---|
2202 | + s = set([lit1, chk1, chk2, unk]) |
---|
2203 | + self.failUnlessEqual(len(s), 3) # since chk1==chk2 |
---|
2204 | |
---|
2205 | def test_is_uri(self): |
---|
2206 | lit1 = uri.LiteralFileURI("some data").to_string() |
---|
2207 | self.failUnless(uri.is_uri(lit1)) |
---|
2208 | self.failIf(uri.is_uri(None)) |
---|
2209 | |
---|
2210 | + def test_is_literal_file_uri(self): |
---|
2211 | + lit1 = uri.LiteralFileURI("some data").to_string() |
---|
2212 | + self.failUnless(uri.is_literal_file_uri(lit1)) |
---|
2213 | + self.failIf(uri.is_literal_file_uri(None)) |
---|
2214 | + self.failIf(uri.is_literal_file_uri("foo")) |
---|
2215 | + self.failIf(uri.is_literal_file_uri("ro.foo")) |
---|
2216 | + self.failIf(uri.is_literal_file_uri("URI:LITfoo")) |
---|
2217 | + self.failUnless(uri.is_literal_file_uri("ro.URI:LIT:foo")) |
---|
2218 | + self.failUnless(uri.is_literal_file_uri("imm.URI:LIT:foo")) |
---|
2219 | + |
---|
2220 | + def test_has_uri_prefix(self): |
---|
2221 | + self.failUnless(uri.has_uri_prefix("URI:foo")) |
---|
2222 | + self.failUnless(uri.has_uri_prefix("ro.URI:foo")) |
---|
2223 | + self.failUnless(uri.has_uri_prefix("imm.URI:foo")) |
---|
2224 | + self.failIf(uri.has_uri_prefix(None)) |
---|
2225 | + self.failIf(uri.has_uri_prefix("foo")) |
---|
2226 | + |
---|
2227 | class CHKFile(unittest.TestCase): |
---|
2228 | def test_pack(self): |
---|
2229 | key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" |
---|
2230 | @@ -88,8 +114,7 @@ |
---|
2231 | self.failUnless(IFileURI.providedBy(u)) |
---|
2232 | self.failIf(IDirnodeURI.providedBy(u)) |
---|
2233 | self.failUnlessEqual(u.get_size(), 1234) |
---|
2234 | - self.failUnless(u.is_readonly()) |
---|
2235 | - self.failIf(u.is_mutable()) |
---|
2236 | + |
---|
2237 | u_ro = u.get_readonly() |
---|
2238 | self.failUnlessIdentical(u, u_ro) |
---|
2239 | he = u.to_human_encoding() |
---|
2240 | @@ -109,11 +134,19 @@ |
---|
2241 | self.failUnless(IFileURI.providedBy(u2)) |
---|
2242 | self.failIf(IDirnodeURI.providedBy(u2)) |
---|
2243 | self.failUnlessEqual(u2.get_size(), 1234) |
---|
2244 | - self.failUnless(u2.is_readonly()) |
---|
2245 | - self.failIf(u2.is_mutable()) |
---|
2246 | + |
---|
2247 | + u2i = uri.from_string(u.to_string(), deep_immutable=True) |
---|
2248 | + self.failUnlessEqual(u.to_string(), u2i.to_string()) |
---|
2249 | + u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string()) |
---|
2250 | + self.failUnlessEqual(u.to_string(), u2ro.to_string()) |
---|
2251 | + u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string()) |
---|
2252 | + self.failUnlessEqual(u.to_string(), u2imm.to_string()) |
---|
2253 | |
---|
2254 | v = u.get_verify_cap() |
---|
2255 | self.failUnless(isinstance(v.to_string(), str)) |
---|
2256 | + self.failUnless(v.is_readonly()) |
---|
2257 | + self.failIf(v.is_mutable()) |
---|
2258 | + |
---|
2259 | v2 = uri.from_string(v.to_string()) |
---|
2260 | self.failUnlessEqual(v, v2) |
---|
2261 | he = v.to_human_encoding() |
---|
2262 | @@ -126,6 +159,8 @@ |
---|
2263 | total_shares=10, |
---|
2264 | size=1234) |
---|
2265 | self.failUnless(isinstance(v3.to_string(), str)) |
---|
2266 | + self.failUnless(v3.is_readonly()) |
---|
2267 | + self.failIf(v3.is_mutable()) |
---|
2268 | |
---|
2269 | def test_pack_badly(self): |
---|
2270 | key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" |
---|
2271 | @@ -179,13 +214,20 @@ |
---|
2272 | self.failUnlessEqual(readable["UEB_hash"], |
---|
2273 | base32.b2a(hashutil.uri_extension_hash(ext))) |
---|
2274 | |
---|
2275 | -class Invalid(unittest.TestCase): |
---|
2276 | +class Unknown(unittest.TestCase): |
---|
2277 | def test_from_future(self): |
---|
2278 | # any URI type that we don't recognize should be treated as unknown |
---|
2279 | future_uri = "I am a URI from the future. Whatever you do, don't " |
---|
2280 | u = uri.from_string(future_uri) |
---|
2281 | self.failUnless(isinstance(u, uri.UnknownURI)) |
---|
2282 | self.failUnlessEqual(u.to_string(), future_uri) |
---|
2283 | + self.failUnless(u.get_readonly() is None) |
---|
2284 | + self.failUnless(u.get_error() is None) |
---|
2285 | + |
---|
2286 | + u2 = uri.UnknownURI(future_uri, error=CapConstraintError("...")) |
---|
2287 | + self.failUnlessEqual(u.to_string(), future_uri) |
---|
2288 | + self.failUnless(u2.get_readonly() is None) |
---|
2289 | + self.failUnless(isinstance(u2.get_error(), CapConstraintError)) |
---|
2290 | |
---|
2291 | class Constraint(unittest.TestCase): |
---|
2292 | def test_constraint(self): |
---|
2293 | @@ -226,6 +268,13 @@ |
---|
2294 | self.failUnless(IMutableFileURI.providedBy(u2)) |
---|
2295 | self.failIf(IDirnodeURI.providedBy(u2)) |
---|
2296 | |
---|
2297 | + u2i = uri.from_string(u.to_string(), deep_immutable=True) |
---|
2298 | + self.failUnless(isinstance(u2i, uri.UnknownURI), u2i) |
---|
2299 | + u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string()) |
---|
2300 | + self.failUnless(isinstance(u2ro, uri.UnknownURI), u2ro) |
---|
2301 | + u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string()) |
---|
2302 | + self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm) |
---|
2303 | + |
---|
2304 | u3 = u2.get_readonly() |
---|
2305 | readkey = hashutil.ssk_readkey_hash(writekey) |
---|
2306 | self.failUnlessEqual(u3.fingerprint, fingerprint) |
---|
2307 | @@ -236,6 +285,13 @@ |
---|
2308 | self.failUnless(IMutableFileURI.providedBy(u3)) |
---|
2309 | self.failIf(IDirnodeURI.providedBy(u3)) |
---|
2310 | |
---|
2311 | + u3i = uri.from_string(u3.to_string(), deep_immutable=True) |
---|
2312 | + self.failUnless(isinstance(u3i, uri.UnknownURI), u3i) |
---|
2313 | + u3ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u3.to_string()) |
---|
2314 | + self.failUnlessEqual(u3.to_string(), u3ro.to_string()) |
---|
2315 | + u3imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u3.to_string()) |
---|
2316 | + self.failUnless(isinstance(u3imm, uri.UnknownURI), u3imm) |
---|
2317 | + |
---|
2318 | he = u3.to_human_encoding() |
---|
2319 | u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he) |
---|
2320 | self.failUnlessEqual(u3, u3_h) |
---|
2321 | @@ -249,6 +305,13 @@ |
---|
2322 | self.failUnless(IMutableFileURI.providedBy(u4)) |
---|
2323 | self.failIf(IDirnodeURI.providedBy(u4)) |
---|
2324 | |
---|
2325 | + u4i = uri.from_string(u4.to_string(), deep_immutable=True) |
---|
2326 | + self.failUnless(isinstance(u4i, uri.UnknownURI), u4i) |
---|
2327 | + u4ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u4.to_string()) |
---|
2328 | + self.failUnlessEqual(u4.to_string(), u4ro.to_string()) |
---|
2329 | + u4imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u4.to_string()) |
---|
2330 | + self.failUnless(isinstance(u4imm, uri.UnknownURI), u4imm) |
---|
2331 | + |
---|
2332 | u4a = uri.from_string(u4.to_string()) |
---|
2333 | self.failUnlessEqual(u4a, u4) |
---|
2334 | self.failUnless("ReadonlySSKFileURI" in str(u4a)) |
---|
2335 | @@ -291,12 +354,19 @@ |
---|
2336 | self.failIf(IFileURI.providedBy(u2)) |
---|
2337 | self.failUnless(IDirnodeURI.providedBy(u2)) |
---|
2338 | |
---|
2339 | + u2i = uri.from_string(u1.to_string(), deep_immutable=True) |
---|
2340 | + self.failUnless(isinstance(u2i, uri.UnknownURI)) |
---|
2341 | + |
---|
2342 | u3 = u2.get_readonly() |
---|
2343 | self.failUnless(u3.is_readonly()) |
---|
2344 | self.failUnless(u3.is_mutable()) |
---|
2345 | self.failUnless(IURI.providedBy(u3)) |
---|
2346 | self.failIf(IFileURI.providedBy(u3)) |
---|
2347 | self.failUnless(IDirnodeURI.providedBy(u3)) |
---|
2348 | + |
---|
2349 | + u3i = uri.from_string(u2.to_string(), deep_immutable=True) |
---|
2350 | + self.failUnless(isinstance(u3i, uri.UnknownURI)) |
---|
2351 | + |
---|
2352 | u3n = u3._filenode_uri |
---|
2353 | self.failUnless(u3n.is_readonly()) |
---|
2354 | self.failUnless(u3n.is_mutable()) |
---|
2355 | @@ -363,10 +433,16 @@ |
---|
2356 | self.failIf(IFileURI.providedBy(u2)) |
---|
2357 | self.failUnless(IDirnodeURI.providedBy(u2)) |
---|
2358 | |
---|
2359 | + u2i = uri.from_string(u1.to_string(), deep_immutable=True) |
---|
2360 | + self.failUnlessEqual(u1.to_string(), u2i.to_string()) |
---|
2361 | + |
---|
2362 | u3 = u2.get_readonly() |
---|
2363 | self.failUnlessEqual(u3.to_string(), u2.to_string()) |
---|
2364 | self.failUnless(str(u3)) |
---|
2365 | |
---|
2366 | + u3i = uri.from_string(u2.to_string(), deep_immutable=True) |
---|
2367 | + self.failUnlessEqual(u2.to_string(), u3i.to_string()) |
---|
2368 | + |
---|
2369 | u2_verifier = u2.get_verify_cap() |
---|
2370 | self.failUnless(isinstance(u2_verifier, |
---|
2371 | uri.ImmutableDirectoryURIVerifier), |
---|
2372 | diff -rN -u old-tahoe/src/allmydata/test/test_web.py new-tahoe/src/allmydata/test/test_web.py |
---|
2373 | --- old-tahoe/src/allmydata/test/test_web.py 2010-01-25 11:54:56.933000000 +0000 |
---|
2374 | +++ new-tahoe/src/allmydata/test/test_web.py 2010-01-25 11:55:02.253000000 +0000 |
---|
2375 | @@ -7,7 +7,7 @@ |
---|
2376 | from twisted.web import client, error, http |
---|
2377 | from twisted.python import failure, log |
---|
2378 | from nevow import rend |
---|
2379 | -from allmydata import interfaces, uri, webish |
---|
2380 | +from allmydata import interfaces, uri, webish, dirnode |
---|
2381 | from allmydata.storage.shares import get_share_file |
---|
2382 | from allmydata.storage_client import StorageFarmBroker |
---|
2383 | from allmydata.immutable import upload, download |
---|
2384 | @@ -18,6 +18,7 @@ |
---|
2385 | from allmydata.scripts.debug import CorruptShareOptions, corrupt_share |
---|
2386 | from allmydata.util import fileutil, base32 |
---|
2387 | from allmydata.util.consumer import download_to_data |
---|
2388 | +from allmydata.util.netstring import split_netstring |
---|
2389 | from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \ |
---|
2390 | create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri |
---|
2391 | from allmydata.interfaces import IMutableFileNode |
---|
2392 | @@ -699,11 +700,15 @@ |
---|
2393 | self.PUT, base, "") |
---|
2394 | return d |
---|
2395 | |
---|
2396 | + # TODO: version of this with a Unicode filename |
---|
2397 | def test_GET_FILEURL_save(self): |
---|
2398 | - d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true") |
---|
2399 | - # TODO: look at the headers, expect a Content-Disposition: attachment |
---|
2400 | - # header. |
---|
2401 | - d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2402 | + d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true", |
---|
2403 | + return_response=True) |
---|
2404 | + def _got((res, statuscode, headers)): |
---|
2405 | + content_disposition = headers["content-disposition"][0] |
---|
2406 | + self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition) |
---|
2407 | + self.failUnlessIsBarDotTxt(res) |
---|
2408 | + d.addCallback(_got) |
---|
2409 | return d |
---|
2410 | |
---|
2411 | def test_GET_FILEURL_missing(self): |
---|
2412 | @@ -735,7 +740,7 @@ |
---|
2413 | d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS) |
---|
2414 | # TODO: we lose the response code, so we can't check this |
---|
2415 | #self.failUnlessEqual(responsecode, 201) |
---|
2416 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2417 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt") |
---|
2418 | d.addCallback(lambda res: |
---|
2419 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
2420 | self.NEWFILE_CONTENTS)) |
---|
2421 | @@ -746,7 +751,7 @@ |
---|
2422 | self.NEWFILE_CONTENTS) |
---|
2423 | # TODO: we lose the response code, so we can't check this |
---|
2424 | #self.failUnlessEqual(responsecode, 201) |
---|
2425 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2426 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt") |
---|
2427 | d.addCallback(lambda res: |
---|
2428 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
2429 | self.NEWFILE_CONTENTS)) |
---|
2430 | @@ -776,7 +781,7 @@ |
---|
2431 | self.failIf(u.is_readonly()) |
---|
2432 | return res |
---|
2433 | d.addCallback(_check_uri) |
---|
2434 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2435 | + d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt") |
---|
2436 | d.addCallback(lambda res: |
---|
2437 | self.failUnlessMutableChildContentsAre(self._foo_node, |
---|
2438 | u"new.txt", |
---|
2439 | @@ -796,7 +801,7 @@ |
---|
2440 | d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS) |
---|
2441 | # TODO: we lose the response code, so we can't check this |
---|
2442 | #self.failUnlessEqual(responsecode, 200) |
---|
2443 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt") |
---|
2444 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt") |
---|
2445 | d.addCallback(lambda res: |
---|
2446 | self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", |
---|
2447 | self.NEWFILE_CONTENTS)) |
---|
2448 | @@ -821,7 +826,7 @@ |
---|
2449 | def test_PUT_NEWFILEURL_mkdirs(self): |
---|
2450 | d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS) |
---|
2451 | fn = self._foo_node |
---|
2452 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt") |
---|
2453 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt") |
---|
2454 | d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt")) |
---|
2455 | d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir")) |
---|
2456 | d.addCallback(lambda res: |
---|
2457 | @@ -954,7 +959,7 @@ |
---|
2458 | self.failUnless(re.search(get_sub, res), res) |
---|
2459 | d.addCallback(_check) |
---|
2460 | |
---|
2461 | - # look at a directory which is readonly |
---|
2462 | + # look at a readonly directory |
---|
2463 | d.addCallback(lambda res: |
---|
2464 | self.GET(self.public_url + "/reedownlee", followRedirect=True)) |
---|
2465 | def _check2(res): |
---|
2466 | @@ -1167,23 +1172,33 @@ |
---|
2467 | return d |
---|
2468 | |
---|
2469 | def test_POST_NEWDIRURL_initial_children(self): |
---|
2470 | - (newkids, filecap1, filecap2, filecap3, |
---|
2471 | - dircap) = self._create_initial_children() |
---|
2472 | + (newkids, caps) = self._create_initial_children() |
---|
2473 | d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children", |
---|
2474 | simplejson.dumps(newkids)) |
---|
2475 | def _check(uri): |
---|
2476 | n = self.s.create_node_from_uri(uri.strip()) |
---|
2477 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
2478 | d2.addCallback(lambda ign: |
---|
2479 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
2480 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
2481 | + caps['filecap1'])) |
---|
2482 | + d2.addCallback(lambda ign: |
---|
2483 | + self.failUnlessRWChildURIIs(n, u"child-mutable", |
---|
2484 | + caps['filecap2'])) |
---|
2485 | + d2.addCallback(lambda ign: |
---|
2486 | + self.failUnlessROChildURIIs(n, u"child-mutable-ro", |
---|
2487 | + caps['filecap3'])) |
---|
2488 | d2.addCallback(lambda ign: |
---|
2489 | - self.failUnlessChildURIIs(n, u"child-mutable", |
---|
2490 | - filecap2)) |
---|
2491 | + self.failUnlessROChildURIIs(n, u"unknownchild-ro", |
---|
2492 | + caps['unknown_rocap'])) |
---|
2493 | d2.addCallback(lambda ign: |
---|
2494 | - self.failUnlessChildURIIs(n, u"child-mutable-ro", |
---|
2495 | - filecap3)) |
---|
2496 | + self.failUnlessRWChildURIIs(n, u"unknownchild-rw", |
---|
2497 | + caps['unknown_rwcap'])) |
---|
2498 | d2.addCallback(lambda ign: |
---|
2499 | - self.failUnlessChildURIIs(n, u"dirchild", dircap)) |
---|
2500 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
2501 | + caps['unknown_immcap'])) |
---|
2502 | + d2.addCallback(lambda ign: |
---|
2503 | + self.failUnlessRWChildURIIs(n, u"dirchild", |
---|
2504 | + caps['dircap'])) |
---|
2505 | return d2 |
---|
2506 | d.addCallback(_check) |
---|
2507 | d.addCallback(lambda res: |
---|
2508 | @@ -1191,21 +1206,25 @@ |
---|
2509 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2510 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
2511 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2512 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
2513 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
2514 | return d |
---|
2515 | |
---|
2516 | def test_POST_NEWDIRURL_immutable(self): |
---|
2517 | - (newkids, filecap1, immdircap) = self._create_immutable_children() |
---|
2518 | + (newkids, caps) = self._create_immutable_children() |
---|
2519 | d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable", |
---|
2520 | simplejson.dumps(newkids)) |
---|
2521 | def _check(uri): |
---|
2522 | n = self.s.create_node_from_uri(uri.strip()) |
---|
2523 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
2524 | d2.addCallback(lambda ign: |
---|
2525 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
2526 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
2527 | + caps['filecap1'])) |
---|
2528 | + d2.addCallback(lambda ign: |
---|
2529 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
2530 | + caps['unknown_immcap'])) |
---|
2531 | d2.addCallback(lambda ign: |
---|
2532 | - self.failUnlessChildURIIs(n, u"dirchild-imm", |
---|
2533 | - immdircap)) |
---|
2534 | + self.failUnlessROChildURIIs(n, u"dirchild-imm", |
---|
2535 | + caps['immdircap'])) |
---|
2536 | return d2 |
---|
2537 | d.addCallback(_check) |
---|
2538 | d.addCallback(lambda res: |
---|
2539 | @@ -1213,18 +1232,19 @@ |
---|
2540 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2541 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
2542 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2543 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
2544 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
2545 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2546 | - d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap) |
---|
2547 | + d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap']) |
---|
2548 | + d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2549 | + d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap']) |
---|
2550 | d.addErrback(self.explain_web_error) |
---|
2551 | return d |
---|
2552 | |
---|
2553 | def test_POST_NEWDIRURL_immutable_bad(self): |
---|
2554 | - (newkids, filecap1, filecap2, filecap3, |
---|
2555 | - dircap) = self._create_initial_children() |
---|
2556 | + (newkids, caps) = self._create_initial_children() |
---|
2557 | d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad", |
---|
2558 | "400 Bad Request", |
---|
2559 | - "a mkdir-immutable operation was given a child that was not itself immutable", |
---|
2560 | + "needed to be immutable but was not", |
---|
2561 | self.POST2, |
---|
2562 | self.public_url + "/foo/newdir?t=mkdir-immutable", |
---|
2563 | simplejson.dumps(newkids)) |
---|
2564 | @@ -1346,19 +1366,51 @@ |
---|
2565 | d.addCallback(_check) |
---|
2566 | return d |
---|
2567 | |
---|
2568 | - def failUnlessChildURIIs(self, node, name, expected_uri): |
---|
2569 | + def failUnlessRWChildURIIs(self, node, name, expected_uri): |
---|
2570 | assert isinstance(name, unicode) |
---|
2571 | d = node.get_child_at_path(name) |
---|
2572 | def _check(child): |
---|
2573 | + self.failUnless(child.is_unknown() or not child.is_readonly()) |
---|
2574 | self.failUnlessEqual(child.get_uri(), expected_uri.strip()) |
---|
2575 | + self.failUnlessEqual(child.get_write_uri(), expected_uri.strip()) |
---|
2576 | + expected_ro_uri = self._make_readonly(expected_uri) |
---|
2577 | + if expected_ro_uri: |
---|
2578 | + self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip()) |
---|
2579 | + d.addCallback(_check) |
---|
2580 | + return d |
---|
2581 | + |
---|
2582 | + def failUnlessROChildURIIs(self, node, name, expected_uri): |
---|
2583 | + assert isinstance(name, unicode) |
---|
2584 | + d = node.get_child_at_path(name) |
---|
2585 | + def _check(child): |
---|
2586 | + self.failUnless(child.is_unknown() or child.is_readonly()) |
---|
2587 | + self.failUnlessEqual(child.get_write_uri(), None) |
---|
2588 | + self.failUnlessEqual(child.get_uri(), expected_uri.strip()) |
---|
2589 | + self.failUnlessEqual(child.get_readonly_uri(), expected_uri.strip()) |
---|
2590 | + d.addCallback(_check) |
---|
2591 | + return d |
---|
2592 | + |
---|
2593 | + def failUnlessURIMatchesRWChild(self, got_uri, node, name): |
---|
2594 | + assert isinstance(name, unicode) |
---|
2595 | + d = node.get_child_at_path(name) |
---|
2596 | + def _check(child): |
---|
2597 | + self.failUnless(child.is_unknown() or not child.is_readonly()) |
---|
2598 | + self.failUnlessEqual(child.get_uri(), got_uri.strip()) |
---|
2599 | + self.failUnlessEqual(child.get_write_uri(), got_uri.strip()) |
---|
2600 | + expected_ro_uri = self._make_readonly(got_uri) |
---|
2601 | + if expected_ro_uri: |
---|
2602 | + self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip()) |
---|
2603 | d.addCallback(_check) |
---|
2604 | return d |
---|
2605 | |
---|
2606 | - def failUnlessURIMatchesChild(self, got_uri, node, name): |
---|
2607 | + def failUnlessURIMatchesROChild(self, got_uri, node, name): |
---|
2608 | assert isinstance(name, unicode) |
---|
2609 | d = node.get_child_at_path(name) |
---|
2610 | def _check(child): |
---|
2611 | + self.failUnless(child.is_unknown() or child.is_readonly()) |
---|
2612 | + self.failUnlessEqual(child.get_write_uri(), None) |
---|
2613 | self.failUnlessEqual(got_uri.strip(), child.get_uri()) |
---|
2614 | + self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri()) |
---|
2615 | d.addCallback(_check) |
---|
2616 | return d |
---|
2617 | |
---|
2618 | @@ -1369,7 +1421,7 @@ |
---|
2619 | d = self.POST(self.public_url + "/foo", t="upload", |
---|
2620 | file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
2621 | fn = self._foo_node |
---|
2622 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
2623 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt") |
---|
2624 | d.addCallback(lambda res: |
---|
2625 | self.failUnlessChildContentsAre(fn, u"new.txt", |
---|
2626 | self.NEWFILE_CONTENTS)) |
---|
2627 | @@ -1380,7 +1432,7 @@ |
---|
2628 | d = self.POST(self.public_url + "/foo", t="upload", |
---|
2629 | file=(filename, self.NEWFILE_CONTENTS)) |
---|
2630 | fn = self._foo_node |
---|
2631 | - d.addCallback(self.failUnlessURIMatchesChild, fn, filename) |
---|
2632 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, filename) |
---|
2633 | d.addCallback(lambda res: |
---|
2634 | self.failUnlessChildContentsAre(fn, filename, |
---|
2635 | self.NEWFILE_CONTENTS)) |
---|
2636 | @@ -1397,7 +1449,7 @@ |
---|
2637 | name=filename, |
---|
2638 | file=("overridden", self.NEWFILE_CONTENTS)) |
---|
2639 | fn = self._foo_node |
---|
2640 | - d.addCallback(self.failUnlessURIMatchesChild, fn, filename) |
---|
2641 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, filename) |
---|
2642 | d.addCallback(lambda res: |
---|
2643 | self.failUnlessChildContentsAre(fn, filename, |
---|
2644 | self.NEWFILE_CONTENTS)) |
---|
2645 | @@ -1499,7 +1551,7 @@ |
---|
2646 | d = self.POST(self.public_url + "/foo", t="upload", mutable="true", |
---|
2647 | file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
2648 | fn = self._foo_node |
---|
2649 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
2650 | + d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt") |
---|
2651 | d.addCallback(lambda res: |
---|
2652 | self.failUnlessMutableChildContentsAre(fn, u"new.txt", |
---|
2653 | self.NEWFILE_CONTENTS)) |
---|
2654 | @@ -1518,7 +1570,7 @@ |
---|
2655 | self.POST(self.public_url + "/foo", t="upload", |
---|
2656 | mutable="true", |
---|
2657 | file=("new.txt", NEWER_CONTENTS))) |
---|
2658 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
2659 | + d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt") |
---|
2660 | d.addCallback(lambda res: |
---|
2661 | self.failUnlessMutableChildContentsAre(fn, u"new.txt", |
---|
2662 | NEWER_CONTENTS)) |
---|
2663 | @@ -1534,7 +1586,7 @@ |
---|
2664 | NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n" |
---|
2665 | d.addCallback(lambda res: |
---|
2666 | self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS)) |
---|
2667 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
2668 | + d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt") |
---|
2669 | d.addCallback(lambda res: |
---|
2670 | self.failUnlessMutableChildContentsAre(fn, u"new.txt", |
---|
2671 | NEW2_CONTENTS)) |
---|
2672 | @@ -1663,7 +1715,7 @@ |
---|
2673 | d = self.POST(self.public_url + "/foo", t="upload", |
---|
2674 | file=("bar.txt", self.NEWFILE_CONTENTS)) |
---|
2675 | fn = self._foo_node |
---|
2676 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt") |
---|
2677 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt") |
---|
2678 | d.addCallback(lambda res: |
---|
2679 | self.failUnlessChildContentsAre(fn, u"bar.txt", |
---|
2680 | self.NEWFILE_CONTENTS)) |
---|
2681 | @@ -1714,7 +1766,7 @@ |
---|
2682 | fn = self._foo_node |
---|
2683 | d = self.POST(self.public_url + "/foo", t="upload", |
---|
2684 | name="new.txt", file=self.NEWFILE_CONTENTS) |
---|
2685 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
2686 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt") |
---|
2687 | d.addCallback(lambda res: |
---|
2688 | self.failUnlessChildContentsAre(fn, u"new.txt", |
---|
2689 | self.NEWFILE_CONTENTS)) |
---|
2690 | @@ -1977,7 +2029,7 @@ |
---|
2691 | return d |
---|
2692 | |
---|
2693 | def test_POST_mkdir_initial_children(self): |
---|
2694 | - newkids, filecap1, ign, ign, ign = self._create_initial_children() |
---|
2695 | + (newkids, caps) = self._create_initial_children() |
---|
2696 | d = self.POST2(self.public_url + |
---|
2697 | "/foo?t=mkdir-with-children&name=newdir", |
---|
2698 | simplejson.dumps(newkids)) |
---|
2699 | @@ -1986,11 +2038,11 @@ |
---|
2700 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2701 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
2702 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2703 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
2704 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
2705 | return d |
---|
2706 | |
---|
2707 | def test_POST_mkdir_immutable(self): |
---|
2708 | - (newkids, filecap1, immdircap) = self._create_immutable_children() |
---|
2709 | + (newkids, caps) = self._create_immutable_children() |
---|
2710 | d = self.POST2(self.public_url + |
---|
2711 | "/foo?t=mkdir-immutable&name=newdir", |
---|
2712 | simplejson.dumps(newkids)) |
---|
2713 | @@ -1999,17 +2051,18 @@ |
---|
2714 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2715 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
2716 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2717 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
2718 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
2719 | + d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2720 | + d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap']) |
---|
2721 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2722 | - d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap) |
---|
2723 | + d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap']) |
---|
2724 | return d |
---|
2725 | |
---|
2726 | def test_POST_mkdir_immutable_bad(self): |
---|
2727 | - (newkids, filecap1, filecap2, filecap3, |
---|
2728 | - dircap) = self._create_initial_children() |
---|
2729 | + (newkids, caps) = self._create_initial_children() |
---|
2730 | d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad", |
---|
2731 | "400 Bad Request", |
---|
2732 | - "a mkdir-immutable operation was given a child that was not itself immutable", |
---|
2733 | + "needed to be immutable but was not", |
---|
2734 | self.POST2, |
---|
2735 | self.public_url + |
---|
2736 | "/foo?t=mkdir-immutable&name=newdir", |
---|
2737 | @@ -2049,21 +2102,43 @@ |
---|
2738 | d.addCallback(_check_target) |
---|
2739 | return d |
---|
2740 | |
---|
2741 | + def _make_readonly(self, u): |
---|
2742 | + ro_uri = uri.from_string(u).get_readonly() |
---|
2743 | + if ro_uri is None: |
---|
2744 | + return None |
---|
2745 | + return ro_uri.to_string() |
---|
2746 | + |
---|
2747 | def _create_initial_children(self): |
---|
2748 | contents, n, filecap1 = self.makefile(12) |
---|
2749 | md1 = {"metakey1": "metavalue1"} |
---|
2750 | filecap2 = make_mutable_file_uri() |
---|
2751 | node3 = self.s.create_node_from_uri(make_mutable_file_uri()) |
---|
2752 | filecap3 = node3.get_readonly_uri() |
---|
2753 | + unknown_rwcap = "lafs://from_the_future" |
---|
2754 | + unknown_rocap = "ro.lafs://readonly_from_the_future" |
---|
2755 | + unknown_immcap = "imm.lafs://immutable_from_the_future" |
---|
2756 | node4 = self.s.create_node_from_uri(make_mutable_file_uri()) |
---|
2757 | dircap = DirectoryNode(node4, None, None).get_uri() |
---|
2758 | - newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, |
---|
2759 | - "metadata": md1, }], |
---|
2760 | - u"child-mutable": ["filenode", {"rw_uri": filecap2}], |
---|
2761 | + newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1, |
---|
2762 | + "ro_uri": self._make_readonly(filecap1), |
---|
2763 | + "metadata": md1, }], |
---|
2764 | + u"child-mutable": ["filenode", {"rw_uri": filecap2, |
---|
2765 | + "ro_uri": self._make_readonly(filecap2)}], |
---|
2766 | u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}], |
---|
2767 | - u"dirchild": ["dirnode", {"rw_uri": dircap}], |
---|
2768 | + u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap, |
---|
2769 | + "ro_uri": unknown_rocap}], |
---|
2770 | + u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}], |
---|
2771 | + u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}], |
---|
2772 | + u"dirchild": ["dirnode", {"rw_uri": dircap, |
---|
2773 | + "ro_uri": self._make_readonly(dircap)}], |
---|
2774 | } |
---|
2775 | - return newkids, filecap1, filecap2, filecap3, dircap |
---|
2776 | + return newkids, {'filecap1': filecap1, |
---|
2777 | + 'filecap2': filecap2, |
---|
2778 | + 'filecap3': filecap3, |
---|
2779 | + 'unknown_rwcap': unknown_rwcap, |
---|
2780 | + 'unknown_rocap': unknown_rocap, |
---|
2781 | + 'unknown_immcap': unknown_immcap, |
---|
2782 | + 'dircap': dircap} |
---|
2783 | |
---|
2784 | def _create_immutable_children(self): |
---|
2785 | contents, n, filecap1 = self.makefile(12) |
---|
2786 | @@ -2071,31 +2146,45 @@ |
---|
2787 | tnode = create_chk_filenode("immutable directory contents\n"*10) |
---|
2788 | dnode = DirectoryNode(tnode, None, None) |
---|
2789 | assert not dnode.is_mutable() |
---|
2790 | + unknown_immcap = "imm.lafs://immutable_from_the_future" |
---|
2791 | immdircap = dnode.get_uri() |
---|
2792 | - newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, |
---|
2793 | - "metadata": md1, }], |
---|
2794 | - u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}], |
---|
2795 | + newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, |
---|
2796 | + "metadata": md1, }], |
---|
2797 | + u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}], |
---|
2798 | + u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}], |
---|
2799 | } |
---|
2800 | - return newkids, filecap1, immdircap |
---|
2801 | + return newkids, {'filecap1': filecap1, |
---|
2802 | + 'unknown_immcap': unknown_immcap, |
---|
2803 | + 'immdircap': immdircap} |
---|
2804 | |
---|
2805 | def test_POST_mkdir_no_parentdir_initial_children(self): |
---|
2806 | - (newkids, filecap1, filecap2, filecap3, |
---|
2807 | - dircap) = self._create_initial_children() |
---|
2808 | + (newkids, caps) = self._create_initial_children() |
---|
2809 | d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids)) |
---|
2810 | def _after_mkdir(res): |
---|
2811 | self.failUnless(res.startswith("URI:DIR"), res) |
---|
2812 | n = self.s.create_node_from_uri(res) |
---|
2813 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
2814 | d2.addCallback(lambda ign: |
---|
2815 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
2816 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
2817 | + caps['filecap1'])) |
---|
2818 | + d2.addCallback(lambda ign: |
---|
2819 | + self.failUnlessRWChildURIIs(n, u"child-mutable", |
---|
2820 | + caps['filecap2'])) |
---|
2821 | + d2.addCallback(lambda ign: |
---|
2822 | + self.failUnlessROChildURIIs(n, u"child-mutable-ro", |
---|
2823 | + caps['filecap3'])) |
---|
2824 | + d2.addCallback(lambda ign: |
---|
2825 | + self.failUnlessRWChildURIIs(n, u"unknownchild-rw", |
---|
2826 | + caps['unknown_rwcap'])) |
---|
2827 | d2.addCallback(lambda ign: |
---|
2828 | - self.failUnlessChildURIIs(n, u"child-mutable", |
---|
2829 | - filecap2)) |
---|
2830 | + self.failUnlessROChildURIIs(n, u"unknownchild-ro", |
---|
2831 | + caps['unknown_rocap'])) |
---|
2832 | d2.addCallback(lambda ign: |
---|
2833 | - self.failUnlessChildURIIs(n, u"child-mutable-ro", |
---|
2834 | - filecap3)) |
---|
2835 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
2836 | + caps['unknown_immcap'])) |
---|
2837 | d2.addCallback(lambda ign: |
---|
2838 | - self.failUnlessChildURIIs(n, u"dirchild", dircap)) |
---|
2839 | + self.failUnlessRWChildURIIs(n, u"dirchild", |
---|
2840 | + caps['dircap'])) |
---|
2841 | return d2 |
---|
2842 | d.addCallback(_after_mkdir) |
---|
2843 | return d |
---|
2844 | @@ -2103,8 +2192,7 @@ |
---|
2845 | def test_POST_mkdir_no_parentdir_unexpected_children(self): |
---|
2846 | # the regular /uri?t=mkdir operation is specified to ignore its body. |
---|
2847 | # Only t=mkdir-with-children pays attention to it. |
---|
2848 | - (newkids, filecap1, filecap2, filecap3, |
---|
2849 | - dircap) = self._create_initial_children() |
---|
2850 | + (newkids, caps) = self._create_initial_children() |
---|
2851 | d = self.shouldHTTPError("POST t=mkdir unexpected children", |
---|
2852 | 400, "Bad Request", |
---|
2853 | "t=mkdir does not accept children=, " |
---|
2854 | @@ -2121,28 +2209,31 @@ |
---|
2855 | return d |
---|
2856 | |
---|
2857 | def test_POST_mkdir_no_parentdir_immutable(self): |
---|
2858 | - (newkids, filecap1, immdircap) = self._create_immutable_children() |
---|
2859 | + (newkids, caps) = self._create_immutable_children() |
---|
2860 | d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids)) |
---|
2861 | def _after_mkdir(res): |
---|
2862 | self.failUnless(res.startswith("URI:DIR"), res) |
---|
2863 | n = self.s.create_node_from_uri(res) |
---|
2864 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
2865 | d2.addCallback(lambda ign: |
---|
2866 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
2867 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
2868 | + caps['filecap1'])) |
---|
2869 | d2.addCallback(lambda ign: |
---|
2870 | - self.failUnlessChildURIIs(n, u"dirchild-imm", |
---|
2871 | - immdircap)) |
---|
2872 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
2873 | + caps['unknown_immcap'])) |
---|
2874 | + d2.addCallback(lambda ign: |
---|
2875 | + self.failUnlessROChildURIIs(n, u"dirchild-imm", |
---|
2876 | + caps['immdircap'])) |
---|
2877 | return d2 |
---|
2878 | d.addCallback(_after_mkdir) |
---|
2879 | return d |
---|
2880 | |
---|
2881 | def test_POST_mkdir_no_parentdir_immutable_bad(self): |
---|
2882 | - (newkids, filecap1, filecap2, filecap3, |
---|
2883 | - dircap) = self._create_initial_children() |
---|
2884 | + (newkids, caps) = self._create_initial_children() |
---|
2885 | d = self.shouldFail2(error.Error, |
---|
2886 | "test_POST_mkdir_no_parentdir_immutable_bad", |
---|
2887 | "400 Bad Request", |
---|
2888 | - "a mkdir-immutable operation was given a child that was not itself immutable", |
---|
2889 | + "needed to be immutable but was not", |
---|
2890 | self.POST2, |
---|
2891 | "/uri?t=mkdir-immutable", |
---|
2892 | simplejson.dumps(newkids)) |
---|
2893 | @@ -2152,7 +2243,12 @@ |
---|
2894 | # Fetch the welcome page. |
---|
2895 | d = self.GET("/") |
---|
2896 | def _after_get_welcome_page(res): |
---|
2897 | - MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*?<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create a directory" />', re.I) |
---|
2898 | + MKDIR_BUTTON_RE = re.compile( |
---|
2899 | + '<form action="([^"]*)" method="post".*?' |
---|
2900 | + '<input type="hidden" name="t" value="([^"]*)" />' |
---|
2901 | + '<input type="hidden" name="([^"]*)" value="([^"]*)" />' |
---|
2902 | + '<input type="submit" value="Create a directory" />', |
---|
2903 | + re.I) |
---|
2904 | mo = MKDIR_BUTTON_RE.search(res) |
---|
2905 | formaction = mo.group(1) |
---|
2906 | formt = mo.group(2) |
---|
2907 | @@ -2250,9 +2346,9 @@ |
---|
2908 | |
---|
2909 | d = client.getPage(url, method="POST", postdata=reqbody) |
---|
2910 | def _then(res): |
---|
2911 | - self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1") |
---|
2912 | - self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2") |
---|
2913 | - self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3") |
---|
2914 | + self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1") |
---|
2915 | + self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2") |
---|
2916 | + self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3") |
---|
2917 | |
---|
2918 | d.addCallback(_then) |
---|
2919 | d.addErrback(self.dump_error) |
---|
2920 | @@ -2264,7 +2360,7 @@ |
---|
2921 | def test_POST_put_uri(self): |
---|
2922 | contents, n, newuri = self.makefile(8) |
---|
2923 | d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri) |
---|
2924 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2925 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt") |
---|
2926 | d.addCallback(lambda res: |
---|
2927 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
2928 | contents)) |
---|
2929 | @@ -2273,7 +2369,7 @@ |
---|
2930 | def test_POST_put_uri_replace(self): |
---|
2931 | contents, n, newuri = self.makefile(8) |
---|
2932 | d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri) |
---|
2933 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt") |
---|
2934 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt") |
---|
2935 | d.addCallback(lambda res: |
---|
2936 | self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", |
---|
2937 | contents)) |
---|
2938 | @@ -2502,9 +2598,9 @@ |
---|
2939 | d.addCallback(lambda res: |
---|
2940 | self.failUnlessEqual(res.strip(), new_uri)) |
---|
2941 | d.addCallback(lambda res: |
---|
2942 | - self.failUnlessChildURIIs(self.public_root, |
---|
2943 | - u"foo", |
---|
2944 | - new_uri)) |
---|
2945 | + self.failUnlessRWChildURIIs(self.public_root, |
---|
2946 | + u"foo", |
---|
2947 | + new_uri)) |
---|
2948 | return d |
---|
2949 | d.addCallback(_made_dir) |
---|
2950 | return d |
---|
2951 | @@ -2521,9 +2617,9 @@ |
---|
2952 | self.public_url + "/foo?t=uri&replace=false", |
---|
2953 | new_uri) |
---|
2954 | d.addCallback(lambda res: |
---|
2955 | - self.failUnlessChildURIIs(self.public_root, |
---|
2956 | - u"foo", |
---|
2957 | - self._foo_uri)) |
---|
2958 | + self.failUnlessRWChildURIIs(self.public_root, |
---|
2959 | + u"foo", |
---|
2960 | + self._foo_uri)) |
---|
2961 | return d |
---|
2962 | d.addCallback(_made_dir) |
---|
2963 | return d |
---|
2964 | @@ -2533,9 +2629,9 @@ |
---|
2965 | "400 Bad Request", "PUT to a directory", |
---|
2966 | self.PUT, self.public_url + "/foo?t=BOGUS", "") |
---|
2967 | d.addCallback(lambda res: |
---|
2968 | - self.failUnlessChildURIIs(self.public_root, |
---|
2969 | - u"foo", |
---|
2970 | - self._foo_uri)) |
---|
2971 | + self.failUnlessRWChildURIIs(self.public_root, |
---|
2972 | + u"foo", |
---|
2973 | + self._foo_uri)) |
---|
2974 | return d |
---|
2975 | |
---|
2976 | def test_PUT_NEWFILEURL_uri(self): |
---|
2977 | @@ -3062,71 +3158,246 @@ |
---|
2978 | d.addErrback(self.explain_web_error) |
---|
2979 | return d |
---|
2980 | |
---|
2981 | - def test_unknown(self): |
---|
2982 | + def test_unknown(self, immutable=False): |
---|
2983 | self.basedir = "web/Grid/unknown" |
---|
2984 | + if immutable: |
---|
2985 | + self.basedir = "web/Grid/unknown-immutable" |
---|
2986 | + |
---|
2987 | self.set_up_grid() |
---|
2988 | c0 = self.g.clients[0] |
---|
2989 | self.uris = {} |
---|
2990 | self.fileurls = {} |
---|
2991 | |
---|
2992 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
2993 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
2994 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
2995 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
2996 | # the future cap format may contain slashes, which must be tolerated |
---|
2997 | - expected_info_url = "uri/%s?t=info" % urllib.quote(future_writecap, |
---|
2998 | + expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri, |
---|
2999 | safe="") |
---|
3000 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
3001 | |
---|
3002 | - d = c0.create_dirnode() |
---|
3003 | + if immutable: |
---|
3004 | + name = u"future-imm" |
---|
3005 | + future_node = UnknownNode(None, future_read_uri, deep_immutable=True) |
---|
3006 | + d = c0.create_immutable_dirnode({name: (future_node, {})}) |
---|
3007 | + else: |
---|
3008 | + name = u"future" |
---|
3009 | + future_node = UnknownNode(future_write_uri, future_read_uri) |
---|
3010 | + d = c0.create_dirnode() |
---|
3011 | + |
---|
3012 | def _stash_root_and_create_file(n): |
---|
3013 | self.rootnode = n |
---|
3014 | self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/" |
---|
3015 | self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/" |
---|
3016 | - return self.rootnode.set_node(u"future", future_node) |
---|
3017 | + if not immutable: |
---|
3018 | + return self.rootnode.set_node(name, future_node) |
---|
3019 | d.addCallback(_stash_root_and_create_file) |
---|
3020 | + |
---|
3021 | # make sure directory listing tolerates unknown nodes |
---|
3022 | d.addCallback(lambda ign: self.GET(self.rooturl)) |
---|
3023 | - def _check_html(res): |
---|
3024 | - self.failUnlessIn("<td>future</td>", res) |
---|
3025 | - # find the More Info link for "future", should be relative |
---|
3026 | + def _check_directory_html(res): |
---|
3027 | + self.failUnlessIn("<td>%s</td>" % (str(name),), res) |
---|
3028 | + # find the More Info link for name, should be relative |
---|
3029 | mo = re.search(r'<a href="([^"]+)">More Info</a>', res) |
---|
3030 | info_url = mo.group(1) |
---|
3031 | - self.failUnlessEqual(info_url, "future?t=info") |
---|
3032 | + self.failUnlessEqual(info_url, "%s?t=info" % (str(name),)) |
---|
3033 | + d.addCallback(_check_directory_html) |
---|
3034 | |
---|
3035 | - d.addCallback(_check_html) |
---|
3036 | d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json")) |
---|
3037 | - def _check_json(res, expect_writecap): |
---|
3038 | + def _check_directory_json(res, expect_rw_uri): |
---|
3039 | data = simplejson.loads(res) |
---|
3040 | self.failUnlessEqual(data[0], "dirnode") |
---|
3041 | - f = data[1]["children"]["future"] |
---|
3042 | + f = data[1]["children"][name] |
---|
3043 | self.failUnlessEqual(f[0], "unknown") |
---|
3044 | - if expect_writecap: |
---|
3045 | - self.failUnlessEqual(f[1]["rw_uri"], future_writecap) |
---|
3046 | + if expect_rw_uri: |
---|
3047 | + self.failUnlessEqual(f[1]["rw_uri"], future_write_uri) |
---|
3048 | else: |
---|
3049 | self.failIfIn("rw_uri", f[1]) |
---|
3050 | - self.failUnlessEqual(f[1]["ro_uri"], future_readcap) |
---|
3051 | + self.failUnlessEqual(f[1]["ro_uri"], |
---|
3052 | + ("imm." if immutable else "ro.") + future_read_uri) |
---|
3053 | self.failUnless("metadata" in f[1]) |
---|
3054 | - d.addCallback(_check_json, expect_writecap=True) |
---|
3055 | - d.addCallback(lambda ign: self.GET(expected_info_url)) |
---|
3056 | - def _check_info(res, expect_readcap): |
---|
3057 | + d.addCallback(_check_directory_json, expect_rw_uri=not immutable) |
---|
3058 | + |
---|
3059 | + def _check_info(res, expect_rw_uri, expect_ro_uri): |
---|
3060 | self.failUnlessIn("Object Type: <span>unknown</span>", res) |
---|
3061 | - self.failUnlessIn(future_writecap, res) |
---|
3062 | - if expect_readcap: |
---|
3063 | - self.failUnlessIn(future_readcap, res) |
---|
3064 | + if expect_rw_uri: |
---|
3065 | + self.failUnlessIn(future_write_uri, res) |
---|
3066 | + if expect_ro_uri: |
---|
3067 | + self.failUnlessIn(future_read_uri, res) |
---|
3068 | + else: |
---|
3069 | + self.failIfIn(future_read_uri, res) |
---|
3070 | self.failIfIn("Raw data as", res) |
---|
3071 | self.failIfIn("Directory writecap", res) |
---|
3072 | self.failIfIn("Checker Operations", res) |
---|
3073 | self.failIfIn("Mutable File Operations", res) |
---|
3074 | self.failIfIn("Directory Operations", res) |
---|
3075 | - d.addCallback(_check_info, expect_readcap=False) |
---|
3076 | - d.addCallback(lambda ign: self.GET(self.rooturl+"future?t=info")) |
---|
3077 | - d.addCallback(_check_info, expect_readcap=True) |
---|
3078 | + |
---|
3079 | + # FIXME: these should have expect_rw_uri=not immutable; I don't know |
---|
3080 | + # why they fail. Possibly related to ticket #922. |
---|
3081 | + |
---|
3082 | + d.addCallback(lambda ign: self.GET(expected_info_url)) |
---|
3083 | + d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False) |
---|
3084 | + d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name)))) |
---|
3085 | + d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True) |
---|
3086 | + |
---|
3087 | + def _check_json(res, expect_rw_uri): |
---|
3088 | + data = simplejson.loads(res) |
---|
3089 | + self.failUnlessEqual(data[0], "unknown") |
---|
3090 | + if expect_rw_uri: |
---|
3091 | + self.failUnlessEqual(data[1]["rw_uri"], future_write_uri) |
---|
3092 | + else: |
---|
3093 | + self.failIfIn("rw_uri", data[1]) |
---|
3094 | + self.failUnlessEqual(data[1]["ro_uri"], |
---|
3095 | + ("imm." if immutable else "ro.") + future_read_uri) |
---|
3096 | + # TODO: check metadata contents |
---|
3097 | + self.failUnless("metadata" in data[1]) |
---|
3098 | + |
---|
3099 | + d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name)))) |
---|
3100 | + d.addCallback(_check_json, expect_rw_uri=not immutable) |
---|
3101 | |
---|
3102 | # and make sure that a read-only version of the directory can be |
---|
3103 | - # rendered too. This version will not have future_writecap |
---|
3104 | + # rendered too. This version will not have future_write_uri, whether |
---|
3105 | + # or not future_node was immutable. |
---|
3106 | d.addCallback(lambda ign: self.GET(self.rourl)) |
---|
3107 | - d.addCallback(_check_html) |
---|
3108 | + d.addCallback(_check_directory_html) |
---|
3109 | d.addCallback(lambda ign: self.GET(self.rourl+"?t=json")) |
---|
3110 | - d.addCallback(_check_json, expect_writecap=False) |
---|
3111 | + d.addCallback(_check_directory_json, expect_rw_uri=False) |
---|
3112 | + |
---|
3113 | + d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name)))) |
---|
3114 | + d.addCallback(_check_json, expect_rw_uri=False) |
---|
3115 | + |
---|
3116 | + # TODO: check that getting t=info from the Info link in the ro directory |
---|
3117 | + # works, and does not include the writecap URI. |
---|
3118 | + return d |
---|
3119 | + |
---|
3120 | + def test_immutable_unknown(self): |
---|
3121 | + return self.test_unknown(immutable=True) |
---|
3122 | + |
---|
3123 | + def test_mutant_dirnodes_are_omitted(self): |
---|
3124 | + self.basedir = "web/Grid/mutant_dirnodes_are_omitted" |
---|
3125 | + |
---|
3126 | + self.set_up_grid() |
---|
3127 | + c = self.g.clients[0] |
---|
3128 | + nm = c.nodemaker |
---|
3129 | + self.uris = {} |
---|
3130 | + self.fileurls = {} |
---|
3131 | + |
---|
3132 | + lonely_uri = "URI:LIT:n5xgk" # LIT for "one" |
---|
3133 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
3134 | + mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
3135 | + |
---|
3136 | + # This method tests mainly dirnode, but we'd have to duplicate code in order to |
---|
3137 | + # test the dirnode and web layers separately. |
---|
3138 | + |
---|
3139 | + # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap, |
---|
3140 | + # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field. |
---|
3141 | + # When the directory is read, the mutants should be silently disposed of, leaving |
---|
3142 | + # their lonely sibling. |
---|
3143 | + # We don't test the case of a retrieving a cap from the encrypted rw_uri field, |
---|
3144 | + # because immutable directories don't have a writecap and therefore that field |
---|
3145 | + # isn't (and can't be) decrypted. |
---|
3146 | + # TODO: The field still exists in the netstring. Technically we should check what |
---|
3147 | + # happens if something is put there (it should be ignored), but that can wait. |
---|
3148 | + |
---|
3149 | + lonely_child = nm.create_from_cap(lonely_uri) |
---|
3150 | + mutant_ro_child = nm.create_from_cap(mut_read_uri) |
---|
3151 | + mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri) |
---|
3152 | + |
---|
3153 | + def _by_hook_or_by_crook(): |
---|
3154 | + return True |
---|
3155 | + for n in [mutant_ro_child, mutant_write_in_ro_child]: |
---|
3156 | + n.is_allowed_in_immutable_directory = _by_hook_or_by_crook |
---|
3157 | + |
---|
3158 | + mutant_write_in_ro_child.get_write_uri = lambda: None |
---|
3159 | + mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri |
---|
3160 | + |
---|
3161 | + kids = {u"lonely": (lonely_child, {}), |
---|
3162 | + u"ro": (mutant_ro_child, {}), |
---|
3163 | + u"write-in-ro": (mutant_write_in_ro_child, {}), |
---|
3164 | + } |
---|
3165 | + d = c.create_immutable_dirnode(kids) |
---|
3166 | + |
---|
3167 | + def _created(dn): |
---|
3168 | + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
3169 | + self.failIf(dn.is_mutable()) |
---|
3170 | + self.failUnless(dn.is_readonly()) |
---|
3171 | + # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail. |
---|
3172 | + self.failIf(hasattr(dn._node, 'get_writekey')) |
---|
3173 | + rep = str(dn) |
---|
3174 | + self.failUnless("RO-IMM" in rep) |
---|
3175 | + cap = dn.get_cap() |
---|
3176 | + self.failUnlessIn("CHK", cap.to_string()) |
---|
3177 | + self.cap = cap |
---|
3178 | + self.rootnode = dn |
---|
3179 | + self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/" |
---|
3180 | + return download_to_data(dn._node) |
---|
3181 | + d.addCallback(_created) |
---|
3182 | + |
---|
3183 | + def _check_data(data): |
---|
3184 | + # Decode the netstring representation of the directory to check that all children |
---|
3185 | + # are present. This is a bit of an abstraction violation, but there's not really |
---|
3186 | + # any other way to do it given that the real DirectoryNode._unpack_contents would |
---|
3187 | + # strip the mutant children out (which is what we're trying to test, later). |
---|
3188 | + position = 0 |
---|
3189 | + numkids = 0 |
---|
3190 | + while position < len(data): |
---|
3191 | + entries, position = split_netstring(data, 1, position) |
---|
3192 | + entry = entries[0] |
---|
3193 | + (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) |
---|
3194 | + name = name.decode("utf-8") |
---|
3195 | + self.failUnless(rwcapdata == "") |
---|
3196 | + ro_uri = ro_uri.strip() |
---|
3197 | + if name in kids: |
---|
3198 | + self.failIfEqual(ro_uri, "") |
---|
3199 | + (expected_child, ign) = kids[name] |
---|
3200 | + self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri()) |
---|
3201 | + numkids += 1 |
---|
3202 | + |
---|
3203 | + self.failUnlessEqual(numkids, 3) |
---|
3204 | + return self.rootnode.list() |
---|
3205 | + d.addCallback(_check_data) |
---|
3206 | + |
---|
3207 | + # Now when we use the real directory listing code, the mutants should be absent. |
---|
3208 | + def _check_kids(children): |
---|
3209 | + self.failUnlessEqual(sorted(children.keys()), [u"lonely"]) |
---|
3210 | + lonely_node, lonely_metadata = children[u"lonely"] |
---|
3211 | + |
---|
3212 | + self.failUnlessEqual(lonely_node.get_write_uri(), None) |
---|
3213 | + self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri) |
---|
3214 | + d.addCallback(_check_kids) |
---|
3215 | + |
---|
3216 | + d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) |
---|
3217 | + d.addCallback(lambda n: n.list()) |
---|
3218 | + d.addCallback(_check_kids) # again with dirnode recreated from cap |
---|
3219 | + |
---|
3220 | + # Make sure the lonely child can be listed in HTML... |
---|
3221 | + d.addCallback(lambda ign: self.GET(self.rooturl)) |
---|
3222 | + def _check_html(res): |
---|
3223 | + self.failIfIn("URI:SSK", res) |
---|
3224 | + get_lonely = "".join([r'<td>FILE</td>', |
---|
3225 | + r'\s+<td>', |
---|
3226 | + r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),), |
---|
3227 | + r'</td>', |
---|
3228 | + r'\s+<td>%d</td>' % len("one"), |
---|
3229 | + ]) |
---|
3230 | + self.failUnless(re.search(get_lonely, res), res) |
---|
3231 | + |
---|
3232 | + # find the More Info link for name, should be relative |
---|
3233 | + mo = re.search(r'<a href="([^"]+)">More Info</a>', res) |
---|
3234 | + info_url = mo.group(1) |
---|
3235 | + self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url) |
---|
3236 | + d.addCallback(_check_html) |
---|
3237 | + |
---|
3238 | + # ... and in JSON. |
---|
3239 | + d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json")) |
---|
3240 | + def _check_json(res): |
---|
3241 | + data = simplejson.loads(res) |
---|
3242 | + self.failUnlessEqual(data[0], "dirnode") |
---|
3243 | + listed_children = data[1]["children"] |
---|
3244 | + self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"]) |
---|
3245 | + ll_type, ll_data = listed_children[u"lonely"] |
---|
3246 | + self.failUnlessEqual(ll_type, "filenode") |
---|
3247 | + self.failIf("rw_uri" in ll_data) |
---|
3248 | + self.failUnlessEqual(ll_data["ro_uri"], lonely_uri) |
---|
3249 | + d.addCallback(_check_json) |
---|
3250 | return d |
---|
3251 | |
---|
3252 | def test_deep_check(self): |
---|
3253 | @@ -3159,10 +3430,10 @@ |
---|
3254 | |
---|
3255 | # this tests that deep-check and stream-manifest will ignore |
---|
3256 | # UnknownNode instances. Hopefully this will also cover deep-stats. |
---|
3257 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
3258 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
3259 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
3260 | - d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node)) |
---|
3261 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
3262 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
3263 | + future_node = UnknownNode(future_write_uri, future_read_uri) |
---|
3264 | + d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node)) |
---|
3265 | |
---|
3266 | def _clobber_shares(ignored): |
---|
3267 | self.delete_shares_numbered(self.uris["sick"], [0,1]) |
---|
3268 | diff -rN -u old-tahoe/src/allmydata/unknown.py new-tahoe/src/allmydata/unknown.py |
---|
3269 | --- old-tahoe/src/allmydata/unknown.py 2010-01-25 11:54:56.948000000 +0000 |
---|
3270 | +++ new-tahoe/src/allmydata/unknown.py 2010-01-25 11:55:02.278000000 +0000 |
---|
3271 | @@ -1,29 +1,183 @@ |
---|
3272 | + |
---|
3273 | from zope.interface import implements |
---|
3274 | from twisted.internet import defer |
---|
3275 | -from allmydata.interfaces import IFilesystemNode |
---|
3276 | +from allmydata.interfaces import IFilesystemNode, MustNotBeUnknownRWError, \ |
---|
3277 | + MustBeDeepImmutableError |
---|
3278 | +from allmydata import uri |
---|
3279 | +from allmydata.uri import ALLEGED_READONLY_PREFIX, ALLEGED_IMMUTABLE_PREFIX |
---|
3280 | + |
---|
3281 | + |
---|
3282 | +# See ticket #833 for design rationale of UnknownNodes. |
---|
3283 | + |
---|
3284 | +"""Strip prefixes when storing an URI in a ro_uri slot.""" |
---|
3285 | +def strip_prefix_for_ro(ro_uri, deep_immutable): |
---|
3286 | + # It is possible for an alleged-immutable URI to be put into a |
---|
3287 | + # mutable directory. In that case the ALLEGED_IMMUTABLE_PREFIX |
---|
3288 | + # should not be stripped. In other cases, the prefix can safely |
---|
3289 | + # be stripped because it is implied by the context. |
---|
3290 | + |
---|
3291 | + if ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
3292 | + if not deep_immutable: |
---|
3293 | + return ro_uri |
---|
3294 | + return ro_uri[len(ALLEGED_IMMUTABLE_PREFIX):] |
---|
3295 | + elif ro_uri.startswith(ALLEGED_READONLY_PREFIX): |
---|
3296 | + return ro_uri[len(ALLEGED_READONLY_PREFIX):] |
---|
3297 | + else: |
---|
3298 | + return ro_uri |
---|
3299 | |
---|
3300 | class UnknownNode: |
---|
3301 | implements(IFilesystemNode) |
---|
3302 | - def __init__(self, writecap, readcap): |
---|
3303 | - assert writecap is None or isinstance(writecap, str) |
---|
3304 | - self.writecap = writecap |
---|
3305 | - assert readcap is None or isinstance(readcap, str) |
---|
3306 | - self.readcap = readcap |
---|
3307 | + |
---|
3308 | + def __init__(self, given_rw_uri, given_ro_uri, deep_immutable=False, |
---|
3309 | + name=u"<unknown name>"): |
---|
3310 | + #import traceback |
---|
3311 | + #traceback.print_stack() |
---|
3312 | + #print '%r.__init__(%r, %r, deep_immutable=%r, name=%r)' % (self, given_rw_uri, given_ro_uri, deep_immutable, name) |
---|
3313 | + assert given_rw_uri is None or isinstance(given_rw_uri, str) |
---|
3314 | + assert given_ro_uri is None or isinstance(given_ro_uri, str) |
---|
3315 | + |
---|
3316 | + # We don't raise errors when creating an UnknownNode; we instead create an |
---|
3317 | + # opaque node (with rw_uri and ro_uri both None) that records the error. |
---|
3318 | + # This avoids breaking operations that never store the opaque node. |
---|
3319 | + # Note that this means that if a stored dirnode has only a rw_uri, it |
---|
3320 | + # might be dropped. Any future "write-only" cap formats should have a dummy |
---|
3321 | + # unusable readcap to stop that from happening. |
---|
3322 | + |
---|
3323 | + self.error = None |
---|
3324 | + self.rw_uri = self.ro_uri = None |
---|
3325 | + if given_rw_uri is not None: |
---|
3326 | + if deep_immutable: |
---|
3327 | + if given_rw_uri.startswith(ALLEGED_IMMUTABLE_PREFIX) and given_ro_uri is None: |
---|
3328 | + # We needed an immutable cap, and were given one. It was given in the |
---|
3329 | + # rw_uri slot, but that's fine; we'll move it to ro_uri below. |
---|
3330 | + pass |
---|
3331 | + elif given_ro_uri is None: |
---|
3332 | + self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as immutable child", |
---|
3333 | + name, True) |
---|
3334 | + return # node will be opaque |
---|
3335 | + else: |
---|
3336 | + # We could report either error, but this probably makes more sense. |
---|
3337 | + self.error = MustBeDeepImmutableError("cannot attach unknown rw cap as immutable child", |
---|
3338 | + name) |
---|
3339 | + return # node will be opaque |
---|
3340 | + |
---|
3341 | + if given_ro_uri is None: |
---|
3342 | + # We were given a single cap argument, or a rw_uri with no ro_uri. |
---|
3343 | + |
---|
3344 | + if not (given_rw_uri.startswith(ALLEGED_READONLY_PREFIX) |
---|
3345 | + or given_rw_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)): |
---|
3346 | + # If the single cap is unprefixed, then we cannot tell whether it is a |
---|
3347 | + # writecap, and we don't know how to diminish it to a readcap if it is one. |
---|
3348 | + # If it didn't *already* have at least an ALLEGED_READONLY_PREFIX, then |
---|
3349 | + # prefixing it would be a bad idea because we have been given no reason |
---|
3350 | + # to believe that it is a readcap, so we might be letting a client |
---|
3351 | + # inadvertently grant excess write authority. |
---|
3352 | + self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as child", |
---|
3353 | + name, False) |
---|
3354 | + return # node will be opaque |
---|
3355 | + |
---|
3356 | + # OTOH, if the single cap already had a prefix (which is of the required |
---|
3357 | + # strength otherwise an error would have been thrown above), then treat it |
---|
3358 | + # as though it had been given in the ro_uri slot. This has a similar effect |
---|
3359 | + # to the use for known caps of 'bigcap = writecap or readcap' in |
---|
3360 | + # nodemaker.py: create_from_cap. It enables copying of unknown readcaps to |
---|
3361 | + # work in as many cases as we can securely allow. |
---|
3362 | + given_ro_uri = given_rw_uri |
---|
3363 | + given_rw_uri = None |
---|
3364 | + elif given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
3365 | + # Strange corner case: we were given a cap in both slots, with the ro_uri |
---|
3366 | + # alleged to be immutable. A real immutable object wouldn't have a writecap. |
---|
3367 | + self.error = MustBeDeepImmutableError("cannot accept a child entry that specifies " |
---|
3368 | + "both rw_uri, and ro_uri with an imm. prefix", |
---|
3369 | + name) |
---|
3370 | + return # node will be opaque |
---|
3371 | + |
---|
3372 | + # If the ro_uri definitely fails the constraint, it should be treated as opaque and |
---|
3373 | + # the error recorded. |
---|
3374 | + if given_ro_uri is not None: |
---|
3375 | + read_cap = uri.from_string(given_ro_uri, deep_immutable=deep_immutable, name=name) |
---|
3376 | + if isinstance(read_cap, uri.UnknownURI): |
---|
3377 | + self.error = read_cap.get_error() |
---|
3378 | + if self.error: |
---|
3379 | + assert self.rw_uri is None and self.ro_uri is None |
---|
3380 | + return |
---|
3381 | + |
---|
3382 | + if deep_immutable: |
---|
3383 | + assert self.rw_uri is None |
---|
3384 | + # strengthen the constraint on ro_uri to ALLEGED_IMMUTABLE_PREFIX |
---|
3385 | + if given_ro_uri is not None: |
---|
3386 | + if given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
3387 | + self.ro_uri = given_ro_uri |
---|
3388 | + elif given_ro_uri.startswith(ALLEGED_READONLY_PREFIX): |
---|
3389 | + self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + given_ro_uri[len(ALLEGED_READONLY_PREFIX):] |
---|
3390 | + else: |
---|
3391 | + self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + given_ro_uri |
---|
3392 | + else: |
---|
3393 | + # not immutable, so a writecap is allowed |
---|
3394 | + self.rw_uri = given_rw_uri |
---|
3395 | + # strengthen the constraint on ro_uri to ALLEGED_READONLY_PREFIX |
---|
3396 | + if given_ro_uri is not None: |
---|
3397 | + if (given_ro_uri.startswith(ALLEGED_READONLY_PREFIX) or |
---|
3398 | + given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)): |
---|
3399 | + self.ro_uri = given_ro_uri |
---|
3400 | + else: |
---|
3401 | + self.ro_uri = ALLEGED_READONLY_PREFIX + given_ro_uri |
---|
3402 | + |
---|
3403 | + #print 'self.(error, rw_uri, ro_uri) = (%r, %r, %r)' % (self.error, self.rw_uri, self.ro_uri) |
---|
3404 | + |
---|
3405 | + def get_cap(self): |
---|
3406 | + return uri.UnknownURI(self.rw_uri or self.ro_uri) |
---|
3407 | + |
---|
3408 | + def get_readcap(self): |
---|
3409 | + return uri.UnknownURI(self.ro_uri) |
---|
3410 | + |
---|
3411 | + def is_readonly(self): |
---|
3412 | + raise AssertionError("an UnknownNode might be either read-only or " |
---|
3413 | + "read/write, so we shouldn't be calling is_readonly") |
---|
3414 | + |
---|
3415 | + def is_mutable(self): |
---|
3416 | + raise AssertionError("an UnknownNode might be either mutable or immutable, " |
---|
3417 | + "so we shouldn't be calling is_mutable") |
---|
3418 | + |
---|
3419 | + def is_unknown(self): |
---|
3420 | + return True |
---|
3421 | + |
---|
3422 | + def is_allowed_in_immutable_directory(self): |
---|
3423 | + # An UnknownNode consisting only of a ro_uri is allowed in an |
---|
3424 | + # immutable directory, even though we do not know that it is |
---|
3425 | + # immutable (or even read-only), provided that no error was detected. |
---|
3426 | + return not self.error and not self.rw_uri |
---|
3427 | + |
---|
3428 | + def raise_error(self): |
---|
3429 | + if self.error is not None: |
---|
3430 | + raise self.error |
---|
3431 | + |
---|
3432 | def get_uri(self): |
---|
3433 | - return self.writecap |
---|
3434 | + return self.rw_uri or self.ro_uri |
---|
3435 | + |
---|
3436 | + def get_write_uri(self): |
---|
3437 | + return self.rw_uri |
---|
3438 | + |
---|
3439 | def get_readonly_uri(self): |
---|
3440 | - return self.readcap |
---|
3441 | + return self.ro_uri |
---|
3442 | + |
---|
3443 | def get_storage_index(self): |
---|
3444 | return None |
---|
3445 | + |
---|
3446 | def get_verify_cap(self): |
---|
3447 | return None |
---|
3448 | + |
---|
3449 | def get_repair_cap(self): |
---|
3450 | return None |
---|
3451 | + |
---|
3452 | def get_size(self): |
---|
3453 | return None |
---|
3454 | + |
---|
3455 | def get_current_size(self): |
---|
3456 | return defer.succeed(None) |
---|
3457 | + |
---|
3458 | def check(self, monitor, verify, add_lease): |
---|
3459 | return defer.succeed(None) |
---|
3460 | + |
---|
3461 | def check_and_repair(self, monitor, verify, add_lease): |
---|
3462 | return defer.succeed(None) |
---|
3463 | diff -rN -u old-tahoe/src/allmydata/uri.py new-tahoe/src/allmydata/uri.py |
---|
3464 | --- old-tahoe/src/allmydata/uri.py 2010-01-25 11:54:56.956000000 +0000 |
---|
3465 | +++ new-tahoe/src/allmydata/uri.py 2010-01-25 11:55:02.284000000 +0000 |
---|
3466 | @@ -5,14 +5,16 @@ |
---|
3467 | from allmydata.storage.server import si_a2b, si_b2a |
---|
3468 | from allmydata.util import base32, hashutil |
---|
3469 | from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \ |
---|
3470 | - IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI |
---|
3471 | + IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI, \ |
---|
3472 | + MustBeDeepImmutableError, MustBeReadonlyError, CapConstraintError |
---|
3473 | |
---|
3474 | -class BadURIError(Exception): |
---|
3475 | +class BadURIError(CapConstraintError): |
---|
3476 | pass |
---|
3477 | |
---|
3478 | -# the URI shall be an ascii representation of the file. It shall contain |
---|
3479 | -# enough information to retrieve and validate the contents. It shall be |
---|
3480 | -# expressed in a limited character set (namely [TODO]). |
---|
3481 | +# The URI shall be an ASCII representation of a reference to the file/directory. |
---|
3482 | +# It shall contain enough information to retrieve and validate the contents. |
---|
3483 | +# It shall be expressed in a limited character set (currently base32 plus ':' and |
---|
3484 | +# capital letters, but future URIs might use a larger charset). |
---|
3485 | |
---|
3486 | BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits) |
---|
3487 | BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits) |
---|
3488 | @@ -39,6 +41,10 @@ |
---|
3489 | return self.to_string() != them.to_string() |
---|
3490 | else: |
---|
3491 | return True |
---|
3492 | + |
---|
3493 | + def is_unknown(self): |
---|
3494 | + return False |
---|
3495 | + |
---|
3496 | def to_human_encoding(self): |
---|
3497 | return 'http://127.0.0.1:3456/uri/'+self.to_string() |
---|
3498 | |
---|
3499 | @@ -97,8 +103,10 @@ |
---|
3500 | |
---|
3501 | def is_readonly(self): |
---|
3502 | return True |
---|
3503 | + |
---|
3504 | def is_mutable(self): |
---|
3505 | return False |
---|
3506 | + |
---|
3507 | def get_readonly(self): |
---|
3508 | return self |
---|
3509 | |
---|
3510 | @@ -157,6 +165,18 @@ |
---|
3511 | self.total_shares, |
---|
3512 | self.size)) |
---|
3513 | |
---|
3514 | + def is_readonly(self): |
---|
3515 | + return True |
---|
3516 | + |
---|
3517 | + def is_mutable(self): |
---|
3518 | + return False |
---|
3519 | + |
---|
3520 | + def get_readonly(self): |
---|
3521 | + return self |
---|
3522 | + |
---|
3523 | + def get_verify_cap(self): |
---|
3524 | + return self |
---|
3525 | + |
---|
3526 | |
---|
3527 | class LiteralFileURI(_BaseURI): |
---|
3528 | implements(IURI, IImmutableFileURI) |
---|
3529 | @@ -297,10 +317,13 @@ |
---|
3530 | |
---|
3531 | def is_readonly(self): |
---|
3532 | return True |
---|
3533 | + |
---|
3534 | def is_mutable(self): |
---|
3535 | return True |
---|
3536 | + |
---|
3537 | def get_readonly(self): |
---|
3538 | return self |
---|
3539 | + |
---|
3540 | def get_verify_cap(self): |
---|
3541 | return SSKVerifierURI(self.storage_index, self.fingerprint) |
---|
3542 | |
---|
3543 | @@ -334,6 +357,15 @@ |
---|
3544 | return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index), |
---|
3545 | base32.b2a(self.fingerprint)) |
---|
3546 | |
---|
3547 | + def is_readonly(self): |
---|
3548 | + return True |
---|
3549 | + def is_mutable(self): |
---|
3550 | + return False |
---|
3551 | + def get_readonly(self): |
---|
3552 | + return self |
---|
3553 | + def get_verify_cap(self): |
---|
3554 | + return self |
---|
3555 | + |
---|
3556 | class _DirectoryBaseURI(_BaseURI): |
---|
3557 | implements(IURI, IDirnodeURI) |
---|
3558 | def __init__(self, filenode_uri=None): |
---|
3559 | @@ -376,12 +408,12 @@ |
---|
3560 | def abbrev_si(self): |
---|
3561 | return base32.b2a(self._filenode_uri.storage_index)[:5] |
---|
3562 | |
---|
3563 | - def get_filenode_cap(self): |
---|
3564 | - return self._filenode_uri |
---|
3565 | - |
---|
3566 | def is_mutable(self): |
---|
3567 | return True |
---|
3568 | |
---|
3569 | + def get_filenode_cap(self): |
---|
3570 | + return self._filenode_uri |
---|
3571 | + |
---|
3572 | def get_verify_cap(self): |
---|
3573 | return DirectoryURIVerifier(self._filenode_uri.get_verify_cap()) |
---|
3574 | |
---|
3575 | @@ -432,12 +464,12 @@ |
---|
3576 | assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri |
---|
3577 | _DirectoryBaseURI.__init__(self, filenode_uri) |
---|
3578 | |
---|
3579 | - def is_mutable(self): |
---|
3580 | - return False |
---|
3581 | - |
---|
3582 | def is_readonly(self): |
---|
3583 | return True |
---|
3584 | |
---|
3585 | + def is_mutable(self): |
---|
3586 | + return False |
---|
3587 | + |
---|
3588 | def get_readonly(self): |
---|
3589 | return self |
---|
3590 | |
---|
3591 | @@ -460,6 +492,7 @@ |
---|
3592 | # LIT caps have no verifier, since they aren't distributed |
---|
3593 | return None |
---|
3594 | |
---|
3595 | + |
---|
3596 | def wrap_dirnode_cap(filecap): |
---|
3597 | if isinstance(filecap, WriteableSSKFileURI): |
---|
3598 | return DirectoryURI(filecap) |
---|
3599 | @@ -469,7 +502,8 @@ |
---|
3600 | return ImmutableDirectoryURI(filecap) |
---|
3601 | if isinstance(filecap, LiteralFileURI): |
---|
3602 | return LiteralDirectoryURI(filecap) |
---|
3603 | - assert False, "cannot wrap a dirnode around %s" % filecap.__class__ |
---|
3604 | + assert False, "cannot interpret as a directory cap: %s" % filecap.__class__ |
---|
3605 | + |
---|
3606 | |
---|
3607 | class DirectoryURIVerifier(_DirectoryBaseURI): |
---|
3608 | implements(IVerifierURI) |
---|
3609 | @@ -487,6 +521,10 @@ |
---|
3610 | def get_filenode_cap(self): |
---|
3611 | return self._filenode_uri |
---|
3612 | |
---|
3613 | + def is_mutable(self): |
---|
3614 | + return False |
---|
3615 | + |
---|
3616 | + |
---|
3617 | class ImmutableDirectoryURIVerifier(DirectoryURIVerifier): |
---|
3618 | implements(IVerifierURI) |
---|
3619 | BASE_STRING='URI:DIR2-CHK-Verifier:' |
---|
3620 | @@ -494,68 +532,141 @@ |
---|
3621 | BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP) |
---|
3622 | INNER_URI_CLASS=CHKFileVerifierURI |
---|
3623 | |
---|
3624 | + |
---|
3625 | class UnknownURI: |
---|
3626 | - def __init__(self, uri): |
---|
3627 | + def __init__(self, uri, error=None): |
---|
3628 | self._uri = uri |
---|
3629 | + self._error = error |
---|
3630 | + |
---|
3631 | def to_string(self): |
---|
3632 | return self._uri |
---|
3633 | |
---|
3634 | -def from_string(s): |
---|
3635 | - if not isinstance(s, str): |
---|
3636 | - raise TypeError("unknown URI type: %s.." % str(s)[:100]) |
---|
3637 | - elif s.startswith('URI:CHK:'): |
---|
3638 | - return CHKFileURI.init_from_string(s) |
---|
3639 | - elif s.startswith('URI:CHK-Verifier:'): |
---|
3640 | - return CHKFileVerifierURI.init_from_string(s) |
---|
3641 | - elif s.startswith('URI:LIT:'): |
---|
3642 | - return LiteralFileURI.init_from_string(s) |
---|
3643 | - elif s.startswith('URI:SSK:'): |
---|
3644 | - return WriteableSSKFileURI.init_from_string(s) |
---|
3645 | - elif s.startswith('URI:SSK-RO:'): |
---|
3646 | - return ReadonlySSKFileURI.init_from_string(s) |
---|
3647 | - elif s.startswith('URI:SSK-Verifier:'): |
---|
3648 | - return SSKVerifierURI.init_from_string(s) |
---|
3649 | - elif s.startswith('URI:DIR2:'): |
---|
3650 | - return DirectoryURI.init_from_string(s) |
---|
3651 | - elif s.startswith('URI:DIR2-RO:'): |
---|
3652 | - return ReadonlyDirectoryURI.init_from_string(s) |
---|
3653 | - elif s.startswith('URI:DIR2-Verifier:'): |
---|
3654 | - return DirectoryURIVerifier.init_from_string(s) |
---|
3655 | - elif s.startswith('URI:DIR2-CHK:'): |
---|
3656 | - return ImmutableDirectoryURI.init_from_string(s) |
---|
3657 | - elif s.startswith('URI:DIR2-LIT:'): |
---|
3658 | - return LiteralDirectoryURI.init_from_string(s) |
---|
3659 | - return UnknownURI(s) |
---|
3660 | + def get_readonly(self): |
---|
3661 | + return None |
---|
3662 | + |
---|
3663 | + def get_error(self): |
---|
3664 | + return self._error |
---|
3665 | + |
---|
3666 | + |
---|
3667 | +ALLEGED_READONLY_PREFIX = 'ro.' |
---|
3668 | +ALLEGED_IMMUTABLE_PREFIX = 'imm.' |
---|
3669 | + |
---|
3670 | +def from_string(u, deep_immutable=False, name=u"<unknown name>"): |
---|
3671 | + if not isinstance(u, str): |
---|
3672 | + raise TypeError("unknown URI type: %s.." % str(u)[:100]) |
---|
3673 | + |
---|
3674 | + # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX |
---|
3675 | + # on all URIs, even though we would only strictly need to do so for caps of |
---|
3676 | + # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their |
---|
3677 | + # prefix are treated as unknown. This should be revisited when we add the |
---|
3678 | + # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>. |
---|
3679 | + s = u |
---|
3680 | + can_be_mutable = can_be_writeable = not deep_immutable |
---|
3681 | + if s.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
3682 | + can_be_mutable = can_be_writeable = False |
---|
3683 | + s = s[len(ALLEGED_IMMUTABLE_PREFIX):] |
---|
3684 | + elif s.startswith(ALLEGED_READONLY_PREFIX): |
---|
3685 | + can_be_writeable = False |
---|
3686 | + s = s[len(ALLEGED_READONLY_PREFIX):] |
---|
3687 | + |
---|
3688 | + #print "can_be_mutable = %r, can_be_writeable = %r, s = %r" % (can_be_mutable, can_be_writeable, s) |
---|
3689 | + error = None |
---|
3690 | + kind = "cap" |
---|
3691 | + try: |
---|
3692 | + if s.startswith('URI:CHK:'): |
---|
3693 | + return CHKFileURI.init_from_string(s) |
---|
3694 | + elif s.startswith('URI:CHK-Verifier:'): |
---|
3695 | + return CHKFileVerifierURI.init_from_string(s) |
---|
3696 | + elif s.startswith('URI:LIT:'): |
---|
3697 | + return LiteralFileURI.init_from_string(s) |
---|
3698 | + elif s.startswith('URI:SSK:'): |
---|
3699 | + if can_be_writeable: |
---|
3700 | + return WriteableSSKFileURI.init_from_string(s) |
---|
3701 | + kind = "URI:SSK file writecap" |
---|
3702 | + elif s.startswith('URI:SSK-RO:'): |
---|
3703 | + if can_be_mutable: |
---|
3704 | + return ReadonlySSKFileURI.init_from_string(s) |
---|
3705 | + kind = "URI:SSK-RO readcap to a mutable file" |
---|
3706 | + elif s.startswith('URI:SSK-Verifier:'): |
---|
3707 | + return SSKVerifierURI.init_from_string(s) |
---|
3708 | + elif s.startswith('URI:DIR2:'): |
---|
3709 | + if can_be_writeable: |
---|
3710 | + return DirectoryURI.init_from_string(s) |
---|
3711 | + kind = "URI:DIR2 directory writecap" |
---|
3712 | + elif s.startswith('URI:DIR2-RO:'): |
---|
3713 | + if can_be_mutable: |
---|
3714 | + return ReadonlyDirectoryURI.init_from_string(s) |
---|
3715 | + kind = "URI:DIR2-RO readcap to a mutable directory" |
---|
3716 | + elif s.startswith('URI:DIR2-Verifier:'): |
---|
3717 | + return DirectoryURIVerifier.init_from_string(s) |
---|
3718 | + elif s.startswith('URI:DIR2-CHK:'): |
---|
3719 | + return ImmutableDirectoryURI.init_from_string(s) |
---|
3720 | + elif s.startswith('URI:DIR2-LIT:'): |
---|
3721 | + return LiteralDirectoryURI.init_from_string(s) |
---|
3722 | + elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable: |
---|
3723 | + # For testing how future writeable caps would behave in read-only contexts. |
---|
3724 | + kind = "x-tahoe-future-test-writeable: testing cap" |
---|
3725 | + elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable: |
---|
3726 | + # For testing how future mutable readcaps would behave in immutable contexts. |
---|
3727 | + kind = "x-tahoe-future-test-mutable: testing cap" |
---|
3728 | + else: |
---|
3729 | + return UnknownURI(u) |
---|
3730 | + |
---|
3731 | + # we fell through because a constraint was not met |
---|
3732 | + if not can_be_mutable: |
---|
3733 | + error = MustBeDeepImmutableError(kind + " used in an immutable context", name) |
---|
3734 | + else: |
---|
3735 | + error = MustBeReadonlyError(kind + " used in a read-only context", name) |
---|
3736 | + |
---|
3737 | + except BadURIError, e: |
---|
3738 | + error = e |
---|
3739 | + |
---|
3740 | + #if error: print error |
---|
3741 | + return UnknownURI(u, error=error) |
---|
3742 | |
---|
3743 | def is_uri(s): |
---|
3744 | try: |
---|
3745 | - from_string(s) |
---|
3746 | + from_string(s, deep_immutable=False) |
---|
3747 | return True |
---|
3748 | except (TypeError, AssertionError): |
---|
3749 | return False |
---|
3750 | |
---|
3751 | -def from_string_dirnode(s): |
---|
3752 | - u = from_string(s) |
---|
3753 | +def is_literal_file_uri(s): |
---|
3754 | + if not isinstance(s, str): |
---|
3755 | + return False |
---|
3756 | + return (s.startswith('URI:LIT:') or |
---|
3757 | + s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or |
---|
3758 | + s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:')) |
---|
3759 | + |
---|
3760 | +def has_uri_prefix(s): |
---|
3761 | + if not isinstance(s, str): |
---|
3762 | + return False |
---|
3763 | + return (s.startswith("URI:") or |
---|
3764 | + s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or |
---|
3765 | + s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:')) |
---|
3766 | + |
---|
3767 | +def from_string_dirnode(s, **kwargs): |
---|
3768 | + u = from_string(s, **kwargs) |
---|
3769 | assert IDirnodeURI.providedBy(u) |
---|
3770 | return u |
---|
3771 | |
---|
3772 | registerAdapter(from_string_dirnode, str, IDirnodeURI) |
---|
3773 | |
---|
3774 | -def from_string_filenode(s): |
---|
3775 | - u = from_string(s) |
---|
3776 | +def from_string_filenode(s, **kwargs): |
---|
3777 | + u = from_string(s, **kwargs) |
---|
3778 | assert IFileURI.providedBy(u) |
---|
3779 | return u |
---|
3780 | |
---|
3781 | registerAdapter(from_string_filenode, str, IFileURI) |
---|
3782 | |
---|
3783 | -def from_string_mutable_filenode(s): |
---|
3784 | - u = from_string(s) |
---|
3785 | +def from_string_mutable_filenode(s, **kwargs): |
---|
3786 | + u = from_string(s, **kwargs) |
---|
3787 | assert IMutableFileURI.providedBy(u) |
---|
3788 | return u |
---|
3789 | registerAdapter(from_string_mutable_filenode, str, IMutableFileURI) |
---|
3790 | |
---|
3791 | -def from_string_verifier(s): |
---|
3792 | - u = from_string(s) |
---|
3793 | +def from_string_verifier(s, **kwargs): |
---|
3794 | + u = from_string(s, **kwargs) |
---|
3795 | assert IVerifierURI.providedBy(u) |
---|
3796 | return u |
---|
3797 | registerAdapter(from_string_verifier, str, IVerifierURI) |
---|
3798 | diff -rN -u old-tahoe/src/allmydata/web/common.py new-tahoe/src/allmydata/web/common.py |
---|
3799 | --- old-tahoe/src/allmydata/web/common.py 2010-01-25 11:54:57.339000000 +0000 |
---|
3800 | +++ new-tahoe/src/allmydata/web/common.py 2010-01-25 11:55:02.584000000 +0000 |
---|
3801 | @@ -8,7 +8,8 @@ |
---|
3802 | from nevow.util import resource_filename |
---|
3803 | from allmydata.interfaces import ExistingChildError, NoSuchChildError, \ |
---|
3804 | FileTooLargeError, NotEnoughSharesError, NoSharesError, \ |
---|
3805 | - NotDeepImmutableError, EmptyPathnameComponentError |
---|
3806 | + EmptyPathnameComponentError, MustBeDeepImmutableError, \ |
---|
3807 | + MustBeReadonlyError, MustNotBeUnknownRWError |
---|
3808 | from allmydata.mutable.common import UnrecoverableFileError |
---|
3809 | from allmydata.util import abbreviate # TODO: consolidate |
---|
3810 | |
---|
3811 | @@ -181,9 +182,42 @@ |
---|
3812 | "failure, or disk corruption. You should perform a filecheck on " |
---|
3813 | "this object to learn more.") |
---|
3814 | return (t, http.GONE) |
---|
3815 | - if f.check(NotDeepImmutableError): |
---|
3816 | - t = ("NotDeepImmutableError: a mkdir-immutable operation was given " |
---|
3817 | - "a child that was not itself immutable: %s" % (f.value,)) |
---|
3818 | + if f.check(MustNotBeUnknownRWError): |
---|
3819 | + name = f.value.args[1] |
---|
3820 | + immutable = f.value.args[2] |
---|
3821 | + if immutable: |
---|
3822 | + t = ("MustNotBeUnknownRWError: an operation to add a child named " |
---|
3823 | + "'%s' to a directory was given an unknown cap in a write slot.\n" |
---|
3824 | + "If the cap is actually an immutable readcap, then using a " |
---|
3825 | + "webapi server that supports a later version of Tahoe may help.\n\n" |
---|
3826 | + "If you are using the webapi directly, then specifying an immutable " |
---|
3827 | + "readcap in the read slot (ro_uri) of the JSON PROPDICT, and " |
---|
3828 | + "omitting the write slot (rw_uri), would also work in this " |
---|
3829 | + "case.") % name.encode("utf-8") |
---|
3830 | + else: |
---|
3831 | + t = ("MustNotBeUnknownRWError: an operation to add a child named " |
---|
3832 | + "'%s' to a directory was given an unknown cap in a write slot.\n" |
---|
3833 | + "Using a webapi server that supports a later version of Tahoe " |
---|
3834 | + "may help.\n\n" |
---|
3835 | + "If you are using the webapi directly, specifying a readcap in " |
---|
3836 | + "the read slot (ro_uri) of the JSON PROPDICT, as well as a " |
---|
3837 | + "writecap in the write slot if desired, would also work in this " |
---|
3838 | + "case.") % name.encode("utf-8") |
---|
3839 | + return (t, http.BAD_REQUEST) |
---|
3840 | + if f.check(MustBeDeepImmutableError): |
---|
3841 | + name = f.value.args[1] |
---|
3842 | + t = ("MustBeDeepImmutableError: a cap passed to this operation for " |
---|
3843 | + "the child named '%s', needed to be immutable but was not. Either " |
---|
3844 | + "the cap is being added to an immutable directory, or it was " |
---|
3845 | + "originally retrieved from an immutable directory as an unknown " |
---|
3846 | + "cap." % name.encode("utf-8")) |
---|
3847 | + return (t, http.BAD_REQUEST) |
---|
3848 | + if f.check(MustBeReadonlyError): |
---|
3849 | + name = f.value.args[1] |
---|
3850 | + t = ("MustBeReadonlyError: a cap passed to this operation for " |
---|
3851 | + "the child named '%s', needed to be read-only but was not. " |
---|
3852 | + "The cap is being passed in a read slot (ro_uri), or was retrieved " |
---|
3853 | + "from a read slot as an unknown cap." % name.encode("utf-8")) |
---|
3854 | return (t, http.BAD_REQUEST) |
---|
3855 | if f.check(WebError): |
---|
3856 | return (f.value.text, f.value.code) |
---|
3857 | diff -rN -u old-tahoe/src/allmydata/web/directory.py new-tahoe/src/allmydata/web/directory.py |
---|
3858 | --- old-tahoe/src/allmydata/web/directory.py 2010-01-25 11:54:57.375000000 +0000 |
---|
3859 | +++ new-tahoe/src/allmydata/web/directory.py 2010-01-25 11:55:02.619000000 +0000 |
---|
3860 | @@ -352,7 +352,12 @@ |
---|
3861 | charset = get_arg(req, "_charset", "utf-8") |
---|
3862 | name = name.decode(charset) |
---|
3863 | replace = boolean_of_arg(get_arg(req, "replace", "true")) |
---|
3864 | - d = self.node.set_uri(name, childcap, childcap, overwrite=replace) |
---|
3865 | + |
---|
3866 | + # We mustn't pass childcap for the readcap argument because we don't |
---|
3867 | + # know whether it is a read cap. Passing a read cap as the writecap |
---|
3868 | + # argument will work (it ends up calling NodeMaker.create_from_cap, |
---|
3869 | + # which derives a readcap if necessary and possible). |
---|
3870 | + d = self.node.set_uri(name, childcap, None, overwrite=replace) |
---|
3871 | d.addCallback(lambda res: childcap) |
---|
3872 | return d |
---|
3873 | |
---|
3874 | @@ -363,9 +368,9 @@ |
---|
3875 | # won't show up in the resulting encoded form.. the 'name' |
---|
3876 | # field is completely missing. So to allow deletion of an |
---|
3877 | # empty file, we have to pretend that None means ''. The only |
---|
3878 | - # downide of this is a slightly confusing error message if |
---|
3879 | + # downside of this is a slightly confusing error message if |
---|
3880 | # someone does a POST without a name= field. For our own HTML |
---|
3881 | - # thisn't a big deal, because we create the 'delete' POST |
---|
3882 | + # this isn't a big deal, because we create the 'delete' POST |
---|
3883 | # buttons ourselves. |
---|
3884 | name = '' |
---|
3885 | charset = get_arg(req, "_charset", "utf-8") |
---|
3886 | @@ -585,7 +590,11 @@ |
---|
3887 | def render_title(self, ctx, data): |
---|
3888 | si_s = abbreviated_dirnode(self.node) |
---|
3889 | header = ["Tahoe-LAFS - Directory SI=%s" % si_s] |
---|
3890 | - if self.node.is_readonly(): |
---|
3891 | + if self.node.is_unknown(): |
---|
3892 | + header.append(" (unknown)") |
---|
3893 | + elif not self.node.is_mutable(): |
---|
3894 | + header.append(" (immutable)") |
---|
3895 | + elif self.node.is_readonly(): |
---|
3896 | header.append(" (read-only)") |
---|
3897 | else: |
---|
3898 | header.append(" (modifiable)") |
---|
3899 | @@ -594,7 +603,11 @@ |
---|
3900 | def render_header(self, ctx, data): |
---|
3901 | si_s = abbreviated_dirnode(self.node) |
---|
3902 | header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]] |
---|
3903 | - if self.node.is_readonly(): |
---|
3904 | + if self.node.is_unknown(): |
---|
3905 | + header.append(" (unknown)") |
---|
3906 | + elif not self.node.is_mutable(): |
---|
3907 | + header.append(" (immutable)") |
---|
3908 | + elif self.node.is_readonly(): |
---|
3909 | header.append(" (read-only)") |
---|
3910 | return ctx.tag[header] |
---|
3911 | |
---|
3912 | @@ -603,7 +616,7 @@ |
---|
3913 | return T.div[T.a(href=link)["Return to Welcome page"]] |
---|
3914 | |
---|
3915 | def render_show_readonly(self, ctx, data): |
---|
3916 | - if self.node.is_readonly(): |
---|
3917 | + if self.node.is_unknown() or self.node.is_readonly(): |
---|
3918 | return "" |
---|
3919 | rocap = self.node.get_readonly_uri() |
---|
3920 | root = get_root(ctx) |
---|
3921 | @@ -630,7 +643,7 @@ |
---|
3922 | |
---|
3923 | root = get_root(ctx) |
---|
3924 | here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri())) |
---|
3925 | - if self.node.is_readonly(): |
---|
3926 | + if self.node.is_unknown() or self.node.is_readonly(): |
---|
3927 | delete = "-" |
---|
3928 | rename = "-" |
---|
3929 | else: |
---|
3930 | @@ -678,8 +691,8 @@ |
---|
3931 | ctx.fillSlots("times", times) |
---|
3932 | |
---|
3933 | assert IFilesystemNode.providedBy(target), target |
---|
3934 | - writecap = target.get_uri() or "" |
---|
3935 | - quoted_uri = urllib.quote(writecap, safe="") # escape slashes too |
---|
3936 | + target_uri = target.get_uri() or "" |
---|
3937 | + quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too |
---|
3938 | |
---|
3939 | if IMutableFileNode.providedBy(target): |
---|
3940 | # to prevent javascript in displayed .html files from stealing a |
---|
3941 | @@ -708,7 +721,7 @@ |
---|
3942 | |
---|
3943 | elif IDirectoryNode.providedBy(target): |
---|
3944 | # directory |
---|
3945 | - uri_link = "%s/uri/%s/" % (root, urllib.quote(writecap)) |
---|
3946 | + uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri)) |
---|
3947 | ctx.fillSlots("filename", |
---|
3948 | T.a(href=uri_link)[html.escape(name)]) |
---|
3949 | if not target.is_mutable(): |
---|
3950 | @@ -795,35 +808,30 @@ |
---|
3951 | kids = {} |
---|
3952 | for name, (childnode, metadata) in children.iteritems(): |
---|
3953 | assert IFilesystemNode.providedBy(childnode), childnode |
---|
3954 | - rw_uri = childnode.get_uri() |
---|
3955 | + rw_uri = childnode.get_write_uri() |
---|
3956 | ro_uri = childnode.get_readonly_uri() |
---|
3957 | if IFileNode.providedBy(childnode): |
---|
3958 | - if childnode.is_readonly(): |
---|
3959 | - rw_uri = None |
---|
3960 | kiddata = ("filenode", {'size': childnode.get_size(), |
---|
3961 | 'mutable': childnode.is_mutable(), |
---|
3962 | }) |
---|
3963 | elif IDirectoryNode.providedBy(childnode): |
---|
3964 | - if childnode.is_readonly(): |
---|
3965 | - rw_uri = None |
---|
3966 | kiddata = ("dirnode", {'mutable': childnode.is_mutable()}) |
---|
3967 | else: |
---|
3968 | kiddata = ("unknown", {}) |
---|
3969 | + |
---|
3970 | kiddata[1]["metadata"] = metadata |
---|
3971 | - if ro_uri: |
---|
3972 | - kiddata[1]["ro_uri"] = ro_uri |
---|
3973 | if rw_uri: |
---|
3974 | kiddata[1]["rw_uri"] = rw_uri |
---|
3975 | + if ro_uri: |
---|
3976 | + kiddata[1]["ro_uri"] = ro_uri |
---|
3977 | verifycap = childnode.get_verify_cap() |
---|
3978 | if verifycap: |
---|
3979 | kiddata[1]['verify_uri'] = verifycap.to_string() |
---|
3980 | + |
---|
3981 | kids[name] = kiddata |
---|
3982 | - if dirnode.is_readonly(): |
---|
3983 | - drw_uri = None |
---|
3984 | - dro_uri = dirnode.get_uri() |
---|
3985 | - else: |
---|
3986 | - drw_uri = dirnode.get_uri() |
---|
3987 | - dro_uri = dirnode.get_readonly_uri() |
---|
3988 | + |
---|
3989 | + drw_uri = dirnode.get_write_uri() |
---|
3990 | + dro_uri = dirnode.get_readonly_uri() |
---|
3991 | contents = { 'children': kids } |
---|
3992 | if dro_uri: |
---|
3993 | contents['ro_uri'] = dro_uri |
---|
3994 | @@ -834,13 +842,14 @@ |
---|
3995 | contents['verify_uri'] = verifycap.to_string() |
---|
3996 | contents['mutable'] = dirnode.is_mutable() |
---|
3997 | data = ("dirnode", contents) |
---|
3998 | - return simplejson.dumps(data, indent=1) + "\n" |
---|
3999 | + json = simplejson.dumps(data, indent=1) + "\n" |
---|
4000 | + #print json |
---|
4001 | + return json |
---|
4002 | d.addCallback(_got) |
---|
4003 | d.addCallback(text_plain, ctx) |
---|
4004 | return d |
---|
4005 | |
---|
4006 | |
---|
4007 | - |
---|
4008 | def DirectoryURI(ctx, dirnode): |
---|
4009 | return text_plain(dirnode.get_uri(), ctx) |
---|
4010 | |
---|
4011 | @@ -1132,18 +1141,39 @@ |
---|
4012 | self.req.write(j+"\n") |
---|
4013 | return "" |
---|
4014 | |
---|
4015 | -class UnknownNodeHandler(RenderMixin, rend.Page): |
---|
4016 | |
---|
4017 | +class UnknownNodeHandler(RenderMixin, rend.Page): |
---|
4018 | def __init__(self, client, node, parentnode=None, name=None): |
---|
4019 | rend.Page.__init__(self) |
---|
4020 | assert node |
---|
4021 | self.node = node |
---|
4022 | + self.parentnode = parentnode |
---|
4023 | + self.name = name |
---|
4024 | |
---|
4025 | def render_GET(self, ctx): |
---|
4026 | req = IRequest(ctx) |
---|
4027 | t = get_arg(req, "t", "").strip() |
---|
4028 | if t == "info": |
---|
4029 | return MoreInfo(self.node) |
---|
4030 | - raise WebError("GET unknown URI type: can only do t=info, not t=%s" % t) |
---|
4031 | - |
---|
4032 | - |
---|
4033 | + if t == "json": |
---|
4034 | + if self.parentnode and self.name: |
---|
4035 | + d = self.parentnode.get_metadata_for(self.name) |
---|
4036 | + else: |
---|
4037 | + d = defer.succeed(None) |
---|
4038 | + d.addCallback(lambda md: UnknownJSONMetadata(ctx, self.node, md)) |
---|
4039 | + return d |
---|
4040 | + raise WebError("GET unknown URI type: can only do t=info and t=json, not t=%s.\n" |
---|
4041 | + "Using a webapi server that supports a later version of Tahoe " |
---|
4042 | + "may help." % t) |
---|
4043 | + |
---|
4044 | +def UnknownJSONMetadata(ctx, filenode, edge_metadata): |
---|
4045 | + rw_uri = filenode.get_write_uri() |
---|
4046 | + ro_uri = filenode.get_readonly_uri() |
---|
4047 | + data = ("unknown", {}) |
---|
4048 | + if ro_uri: |
---|
4049 | + data[1]['ro_uri'] = ro_uri |
---|
4050 | + if rw_uri: |
---|
4051 | + data[1]['rw_uri'] = rw_uri |
---|
4052 | + if edge_metadata is not None: |
---|
4053 | + data[1]['metadata'] = edge_metadata |
---|
4054 | + return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx) |
---|
4055 | diff -rN -u old-tahoe/src/allmydata/web/filenode.py new-tahoe/src/allmydata/web/filenode.py |
---|
4056 | --- old-tahoe/src/allmydata/web/filenode.py 2010-01-25 11:54:57.405000000 +0000 |
---|
4057 | +++ new-tahoe/src/allmydata/web/filenode.py 2010-01-25 11:55:02.779000000 +0000 |
---|
4058 | @@ -6,10 +6,9 @@ |
---|
4059 | from nevow import url, rend |
---|
4060 | from nevow.inevow import IRequest |
---|
4061 | |
---|
4062 | -from allmydata.interfaces import ExistingChildError, CannotPackUnknownNodeError |
---|
4063 | +from allmydata.interfaces import ExistingChildError |
---|
4064 | from allmydata.monitor import Monitor |
---|
4065 | from allmydata.immutable.upload import FileHandle |
---|
4066 | -from allmydata.unknown import UnknownNode |
---|
4067 | from allmydata.util import log, base32 |
---|
4068 | |
---|
4069 | from allmydata.web.common import text_plain, WebError, RenderMixin, \ |
---|
4070 | @@ -20,7 +19,6 @@ |
---|
4071 | from allmydata.web.info import MoreInfo |
---|
4072 | |
---|
4073 | class ReplaceMeMixin: |
---|
4074 | - |
---|
4075 | def replace_me_with_a_child(self, req, client, replace): |
---|
4076 | # a new file is being uploaded in our place. |
---|
4077 | mutable = boolean_of_arg(get_arg(req, "mutable", "false")) |
---|
4078 | @@ -55,14 +53,7 @@ |
---|
4079 | def replace_me_with_a_childcap(self, req, client, replace): |
---|
4080 | req.content.seek(0) |
---|
4081 | childcap = req.content.read() |
---|
4082 | - childnode = client.create_node_from_uri(childcap, childcap+"readonly") |
---|
4083 | - if isinstance(childnode, UnknownNode): |
---|
4084 | - # don't be willing to pack unknown nodes: we might accidentally |
---|
4085 | - # put some write-authority into the rocap slot because we don't |
---|
4086 | - # know how to diminish the URI they gave us. We don't even know |
---|
4087 | - # if they gave us a readcap or a writecap. |
---|
4088 | - msg = "cannot attach unknown node as child %s" % str(self.name) |
---|
4089 | - raise CannotPackUnknownNodeError(msg) |
---|
4090 | + childnode = client.create_node_from_uri(childcap, None, name=self.name) |
---|
4091 | d = self.parentnode.set_node(self.name, childnode, overwrite=replace) |
---|
4092 | d.addCallback(lambda res: childnode.get_uri()) |
---|
4093 | return d |
---|
4094 | @@ -426,12 +417,8 @@ |
---|
4095 | |
---|
4096 | |
---|
4097 | def FileJSONMetadata(ctx, filenode, edge_metadata): |
---|
4098 | - if filenode.is_readonly(): |
---|
4099 | - rw_uri = None |
---|
4100 | - ro_uri = filenode.get_uri() |
---|
4101 | - else: |
---|
4102 | - rw_uri = filenode.get_uri() |
---|
4103 | - ro_uri = filenode.get_readonly_uri() |
---|
4104 | + rw_uri = filenode.get_write_uri() |
---|
4105 | + ro_uri = filenode.get_readonly_uri() |
---|
4106 | data = ("filenode", {}) |
---|
4107 | data[1]['size'] = filenode.get_size() |
---|
4108 | if ro_uri: |
---|
4109 | diff -rN -u old-tahoe/src/allmydata/web/info.py new-tahoe/src/allmydata/web/info.py |
---|
4110 | --- old-tahoe/src/allmydata/web/info.py 2010-01-25 11:54:57.421000000 +0000 |
---|
4111 | +++ new-tahoe/src/allmydata/web/info.py 2010-01-25 11:55:02.793000000 +0000 |
---|
4112 | @@ -21,6 +21,8 @@ |
---|
4113 | def get_type(self): |
---|
4114 | node = self.original |
---|
4115 | if IDirectoryNode.providedBy(node): |
---|
4116 | + if not node.is_mutable(): |
---|
4117 | + return "immutable directory" |
---|
4118 | return "directory" |
---|
4119 | if IFileNode.providedBy(node): |
---|
4120 | si = node.get_storage_index() |
---|
4121 | @@ -28,7 +30,7 @@ |
---|
4122 | if node.is_mutable(): |
---|
4123 | return "mutable file" |
---|
4124 | return "immutable file" |
---|
4125 | - return "LIT file" |
---|
4126 | + return "immutable LIT file" |
---|
4127 | return "unknown" |
---|
4128 | |
---|
4129 | def render_title(self, ctx, data): |
---|
4130 | @@ -68,10 +70,10 @@ |
---|
4131 | |
---|
4132 | def render_directory_writecap(self, ctx, data): |
---|
4133 | node = self.original |
---|
4134 | - if node.is_readonly(): |
---|
4135 | - return "" |
---|
4136 | if not IDirectoryNode.providedBy(node): |
---|
4137 | return "" |
---|
4138 | + if node.is_readonly(): |
---|
4139 | + return "" |
---|
4140 | return ctx.tag[node.get_uri()] |
---|
4141 | |
---|
4142 | def render_directory_readcap(self, ctx, data): |
---|
4143 | @@ -86,27 +88,24 @@ |
---|
4144 | return "" |
---|
4145 | return ctx.tag[node.get_verify_cap().to_string()] |
---|
4146 | |
---|
4147 | - |
---|
4148 | def render_file_writecap(self, ctx, data): |
---|
4149 | node = self.original |
---|
4150 | if IDirectoryNode.providedBy(node): |
---|
4151 | node = node._node |
---|
4152 | - if ((IDirectoryNode.providedBy(node) or IFileNode.providedBy(node)) |
---|
4153 | - and node.is_readonly()): |
---|
4154 | - return "" |
---|
4155 | - writecap = node.get_uri() |
---|
4156 | - if not writecap: |
---|
4157 | + write_uri = node.get_write_uri() |
---|
4158 | + #print "write_uri = %r, node = %r" % (write_uri, node) |
---|
4159 | + if not write_uri: |
---|
4160 | return "" |
---|
4161 | - return ctx.tag[writecap] |
---|
4162 | + return ctx.tag[write_uri] |
---|
4163 | |
---|
4164 | def render_file_readcap(self, ctx, data): |
---|
4165 | node = self.original |
---|
4166 | if IDirectoryNode.providedBy(node): |
---|
4167 | node = node._node |
---|
4168 | - readcap = node.get_readonly_uri() |
---|
4169 | - if not readcap: |
---|
4170 | + read_uri = node.get_readonly_uri() |
---|
4171 | + if not read_uri: |
---|
4172 | return "" |
---|
4173 | - return ctx.tag[readcap] |
---|
4174 | + return ctx.tag[read_uri] |
---|
4175 | |
---|
4176 | def render_file_verifycap(self, ctx, data): |
---|
4177 | node = self.original |
---|
4178 | diff -rN -u old-tahoe/src/allmydata/web/root.py new-tahoe/src/allmydata/web/root.py |
---|
4179 | --- old-tahoe/src/allmydata/web/root.py 2010-01-25 11:54:57.564000000 +0000 |
---|
4180 | +++ new-tahoe/src/allmydata/web/root.py 2010-01-25 11:55:03.004000000 +0000 |
---|
4181 | @@ -12,7 +12,7 @@ |
---|
4182 | from allmydata import get_package_versions_string |
---|
4183 | from allmydata import provisioning |
---|
4184 | from allmydata.util import idlib, log |
---|
4185 | -from allmydata.interfaces import IFileNode, UnhandledCapTypeError |
---|
4186 | +from allmydata.interfaces import IFileNode |
---|
4187 | from allmydata.web import filenode, directory, unlinked, status, operations |
---|
4188 | from allmydata.web import reliability, storage |
---|
4189 | from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \ |
---|
4190 | @@ -85,7 +85,7 @@ |
---|
4191 | try: |
---|
4192 | node = self.client.create_node_from_uri(name) |
---|
4193 | return directory.make_handler_for(node, self.client) |
---|
4194 | - except (TypeError, UnhandledCapTypeError, AssertionError): |
---|
4195 | + except (TypeError, AssertionError): |
---|
4196 | raise WebError("'%s' is not a valid file- or directory- cap" |
---|
4197 | % name) |
---|
4198 | |
---|
4199 | @@ -104,7 +104,7 @@ |
---|
4200 | # 'name' must be a file URI |
---|
4201 | try: |
---|
4202 | node = self.client.create_node_from_uri(name) |
---|
4203 | - except (TypeError, UnhandledCapTypeError, AssertionError): |
---|
4204 | + except (TypeError, AssertionError): |
---|
4205 | # I think this can no longer be reached |
---|
4206 | raise WebError("'%s' is not a valid file- or directory- cap" |
---|
4207 | % name) |
---|