#!/usr/bin/env python
#-----------------------------------------------------------------------------------------------
-from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI
+from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI, is_literal_file_uri
from allmydata.scripts.common_http import do_http as do_http_req
from allmydata.util.hashutil import tagged_hash
from allmydata.util.assertutil import precondition
self.fname = self.tfs.cache.tmp_file(os.urandom(20))
if self.fnode is None:
log('TFF: [%s] open() for write: no file node, creating new File %s' % (self.name, self.fname, ))
- self.fnode = File(0, 'URI:LIT:')
+ self.fnode = File(0, LiteralFileURI.BASE_STRING)
self.fnode.tmp_fname = self.fname # XXX kill this
self.parent.add_child(self.name, self.fnode, {})
elif hasattr(self.fnode, 'tmp_fname'):
self.fname = self.fnode.tmp_fname
log('TFF: reopening(%s) for reading' % self.fname)
else:
- if uri.startswith("URI:LIT") or not self.tfs.async:
+ if is_literal_file_uri(uri) or not self.tfs.async:
log('TFF: synchronously fetching file from cache for reading')
self.fname = self.tfs.cache.get_file(uri)
else:
def get_file(self, uri):
self.log('get_file(%s)' % (uri,))
- if uri.startswith("URI:LIT"):
+ if is_literal_file_uri(uri):
return self.get_literal(uri)
else:
return self.get_chk(uri, async=False)
=== Child Lookup ===
-Tahoe directories contain named children, just like directories in a regular
-local filesystem. These children can be either files or subdirectories.
+Tahoe directories contain named child entries, just like directories in a regular
+local filesystem. These child entries, called "dirnodes", consist of a name,
+metadata, a write slot, and a read slot. The write and read slots normally contain
+a write-cap and read-cap referring to the same object, which can be either a file
+or a subdirectory. The write slot may be empty (actually, both may be empty,
+but that is unusual).
If you have a Tahoe URL that refers to a directory, and want to reference a
named child inside it, just append the child name to the URL. For example, if
} } } ]
}
+ For forward-compatibility, a mutable directory can also contain caps in
+ a format that is unknown to the webapi server. When such caps are retrieved
+ from a mutable directory in a "ro_uri" field, they will be prefixed with
+ the string "ro.", indicating that they must not be decoded without
+ checking that they are read-only. The "ro." prefix must not be stripped
+ off without performing this check. (Future versions of the webapi server
+ will perform it where necessary.)
+
+ If both the "rw_uri" and "ro_uri" fields are present in a given PROPDICT,
+ and the webapi server recognizes the rw_uri as a write cap, then it will
+ reset the ro_uri to the corresponding read cap and discard the original
+ contents of ro_uri (in order to ensure that the two caps correspond to the
+ same object and that the ro_uri is in fact read-only). However this may not
+ happen for caps in a format unknown to the webapi server. Therefore, when
+ writing a directory the webapi client should ensure that the contents
+ of "rw_uri" and "ro_uri" for a given PROPDICT are a consistent
+ (write cap, read cap) pair if possible. If the webapi client only has
+ one cap and does not know whether it is a write cap or read cap, then
+ it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The
+ client must not put a write cap into a "ro_uri" field.
+
Note that the webapi-using client application must not provide the
"Content-Type: multipart/form-data" header that usually accompanies HTML
form submissions, since the body is not formatted this way. Doing so will
Like t=mkdir-with-children above, but the new directory will be
deep-immutable. This means that the directory itself is immutable, and that
- it can only contain deep-immutable objects, like immutable files, literal
- files, and deep-immutable directories. A non-empty request body is
- mandatory, since after the directory is created, it will not be possible to
- add more children to it.
+ it can only contain objects that are treated as being deep-immutable, like
+ immutable files, literal files, and deep-immutable directories.
+
+ For forward-compatibility, a deep-immutable directory can also contain caps
+ in a format that is unknown to the webapi server. When such caps are retrieved
+ from a deep-immutable directory in a "ro_uri" field, they will be prefixed
+ with the string "imm.", indicating that they must not be decoded without
+ checking that they are immutable. The "imm." prefix must not be stripped
+ off without performing this check. (Future versions of the webapi server
+ will perform it where necessary.)
+
+ The cap for each child may be given either in the "rw_uri" or "ro_uri"
+ field of the PROPDICT (not both). If a cap is given in the "rw_uri" field,
+ then the webapi server will check that it is an immutable read-cap of a
+ *known* format, and give an error if it is not. If a cap is given in the
+ "ro_uri" field, then the webapi server will still check whether known
+ caps are immutable, but for unknown caps it will simply assume that the
+ cap can be stored, as described above. Note that an attacker would be
+ able to store any cap in an immutable directory, so this check when
+ creating the directory is only to help non-malicious clients to avoid
+ accidentally giving away more authority than intended.
+
+ A non-empty request body is mandatory, since after the directory is created,
+ it will not be possible to add more children to it.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
Create new directories as necessary to make sure that the named target
($DIRCAP/SUBDIRS../SUBDIR) is a directory. This will create additional
- intermediate directories as necessary. If the named target directory already
- exists, this will make no changes to it.
+ intermediate mutable directories as necessary. If the named target directory
+ already exists, this will make no changes to it.
If the final directory is created, it will be empty.
- This will return an error if a blocking file is present at any of the parent
- names, preventing the server from creating the necessary parent directory.
+ This operation will return an error if a blocking file is present at any of
+ the parent names, preventing the server from creating the necessary parent
+ directory; or if it would require changing an immutable directory.
The write-cap of the new directory will be returned as the HTTP response
body.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children
- Like above, but if the final directory is created, it will be populated with
- initial children from the POST request body, as described above in the
- /uri?t=mkdir-with-children operation.
+ Like /uri?t=mkdir-with-children, but the final directory is created as a
+ child of an existing mutable directory. This will create additional
+ intermediate mutable directories as necessary. If the final directory is
+ created, it will be populated with initial children from the POST request
+ body, as described above.
+
+ This operation will return an error if a blocking file is present at any of
+ the parent names, preventing the server from creating the necessary parent
+ directory; or if it would require changing an immutable directory; or if
+ the immediate parent directory already has a a child named SUBDIR.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-immutable
- Like above, but the final directory will be deep-immutable, with the
- children specified as a JSON dictionary in the POST request body.
+ Like /uri?t=mkdir-immutable, but the final directory is created as a child
+ of an existing mutable directory. The final directory will be deep-immutable,
+ and will be populated with the children specified as a JSON dictionary in
+ the POST request body.
+
+ In Tahoe 1.6 this operation creates intermediate mutable directories if
+ necessary, but that behaviour should not be relied on; see ticket #920.
+
+ This operation will return an error if the parent directory is immutable,
+ or already has a child named SUBDIR.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
- Create a new empty directory and attach it to the given existing directory.
- This will create additional intermediate directories as necessary.
+ Create a new empty mutable directory and attach it to the given existing
+ directory. This will create additional intermediate directories as necessary.
- The URL of this form points to the parent of the bottom-most new directory,
- whereas the previous form has a URL that points directly to the bottom-most
- new directory.
+ This operation will return an error if a blocking file is present at any of
+ the parent names, preventing the server from creating the necessary parent
+ directory, or if it would require changing any immutable directory.
+
+ The URL of this operation points to the parent of the bottommost new directory,
+ whereas the /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir operation above has a URL
+ that points directly to the bottommost new directory.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME
- As above, but the new directory will be populated with initial children via
- the POST request body, as described in /uri?t=mkdir-with-children above.
+ Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME, but the new directory will
+ be populated with initial children via the POST request body. This command
+ will create additional intermediate mutable directories as necessary.
+
+ This operation will return an error if a blocking file is present at any of
+ the parent names, preventing the server from creating the necessary parent
+ directory; or if it would require changing an immutable directory; or if
+ the immediate parent directory already has a a child named NAME.
+
Note that the name= argument must be passed as a queryarg, because the POST
request body is used for the initial children JSON.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME
- As above, but the new directory will be deep-immutable, with the children
- specified as a JSON dictionary in the POST request body. Again, the name=
- argument must be passed as a queryarg.
+ Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME, but the
+ final directory will be deep-immutable. The children are specified as a
+ JSON dictionary in the POST request body. Again, the name= argument must be
+ passed as a queryarg.
+
+ In Tahoe 1.6 this operation creates intermediate mutable directories if
+ necessary, but that behaviour should not be relied on; see ticket #920.
+
+ This operation will return an error if the parent directory is immutable,
+ or already has a child named NAME.
=== Get Information About A File Or Directory (as JSON) ===
"childinfo" is a dictionary that contains "rw_uri", "ro_uri", and
"metadata" keys. You can take the output of "GET /uri/$DIRCAP1?t=json" and
use it as the input to "POST /uri/$DIRCAP2?t=set_children" to make DIR2
- look very much like DIR1.
+ look very much like DIR1 (except for any existing children of DIR2 that
+ were not overwritten, and any existing "tahoe" metadata keys as described
+ below).
When the set_children request contains a child name that already exists in
the target directory, this command defaults to overwriting that child with
POST /uri/$DIRCAP/[SUBDIRS../]?t=upload
- This uploads a file, and attaches it as a new child of the given directory.
- The file must be provided as the "file" field of an HTML encoded form body,
- produced in response to an HTML form like this:
+ This uploads a file, and attaches it as a new child of the given directory,
+ which must be mutable. The file must be provided as the "file" field of an
+ HTML-encoded form body, produced in response to an HTML form like this:
<form action="." method="POST" enctype="multipart/form-data">
<input type="hidden" name="t" value="upload" />
<input type="file" name="file" />
POST /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=upload
This also uploads a file and attaches it as a new child of the given
- directory. It is a slight variant of the previous operation, as the URL
- refers to the target file rather than the parent directory. It is otherwise
- identical: this accepts mutable= and when_done= arguments too.
+ directory, which must be mutable. It is a slight variant of the previous
+ operation, as the URL refers to the target file rather than the parent
+ directory. It is otherwise identical: this accepts mutable= and when_done=
+ arguments too.
POST /uri/$FILECAP?t=upload
POST /uri/$DIRCAP/[SUBDIRS../]?t=delete&name=CHILDNAME
- This instructs the node to delete a child object (file or subdirectory) from
- the given directory. Note that the entire subtree is removed. This is
- somewhat like "rm -rf" (from the point of view of the parent), but other
- references into the subtree will see that the child subdirectories are not
- modified by this operation. Only the link from the given directory to its
- child is severed.
+ This instructs the node to remove a child object (file or subdirectory) from
+ the given directory, which must be mutable. Note that the entire subtree is
+ unlinked from the parent. Unlike deleting a subdirectory in a UNIX local
+ filesystem, the subtree need not be empty; if it isn't, then other references
+ into the subtree will see that the child subdirectories are not modified by
+ this operation. Only the link from the given directory to its child is severed.
=== Renaming A Child ===
POST /uri/$DIRCAP/[SUBDIRS../]?t=rename&from_name=OLD&to_name=NEW
- This instructs the node to rename a child of the given directory. This is
- exactly the same as removing the child, then adding the same child-cap under
- the new name. This operation cannot move the child to a different directory.
+ This instructs the node to rename a child of the given directory, which must
+ be mutable. This has a similar effect to removing the child, then adding the
+ same child-cap under the new name, except that it preserves metadata. This
+ operation cannot move the child to a different directory.
This operation will replace any existing child of the new name, making it
behave like the UNIX "mv -f" command.
"path": a list of strings, with the path that is traversed to reach the
object
- "cap": a writecap for the file or directory, if available, else a readcap
- "verifycap": a verifycap for the file or directory
- "repaircap": the weakest cap which can still be used to repair the object
+ "cap": a write-cap URI for the file or directory, if available, else a
+ read-cap URI
+ "verifycap": a verify-cap URI for the file or directory
+ "repaircap": an URI for the weakest cap that can still be used to repair
+ the object
"storage-index": a base32 storage index for the object
"check-results": a copy of the dictionary which would be returned by
t=check&output=json, with three top-level keys:
"path": a list of strings, with the path that is traversed to reach the
object
- "cap": a writecap for the file or directory, if available, else a readcap
- "verifycap": a verifycap for the file or directory
- "repaircap": the weakest cap which can still be used to repair the object
+ "cap": a write-cap URI for the file or directory, if available, else a
+ read-cap URI
+ "verifycap": a verify-cap URI for the file or directory
+ "repaircap": an URI for the weakest cap that can still be used to repair
+ the object
"storage-index": a base32 storage index for the object
Note that non-distributed files (i.e. LIT files) will have values of None
child's name and the child's URI are included in the results of listing the
parent directory, so it isn't any harder to use the URI for this purpose.
+The read and write caps in a given directory node are separate URIs, and
+can't be assumed to point to the same object even if they were retrieved in
+the same operation (although the webapi server attempts to ensure this
+in most cases). If you need to rely on that property, you should explicitly
+verify it. More generally, you should not make assumptions about the
+internal consistency of the contents of mutable directories. As a result
+of the signatures on mutable object versions, it is guaranteed that a given
+version was written in a single update, but -- as in the case of a file --
+the contents may have been chosen by a malicious writer in a way that is
+designed to confuse applications that rely on their consistency.
+
In general, use names if you want "whatever object (whether file or
directory) is found by following this name (or sequence of names) when my
request reaches the server". Use URIs if you want "this particular object".
# dirnodes. The first takes a URI and produces a filenode or (new-style)
# dirnode. The other three create brand-new filenodes/dirnodes.
- def create_node_from_uri(self, writecap, readcap=None):
- # this returns synchronously.
- return self.nodemaker.create_from_cap(writecap, readcap)
+ def create_node_from_uri(self, write_uri, read_uri=None, deep_immutable=False, name="<unknown name>"):
+ # This returns synchronously.
+ # Note that it does *not* validate the write_uri and read_uri; instead we
+ # may get an opaque node if there were any problems.
+ return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name)
def create_dirnode(self, initial_children={}):
d = self.nodemaker.create_new_mutable_directory(initial_children)
return d
+
def create_immutable_dirnode(self, children, convergence=None):
return self.nodemaker.create_immutable_directory(children, convergence)
from twisted.internet import defer
from twisted.internet.interfaces import IConsumer
from foolscap.api import Referenceable
-from allmydata.interfaces import RIControlClient
+from allmydata.interfaces import RIControlClient, IFileNode
from allmydata.util import fileutil, mathutil
from allmydata.immutable import upload
from twisted.python import log
return d
def remote_download_from_uri_to_file(self, uri, filename):
- filenode = self.parent.create_node_from_uri(uri)
+ filenode = self.parent.create_node_from_uri(uri, name=filename)
+ if not IFileNode.providedBy(filenode):
+ raise AssertionError("The URI does not reference a file.")
c = FileWritingConsumer(filename)
d = filenode.read(c)
d.addCallback(lambda res: filename)
if i >= self.count:
return
n = self.parent.create_node_from_uri(self.uris[i])
+ if not IFileNode.providedBy(n):
+ raise AssertionError("The URI does not reference a file.")
if n.is_mutable():
d1 = n.download_best_version()
else:
from twisted.internet import defer
from foolscap.api import fireEventually
import simplejson
-from allmydata.mutable.common import NotMutableError
+from allmydata.mutable.common import NotWriteableError
from allmydata.mutable.filenode import MutableFileNode
-from allmydata.unknown import UnknownNode
+from allmydata.unknown import UnknownNode, strip_prefix_for_ro
from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
IImmutableFileNode, IMutableFileNode, \
ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
- CannotPackUnknownNodeError
+ MustBeDeepImmutableError, CapConstraintError
from allmydata.check_results import DeepCheckResults, \
DeepCheckAndRepairResults
from allmydata.monitor import Monitor
new_contents = self.node._pack_contents(children)
return new_contents
+
class MetadataSetter:
def __init__(self, node, name, metadata):
self.node = node
for (name, (child, new_metadata)) in self.entries.iteritems():
precondition(isinstance(name, unicode), name)
precondition(IFilesystemNode.providedBy(child), child)
+
+ # Strictly speaking this is redundant because we would raise the
+ # error again in pack_children.
+ child.raise_error()
+
if name in children:
if not self.overwrite:
raise ExistingChildError("child '%s' already exists" % name)
new_contents = self.node._pack_contents(children)
return new_contents
-def _encrypt_rwcap(filenode, rwcap):
- assert isinstance(rwcap, str)
+def _encrypt_rw_uri(filenode, rw_uri):
+ assert isinstance(rw_uri, str)
writekey = filenode.get_writekey()
if not writekey:
return ""
- salt = hashutil.mutable_rwcap_salt_hash(rwcap)
+ salt = hashutil.mutable_rwcap_salt_hash(rw_uri)
key = hashutil.mutable_rwcap_key_hash(salt, writekey)
cryptor = AES(key)
- crypttext = cryptor.process(rwcap)
+ crypttext = cryptor.process(rw_uri)
mac = hashutil.hmac(key, salt + crypttext)
assert len(mac) == 32
return salt + crypttext + mac
# The MAC is not checked by readers in Tahoe >= 1.3.0, but we still
# produce it for the sake of older readers.
-class MustBeDeepImmutable(Exception):
- """You tried to add a non-deep-immutable node to a deep-immutable
- directory."""
-
def pack_children(filenode, children, deep_immutable=False):
"""Take a dict that maps:
children[unicode_name] = (IFileSystemNode, metadata_dict)
time.
If deep_immutable is True, I will require that all my children are deeply
- immutable, and will raise a MustBeDeepImmutable exception if not.
+ immutable, and will raise a MustBeDeepImmutableError if not.
"""
has_aux = isinstance(children, AuxValueDict)
assert isinstance(name, unicode)
entry = None
(child, metadata) = children[name]
- if deep_immutable and child.is_mutable():
- # TODO: consider adding IFileSystemNode.is_deep_immutable()
- raise MustBeDeepImmutable("child '%s' is mutable" % (name,))
+ child.raise_error()
+ if deep_immutable and not child.is_allowed_in_immutable_directory():
+ raise MustBeDeepImmutableError("child '%s' is not allowed in an immutable directory" % (name,), name)
if has_aux:
entry = children.get_aux(name)
if not entry:
assert IFilesystemNode.providedBy(child), (name,child)
assert isinstance(metadata, dict)
- rwcap = child.get_uri() # might be RO if the child is not writeable
- if rwcap is None:
- rwcap = ""
- assert isinstance(rwcap, str), rwcap
- rocap = child.get_readonly_uri()
- if rocap is None:
- rocap = ""
- assert isinstance(rocap, str), rocap
+ rw_uri = child.get_write_uri()
+ if rw_uri is None:
+ rw_uri = ""
+ assert isinstance(rw_uri, str), rw_uri
+
+ # should be prevented by MustBeDeepImmutableError check above
+ assert not (rw_uri and deep_immutable)
+
+ ro_uri = child.get_readonly_uri()
+ if ro_uri is None:
+ ro_uri = ""
+ assert isinstance(ro_uri, str), ro_uri
entry = "".join([netstring(name.encode("utf-8")),
- netstring(rocap),
- netstring(_encrypt_rwcap(filenode, rwcap)),
+ netstring(strip_prefix_for_ro(ro_uri, deep_immutable)),
+ netstring(_encrypt_rw_uri(filenode, rw_uri)),
netstring(simplejson.dumps(metadata))])
entries.append(netstring(entry))
return "".join(entries)
plaintext = cryptor.process(crypttext)
return plaintext
- def _create_node(self, rwcap, rocap):
- return self._nodemaker.create_from_cap(rwcap, rocap)
+ def _create_and_validate_node(self, rw_uri, ro_uri, name):
+ node = self._nodemaker.create_from_cap(rw_uri, ro_uri,
+ deep_immutable=not self.is_mutable(),
+ name=name)
+ node.raise_error()
+ return node
def _unpack_contents(self, data):
# the directory is serialized as a list of netstrings, one per child.
- # Each child is serialized as a list of four netstrings: (name,
- # rocap, rwcap, metadata), in which the name,rocap,metadata are in
- # cleartext. The 'name' is UTF-8 encoded. The rwcap is formatted as:
- # pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac)
+ # Each child is serialized as a list of four netstrings: (name, ro_uri,
+ # rwcapdata, metadata), in which the name, ro_uri, metadata are in
+ # cleartext. The 'name' is UTF-8 encoded. The rwcapdata is formatted as:
+ # pack("16ss32s", iv, AES(H(writekey+iv), plaintext_rw_uri), mac)
assert isinstance(data, str), (repr(data), type(data))
# an empty directory is serialized as an empty string
if data == "":
return AuxValueDict()
writeable = not self.is_readonly()
+ mutable = self.is_mutable()
children = AuxValueDict()
position = 0
while position < len(data):
entries, position = split_netstring(data, 1, position)
entry = entries[0]
- (name, rocap, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
+ (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
+ if not mutable and len(rwcapdata) > 0:
+ raise ValueError("the rwcapdata field of a dirnode in an immutable directory was not empty")
name = name.decode("utf-8")
- rwcap = None
+ rw_uri = ""
if writeable:
- rwcap = self._decrypt_rwcapdata(rwcapdata)
- if not rwcap:
- rwcap = None # rwcap is None or a non-empty string
- if not rocap:
- rocap = None # rocap is None or a non-empty string
- child = self._create_node(rwcap, rocap)
- metadata = simplejson.loads(metadata_s)
- assert isinstance(metadata, dict)
- children.set_with_aux(name, (child, metadata), auxilliary=entry)
+ rw_uri = self._decrypt_rwcapdata(rwcapdata)
+
+ # Since the encryption uses CTR mode, it currently leaks the length of the
+ # plaintext rw_uri -- and therefore whether it is present, i.e. whether the
+ # dirnode is writeable (ticket #925). By stripping spaces in Tahoe >= 1.6.0,
+ # we may make it easier for future versions to plug this leak.
+ # ro_uri is treated in the same way for consistency.
+ # rw_uri and ro_uri will be either None or a non-empty string.
+
+ rw_uri = rw_uri.strip(' ') or None
+ ro_uri = ro_uri.strip(' ') or None
+
+ try:
+ child = self._create_and_validate_node(rw_uri, ro_uri, name)
+ if mutable or child.is_allowed_in_immutable_directory():
+ metadata = simplejson.loads(metadata_s)
+ assert isinstance(metadata, dict)
+ children[name] = (child, metadata)
+ children.set_with_aux(name, (child, metadata), auxilliary=entry)
+ else:
+ log.msg(format="mutable cap for child '%(name)s' unpacked from an immutable directory",
+ name=name.encode("utf-8"),
+ facility="tahoe.webish", level=log.UNUSUAL)
+ except CapConstraintError, e:
+ log.msg(format="unmet constraint on cap for child '%(name)s' unpacked from a directory:\n"
+ "%(message)s", message=e.args[0], name=name.encode("utf-8"),
+ facility="tahoe.webish", level=log.UNUSUAL)
+
return children
def _pack_contents(self, children):
def is_readonly(self):
return self._node.is_readonly()
+
def is_mutable(self):
return self._node.is_mutable()
+ def is_unknown(self):
+ return False
+
+ def is_allowed_in_immutable_directory(self):
+ return not self._node.is_mutable()
+
+ def raise_error(self):
+ pass
+
def get_uri(self):
return self._uri.to_string()
+ def get_write_uri(self):
+ if self.is_readonly():
+ return None
+ return self._uri.to_string()
+
def get_readonly_uri(self):
return self._uri.get_readonly().to_string()
def get_cap(self):
return self._uri
+
def get_readcap(self):
return self._uri.get_readonly()
+
def get_verify_cap(self):
return self._uri.get_verify_cap()
+
def get_repair_cap(self):
if self._node.is_readonly():
return None # readonly (mutable) dirnodes are not yet repairable
def set_metadata_for(self, name, metadata):
assert isinstance(name, unicode)
if self.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
assert isinstance(metadata, dict)
s = MetadataSetter(self, name, metadata)
d = self._node.modify(s.modify)
precondition(isinstance(name, unicode), name)
precondition(isinstance(writecap, (str,type(None))), writecap)
precondition(isinstance(readcap, (str,type(None))), readcap)
- child_node = self._create_node(writecap, readcap)
- if isinstance(child_node, UnknownNode):
- # don't be willing to pack unknown nodes: we might accidentally
- # put some write-authority into the rocap slot because we don't
- # know how to diminish the URI they gave us. We don't even know
- # if they gave us a readcap or a writecap.
- msg = "cannot pack unknown node as child %s" % str(name)
- raise CannotPackUnknownNodeError(msg)
+
+ # We now allow packing unknown nodes, provided they are valid
+ # for this type of directory.
+ child_node = self._create_and_validate_node(writecap, readcap, name)
d = self.set_node(name, child_node, metadata, overwrite)
d.addCallback(lambda res: child_node)
return d
writecap, readcap, metadata = e
precondition(isinstance(writecap, (str,type(None))), writecap)
precondition(isinstance(readcap, (str,type(None))), readcap)
- child_node = self._create_node(writecap, readcap)
- if isinstance(child_node, UnknownNode):
- msg = "cannot pack unknown node as child %s" % str(name)
- raise CannotPackUnknownNodeError(msg)
+
+ # We now allow packing unknown nodes, provided they are valid
+ # for this type of directory.
+ child_node = self._create_and_validate_node(writecap, readcap, name)
a.set_node(name, child_node, metadata)
d = self._node.modify(a.modify)
d.addCallback(lambda ign: self)
same name.
If this directory node is read-only, the Deferred will errback with a
- NotMutableError."""
+ NotWriteableError."""
precondition(IFilesystemNode.providedBy(child), child)
if self.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
assert isinstance(name, unicode)
assert IFilesystemNode.providedBy(child), child
a = Adder(self, overwrite=overwrite)
def set_nodes(self, entries, overwrite=True):
precondition(isinstance(entries, dict), entries)
if self.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
a = Adder(self, entries, overwrite=overwrite)
d = self._node.modify(a.modify)
d.addCallback(lambda res: self)
the operation completes."""
assert isinstance(name, unicode)
if self.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
d = self._uploader.upload(uploadable)
- d.addCallback(lambda results: results.uri)
- d.addCallback(self._nodemaker.create_from_cap)
+ d.addCallback(lambda results:
+ self._create_and_validate_node(results.uri, None, name))
d.addCallback(lambda node:
self.set_node(name, node, metadata, overwrite))
return d
fires (with the node just removed) when the operation finishes."""
assert isinstance(name, unicode)
if self.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
deleter = Deleter(self, name)
d = self._node.modify(deleter.modify)
d.addCallback(lambda res: deleter.old_child)
mutable=True):
assert isinstance(name, unicode)
if self.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
if mutable:
d = self._nodemaker.create_new_mutable_directory(initial_children)
else:
Deferred that fires when the operation finishes."""
assert isinstance(current_child_name, unicode)
if self.is_readonly() or new_parent.is_readonly():
- return defer.fail(NotMutableError())
+ return defer.fail(NotWriteableError())
if new_child_name is None:
new_child_name = current_child_name
assert isinstance(new_child_name, unicode)
class _ImmutableFileNodeBase(object):
implements(IImmutableFileNode, ICheckable)
+ def get_write_uri(self):
+ return None
+
def get_readonly_uri(self):
return self.get_uri()
def is_readonly(self):
return True
+ def is_unknown(self):
+ return False
+
+ def is_allowed_in_immutable_directory(self):
+ return True
+
+ def raise_error(self):
+ pass
+
def __hash__(self):
return self.u.__hash__()
def __eq__(self, other):
class IDirnodeURI(Interface):
"""I am a URI which represents a dirnode."""
-
class IFileURI(Interface):
"""I am a URI which represents a filenode."""
def get_size():
class IMutableFileURI(Interface):
"""I am a URI which represents a mutable filenode."""
+
class IDirectoryURI(Interface):
pass
+
class IReadonlyDirectoryURI(Interface):
pass
-class CannotPackUnknownNodeError(Exception):
- """UnknownNodes (using filecaps from the future that we don't understand)
- cannot yet be copied safely, so I refuse to copy them."""
+class CapConstraintError(Exception):
+ """A constraint on a cap was violated."""
-class UnhandledCapTypeError(Exception):
- """I recognize the cap/URI, but I cannot create an IFilesystemNode for
- it."""
+class MustBeDeepImmutableError(CapConstraintError):
+ """Mutable children cannot be added to an immutable directory.
+ Also, caps obtained from an immutable directory can trigger this error
+ if they are later found to refer to a mutable object and then used."""
-class NotDeepImmutableError(Exception):
- """Deep-immutable directories can only contain deep-immutable children"""
+class MustBeReadonlyError(CapConstraintError):
+ """Known write caps cannot be specified in a ro_uri field. Also,
+ caps obtained from a ro_uri field can trigger this error if they
+ are later found to be write caps and then used."""
+
+class MustNotBeUnknownRWError(CapConstraintError):
+ """Cannot add an unknown child cap specified in a rw_uri field."""
# The hierarchy looks like this:
# IFilesystemNode
"""
def get_uri():
- """
- Return the URI string that can be used by others to get access to
- this node. If this node is read-only, the URI will only offer
+ """Return the URI string corresponding to the strongest cap associated
+ with this node. If this node is read-only, the URI will only offer
read-only access. If this node is read-write, the URI will offer
read-write access.
read-only access with others, use get_readonly_uri().
"""
+ def get_write_uri(n):
+ """Return the URI string that can be used by others to get write
+ access to this node, if it is writeable. If this is a read-only node,
+ return None."""
+
def get_readonly_uri():
"""Return the URI string that can be used by others to get read-only
access to this node. The result is a read-only URI, regardless of
file.
"""
+ def is_unknown():
+ """Return True if this is an unknown node."""
+
+ def is_allowed_in_immutable_directory():
+ """Return True if this node is allowed as a child of a deep-immutable
+ directory. This is true if either the node is of a known-immutable type,
+ or it is unknown and read-only.
+ """
+
+ def raise_error():
+ """Raise any error associated with this node."""
+
def get_size():
"""Return the length (in bytes) of the data this node represents. For
directory nodes, I return the size of the backing store. I return
ctime/mtime semantics of traditional filesystems.
If this directory node is read-only, the Deferred will errback with a
- NotMutableError."""
+ NotWriteableError."""
def set_children(entries, overwrite=True):
"""Add multiple children (by writecap+readcap) to a directory node.
ctime/mtime semantics of traditional filesystems.
If this directory node is read-only, the Deferred will errback with a
- NotMutableError."""
+ NotWriteableError."""
def set_nodes(entries, overwrite=True):
"""Add multiple children to a directory node. Takes a dict mapping
Tahoe process will typically have a single NodeMaker, but unit tests may
create simplified/mocked forms for testing purposes.
"""
- def create_from_cap(writecap, readcap=None):
+ def create_from_cap(writecap, readcap=None, **kwargs):
"""I create an IFilesystemNode from the given writecap/readcap. I can
only provide nodes for existing file/directory objects: use my other
methods to create new objects. I return synchronously."""
# creation
MODE_READ = "MODE_READ"
-class NotMutableError(Exception):
+class NotWriteableError(Exception):
pass
class NeedMoreDataError(Exception):
def get_uri(self):
return self._uri.to_string()
+
+ def get_write_uri(self):
+ if self.is_readonly():
+ return None
+ return self._uri.to_string()
+
def get_readonly_uri(self):
return self._uri.get_readonly().to_string()
def is_mutable(self):
return self._uri.is_mutable()
+
def is_readonly(self):
return self._uri.is_readonly()
+ def is_unknown(self):
+ return False
+
+ def is_allowed_in_immutable_directory(self):
+ return not self._uri.is_mutable()
+
+ def raise_error(self):
+ pass
+
def __hash__(self):
return hash((self.__class__, self._uri))
def __cmp__(self, them):
import weakref
from zope.interface import implements
from allmydata.util.assertutil import precondition
-from allmydata.interfaces import INodeMaker, NotDeepImmutableError
+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError
from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
from allmydata.immutable.upload import Data
from allmydata.mutable.filenode import MutableFileNode
def _create_dirnode(self, filenode):
return DirectoryNode(filenode, self, self.uploader)
- def create_from_cap(self, writecap, readcap=None):
+ def create_from_cap(self, writecap, readcap=None, deep_immutable=False, name=u"<unknown name>"):
# this returns synchronously. It starts with a "cap string".
assert isinstance(writecap, (str, type(None))), type(writecap)
assert isinstance(readcap, (str, type(None))), type(readcap)
+
bigcap = writecap or readcap
if not bigcap:
# maybe the writecap was hidden because we're in a readonly
# directory, and the future cap format doesn't have a readcap, or
# something.
- return UnknownNode(writecap, readcap)
- if bigcap in self._node_cache:
- return self._node_cache[bigcap]
- cap = uri.from_string(bigcap)
- node = self._create_from_cap(cap)
+ return UnknownNode(None, None) # deep_immutable and name not needed
+
+ # The name doesn't matter for caching since it's only used in the error
+ # attribute of an UnknownNode, and we don't cache those.
+ memokey = ("I" if deep_immutable else "M") + bigcap
+ if memokey in self._node_cache:
+ return self._node_cache[memokey]
+ cap = uri.from_string(bigcap, deep_immutable=deep_immutable, name=name)
+ node = self._create_from_single_cap(cap)
if node:
- self._node_cache[bigcap] = node # note: WeakValueDictionary
+ self._node_cache[memokey] = node # note: WeakValueDictionary
else:
- node = UnknownNode(writecap, readcap) # don't cache UnknownNode
+ # don't cache UnknownNode
+ node = UnknownNode(writecap, readcap, deep_immutable=deep_immutable, name=name)
return node
- def _create_from_cap(self, cap):
- # This starts with a "cap instance"
+ def _create_from_single_cap(self, cap):
if isinstance(cap, uri.LiteralFileURI):
return self._create_lit(cap)
if isinstance(cap, uri.CHKFileURI):
uri.ReadonlyDirectoryURI,
uri.ImmutableDirectoryURI,
uri.LiteralDirectoryURI)):
- filenode = self._create_from_cap(cap.get_filenode_cap())
+ filenode = self._create_from_single_cap(cap.get_filenode_cap())
return self._create_dirnode(filenode)
return None
return d
def create_new_mutable_directory(self, initial_children={}):
- # initial_children must have metadata (i.e. {} instead of None), and
- # should not contain UnknownNodes
+ # initial_children must have metadata (i.e. {} instead of None)
for (name, (node, metadata)) in initial_children.iteritems():
- precondition(not isinstance(node, UnknownNode),
- "create_new_mutable_directory does not accept UnknownNode", node)
precondition(isinstance(metadata, dict),
"create_new_mutable_directory requires metadata to be a dict, not None", metadata)
+ node.raise_error()
d = self.create_mutable_file(lambda n:
pack_children(n, initial_children))
d.addCallback(self._create_dirnode)
if convergence is None:
convergence = self.secret_holder.get_convergence_secret()
for (name, (node, metadata)) in children.iteritems():
- precondition(not isinstance(node, UnknownNode),
- "create_immutable_directory does not accept UnknownNode", node)
precondition(isinstance(metadata, dict),
"create_immutable_directory requires metadata to be a dict, not None", metadata)
- if node.is_mutable():
- raise NotDeepImmutableError("%s is not immutable" % (node,))
+ node.raise_error()
+ if not node.is_allowed_in_immutable_directory():
+ raise MustBeDeepImmutableError("%s is not immutable" % (node,), name)
n = DummyImmutableFileNode() # writekey=None
packed = pack_children(n, children)
uploadable = Data(packed, convergence)
d = self.uploader.upload(uploadable, history=self.history)
- def _uploaded(results):
- filecap = self.create_from_cap(results.uri)
- return filecap
- d.addCallback(_uploaded)
+ d.addCallback(lambda results: self.create_from_cap(None, results.uri))
d.addCallback(self._create_dirnode)
return d
pass
def get_alias(aliases, path, default):
+ from allmydata import uri
# transform "work:path/filename" into (aliases["work"], "path/filename").
# If default=None, then an empty alias is indicated by returning
- # DefaultAliasMarker. We special-case "URI:" to make it easy to access
- # specific files/directories by their read-cap.
+ # DefaultAliasMarker. We special-case strings with a recognized cap URI
+ # prefix, to make it easy to access specific files/directories by their
+ # caps.
path = path.strip()
- if path.startswith("URI:"):
+ if uri.has_uri_prefix(path):
# The only way to get a sub-path is to use URI:blah:./foo, and we
# strip out the :./ sequence.
sep = path.find(":./")
readcap = ascii_or_none(data[1].get("ro_uri"))
self.children[name] = TahoeFileSource(self.nodeurl, mutable,
writecap, readcap)
- else:
- assert data[0] == "dirnode"
+ elif data[0] == "dirnode":
writecap = ascii_or_none(data[1].get("rw_uri"))
readcap = ascii_or_none(data[1].get("ro_uri"))
if writecap and writecap in self.cache:
if recurse:
child.populate(True)
self.children[name] = child
+ else:
+ # TODO: there should be an option to skip unknown nodes.
+ raise TahoeError("Cannot copy unknown nodes (ticket #839). "
+ "You probably need to use a later version of "
+ "Tahoe-LAFS to copy this directory.")
class TahoeMissingTarget:
def __init__(self, url):
urllib.quote(name.encode('utf-8'))])
self.children[name] = TahoeFileTarget(self.nodeurl, mutable,
writecap, readcap, url)
- else:
- assert data[0] == "dirnode"
+ elif data[0] == "dirnode":
writecap = ascii_or_none(data[1].get("rw_uri"))
readcap = ascii_or_none(data[1].get("ro_uri"))
if writecap and writecap in self.cache:
if recurse:
child.populate(True)
self.children[name] = child
+ else:
+ # TODO: there should be an option to skip unknown nodes.
+ raise TahoeError("Cannot copy unknown nodes (ticket #839). "
+ "You probably need to use a later version of "
+ "Tahoe-LAFS to copy this directory.")
def get_child_target(self, name):
# return a new target for a named subdirectory of this dir
set_data = {}
for (name, filecap) in self.new_children.items():
# it just so happens that ?t=set_children will accept both file
- # read-caps and write-caps as ['rw_uri'], and will handle eithe
+ # read-caps and write-caps as ['rw_uri'], and will handle either
# correctly. So don't bother trying to figure out whether the one
# we have is read-only or read-write.
+ # TODO: think about how this affects forward-compatibility for
+ # unknown caps
set_data[name] = ["filenode", {"rw_uri": filecap}]
body = simplejson.dumps(set_data)
POST(url, body)
# local-file-in-the-way
# touch proposed
# tahoe cp -r my:docs/proposed/denver.txt proposed/denver.txt
+# handling of unknown nodes
# things that maybe should be errors but aren't
# local-dir-in-the-way
# DIRCAP:./subdir/foo : DIRCAP/subdir/foo
# MUTABLE-FILE-WRITECAP : filecap
+ # FIXME: this shouldn't rely on a particular prefix.
if to_file.startswith("URI:SSK:"):
url = nodeurl + "uri/%s" % urllib.quote(to_file)
else:
def get_uri(self):
return self.my_uri.to_string()
+ def get_write_uri(self):
+ return None
def get_readonly_uri(self):
return self.my_uri.to_string()
def get_cap(self):
return False
def is_readonly(self):
return True
+ def is_unknown(self):
+ return False
+ def is_allowed_in_immutable_directory(self):
+ return True
+ def raise_error(self):
+ pass
def get_size(self):
try:
return self.my_uri.get_readonly()
def get_uri(self):
return self.my_uri.to_string()
+ def get_write_uri(self):
+ if self.is_readonly():
+ return None
+ return self.my_uri.to_string()
def get_readonly(self):
return self.my_uri.get_readonly()
def get_readonly_uri(self):
return self.my_uri.is_readonly()
def is_mutable(self):
return self.my_uri.is_mutable()
+ def is_unknown(self):
+ return False
+ def is_allowed_in_immutable_directory(self):
+ return not self.my_uri.is_mutable()
+ def raise_error(self):
+ pass
def get_writekey(self):
return "\x00"*16
def get_size(self):
self.failUnless(n.is_readonly())
self.failUnless(n.is_mutable())
- future = "x-tahoe-crazy://future_cap_format."
- n = c.create_node_from_uri(future)
+ unknown_rw = "lafs://from_the_future"
+ unknown_ro = "lafs://readonly_from_the_future"
+ n = c.create_node_from_uri(unknown_rw, unknown_ro)
self.failUnless(IFilesystemNode.providedBy(n))
self.failIf(IFileNode.providedBy(n))
self.failIf(IImmutableFileNode.providedBy(n))
self.failIf(IMutableFileNode.providedBy(n))
self.failIf(IDirectoryNode.providedBy(n))
- self.failUnlessEqual(n.get_uri(), future)
+ self.failUnless(n.is_unknown())
+ self.failUnlessEqual(n.get_uri(), unknown_rw)
+ self.failUnlessEqual(n.get_write_uri(), unknown_rw)
+ self.failUnlessEqual(n.get_readonly_uri(), "ro." + unknown_ro)
from allmydata.client import Client
from allmydata.immutable import upload
from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
- ExistingChildError, NoSuchChildError, NotDeepImmutableError, \
- IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError
+ ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \
+ MustBeDeepImmutableError, MustBeReadonlyError, \
+ IDeepCheckResults, IDeepCheckAndRepairResults
from allmydata.mutable.filenode import MutableFileNode
from allmydata.mutable.common import UncoordinatedWriteError
from allmydata.util import hashutil, base32
from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
ErrorMixin
from allmydata.test.no_network import GridTestMixin
-from allmydata.unknown import UnknownNode
+from allmydata.unknown import UnknownNode, strip_prefix_for_ro
from allmydata.nodemaker import NodeMaker
from base64 import b32decode
import common_util as testutil
d = c.create_dirnode()
def _done(res):
self.failUnless(isinstance(res, dirnode.DirectoryNode))
+ self.failUnless(res.is_mutable())
+ self.failIf(res.is_readonly())
+ self.failIf(res.is_unknown())
+ self.failIf(res.is_allowed_in_immutable_directory())
+ res.raise_error()
rep = str(res)
self.failUnless("RW-MUT" in rep)
d.addCallback(_done)
nm = c.nodemaker
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
one_uri = "URI:LIT:n5xgk" # LIT for "one"
+ mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
+ future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
kids = {u"one": (nm.create_from_cap(one_uri), {}),
u"two": (nm.create_from_cap(setup_py_uri),
{"metakey": "metavalue"}),
+ u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}),
+ u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}),
+ u"fro": (nm.create_from_cap(None, future_read_uri), {}),
}
d = c.create_dirnode(kids)
+
def _created(dn):
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
+ self.failUnless(dn.is_mutable())
+ self.failIf(dn.is_readonly())
+ self.failIf(dn.is_unknown())
+ self.failIf(dn.is_allowed_in_immutable_directory())
+ dn.raise_error()
rep = str(dn)
self.failUnless("RW-MUT" in rep)
return dn.list()
d.addCallback(_created)
+
def _check_kids(children):
- self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
+ self.failUnlessEqual(sorted(children.keys()),
+ [u"fro", u"fut", u"mut", u"one", u"two"])
one_node, one_metadata = children[u"one"]
two_node, two_metadata = children[u"two"]
+ mut_node, mut_metadata = children[u"mut"]
+ fut_node, fut_metadata = children[u"fut"]
+ fro_node, fro_metadata = children[u"fro"]
+
self.failUnlessEqual(one_node.get_size(), 3)
- self.failUnlessEqual(two_node.get_size(), 14861)
+ self.failUnlessEqual(one_node.get_uri(), one_uri)
+ self.failUnlessEqual(one_node.get_readonly_uri(), one_uri)
self.failUnless(isinstance(one_metadata, dict), one_metadata)
+
+ self.failUnlessEqual(two_node.get_size(), 14861)
+ self.failUnlessEqual(two_node.get_uri(), setup_py_uri)
+ self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri)
self.failUnlessEqual(two_metadata["metakey"], "metavalue")
+
+ self.failUnlessEqual(mut_node.get_uri(), mut_write_uri)
+ self.failUnlessEqual(mut_node.get_readonly_uri(), mut_read_uri)
+ self.failUnless(isinstance(mut_metadata, dict), mut_metadata)
+
+ self.failUnless(fut_node.is_unknown())
+ self.failUnlessEqual(fut_node.get_uri(), future_write_uri)
+ self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri)
+ self.failUnless(isinstance(fut_metadata, dict), fut_metadata)
+
+ self.failUnless(fro_node.is_unknown())
+ self.failUnlessEqual(fro_node.get_uri(), "ro." + future_read_uri)
+ self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri)
+ self.failUnless(isinstance(fro_metadata, dict), fro_metadata)
d.addCallback(_check_kids)
+
d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
d.addCallback(lambda dn: dn.list())
d.addCallback(_check_kids)
- future_writecap = "x-tahoe-crazy://I_am_from_the_future."
- future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
- future_node = UnknownNode(future_writecap, future_readcap)
- bad_kids1 = {u"one": (future_node, {})}
+
+ bad_future_node = UnknownNode(future_write_uri, None)
+ bad_kids1 = {u"one": (bad_future_node, {})}
d.addCallback(lambda ign:
- self.shouldFail(AssertionError, "bad_kids1",
- "does not accept UnknownNode",
+ self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
+ "cannot attach unknown",
nm.create_new_mutable_directory,
bad_kids1))
bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
nm = c.nodemaker
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
one_uri = "URI:LIT:n5xgk" # LIT for "one"
- mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
- mut_writecap = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
+ future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
+ future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
kids = {u"one": (nm.create_from_cap(one_uri), {}),
u"two": (nm.create_from_cap(setup_py_uri),
{"metakey": "metavalue"}),
+ u"fut": (nm.create_from_cap(None, future_read_uri), {}),
}
d = c.create_immutable_dirnode(kids)
+
def _created(dn):
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
self.failIf(dn.is_mutable())
self.failUnless(dn.is_readonly())
+ self.failIf(dn.is_unknown())
+ self.failUnless(dn.is_allowed_in_immutable_directory())
+ dn.raise_error()
rep = str(dn)
self.failUnless("RO-IMM" in rep)
cap = dn.get_cap()
self.cap = cap
return dn.list()
d.addCallback(_created)
+
def _check_kids(children):
- self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
+ self.failUnlessEqual(sorted(children.keys()), [u"fut", u"one", u"two"])
one_node, one_metadata = children[u"one"]
two_node, two_metadata = children[u"two"]
+ fut_node, fut_metadata = children[u"fut"]
+
self.failUnlessEqual(one_node.get_size(), 3)
- self.failUnlessEqual(two_node.get_size(), 14861)
+ self.failUnlessEqual(one_node.get_uri(), one_uri)
+ self.failUnlessEqual(one_node.get_readonly_uri(), one_uri)
self.failUnless(isinstance(one_metadata, dict), one_metadata)
+
+ self.failUnlessEqual(two_node.get_size(), 14861)
+ self.failUnlessEqual(two_node.get_uri(), setup_py_uri)
+ self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri)
self.failUnlessEqual(two_metadata["metakey"], "metavalue")
+
+ self.failUnless(fut_node.is_unknown())
+ self.failUnlessEqual(fut_node.get_uri(), "imm." + future_read_uri)
+ self.failUnlessEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri)
+ self.failUnless(isinstance(fut_metadata, dict), fut_metadata)
d.addCallback(_check_kids)
+
d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
d.addCallback(lambda dn: dn.list())
d.addCallback(_check_kids)
- future_writecap = "x-tahoe-crazy://I_am_from_the_future."
- future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
- future_node = UnknownNode(future_writecap, future_readcap)
- bad_kids1 = {u"one": (future_node, {})}
+
+ bad_future_node1 = UnknownNode(future_write_uri, None)
+ bad_kids1 = {u"one": (bad_future_node1, {})}
d.addCallback(lambda ign:
- self.shouldFail(AssertionError, "bad_kids1",
- "does not accept UnknownNode",
+ self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
+ "cannot attach unknown",
c.create_immutable_dirnode,
bad_kids1))
- bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
+ bad_future_node2 = UnknownNode(future_write_uri, future_read_uri)
+ bad_kids2 = {u"one": (bad_future_node2, {})}
d.addCallback(lambda ign:
- self.shouldFail(AssertionError, "bad_kids2",
- "requires metadata to be a dict",
+ self.shouldFail(MustBeDeepImmutableError, "bad_kids2",
+ "is not immutable",
c.create_immutable_dirnode,
bad_kids2))
- bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})}
+ bad_kids3 = {u"one": (nm.create_from_cap(one_uri), None)}
d.addCallback(lambda ign:
- self.shouldFail(NotDeepImmutableError, "bad_kids3",
- "is not immutable",
+ self.shouldFail(AssertionError, "bad_kids3",
+ "requires metadata to be a dict",
c.create_immutable_dirnode,
bad_kids3))
- bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})}
+ bad_kids4 = {u"one": (nm.create_from_cap(mut_write_uri), {})}
d.addCallback(lambda ign:
- self.shouldFail(NotDeepImmutableError, "bad_kids4",
+ self.shouldFail(MustBeDeepImmutableError, "bad_kids4",
"is not immutable",
c.create_immutable_dirnode,
bad_kids4))
+ bad_kids5 = {u"one": (nm.create_from_cap(mut_read_uri), {})}
+ d.addCallback(lambda ign:
+ self.shouldFail(MustBeDeepImmutableError, "bad_kids5",
+ "is not immutable",
+ c.create_immutable_dirnode,
+ bad_kids5))
d.addCallback(lambda ign: c.create_immutable_dirnode({}))
def _created_empty(dn):
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
self.failIf(dn.is_mutable())
self.failUnless(dn.is_readonly())
+ self.failIf(dn.is_unknown())
+ self.failUnless(dn.is_allowed_in_immutable_directory())
+ dn.raise_error()
rep = str(dn)
self.failUnless("RO-IMM" in rep)
cap = dn.get_cap()
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
self.failIf(dn.is_mutable())
self.failUnless(dn.is_readonly())
+ self.failIf(dn.is_unknown())
+ self.failUnless(dn.is_allowed_in_immutable_directory())
+ dn.raise_error()
rep = str(dn)
self.failUnless("RO-IMM" in rep)
cap = dn.get_cap()
d.addCallback(_check_kids)
d.addCallback(lambda ign: n.get(u"subdir"))
d.addCallback(lambda sd: self.failIf(sd.is_mutable()))
- bad_kids = {u"one": (nm.create_from_cap(mut_writecap), {})}
+ bad_kids = {u"one": (nm.create_from_cap(mut_write_uri), {})}
d.addCallback(lambda ign:
- self.shouldFail(NotDeepImmutableError, "YZ",
+ self.shouldFail(MustBeDeepImmutableError, "YZ",
"is not immutable",
n.create_subdirectory,
u"sub2", bad_kids, mutable=False))
d.addCallback(_made_parent)
return d
-
def test_check(self):
self.basedir = "dirnode/Dirnode/test_check"
self.set_up_grid()
ro_dn = c.create_node_from_uri(ro_uri)
self.failUnless(ro_dn.is_readonly())
self.failUnless(ro_dn.is_mutable())
+ self.failIf(ro_dn.is_unknown())
+ self.failIf(ro_dn.is_allowed_in_immutable_directory())
+ ro_dn.raise_error()
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
ro_dn.set_uri, u"newchild", filecap, filecap)
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
ro_dn.set_node, u"newchild", filenode)
- self.shouldFail(dirnode.NotMutableError, "set_nodes ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None,
ro_dn.set_nodes, { u"newchild": (filenode, None) })
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
ro_dn.add_file, u"newchild", uploadable)
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
ro_dn.delete, u"child")
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
ro_dn.create_subdirectory, u"newchild")
- self.shouldFail(dirnode.NotMutableError, "set_metadata_for ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None,
ro_dn.set_metadata_for, u"child", {})
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
ro_dn.move_child_to, u"child", rw_dn)
- self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
+ self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
rw_dn.move_child_to, u"child", ro_dn)
return ro_dn.list()
d.addCallback(_ready)
nodemaker = NodeMaker(None, None, None,
None, None, None,
{"k": 3, "n": 10}, None)
- writecap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
- filenode = nodemaker.create_from_cap(writecap)
+ write_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
+ filenode = nodemaker.create_from_cap(write_uri)
node = dirnode.DirectoryNode(filenode, nodemaker, None)
children = node._unpack_contents(known_tree)
self._check_children(children)
self.failUnlessIn("lit", packed)
kids = self._make_kids(nm, ["imm", "lit", "write"])
- self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+ self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children,
fn, kids, deep_immutable=True)
# read-only is not enough: all children must be immutable
kids = self._make_kids(nm, ["imm", "lit", "read"])
- self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+ self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children,
fn, kids, deep_immutable=True)
kids = self._make_kids(nm, ["imm", "lit", "dirwrite"])
- self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+ self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children,
fn, kids, deep_immutable=True)
kids = self._make_kids(nm, ["imm", "lit", "dirread"])
- self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+ self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
dirnode.pack_children,
fn, kids, deep_immutable=True)
def get_cap(self):
return self.uri
+
def get_uri(self):
return self.uri.to_string()
+
+ def get_write_uri(self):
+ return self.uri.to_string()
+
def download_best_version(self):
return defer.succeed(self.data)
+
def get_writekey(self):
return "writekey"
+
def is_readonly(self):
return False
+
def is_mutable(self):
return True
+
+ def is_unknown(self):
+ return False
+
+ def is_allowed_in_immutable_directory(self):
+ return False
+
def modify(self, modifier):
self.data = modifier(self.data, None, True)
return defer.succeed(None)
self.nodemaker = client.nodemaker
def test_from_future(self):
- # create a dirnode that contains unknown URI types, and make sure we
- # tolerate them properly. Since dirnodes aren't allowed to add
- # unknown node types, we have to be tricky.
+ # Create a mutable directory that contains unknown URI types, and make sure
+ # we tolerate them properly.
d = self.nodemaker.create_new_mutable_directory()
- future_writecap = "x-tahoe-crazy://I_am_from_the_future."
- future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
- future_node = UnknownNode(future_writecap, future_readcap)
+ future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
+ future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
+ future_imm_uri = "x-tahoe-crazy-immutable://I_am_from_the_future."
+ future_node = UnknownNode(future_write_uri, future_read_uri)
def _then(n):
self._node = n
return n.set_node(u"future", future_node)
d.addCallback(_then)
- # we should be prohibited from adding an unknown URI to a directory,
- # since we don't know how to diminish the cap to a readcap (for the
- # dirnode's rocap slot), and we don't want to accidentally grant
- # write access to a holder of the dirnode's readcap.
+ # We should be prohibited from adding an unknown URI to a directory
+ # just in the rw_uri slot, since we don't know how to diminish the cap
+ # to a readcap (for the ro_uri slot).
d.addCallback(lambda ign:
- self.shouldFail(CannotPackUnknownNodeError,
+ self.shouldFail(MustNotBeUnknownRWError,
"copy unknown",
- "cannot pack unknown node as child add",
+ "cannot attach unknown rw cap as child",
self._node.set_uri, u"add",
- future_writecap, future_readcap))
+ future_write_uri, None))
+
+ # However, we should be able to add both rw_uri and ro_uri as a pair of
+ # unknown URIs.
+ d.addCallback(lambda ign: self._node.set_uri(u"add-pair",
+ future_write_uri, future_read_uri))
+
+ # and to add an URI prefixed with "ro." or "imm." when it is given in a
+ # write slot (or URL parameter).
+ d.addCallback(lambda ign: self._node.set_uri(u"add-ro",
+ "ro." + future_read_uri, None))
+ d.addCallback(lambda ign: self._node.set_uri(u"add-imm",
+ "imm." + future_imm_uri, None))
+
d.addCallback(lambda ign: self._node.list())
def _check(children):
- self.failUnlessEqual(len(children), 1)
+ self.failUnlessEqual(len(children), 4)
(fn, metadata) = children[u"future"]
self.failUnless(isinstance(fn, UnknownNode), fn)
- self.failUnlessEqual(fn.get_uri(), future_writecap)
- self.failUnlessEqual(fn.get_readonly_uri(), future_readcap)
- # but we *should* be allowed to copy this node, because the
- # UnknownNode contains all the information that was in the
- # original directory (readcap and writecap), so we're preserving
- # everything.
+ self.failUnlessEqual(fn.get_uri(), future_write_uri)
+ self.failUnlessEqual(fn.get_write_uri(), future_write_uri)
+ self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
+
+ (fn2, metadata2) = children[u"add-pair"]
+ self.failUnless(isinstance(fn2, UnknownNode), fn2)
+ self.failUnlessEqual(fn2.get_uri(), future_write_uri)
+ self.failUnlessEqual(fn2.get_write_uri(), future_write_uri)
+ self.failUnlessEqual(fn2.get_readonly_uri(), "ro." + future_read_uri)
+
+ (fn3, metadata3) = children[u"add-ro"]
+ self.failUnless(isinstance(fn3, UnknownNode), fn3)
+ self.failUnlessEqual(fn3.get_uri(), "ro." + future_read_uri)
+ self.failUnlessEqual(fn3.get_write_uri(), None)
+ self.failUnlessEqual(fn3.get_readonly_uri(), "ro." + future_read_uri)
+
+ (fn4, metadata4) = children[u"add-imm"]
+ self.failUnless(isinstance(fn4, UnknownNode), fn4)
+ self.failUnlessEqual(fn4.get_uri(), "imm." + future_imm_uri)
+ self.failUnlessEqual(fn4.get_write_uri(), None)
+ self.failUnlessEqual(fn4.get_readonly_uri(), "imm." + future_imm_uri)
+
+ # We should also be allowed to copy the "future" UnknownNode, because
+ # it contains all the information that was in the original directory
+ # (readcap and writecap), so we're preserving everything.
return self._node.set_node(u"copy", fn)
d.addCallback(_check)
+
d.addCallback(lambda ign: self._node.list())
def _check2(children):
- self.failUnlessEqual(len(children), 2)
+ self.failUnlessEqual(len(children), 5)
(fn, metadata) = children[u"copy"]
self.failUnless(isinstance(fn, UnknownNode), fn)
- self.failUnlessEqual(fn.get_uri(), future_writecap)
- self.failUnlessEqual(fn.get_readonly_uri(), future_readcap)
+ self.failUnlessEqual(fn.get_uri(), future_write_uri)
+ self.failUnlessEqual(fn.get_write_uri(), future_write_uri)
+ self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
+ d.addCallback(_check2)
return d
+ def test_unknown_strip_prefix_for_ro(self):
+ self.failUnlessEqual(strip_prefix_for_ro("foo", False), "foo")
+ self.failUnlessEqual(strip_prefix_for_ro("ro.foo", False), "foo")
+ self.failUnlessEqual(strip_prefix_for_ro("imm.foo", False), "imm.foo")
+ self.failUnlessEqual(strip_prefix_for_ro("foo", True), "foo")
+ self.failUnlessEqual(strip_prefix_for_ro("ro.foo", True), "foo")
+ self.failUnlessEqual(strip_prefix_for_ro("imm.foo", True), "foo")
+
+ def test_unknownnode(self):
+ mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ lit_uri = "URI:LIT:n5xgk"
+
+ # This does not attempt to be exhaustive.
+ no_no = [# Opaque node, but not an error.
+ ( 0, UnknownNode(None, None)),
+ ( 1, UnknownNode(None, None, deep_immutable=True)),
+ ]
+ unknown_rw = [# These are errors because we're only given a rw_uri, and we can't
+ # diminish it.
+ ( 2, UnknownNode("foo", None)),
+ ( 3, UnknownNode("foo", None, deep_immutable=True)),
+ ( 4, UnknownNode("ro.foo", None, deep_immutable=True)),
+ ( 5, UnknownNode("ro." + mut_read_uri, None, deep_immutable=True)),
+ ( 6, UnknownNode("URI:SSK-RO:foo", None, deep_immutable=True)),
+ ( 7, UnknownNode("URI:SSK:foo", None)),
+ ]
+ must_be_ro = [# These are errors because a readonly constraint is not met.
+ ( 8, UnknownNode("ro." + mut_write_uri, None)),
+ ( 9, UnknownNode(None, "ro." + mut_write_uri)),
+ ]
+ must_be_imm = [# These are errors because an immutable constraint is not met.
+ (10, UnknownNode(None, "ro.URI:SSK-RO:foo", deep_immutable=True)),
+ (11, UnknownNode(None, "imm.URI:SSK:foo")),
+ (12, UnknownNode(None, "imm.URI:SSK-RO:foo")),
+ (13, UnknownNode("bar", "ro.foo", deep_immutable=True)),
+ (14, UnknownNode("bar", "imm.foo", deep_immutable=True)),
+ (15, UnknownNode("bar", "imm." + lit_uri, deep_immutable=True)),
+ (16, UnknownNode("imm." + mut_write_uri, None)),
+ (17, UnknownNode("imm." + mut_read_uri, None)),
+ (18, UnknownNode("bar", "imm.foo")),
+ ]
+ bad_uri = [# These are errors because the URI is bad once we've stripped the prefix.
+ (19, UnknownNode("ro.URI:SSK-RO:foo", None)),
+ (20, UnknownNode("imm.URI:CHK:foo", None, deep_immutable=True)),
+ ]
+ ro_prefixed = [# These are valid, and the readcap should end up with a ro. prefix.
+ (21, UnknownNode(None, "foo")),
+ (22, UnknownNode(None, "ro.foo")),
+ (32, UnknownNode(None, "ro." + lit_uri)),
+ (23, UnknownNode("bar", "foo")),
+ (24, UnknownNode("bar", "ro.foo")),
+ (32, UnknownNode("bar", "ro." + lit_uri)),
+ (25, UnknownNode("ro.foo", None)),
+ (30, UnknownNode("ro." + lit_uri, None)),
+ ]
+ imm_prefixed = [# These are valid, and the readcap should end up with an imm. prefix.
+ (26, UnknownNode(None, "foo", deep_immutable=True)),
+ (27, UnknownNode(None, "ro.foo", deep_immutable=True)),
+ (28, UnknownNode(None, "imm.foo")),
+ (29, UnknownNode(None, "imm.foo", deep_immutable=True)),
+ (31, UnknownNode("imm." + lit_uri, None)),
+ (31, UnknownNode("imm." + lit_uri, None, deep_immutable=True)),
+ (33, UnknownNode(None, "imm." + lit_uri)),
+ (33, UnknownNode(None, "imm." + lit_uri, deep_immutable=True)),
+ ]
+ error = unknown_rw + must_be_ro + must_be_imm + bad_uri
+ ok = ro_prefixed + imm_prefixed
+
+ for (i, n) in no_no + error + ok:
+ self.failUnless(n.is_unknown(), i)
+
+ for (i, n) in no_no + error:
+ self.failUnless(n.get_uri() is None, i)
+ self.failUnless(n.get_write_uri() is None, i)
+ self.failUnless(n.get_readonly_uri() is None, i)
+
+ for (i, n) in no_no + ok:
+ n.raise_error()
+
+ for (i, n) in unknown_rw:
+ self.failUnlessRaises(MustNotBeUnknownRWError, lambda: n.raise_error())
+
+ for (i, n) in must_be_ro:
+ self.failUnlessRaises(MustBeReadonlyError, lambda: n.raise_error())
+
+ for (i, n) in must_be_imm:
+ self.failUnlessRaises(MustBeDeepImmutableError, lambda: n.raise_error())
+
+ for (i, n) in bad_uri:
+ self.failUnlessRaises(uri.BadURIError, lambda: n.raise_error())
+
+ for (i, n) in ok:
+ self.failIf(n.get_readonly_uri() is None, i)
+
+ for (i, n) in ro_prefixed:
+ self.failUnless(n.get_readonly_uri().startswith("ro."), i)
+
+ for (i, n) in imm_prefixed:
+ self.failUnless(n.get_readonly_uri().startswith("imm."), i)
+
+
class DeepStats(unittest.TestCase):
timeout = 240 # It takes longer than 120 seconds on Francois's arm box.
def test_stats(self):
self.failUnlessEqual(fn1.get_readcap(), u)
self.failUnlessEqual(fn1.is_readonly(), True)
self.failUnlessEqual(fn1.is_mutable(), False)
+ self.failUnlessEqual(fn1.is_unknown(), False)
+ self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
+ self.failUnlessEqual(fn1.get_write_uri(), None)
self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
self.failUnlessEqual(fn1.get_size(), 1000)
self.failUnlessEqual(fn1.get_storage_index(), u.storage_index)
+ fn1.raise_error()
+ fn2.raise_error()
d = {}
d[fn1] = 1 # exercise __hash__
v = fn1.get_verify_cap()
self.failUnless(isinstance(v, uri.CHKFileVerifierURI))
self.failUnlessEqual(fn1.get_repair_cap(), v)
+ self.failUnlessEqual(v.is_readonly(), True)
+ self.failUnlessEqual(v.is_mutable(), False)
def test_literal_filenode(self):
self.failUnlessEqual(fn1.get_readcap(), u)
self.failUnlessEqual(fn1.is_readonly(), True)
self.failUnlessEqual(fn1.is_mutable(), False)
+ self.failUnlessEqual(fn1.is_unknown(), False)
+ self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
+ self.failUnlessEqual(fn1.get_write_uri(), None)
self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
self.failUnlessEqual(fn1.get_size(), len(DATA))
self.failUnlessEqual(fn1.get_storage_index(), None)
+ fn1.raise_error()
+ fn2.raise_error()
d = {}
d[fn1] = 1 # exercise __hash__
self.failUnlessEqual(n.get_writekey(), wk)
self.failUnlessEqual(n.get_readkey(), rk)
self.failUnlessEqual(n.get_storage_index(), si)
- # these itmes are populated on first read (or create), so until that
+ # these items are populated on first read (or create), so until that
# happens they'll be None
self.failUnlessEqual(n.get_privkey(), None)
self.failUnlessEqual(n.get_encprivkey(), None)
self.failUnlessEqual(n.get_pubkey(), None)
self.failUnlessEqual(n.get_uri(), u.to_string())
+ self.failUnlessEqual(n.get_write_uri(), u.to_string())
self.failUnlessEqual(n.get_readonly_uri(), u.get_readonly().to_string())
self.failUnlessEqual(n.get_cap(), u)
self.failUnlessEqual(n.get_readcap(), u.get_readonly())
self.failUnlessEqual(n.is_mutable(), True)
self.failUnlessEqual(n.is_readonly(), False)
+ self.failUnlessEqual(n.is_unknown(), False)
+ self.failUnlessEqual(n.is_allowed_in_immutable_directory(), False)
+ n.raise_error()
n2 = MutableFileNode(None, None, client.get_encoding_parameters(),
None).init_from_cap(u)
self.failUnlessEqual(n, n2)
self.failIfEqual(n, "not even the right type")
self.failIfEqual(n, u) # not the right class
+ n.raise_error()
d = {n: "can these be used as dictionary keys?"}
d[n2] = "replace the old one"
self.failUnlessEqual(len(d), 1)
self.failUnlessEqual(nro.get_readonly(), nro)
self.failUnlessEqual(nro.get_cap(), u.get_readonly())
self.failUnlessEqual(nro.get_readcap(), u.get_readonly())
+ self.failUnlessEqual(nro.is_mutable(), True)
+ self.failUnlessEqual(nro.is_readonly(), True)
+ self.failUnlessEqual(nro.is_unknown(), False)
+ self.failUnlessEqual(nro.is_allowed_in_immutable_directory(), False)
nro_u = nro.get_uri()
self.failUnlessEqual(nro_u, nro.get_readonly_uri())
self.failUnlessEqual(nro_u, u.get_readonly().to_string())
- self.failUnlessEqual(nro.is_mutable(), True)
- self.failUnlessEqual(nro.is_readonly(), True)
+ self.failUnlessEqual(nro.get_write_uri(), None)
self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap
+ nro.raise_error()
v = n.get_verify_cap()
self.failUnless(isinstance(v, uri.SSKVerifierURI))
from allmydata.interfaces import IDirectoryNode, IFileNode, \
NoSuchChildError, NoSharesError
from allmydata.monitor import Monitor
-from allmydata.mutable.common import NotMutableError
+from allmydata.mutable.common import NotWriteableError
from allmydata.mutable import layout as mutable_layout
from foolscap.api import DeadReferenceError
from twisted.python.failure import Failure
d1.addCallback(lambda res: dirnode.list())
d1.addCallback(self.log, "dirnode.list")
- d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
+ d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
d1.addCallback(self.log, "doing add_file(ro)")
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)")
- d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
+ d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
d1.addCallback(self.log, "doing get(ro)")
d1.addCallback(lambda res: dirnode.get(u"mydata992"))
self.failUnless(IFileNode.providedBy(filenode)))
d1.addCallback(self.log, "doing delete(ro)")
- d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, u"mydata992"))
+ d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "delete(nope)", None, dirnode.delete, u"mydata992"))
- d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri))
+ d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri))
d1.addCallback(lambda res: self.shouldFail2(NoSuchChildError, "get(missing)", "missing", dirnode.get, u"missing"))
personal = self._personal_node
- d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope"))
+ d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope"))
d1.addCallback(self.log, "doing move_child_to(ro)2")
- d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope"))
+ d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope"))
d1.addCallback(self.log, "finished with _got_s2ro")
return d1
from allmydata import uri
from allmydata.util import hashutil, base32
from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \
- IVerifierURI
+ IVerifierURI, CapConstraintError
class Literal(unittest.TestCase):
def _help_test(self, data):
self.failIf(IDirnodeURI.providedBy(u2))
self.failUnlessEqual(u2.data, data)
self.failUnlessEqual(u2.get_size(), len(data))
- self.failUnless(u.is_readonly())
- self.failIf(u.is_mutable())
+ self.failUnless(u2.is_readonly())
+ self.failIf(u2.is_mutable())
+
+ u2i = uri.from_string(u.to_string(), deep_immutable=True)
+ self.failUnless(IFileURI.providedBy(u2i))
+ self.failIf(IDirnodeURI.providedBy(u2i))
+ self.failUnlessEqual(u2i.data, data)
+ self.failUnlessEqual(u2i.get_size(), len(data))
+ self.failUnless(u2i.is_readonly())
+ self.failIf(u2i.is_mutable())
u3 = u.get_readonly()
self.failUnlessIdentical(u, u3)
fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834'
chk1 = uri.CHKFileURI.init_from_string(fileURI)
chk2 = uri.CHKFileURI.init_from_string(fileURI)
+ unk = uri.UnknownURI("lafs://from_the_future")
self.failIfEqual(lit1, chk1)
self.failUnlessEqual(chk1, chk2)
self.failIfEqual(chk1, "not actually a URI")
# these should be hashable too
- s = set([lit1, chk1, chk2])
- self.failUnlessEqual(len(s), 2) # since chk1==chk2
+ s = set([lit1, chk1, chk2, unk])
+ self.failUnlessEqual(len(s), 3) # since chk1==chk2
def test_is_uri(self):
lit1 = uri.LiteralFileURI("some data").to_string()
self.failUnless(uri.is_uri(lit1))
self.failIf(uri.is_uri(None))
+ def test_is_literal_file_uri(self):
+ lit1 = uri.LiteralFileURI("some data").to_string()
+ self.failUnless(uri.is_literal_file_uri(lit1))
+ self.failIf(uri.is_literal_file_uri(None))
+ self.failIf(uri.is_literal_file_uri("foo"))
+ self.failIf(uri.is_literal_file_uri("ro.foo"))
+ self.failIf(uri.is_literal_file_uri("URI:LITfoo"))
+ self.failUnless(uri.is_literal_file_uri("ro.URI:LIT:foo"))
+ self.failUnless(uri.is_literal_file_uri("imm.URI:LIT:foo"))
+
+ def test_has_uri_prefix(self):
+ self.failUnless(uri.has_uri_prefix("URI:foo"))
+ self.failUnless(uri.has_uri_prefix("ro.URI:foo"))
+ self.failUnless(uri.has_uri_prefix("imm.URI:foo"))
+ self.failIf(uri.has_uri_prefix(None))
+ self.failIf(uri.has_uri_prefix("foo"))
+
class CHKFile(unittest.TestCase):
def test_pack(self):
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
self.failUnless(IFileURI.providedBy(u))
self.failIf(IDirnodeURI.providedBy(u))
self.failUnlessEqual(u.get_size(), 1234)
- self.failUnless(u.is_readonly())
- self.failIf(u.is_mutable())
+
u_ro = u.get_readonly()
self.failUnlessIdentical(u, u_ro)
he = u.to_human_encoding()
self.failUnless(IFileURI.providedBy(u2))
self.failIf(IDirnodeURI.providedBy(u2))
self.failUnlessEqual(u2.get_size(), 1234)
- self.failUnless(u2.is_readonly())
- self.failIf(u2.is_mutable())
+
+ u2i = uri.from_string(u.to_string(), deep_immutable=True)
+ self.failUnlessEqual(u.to_string(), u2i.to_string())
+ u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string())
+ self.failUnlessEqual(u.to_string(), u2ro.to_string())
+ u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string())
+ self.failUnlessEqual(u.to_string(), u2imm.to_string())
v = u.get_verify_cap()
self.failUnless(isinstance(v.to_string(), str))
+ self.failUnless(v.is_readonly())
+ self.failIf(v.is_mutable())
+
v2 = uri.from_string(v.to_string())
self.failUnlessEqual(v, v2)
he = v.to_human_encoding()
total_shares=10,
size=1234)
self.failUnless(isinstance(v3.to_string(), str))
+ self.failUnless(v3.is_readonly())
+ self.failIf(v3.is_mutable())
def test_pack_badly(self):
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
self.failUnlessEqual(readable["UEB_hash"],
base32.b2a(hashutil.uri_extension_hash(ext)))
-class Invalid(unittest.TestCase):
+class Unknown(unittest.TestCase):
def test_from_future(self):
# any URI type that we don't recognize should be treated as unknown
future_uri = "I am a URI from the future. Whatever you do, don't "
u = uri.from_string(future_uri)
self.failUnless(isinstance(u, uri.UnknownURI))
self.failUnlessEqual(u.to_string(), future_uri)
+ self.failUnless(u.get_readonly() is None)
+ self.failUnless(u.get_error() is None)
+
+ u2 = uri.UnknownURI(future_uri, error=CapConstraintError("..."))
+ self.failUnlessEqual(u.to_string(), future_uri)
+ self.failUnless(u2.get_readonly() is None)
+ self.failUnless(isinstance(u2.get_error(), CapConstraintError))
class Constraint(unittest.TestCase):
def test_constraint(self):
self.failUnless(IMutableFileURI.providedBy(u2))
self.failIf(IDirnodeURI.providedBy(u2))
+ u2i = uri.from_string(u.to_string(), deep_immutable=True)
+ self.failUnless(isinstance(u2i, uri.UnknownURI), u2i)
+ u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string())
+ self.failUnless(isinstance(u2ro, uri.UnknownURI), u2ro)
+ u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string())
+ self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm)
+
u3 = u2.get_readonly()
readkey = hashutil.ssk_readkey_hash(writekey)
self.failUnlessEqual(u3.fingerprint, fingerprint)
self.failUnless(IMutableFileURI.providedBy(u3))
self.failIf(IDirnodeURI.providedBy(u3))
+ u3i = uri.from_string(u3.to_string(), deep_immutable=True)
+ self.failUnless(isinstance(u3i, uri.UnknownURI), u3i)
+ u3ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u3.to_string())
+ self.failUnlessEqual(u3.to_string(), u3ro.to_string())
+ u3imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u3.to_string())
+ self.failUnless(isinstance(u3imm, uri.UnknownURI), u3imm)
+
he = u3.to_human_encoding()
u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
self.failUnlessEqual(u3, u3_h)
self.failUnless(IMutableFileURI.providedBy(u4))
self.failIf(IDirnodeURI.providedBy(u4))
+ u4i = uri.from_string(u4.to_string(), deep_immutable=True)
+ self.failUnless(isinstance(u4i, uri.UnknownURI), u4i)
+ u4ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u4.to_string())
+ self.failUnlessEqual(u4.to_string(), u4ro.to_string())
+ u4imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u4.to_string())
+ self.failUnless(isinstance(u4imm, uri.UnknownURI), u4imm)
+
u4a = uri.from_string(u4.to_string())
self.failUnlessEqual(u4a, u4)
self.failUnless("ReadonlySSKFileURI" in str(u4a))
self.failIf(IFileURI.providedBy(u2))
self.failUnless(IDirnodeURI.providedBy(u2))
+ u2i = uri.from_string(u1.to_string(), deep_immutable=True)
+ self.failUnless(isinstance(u2i, uri.UnknownURI))
+
u3 = u2.get_readonly()
self.failUnless(u3.is_readonly())
self.failUnless(u3.is_mutable())
self.failUnless(IURI.providedBy(u3))
self.failIf(IFileURI.providedBy(u3))
self.failUnless(IDirnodeURI.providedBy(u3))
+
+ u3i = uri.from_string(u2.to_string(), deep_immutable=True)
+ self.failUnless(isinstance(u3i, uri.UnknownURI))
+
u3n = u3._filenode_uri
self.failUnless(u3n.is_readonly())
self.failUnless(u3n.is_mutable())
self.failIf(IFileURI.providedBy(u2))
self.failUnless(IDirnodeURI.providedBy(u2))
+ u2i = uri.from_string(u1.to_string(), deep_immutable=True)
+ self.failUnlessEqual(u1.to_string(), u2i.to_string())
+
u3 = u2.get_readonly()
self.failUnlessEqual(u3.to_string(), u2.to_string())
self.failUnless(str(u3))
+ u3i = uri.from_string(u2.to_string(), deep_immutable=True)
+ self.failUnlessEqual(u2.to_string(), u3i.to_string())
+
u2_verifier = u2.get_verify_cap()
self.failUnless(isinstance(u2_verifier,
uri.ImmutableDirectoryURIVerifier),
from twisted.web import client, error, http
from twisted.python import failure, log
from nevow import rend
-from allmydata import interfaces, uri, webish
+from allmydata import interfaces, uri, webish, dirnode
from allmydata.storage.shares import get_share_file
from allmydata.storage_client import StorageFarmBroker
from allmydata.immutable import upload, download
from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
from allmydata.util import fileutil, base32
from allmydata.util.consumer import download_to_data
+from allmydata.util.netstring import split_netstring
from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
from allmydata.interfaces import IMutableFileNode
d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
# TODO: we lose the response code, so we can't check this
#self.failUnlessEqual(responsecode, 201)
- d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
self.NEWFILE_CONTENTS))
self.NEWFILE_CONTENTS)
# TODO: we lose the response code, so we can't check this
#self.failUnlessEqual(responsecode, 201)
- d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
self.NEWFILE_CONTENTS))
self.failIf(u.is_readonly())
return res
d.addCallback(_check_uri)
- d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(self._foo_node,
u"new.txt",
d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
# TODO: we lose the response code, so we can't check this
#self.failUnlessEqual(responsecode, 200)
- d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
self.NEWFILE_CONTENTS))
def test_PUT_NEWFILEURL_mkdirs(self):
d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
fn = self._foo_node
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
d.addCallback(lambda res:
self.failUnless(re.search(get_sub, res), res)
d.addCallback(_check)
- # look at a directory which is readonly
+ # look at a readonly directory
d.addCallback(lambda res:
self.GET(self.public_url + "/reedownlee", followRedirect=True))
def _check2(res):
return d
def test_POST_NEWDIRURL_initial_children(self):
- (newkids, filecap1, filecap2, filecap3,
- dircap) = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-imm", filecap1))
+ self.failUnlessROChildURIIs(n, u"child-imm",
+ caps['filecap1']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-mutable",
- filecap2))
+ self.failUnlessRWChildURIIs(n, u"child-mutable",
+ caps['filecap2']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-mutable-ro",
- filecap3))
+ self.failUnlessROChildURIIs(n, u"child-mutable-ro",
+ caps['filecap3']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"dirchild", dircap))
+ self.failUnlessROChildURIIs(n, u"unknownchild-ro",
+ caps['unknown_rocap']))
+ d2.addCallback(lambda ign:
+ self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
+ caps['unknown_rwcap']))
+ d2.addCallback(lambda ign:
+ self.failUnlessROChildURIIs(n, u"unknownchild-imm",
+ caps['unknown_immcap']))
+ d2.addCallback(lambda ign:
+ self.failUnlessRWChildURIIs(n, u"dirchild",
+ caps['dircap']))
return d2
d.addCallback(_check)
d.addCallback(lambda res:
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
- d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
+ d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
return d
def test_POST_NEWDIRURL_immutable(self):
- (newkids, filecap1, immdircap) = self._create_immutable_children()
+ (newkids, caps) = self._create_immutable_children()
d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-imm", filecap1))
+ self.failUnlessROChildURIIs(n, u"child-imm",
+ caps['filecap1']))
+ d2.addCallback(lambda ign:
+ self.failUnlessROChildURIIs(n, u"unknownchild-imm",
+ caps['unknown_immcap']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"dirchild-imm",
- immdircap))
+ self.failUnlessROChildURIIs(n, u"dirchild-imm",
+ caps['immdircap']))
return d2
d.addCallback(_check)
d.addCallback(lambda res:
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
- d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
+ d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
- d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
+ d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
+ d.addCallback(lambda res: self._foo_node.get(u"newdir"))
+ d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
d.addErrback(self.explain_web_error)
return d
def test_POST_NEWDIRURL_immutable_bad(self):
- (newkids, filecap1, filecap2, filecap3,
- dircap) = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
"400 Bad Request",
- "a mkdir-immutable operation was given a child that was not itself immutable",
+ "needed to be immutable but was not",
self.POST2,
self.public_url + "/foo/newdir?t=mkdir-immutable",
simplejson.dumps(newkids))
d.addCallback(_check)
return d
- def failUnlessChildURIIs(self, node, name, expected_uri):
+ def failUnlessRWChildURIIs(self, node, name, expected_uri):
+ assert isinstance(name, unicode)
+ d = node.get_child_at_path(name)
+ def _check(child):
+ self.failUnless(child.is_unknown() or not child.is_readonly())
+ self.failUnlessEqual(child.get_uri(), expected_uri.strip())
+ self.failUnlessEqual(child.get_write_uri(), expected_uri.strip())
+ expected_ro_uri = self._make_readonly(expected_uri)
+ if expected_ro_uri:
+ self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
+ d.addCallback(_check)
+ return d
+
+ def failUnlessROChildURIIs(self, node, name, expected_uri):
assert isinstance(name, unicode)
d = node.get_child_at_path(name)
def _check(child):
+ self.failUnless(child.is_unknown() or child.is_readonly())
+ self.failUnlessEqual(child.get_write_uri(), None)
self.failUnlessEqual(child.get_uri(), expected_uri.strip())
+ self.failUnlessEqual(child.get_readonly_uri(), expected_uri.strip())
d.addCallback(_check)
return d
- def failUnlessURIMatchesChild(self, got_uri, node, name):
+ def failUnlessURIMatchesRWChild(self, got_uri, node, name):
assert isinstance(name, unicode)
d = node.get_child_at_path(name)
def _check(child):
+ self.failUnless(child.is_unknown() or not child.is_readonly())
+ self.failUnlessEqual(child.get_uri(), got_uri.strip())
+ self.failUnlessEqual(child.get_write_uri(), got_uri.strip())
+ expected_ro_uri = self._make_readonly(got_uri)
+ if expected_ro_uri:
+ self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
+ d.addCallback(_check)
+ return d
+
+ def failUnlessURIMatchesROChild(self, got_uri, node, name):
+ assert isinstance(name, unicode)
+ d = node.get_child_at_path(name)
+ def _check(child):
+ self.failUnless(child.is_unknown() or child.is_readonly())
+ self.failUnlessEqual(child.get_write_uri(), None)
self.failUnlessEqual(got_uri.strip(), child.get_uri())
+ self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri())
d.addCallback(_check)
return d
d = self.POST(self.public_url + "/foo", t="upload",
file=("new.txt", self.NEWFILE_CONTENTS))
fn = self._foo_node
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, u"new.txt",
self.NEWFILE_CONTENTS))
d = self.POST(self.public_url + "/foo", t="upload",
file=(filename, self.NEWFILE_CONTENTS))
fn = self._foo_node
- d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
+ d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, filename,
self.NEWFILE_CONTENTS))
name=filename,
file=("overridden", self.NEWFILE_CONTENTS))
fn = self._foo_node
- d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
+ d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, filename,
self.NEWFILE_CONTENTS))
d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
file=("new.txt", self.NEWFILE_CONTENTS))
fn = self._foo_node
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(fn, u"new.txt",
self.NEWFILE_CONTENTS))
self.POST(self.public_url + "/foo", t="upload",
mutable="true",
file=("new.txt", NEWER_CONTENTS)))
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(fn, u"new.txt",
NEWER_CONTENTS))
NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
d.addCallback(lambda res:
self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
d.addCallback(lambda res:
self.failUnlessMutableChildContentsAre(fn, u"new.txt",
NEW2_CONTENTS))
d = self.POST(self.public_url + "/foo", t="upload",
file=("bar.txt", self.NEWFILE_CONTENTS))
fn = self._foo_node
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, u"bar.txt",
self.NEWFILE_CONTENTS))
fn = self._foo_node
d = self.POST(self.public_url + "/foo", t="upload",
name="new.txt", file=self.NEWFILE_CONTENTS)
- d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(fn, u"new.txt",
self.NEWFILE_CONTENTS))
return d
def test_POST_mkdir_initial_children(self):
- newkids, filecap1, ign, ign, ign = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.POST2(self.public_url +
"/foo?t=mkdir-with-children&name=newdir",
simplejson.dumps(newkids))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
- d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
+ d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
return d
def test_POST_mkdir_immutable(self):
- (newkids, filecap1, immdircap) = self._create_immutable_children()
+ (newkids, caps) = self._create_immutable_children()
d = self.POST2(self.public_url +
"/foo?t=mkdir-immutable&name=newdir",
simplejson.dumps(newkids))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
- d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
+ d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
- d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
+ d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
+ d.addCallback(lambda res: self._foo_node.get(u"newdir"))
+ d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
return d
def test_POST_mkdir_immutable_bad(self):
- (newkids, filecap1, filecap2, filecap3,
- dircap) = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
"400 Bad Request",
- "a mkdir-immutable operation was given a child that was not itself immutable",
+ "needed to be immutable but was not",
self.POST2,
self.public_url +
"/foo?t=mkdir-immutable&name=newdir",
d.addErrback(self.explain_web_error)
return d
+ def _make_readonly(self, u):
+ ro_uri = uri.from_string(u).get_readonly()
+ if ro_uri is None:
+ return None
+ return ro_uri.to_string()
+
def _create_initial_children(self):
contents, n, filecap1 = self.makefile(12)
md1 = {"metakey1": "metavalue1"}
filecap2 = make_mutable_file_uri()
node3 = self.s.create_node_from_uri(make_mutable_file_uri())
filecap3 = node3.get_readonly_uri()
+ unknown_rwcap = "lafs://from_the_future"
+ unknown_rocap = "ro.lafs://readonly_from_the_future"
+ unknown_immcap = "imm.lafs://immutable_from_the_future"
node4 = self.s.create_node_from_uri(make_mutable_file_uri())
dircap = DirectoryNode(node4, None, None).get_uri()
- newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
- "metadata": md1, }],
- u"child-mutable": ["filenode", {"rw_uri": filecap2}],
+ newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
+ "ro_uri": self._make_readonly(filecap1),
+ "metadata": md1, }],
+ u"child-mutable": ["filenode", {"rw_uri": filecap2,
+ "ro_uri": self._make_readonly(filecap2)}],
u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
- u"dirchild": ["dirnode", {"rw_uri": dircap}],
+ u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
+ "ro_uri": unknown_rocap}],
+ u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
+ u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
+ u"dirchild": ["dirnode", {"rw_uri": dircap,
+ "ro_uri": self._make_readonly(dircap)}],
}
- return newkids, filecap1, filecap2, filecap3, dircap
+ return newkids, {'filecap1': filecap1,
+ 'filecap2': filecap2,
+ 'filecap3': filecap3,
+ 'unknown_rwcap': unknown_rwcap,
+ 'unknown_rocap': unknown_rocap,
+ 'unknown_immcap': unknown_immcap,
+ 'dircap': dircap}
def _create_immutable_children(self):
contents, n, filecap1 = self.makefile(12)
tnode = create_chk_filenode("immutable directory contents\n"*10)
dnode = DirectoryNode(tnode, None, None)
assert not dnode.is_mutable()
+ unknown_immcap = "imm.lafs://immutable_from_the_future"
immdircap = dnode.get_uri()
- newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
- "metadata": md1, }],
- u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
+ newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
+ "metadata": md1, }],
+ u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
+ u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
}
- return newkids, filecap1, immdircap
+ return newkids, {'filecap1': filecap1,
+ 'unknown_immcap': unknown_immcap,
+ 'immdircap': immdircap}
def test_POST_mkdir_no_parentdir_initial_children(self):
- (newkids, filecap1, filecap2, filecap3,
- dircap) = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res)
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-imm", filecap1))
+ self.failUnlessROChildURIIs(n, u"child-imm",
+ caps['filecap1']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-mutable",
- filecap2))
+ self.failUnlessRWChildURIIs(n, u"child-mutable",
+ caps['filecap2']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-mutable-ro",
- filecap3))
+ self.failUnlessROChildURIIs(n, u"child-mutable-ro",
+ caps['filecap3']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"dirchild", dircap))
+ self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
+ caps['unknown_rwcap']))
+ d2.addCallback(lambda ign:
+ self.failUnlessROChildURIIs(n, u"unknownchild-ro",
+ caps['unknown_rocap']))
+ d2.addCallback(lambda ign:
+ self.failUnlessROChildURIIs(n, u"unknownchild-imm",
+ caps['unknown_immcap']))
+ d2.addCallback(lambda ign:
+ self.failUnlessRWChildURIIs(n, u"dirchild",
+ caps['dircap']))
return d2
d.addCallback(_after_mkdir)
return d
def test_POST_mkdir_no_parentdir_unexpected_children(self):
# the regular /uri?t=mkdir operation is specified to ignore its body.
# Only t=mkdir-with-children pays attention to it.
- (newkids, filecap1, filecap2, filecap3,
- dircap) = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.shouldHTTPError("POST t=mkdir unexpected children",
400, "Bad Request",
"t=mkdir does not accept children=, "
return d
def test_POST_mkdir_no_parentdir_immutable(self):
- (newkids, filecap1, immdircap) = self._create_immutable_children()
+ (newkids, caps) = self._create_immutable_children()
d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res)
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"child-imm", filecap1))
+ self.failUnlessROChildURIIs(n, u"child-imm",
+ caps['filecap1']))
+ d2.addCallback(lambda ign:
+ self.failUnlessROChildURIIs(n, u"unknownchild-imm",
+ caps['unknown_immcap']))
d2.addCallback(lambda ign:
- self.failUnlessChildURIIs(n, u"dirchild-imm",
- immdircap))
+ self.failUnlessROChildURIIs(n, u"dirchild-imm",
+ caps['immdircap']))
return d2
d.addCallback(_after_mkdir)
return d
def test_POST_mkdir_no_parentdir_immutable_bad(self):
- (newkids, filecap1, filecap2, filecap3,
- dircap) = self._create_initial_children()
+ (newkids, caps) = self._create_initial_children()
d = self.shouldFail2(error.Error,
"test_POST_mkdir_no_parentdir_immutable_bad",
"400 Bad Request",
- "a mkdir-immutable operation was given a child that was not itself immutable",
+ "needed to be immutable but was not",
self.POST2,
"/uri?t=mkdir-immutable",
simplejson.dumps(newkids))
d = client.getPage(url, method="POST", postdata=reqbody)
def _then(res):
- self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
- self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
- self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
+ self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
+ self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
+ self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
d.addCallback(_then)
d.addErrback(self.dump_error)
def test_POST_put_uri(self):
contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
- d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
contents))
def test_POST_put_uri_replace(self):
contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
- d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
d.addCallback(lambda res:
self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
contents))
d.addCallback(lambda res:
self.failUnlessEqual(res.strip(), new_uri))
d.addCallback(lambda res:
- self.failUnlessChildURIIs(self.public_root,
- u"foo",
- new_uri))
+ self.failUnlessRWChildURIIs(self.public_root,
+ u"foo",
+ new_uri))
return d
d.addCallback(_made_dir)
return d
self.public_url + "/foo?t=uri&replace=false",
new_uri)
d.addCallback(lambda res:
- self.failUnlessChildURIIs(self.public_root,
- u"foo",
- self._foo_uri))
+ self.failUnlessRWChildURIIs(self.public_root,
+ u"foo",
+ self._foo_uri))
return d
d.addCallback(_made_dir)
return d
"400 Bad Request", "PUT to a directory",
self.PUT, self.public_url + "/foo?t=BOGUS", "")
d.addCallback(lambda res:
- self.failUnlessChildURIIs(self.public_root,
- u"foo",
- self._foo_uri))
+ self.failUnlessRWChildURIIs(self.public_root,
+ u"foo",
+ self._foo_uri))
return d
def test_PUT_NEWFILEURL_uri(self):
d.addErrback(self.explain_web_error)
return d
- def test_unknown(self):
+ def test_unknown(self, immutable=False):
self.basedir = "web/Grid/unknown"
+ if immutable:
+ self.basedir = "web/Grid/unknown-immutable"
+
self.set_up_grid()
c0 = self.g.clients[0]
self.uris = {}
self.fileurls = {}
- future_writecap = "x-tahoe-crazy://I_am_from_the_future."
- future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
+ future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
+ future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
# the future cap format may contain slashes, which must be tolerated
- expected_info_url = "uri/%s?t=info" % urllib.quote(future_writecap,
+ expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri,
safe="")
- future_node = UnknownNode(future_writecap, future_readcap)
- d = c0.create_dirnode()
+ if immutable:
+ name = u"future-imm"
+ future_node = UnknownNode(None, future_read_uri, deep_immutable=True)
+ d = c0.create_immutable_dirnode({name: (future_node, {})})
+ else:
+ name = u"future"
+ future_node = UnknownNode(future_write_uri, future_read_uri)
+ d = c0.create_dirnode()
+
def _stash_root_and_create_file(n):
self.rootnode = n
self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
- return self.rootnode.set_node(u"future", future_node)
+ if not immutable:
+ return self.rootnode.set_node(name, future_node)
d.addCallback(_stash_root_and_create_file)
+
# make sure directory listing tolerates unknown nodes
d.addCallback(lambda ign: self.GET(self.rooturl))
- def _check_html(res):
- self.failUnlessIn("<td>future</td>", res)
- # find the More Info link for "future", should be relative
+ def _check_directory_html(res):
+ self.failUnlessIn("<td>%s</td>" % (str(name),), res)
+ # find the More Info link for name, should be relative
mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
info_url = mo.group(1)
- self.failUnlessEqual(info_url, "future?t=info")
+ self.failUnlessEqual(info_url, "%s?t=info" % (str(name),))
+ d.addCallback(_check_directory_html)
- d.addCallback(_check_html)
d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
- def _check_json(res, expect_writecap):
+ def _check_directory_json(res, expect_rw_uri):
data = simplejson.loads(res)
self.failUnlessEqual(data[0], "dirnode")
- f = data[1]["children"]["future"]
+ f = data[1]["children"][name]
self.failUnlessEqual(f[0], "unknown")
- if expect_writecap:
- self.failUnlessEqual(f[1]["rw_uri"], future_writecap)
+ if expect_rw_uri:
+ self.failUnlessEqual(f[1]["rw_uri"], future_write_uri)
else:
self.failIfIn("rw_uri", f[1])
- self.failUnlessEqual(f[1]["ro_uri"], future_readcap)
+ self.failUnlessEqual(f[1]["ro_uri"],
+ ("imm." if immutable else "ro.") + future_read_uri)
self.failUnless("metadata" in f[1])
- d.addCallback(_check_json, expect_writecap=True)
- d.addCallback(lambda ign: self.GET(expected_info_url))
- def _check_info(res, expect_readcap):
+ d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
+
+ def _check_info(res, expect_rw_uri, expect_ro_uri):
self.failUnlessIn("Object Type: <span>unknown</span>", res)
- self.failUnlessIn(future_writecap, res)
- if expect_readcap:
- self.failUnlessIn(future_readcap, res)
+ if expect_rw_uri:
+ self.failUnlessIn(future_write_uri, res)
+ if expect_ro_uri:
+ self.failUnlessIn(future_read_uri, res)
+ else:
+ self.failIfIn(future_read_uri, res)
self.failIfIn("Raw data as", res)
self.failIfIn("Directory writecap", res)
self.failIfIn("Checker Operations", res)
self.failIfIn("Mutable File Operations", res)
self.failIfIn("Directory Operations", res)
- d.addCallback(_check_info, expect_readcap=False)
- d.addCallback(lambda ign: self.GET(self.rooturl+"future?t=info"))
- d.addCallback(_check_info, expect_readcap=True)
+
+ # FIXME: these should have expect_rw_uri=not immutable; I don't know
+ # why they fail. Possibly related to ticket #922.
+
+ d.addCallback(lambda ign: self.GET(expected_info_url))
+ d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
+ d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
+ d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
+
+ def _check_json(res, expect_rw_uri):
+ data = simplejson.loads(res)
+ self.failUnlessEqual(data[0], "unknown")
+ if expect_rw_uri:
+ self.failUnlessEqual(data[1]["rw_uri"], future_write_uri)
+ else:
+ self.failIfIn("rw_uri", data[1])
+ self.failUnlessEqual(data[1]["ro_uri"],
+ ("imm." if immutable else "ro.") + future_read_uri)
+ # TODO: check metadata contents
+ self.failUnless("metadata" in data[1])
+
+ d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
+ d.addCallback(_check_json, expect_rw_uri=not immutable)
# and make sure that a read-only version of the directory can be
- # rendered too. This version will not have future_writecap
+ # rendered too. This version will not have future_write_uri, whether
+ # or not future_node was immutable.
d.addCallback(lambda ign: self.GET(self.rourl))
- d.addCallback(_check_html)
+ d.addCallback(_check_directory_html)
d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
- d.addCallback(_check_json, expect_writecap=False)
+ d.addCallback(_check_directory_json, expect_rw_uri=False)
+
+ d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
+ d.addCallback(_check_json, expect_rw_uri=False)
+
+ # TODO: check that getting t=info from the Info link in the ro directory
+ # works, and does not include the writecap URI.
+ return d
+
+ def test_immutable_unknown(self):
+ return self.test_unknown(immutable=True)
+
+ def test_mutant_dirnodes_are_omitted(self):
+ self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
+
+ self.set_up_grid()
+ c = self.g.clients[0]
+ nm = c.nodemaker
+ self.uris = {}
+ self.fileurls = {}
+
+ lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
+ mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
+ mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
+
+ # This method tests mainly dirnode, but we'd have to duplicate code in order to
+ # test the dirnode and web layers separately.
+
+ # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
+ # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
+ # When the directory is read, the mutants should be silently disposed of, leaving
+ # their lonely sibling.
+ # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
+ # because immutable directories don't have a writecap and therefore that field
+ # isn't (and can't be) decrypted.
+ # TODO: The field still exists in the netstring. Technically we should check what
+ # happens if something is put there (it should be ignored), but that can wait.
+
+ lonely_child = nm.create_from_cap(lonely_uri)
+ mutant_ro_child = nm.create_from_cap(mut_read_uri)
+ mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
+
+ def _by_hook_or_by_crook():
+ return True
+ for n in [mutant_ro_child, mutant_write_in_ro_child]:
+ n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
+
+ mutant_write_in_ro_child.get_write_uri = lambda: None
+ mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
+
+ kids = {u"lonely": (lonely_child, {}),
+ u"ro": (mutant_ro_child, {}),
+ u"write-in-ro": (mutant_write_in_ro_child, {}),
+ }
+ d = c.create_immutable_dirnode(kids)
+
+ def _created(dn):
+ self.failUnless(isinstance(dn, dirnode.DirectoryNode))
+ self.failIf(dn.is_mutable())
+ self.failUnless(dn.is_readonly())
+ # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
+ self.failIf(hasattr(dn._node, 'get_writekey'))
+ rep = str(dn)
+ self.failUnless("RO-IMM" in rep)
+ cap = dn.get_cap()
+ self.failUnlessIn("CHK", cap.to_string())
+ self.cap = cap
+ self.rootnode = dn
+ self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
+ return download_to_data(dn._node)
+ d.addCallback(_created)
+
+ def _check_data(data):
+ # Decode the netstring representation of the directory to check that all children
+ # are present. This is a bit of an abstraction violation, but there's not really
+ # any other way to do it given that the real DirectoryNode._unpack_contents would
+ # strip the mutant children out (which is what we're trying to test, later).
+ position = 0
+ numkids = 0
+ while position < len(data):
+ entries, position = split_netstring(data, 1, position)
+ entry = entries[0]
+ (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
+ name = name.decode("utf-8")
+ self.failUnless(rwcapdata == "")
+ ro_uri = ro_uri.strip()
+ if name in kids:
+ self.failIfEqual(ro_uri, "")
+ (expected_child, ign) = kids[name]
+ self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
+ numkids += 1
+
+ self.failUnlessEqual(numkids, 3)
+ return self.rootnode.list()
+ d.addCallback(_check_data)
+
+ # Now when we use the real directory listing code, the mutants should be absent.
+ def _check_kids(children):
+ self.failUnlessEqual(sorted(children.keys()), [u"lonely"])
+ lonely_node, lonely_metadata = children[u"lonely"]
+
+ self.failUnlessEqual(lonely_node.get_write_uri(), None)
+ self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri)
+ d.addCallback(_check_kids)
+
+ d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
+ d.addCallback(lambda n: n.list())
+ d.addCallback(_check_kids) # again with dirnode recreated from cap
+
+ # Make sure the lonely child can be listed in HTML...
+ d.addCallback(lambda ign: self.GET(self.rooturl))
+ def _check_html(res):
+ self.failIfIn("URI:SSK", res)
+ get_lonely = "".join([r'<td>FILE</td>',
+ r'\s+<td>',
+ r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
+ r'</td>',
+ r'\s+<td>%d</td>' % len("one"),
+ ])
+ self.failUnless(re.search(get_lonely, res), res)
+
+ # find the More Info link for name, should be relative
+ mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
+ info_url = mo.group(1)
+ self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
+ d.addCallback(_check_html)
+
+ # ... and in JSON.
+ d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
+ def _check_json(res):
+ data = simplejson.loads(res)
+ self.failUnlessEqual(data[0], "dirnode")
+ listed_children = data[1]["children"]
+ self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"])
+ ll_type, ll_data = listed_children[u"lonely"]
+ self.failUnlessEqual(ll_type, "filenode")
+ self.failIf("rw_uri" in ll_data)
+ self.failUnlessEqual(ll_data["ro_uri"], lonely_uri)
+ d.addCallback(_check_json)
return d
def test_deep_check(self):
# this tests that deep-check and stream-manifest will ignore
# UnknownNode instances. Hopefully this will also cover deep-stats.
- future_writecap = "x-tahoe-crazy://I_am_from_the_future."
- future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
- future_node = UnknownNode(future_writecap, future_readcap)
- d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node))
+ future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
+ future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
+ future_node = UnknownNode(future_write_uri, future_read_uri)
+ d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
def _clobber_shares(ignored):
self.delete_shares_numbered(self.uris["sick"], [0,1])
+
from zope.interface import implements
from twisted.internet import defer
-from allmydata.interfaces import IFilesystemNode
+from allmydata.interfaces import IFilesystemNode, MustNotBeUnknownRWError, \
+ MustBeDeepImmutableError
+from allmydata import uri
+from allmydata.uri import ALLEGED_READONLY_PREFIX, ALLEGED_IMMUTABLE_PREFIX
+
+
+# See ticket #833 for design rationale of UnknownNodes.
+
+def strip_prefix_for_ro(ro_uri, deep_immutable):
+ """Strip prefixes when storing an URI in a ro_uri slot."""
+
+ # It is possible for an alleged-immutable URI to be put into a
+ # mutable directory. In that case the ALLEGED_IMMUTABLE_PREFIX
+ # should not be stripped. In other cases, the prefix can safely
+ # be stripped because it is implied by the context.
+
+ if ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX):
+ if not deep_immutable:
+ return ro_uri
+ return ro_uri[len(ALLEGED_IMMUTABLE_PREFIX):]
+ elif ro_uri.startswith(ALLEGED_READONLY_PREFIX):
+ return ro_uri[len(ALLEGED_READONLY_PREFIX):]
+ else:
+ return ro_uri
class UnknownNode:
implements(IFilesystemNode)
- def __init__(self, writecap, readcap):
- assert writecap is None or isinstance(writecap, str)
- self.writecap = writecap
- assert readcap is None or isinstance(readcap, str)
- self.readcap = readcap
+
+ def __init__(self, given_rw_uri, given_ro_uri, deep_immutable=False,
+ name=u"<unknown name>"):
+ assert given_rw_uri is None or isinstance(given_rw_uri, str)
+ assert given_ro_uri is None or isinstance(given_ro_uri, str)
+ given_rw_uri = given_rw_uri or None
+ given_ro_uri = given_ro_uri or None
+
+ # We don't raise errors when creating an UnknownNode; we instead create an
+ # opaque node (with rw_uri and ro_uri both None) that records the error.
+ # This avoids breaking operations that never store the opaque node.
+ # Note that this means that if a stored dirnode has only a rw_uri, it
+ # might be dropped. Any future "write-only" cap formats should have a dummy
+ # unusable readcap to stop that from happening.
+
+ self.error = None
+ self.rw_uri = self.ro_uri = None
+ if given_rw_uri:
+ if deep_immutable:
+ if given_rw_uri.startswith(ALLEGED_IMMUTABLE_PREFIX) and not given_ro_uri:
+ # We needed an immutable cap, and were given one. It was given in the
+ # rw_uri slot, but that's fine; we'll move it to ro_uri below.
+ pass
+ elif not given_ro_uri:
+ self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as immutable child",
+ name, True)
+ return # node will be opaque
+ else:
+ # We could report either error, but this probably makes more sense.
+ self.error = MustBeDeepImmutableError("cannot attach unknown rw cap as immutable child",
+ name)
+ return # node will be opaque
+
+ if not given_ro_uri:
+ # We were given a single cap argument, or a rw_uri with no ro_uri.
+
+ if not (given_rw_uri.startswith(ALLEGED_READONLY_PREFIX)
+ or given_rw_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)):
+ # If the single cap is unprefixed, then we cannot tell whether it is a
+ # writecap, and we don't know how to diminish it to a readcap if it is one.
+ # If it didn't *already* have at least an ALLEGED_READONLY_PREFIX, then
+ # prefixing it would be a bad idea because we have been given no reason
+ # to believe that it is a readcap, so we might be letting a client
+ # inadvertently grant excess write authority.
+ self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as child",
+ name, False)
+ return # node will be opaque
+
+ # OTOH, if the single cap already had a prefix (which is of the required
+ # strength otherwise an error would have been thrown above), then treat it
+ # as though it had been given in the ro_uri slot. This has a similar effect
+ # to the use for known caps of 'bigcap = writecap or readcap' in
+ # nodemaker.py: create_from_cap. It enables copying of unknown readcaps to
+ # work in as many cases as we can securely allow.
+ given_ro_uri = given_rw_uri
+ given_rw_uri = None
+ elif given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX):
+ # Strange corner case: we were given a cap in both slots, with the ro_uri
+ # alleged to be immutable. A real immutable object wouldn't have a writecap.
+ self.error = MustBeDeepImmutableError("cannot accept a child entry that specifies "
+ "both rw_uri, and ro_uri with an imm. prefix",
+ name)
+ return # node will be opaque
+
+ # If the ro_uri definitely fails the constraint, it should be treated as opaque and
+ # the error recorded.
+ if given_ro_uri:
+ read_cap = uri.from_string(given_ro_uri, deep_immutable=deep_immutable, name=name)
+ if isinstance(read_cap, uri.UnknownURI):
+ self.error = read_cap.get_error()
+ if self.error:
+ assert self.rw_uri is None and self.ro_uri is None
+ return
+
+ if deep_immutable:
+ assert self.rw_uri is None
+ # strengthen the constraint on ro_uri to ALLEGED_IMMUTABLE_PREFIX
+ if given_ro_uri:
+ if given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX):
+ self.ro_uri = given_ro_uri
+ elif given_ro_uri.startswith(ALLEGED_READONLY_PREFIX):
+ self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + given_ro_uri[len(ALLEGED_READONLY_PREFIX):]
+ else:
+ self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + given_ro_uri
+ else:
+ # not immutable, so a writecap is allowed
+ self.rw_uri = given_rw_uri
+ # strengthen the constraint on ro_uri to ALLEGED_READONLY_PREFIX
+ if given_ro_uri:
+ if (given_ro_uri.startswith(ALLEGED_READONLY_PREFIX) or
+ given_ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)):
+ self.ro_uri = given_ro_uri
+ else:
+ self.ro_uri = ALLEGED_READONLY_PREFIX + given_ro_uri
+
+ def get_cap(self):
+ return uri.UnknownURI(self.rw_uri or self.ro_uri)
+
+ def get_readcap(self):
+ return uri.UnknownURI(self.ro_uri)
+
+ def is_readonly(self):
+ raise AssertionError("an UnknownNode might be either read-only or "
+ "read/write, so we shouldn't be calling is_readonly")
+
+ def is_mutable(self):
+ raise AssertionError("an UnknownNode might be either mutable or immutable, "
+ "so we shouldn't be calling is_mutable")
+
+ def is_unknown(self):
+ return True
+
+ def is_allowed_in_immutable_directory(self):
+ # An UnknownNode consisting only of a ro_uri is allowed in an
+ # immutable directory, even though we do not know that it is
+ # immutable (or even read-only), provided that no error was detected.
+ return not self.error and not self.rw_uri
+
+ def raise_error(self):
+ if self.error is not None:
+ raise self.error
+
def get_uri(self):
- return self.writecap
+ return self.rw_uri or self.ro_uri
+
+ def get_write_uri(self):
+ return self.rw_uri
+
def get_readonly_uri(self):
- return self.readcap
+ return self.ro_uri
+
def get_storage_index(self):
return None
+
def get_verify_cap(self):
return None
+
def get_repair_cap(self):
return None
+
def get_size(self):
return None
+
def get_current_size(self):
return defer.succeed(None)
+
def check(self, monitor, verify, add_lease):
return defer.succeed(None)
+
def check_and_repair(self, monitor, verify, add_lease):
return defer.succeed(None)
from allmydata.storage.server import si_a2b, si_b2a
from allmydata.util import base32, hashutil
from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \
- IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI
+ IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI, \
+ MustBeDeepImmutableError, MustBeReadonlyError, CapConstraintError
-class BadURIError(Exception):
+class BadURIError(CapConstraintError):
pass
# the URI shall be an ascii representation of the file. It shall contain
def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri)
if not mo:
- raise BadURIError("%s doesn't look like a cap" % (uri,))
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri)
if not mo:
- raise BadURIError("%s doesn't look like a cap" % (uri,))
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
def is_readonly(self):
return True
+
def is_mutable(self):
return False
+
def get_readonly(self):
return self
@classmethod
def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri)
- assert mo, uri
+ if not mo:
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
@classmethod
def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri)
- assert mo, (uri, cls, cls.STRING_RE)
+ if not mo:
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)),
int(mo.group(3)), int(mo.group(4)), int(mo.group(5)))
self.total_shares,
self.size))
+ def is_readonly(self):
+ return True
+
+ def is_mutable(self):
+ return False
+
+ def get_readonly(self):
+ return self
+
+ def get_verify_cap(self):
+ return self
+
class LiteralFileURI(_BaseURI):
implements(IURI, IImmutableFileURI)
@classmethod
def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri)
- assert mo, uri
+ if not mo:
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)))
@classmethod
def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri)
- assert mo, uri
+ if not mo:
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)))
def to_string(self):
def is_readonly(self):
return True
+
def is_mutable(self):
return False
+
def get_readonly(self):
return self
+
def get_storage_index(self):
return None
def get_size(self):
return len(self.data)
+
class WriteableSSKFileURI(_BaseURI):
implements(IURI, IMutableFileURI)
def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri)
if not mo:
- raise BadURIError("'%s' doesn't look like a cap" % (uri,))
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
@classmethod
def abbrev(self):
return base32.b2a(self.writekey[:5])
+
def abbrev_si(self):
return base32.b2a(self.storage_index)[:5]
def is_readonly(self):
return False
+
def is_mutable(self):
return True
+
def get_readonly(self):
return ReadonlySSKFileURI(self.readkey, self.fingerprint)
+
def get_verify_cap(self):
return SSKVerifierURI(self.storage_index, self.fingerprint)
+
class ReadonlySSKFileURI(_BaseURI):
implements(IURI, IMutableFileURI)
def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri)
if not mo:
- raise BadURIError("'%s' doesn't look like a cap" % (uri,))
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
@classmethod
def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri)
if not mo:
- raise BadURIError("'%s' doesn't look like a cap" % (uri,))
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)))
def to_string(self):
def abbrev(self):
return base32.b2a(self.readkey[:5])
+
def abbrev_si(self):
return base32.b2a(self.storage_index)[:5]
def is_readonly(self):
return True
+
def is_mutable(self):
return True
+
def get_readonly(self):
return self
+
def get_verify_cap(self):
return SSKVerifierURI(self.storage_index, self.fingerprint)
+
class SSKVerifierURI(_BaseURI):
implements(IVerifierURI)
@classmethod
def init_from_human_encoding(cls, uri):
mo = cls.HUMAN_RE.search(uri)
- assert mo, uri
+ if not mo:
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
@classmethod
def init_from_string(cls, uri):
mo = cls.STRING_RE.search(uri)
- assert mo, (uri, cls)
+ if not mo:
+ raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)))
def to_string(self):
return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index),
base32.b2a(self.fingerprint))
+ def is_readonly(self):
+ return True
+
+ def is_mutable(self):
+ return False
+
+ def get_readonly(self):
+ return self
+
+ def get_verify_cap(self):
+ return self
+
class _DirectoryBaseURI(_BaseURI):
implements(IURI, IDirnodeURI)
def __init__(self, filenode_uri=None):
def abbrev(self):
return self._filenode_uri.to_string().split(':')[2][:5]
+
def abbrev_si(self):
return base32.b2a(self._filenode_uri.storage_index)[:5]
- def get_filenode_cap(self):
- return self._filenode_uri
-
def is_mutable(self):
return True
+ def get_filenode_cap(self):
+ return self._filenode_uri
+
def get_verify_cap(self):
return DirectoryURIVerifier(self._filenode_uri.get_verify_cap())
def get_readonly(self):
return ReadonlyDirectoryURI(self._filenode_uri.get_readonly())
+
class ReadonlyDirectoryURI(_DirectoryBaseURI):
implements(IReadonlyDirectoryURI)
def get_readonly(self):
return self
+
class _ImmutableDirectoryBaseURI(_DirectoryBaseURI):
def __init__(self, filenode_uri=None):
if filenode_uri:
assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri
+ assert not filenode_uri.is_mutable()
_DirectoryBaseURI.__init__(self, filenode_uri)
- def is_mutable(self):
- return False
-
def is_readonly(self):
return True
+ def is_mutable(self):
+ return False
+
def get_readonly(self):
return self
+
class ImmutableDirectoryURI(_ImmutableDirectoryBaseURI):
BASE_STRING='URI:DIR2-CHK:'
BASE_STRING_RE=re.compile('^'+BASE_STRING)
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK'+SEP)
INNER_URI_CLASS=CHKFileURI
+
def get_verify_cap(self):
vcap = self._filenode_uri.get_verify_cap()
return ImmutableDirectoryURIVerifier(vcap)
BASE_STRING_RE=re.compile('^'+BASE_STRING)
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
INNER_URI_CLASS=LiteralFileURI
+
def get_verify_cap(self):
# LIT caps have no verifier, since they aren't distributed
return None
+
def wrap_dirnode_cap(filecap):
if isinstance(filecap, WriteableSSKFileURI):
return DirectoryURI(filecap)
return ImmutableDirectoryURI(filecap)
if isinstance(filecap, LiteralFileURI):
return LiteralDirectoryURI(filecap)
- assert False, "cannot wrap a dirnode around %s" % filecap.__class__
+ assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
+
class DirectoryURIVerifier(_DirectoryBaseURI):
implements(IVerifierURI)
def get_filenode_cap(self):
return self._filenode_uri
+ def is_mutable(self):
+ return False
+
+
class ImmutableDirectoryURIVerifier(DirectoryURIVerifier):
implements(IVerifierURI)
BASE_STRING='URI:DIR2-CHK-Verifier:'
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP)
INNER_URI_CLASS=CHKFileVerifierURI
+
class UnknownURI:
- def __init__(self, uri):
+ def __init__(self, uri, error=None):
self._uri = uri
+ self._error = error
+
def to_string(self):
return self._uri
-def from_string(s):
- if not isinstance(s, str):
- raise TypeError("unknown URI type: %s.." % str(s)[:100])
- elif s.startswith('URI:CHK:'):
- return CHKFileURI.init_from_string(s)
- elif s.startswith('URI:CHK-Verifier:'):
- return CHKFileVerifierURI.init_from_string(s)
- elif s.startswith('URI:LIT:'):
- return LiteralFileURI.init_from_string(s)
- elif s.startswith('URI:SSK:'):
- return WriteableSSKFileURI.init_from_string(s)
- elif s.startswith('URI:SSK-RO:'):
- return ReadonlySSKFileURI.init_from_string(s)
- elif s.startswith('URI:SSK-Verifier:'):
- return SSKVerifierURI.init_from_string(s)
- elif s.startswith('URI:DIR2:'):
- return DirectoryURI.init_from_string(s)
- elif s.startswith('URI:DIR2-RO:'):
- return ReadonlyDirectoryURI.init_from_string(s)
- elif s.startswith('URI:DIR2-Verifier:'):
- return DirectoryURIVerifier.init_from_string(s)
- elif s.startswith('URI:DIR2-CHK:'):
- return ImmutableDirectoryURI.init_from_string(s)
- elif s.startswith('URI:DIR2-LIT:'):
- return LiteralDirectoryURI.init_from_string(s)
- return UnknownURI(s)
+ def get_readonly(self):
+ return None
+
+ def get_error(self):
+ return self._error
+
+ def get_verify_cap(self):
+ return None
+
+
+ALLEGED_READONLY_PREFIX = 'ro.'
+ALLEGED_IMMUTABLE_PREFIX = 'imm.'
+
+def from_string(u, deep_immutable=False, name=u"<unknown name>"):
+ if not isinstance(u, str):
+ raise TypeError("unknown URI type: %s.." % str(u)[:100])
+
+ # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX
+ # on all URIs, even though we would only strictly need to do so for caps of
+ # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their
+ # prefix are treated as unknown. This should be revisited when we add the
+ # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>.
+ s = u
+ can_be_mutable = can_be_writeable = not deep_immutable
+ if s.startswith(ALLEGED_IMMUTABLE_PREFIX):
+ can_be_mutable = can_be_writeable = False
+ s = s[len(ALLEGED_IMMUTABLE_PREFIX):]
+ elif s.startswith(ALLEGED_READONLY_PREFIX):
+ can_be_writeable = False
+ s = s[len(ALLEGED_READONLY_PREFIX):]
+
+ error = None
+ kind = "cap"
+ try:
+ if s.startswith('URI:CHK:'):
+ return CHKFileURI.init_from_string(s)
+ elif s.startswith('URI:CHK-Verifier:'):
+ return CHKFileVerifierURI.init_from_string(s)
+ elif s.startswith('URI:LIT:'):
+ return LiteralFileURI.init_from_string(s)
+ elif s.startswith('URI:SSK:'):
+ if can_be_writeable:
+ return WriteableSSKFileURI.init_from_string(s)
+ kind = "URI:SSK file writecap"
+ elif s.startswith('URI:SSK-RO:'):
+ if can_be_mutable:
+ return ReadonlySSKFileURI.init_from_string(s)
+ kind = "URI:SSK-RO readcap to a mutable file"
+ elif s.startswith('URI:SSK-Verifier:'):
+ return SSKVerifierURI.init_from_string(s)
+ elif s.startswith('URI:DIR2:'):
+ if can_be_writeable:
+ return DirectoryURI.init_from_string(s)
+ kind = "URI:DIR2 directory writecap"
+ elif s.startswith('URI:DIR2-RO:'):
+ if can_be_mutable:
+ return ReadonlyDirectoryURI.init_from_string(s)
+ kind = "URI:DIR2-RO readcap to a mutable directory"
+ elif s.startswith('URI:DIR2-Verifier:'):
+ return DirectoryURIVerifier.init_from_string(s)
+ elif s.startswith('URI:DIR2-CHK:'):
+ return ImmutableDirectoryURI.init_from_string(s)
+ elif s.startswith('URI:DIR2-LIT:'):
+ return LiteralDirectoryURI.init_from_string(s)
+ elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
+ # For testing how future writeable caps would behave in read-only contexts.
+ kind = "x-tahoe-future-test-writeable: testing cap"
+ elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable:
+ # For testing how future mutable readcaps would behave in immutable contexts.
+ kind = "x-tahoe-future-test-mutable: testing cap"
+ else:
+ return UnknownURI(u)
+
+ # We fell through because a constraint was not met.
+ # Prefer to report the most specific constraint.
+ if not can_be_mutable:
+ error = MustBeDeepImmutableError(kind + " used in an immutable context", name)
+ else:
+ error = MustBeReadonlyError(kind + " used in a read-only context", name)
+
+ except BadURIError, e:
+ error = e
+
+ return UnknownURI(u, error=error)
def is_uri(s):
try:
- from_string(s)
+ from_string(s, deep_immutable=False)
return True
except (TypeError, AssertionError):
return False
-def from_string_dirnode(s):
- u = from_string(s)
+def is_literal_file_uri(s):
+ if not isinstance(s, str):
+ return False
+ return (s.startswith('URI:LIT:') or
+ s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or
+ s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:'))
+
+def has_uri_prefix(s):
+ if not isinstance(s, str):
+ return False
+ return (s.startswith("URI:") or
+ s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or
+ s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:'))
+
+
+# These take the same keyword arguments as from_string above.
+
+def from_string_dirnode(s, **kwargs):
+ u = from_string(s, **kwargs)
assert IDirnodeURI.providedBy(u)
return u
registerAdapter(from_string_dirnode, str, IDirnodeURI)
-def from_string_filenode(s):
- u = from_string(s)
+def from_string_filenode(s, **kwargs):
+ u = from_string(s, **kwargs)
assert IFileURI.providedBy(u)
return u
registerAdapter(from_string_filenode, str, IFileURI)
-def from_string_mutable_filenode(s):
- u = from_string(s)
+def from_string_mutable_filenode(s, **kwargs):
+ u = from_string(s, **kwargs)
assert IMutableFileURI.providedBy(u)
return u
registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
-def from_string_verifier(s):
- u = from_string(s)
+def from_string_verifier(s, **kwargs):
+ u = from_string(s, **kwargs)
assert IVerifierURI.providedBy(u)
return u
registerAdapter(from_string_verifier, str, IVerifierURI)
from nevow.util import resource_filename
from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
FileTooLargeError, NotEnoughSharesError, NoSharesError, \
- NotDeepImmutableError, EmptyPathnameComponentError
+ EmptyPathnameComponentError, MustBeDeepImmutableError, \
+ MustBeReadonlyError, MustNotBeUnknownRWError
from allmydata.mutable.common import UnrecoverableFileError
from allmydata.util import abbreviate # TODO: consolidate
"failure, or disk corruption. You should perform a filecheck on "
"this object to learn more.")
return (t, http.GONE)
- if f.check(NotDeepImmutableError):
- t = ("NotDeepImmutableError: a mkdir-immutable operation was given "
- "a child that was not itself immutable: %s" % (f.value,))
+ if f.check(MustNotBeUnknownRWError):
+ name = f.value.args[1]
+ immutable = f.value.args[2]
+ if immutable:
+ t = ("MustNotBeUnknownRWError: an operation to add a child named "
+ "'%s' to a directory was given an unknown cap in a write slot.\n"
+ "If the cap is actually an immutable readcap, then using a "
+ "webapi server that supports a later version of Tahoe may help.\n\n"
+ "If you are using the webapi directly, then specifying an immutable "
+ "readcap in the read slot (ro_uri) of the JSON PROPDICT, and "
+ "omitting the write slot (rw_uri), would also work in this "
+ "case.") % name.encode("utf-8")
+ else:
+ t = ("MustNotBeUnknownRWError: an operation to add a child named "
+ "'%s' to a directory was given an unknown cap in a write slot.\n"
+ "Using a webapi server that supports a later version of Tahoe "
+ "may help.\n\n"
+ "If you are using the webapi directly, specifying a readcap in "
+ "the read slot (ro_uri) of the JSON PROPDICT, as well as a "
+ "writecap in the write slot if desired, would also work in this "
+ "case.") % name.encode("utf-8")
+ return (t, http.BAD_REQUEST)
+ if f.check(MustBeDeepImmutableError):
+ name = f.value.args[1]
+ t = ("MustBeDeepImmutableError: a cap passed to this operation for "
+ "the child named '%s', needed to be immutable but was not. Either "
+ "the cap is being added to an immutable directory, or it was "
+ "originally retrieved from an immutable directory as an unknown "
+ "cap." % name.encode("utf-8"))
+ return (t, http.BAD_REQUEST)
+ if f.check(MustBeReadonlyError):
+ name = f.value.args[1]
+ t = ("MustBeReadonlyError: a cap passed to this operation for "
+ "the child named '%s', needed to be read-only but was not. "
+ "The cap is being passed in a read slot (ro_uri), or was retrieved "
+ "from a read slot as an unknown cap." % name.encode("utf-8"))
return (t, http.BAD_REQUEST)
if f.check(WebError):
return (f.value.text, f.value.code)
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
replace = boolean_of_arg(get_arg(req, "replace", "true"))
- d = self.node.set_uri(name, childcap, childcap, overwrite=replace)
+
+ # We mustn't pass childcap for the readcap argument because we don't
+ # know whether it is a read cap. Passing a read cap as the writecap
+ # argument will work (it ends up calling NodeMaker.create_from_cap,
+ # which derives a readcap if necessary and possible).
+ d = self.node.set_uri(name, childcap, None, overwrite=replace)
d.addCallback(lambda res: childcap)
return d
# won't show up in the resulting encoded form.. the 'name'
# field is completely missing. So to allow deletion of an
# empty file, we have to pretend that None means ''. The only
- # downide of this is a slightly confusing error message if
+ # downside of this is a slightly confusing error message if
# someone does a POST without a name= field. For our own HTML
- # thisn't a big deal, because we create the 'delete' POST
+ # this isn't a big deal, because we create the 'delete' POST
# buttons ourselves.
name = ''
charset = get_arg(req, "_charset", "utf-8")
def render_title(self, ctx, data):
si_s = abbreviated_dirnode(self.node)
header = ["Tahoe-LAFS - Directory SI=%s" % si_s]
- if self.node.is_readonly():
+ if self.node.is_unknown():
+ header.append(" (unknown)")
+ elif not self.node.is_mutable():
+ header.append(" (immutable)")
+ elif self.node.is_readonly():
header.append(" (read-only)")
else:
header.append(" (modifiable)")
def render_header(self, ctx, data):
si_s = abbreviated_dirnode(self.node)
header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]]
- if self.node.is_readonly():
+ if self.node.is_unknown():
+ header.append(" (unknown)")
+ elif not self.node.is_mutable():
+ header.append(" (immutable)")
+ elif self.node.is_readonly():
header.append(" (read-only)")
return ctx.tag[header]
return T.div[T.a(href=link)["Return to Welcome page"]]
def render_show_readonly(self, ctx, data):
- if self.node.is_readonly():
+ if self.node.is_unknown() or self.node.is_readonly():
return ""
rocap = self.node.get_readonly_uri()
root = get_root(ctx)
root = get_root(ctx)
here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
- if self.node.is_readonly():
+ if self.node.is_unknown() or self.node.is_readonly():
delete = "-"
rename = "-"
else:
ctx.fillSlots("times", times)
assert IFilesystemNode.providedBy(target), target
- writecap = target.get_uri() or ""
- quoted_uri = urllib.quote(writecap, safe="") # escape slashes too
+ target_uri = target.get_uri() or ""
+ quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too
if IMutableFileNode.providedBy(target):
# to prevent javascript in displayed .html files from stealing a
elif IDirectoryNode.providedBy(target):
# directory
- uri_link = "%s/uri/%s/" % (root, urllib.quote(writecap))
+ uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
ctx.fillSlots("filename",
T.a(href=uri_link)[html.escape(name)])
if not target.is_mutable():
kids = {}
for name, (childnode, metadata) in children.iteritems():
assert IFilesystemNode.providedBy(childnode), childnode
- rw_uri = childnode.get_uri()
+ rw_uri = childnode.get_write_uri()
ro_uri = childnode.get_readonly_uri()
if IFileNode.providedBy(childnode):
- if childnode.is_readonly():
- rw_uri = None
kiddata = ("filenode", {'size': childnode.get_size(),
'mutable': childnode.is_mutable(),
})
elif IDirectoryNode.providedBy(childnode):
- if childnode.is_readonly():
- rw_uri = None
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
else:
kiddata = ("unknown", {})
+
kiddata[1]["metadata"] = metadata
- if ro_uri:
- kiddata[1]["ro_uri"] = ro_uri
if rw_uri:
kiddata[1]["rw_uri"] = rw_uri
+ if ro_uri:
+ kiddata[1]["ro_uri"] = ro_uri
verifycap = childnode.get_verify_cap()
if verifycap:
kiddata[1]['verify_uri'] = verifycap.to_string()
+
kids[name] = kiddata
- if dirnode.is_readonly():
- drw_uri = None
- dro_uri = dirnode.get_uri()
- else:
- drw_uri = dirnode.get_uri()
- dro_uri = dirnode.get_readonly_uri()
+
+ drw_uri = dirnode.get_write_uri()
+ dro_uri = dirnode.get_readonly_uri()
contents = { 'children': kids }
if dro_uri:
contents['ro_uri'] = dro_uri
contents['verify_uri'] = verifycap.to_string()
contents['mutable'] = dirnode.is_mutable()
data = ("dirnode", contents)
- return simplejson.dumps(data, indent=1) + "\n"
+ json = simplejson.dumps(data, indent=1) + "\n"
+ return json
d.addCallback(_got)
d.addCallback(text_plain, ctx)
return d
-
def DirectoryURI(ctx, dirnode):
return text_plain(dirnode.get_uri(), ctx)
self.req.write(j+"\n")
return ""
-class UnknownNodeHandler(RenderMixin, rend.Page):
+class UnknownNodeHandler(RenderMixin, rend.Page):
def __init__(self, client, node, parentnode=None, name=None):
rend.Page.__init__(self)
assert node
self.node = node
+ self.parentnode = parentnode
+ self.name = name
def render_GET(self, ctx):
req = IRequest(ctx)
t = get_arg(req, "t", "").strip()
if t == "info":
return MoreInfo(self.node)
- raise WebError("GET unknown URI type: can only do t=info, not t=%s" % t)
-
-
+ if t == "json":
+ if self.parentnode and self.name:
+ d = self.parentnode.get_metadata_for(self.name)
+ else:
+ d = defer.succeed(None)
+ d.addCallback(lambda md: UnknownJSONMetadata(ctx, self.node, md))
+ return d
+ raise WebError("GET unknown URI type: can only do t=info and t=json, not t=%s.\n"
+ "Using a webapi server that supports a later version of Tahoe "
+ "may help." % t)
+
+def UnknownJSONMetadata(ctx, filenode, edge_metadata):
+ rw_uri = filenode.get_write_uri()
+ ro_uri = filenode.get_readonly_uri()
+ data = ("unknown", {})
+ if ro_uri:
+ data[1]['ro_uri'] = ro_uri
+ if rw_uri:
+ data[1]['rw_uri'] = rw_uri
+ if edge_metadata is not None:
+ data[1]['metadata'] = edge_metadata
+ return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
from nevow import url, rend
from nevow.inevow import IRequest
-from allmydata.interfaces import ExistingChildError, CannotPackUnknownNodeError
+from allmydata.interfaces import ExistingChildError
from allmydata.monitor import Monitor
from allmydata.immutable.upload import FileHandle
-from allmydata.unknown import UnknownNode
from allmydata.util import log, base32
from allmydata.web.common import text_plain, WebError, RenderMixin, \
from allmydata.web.info import MoreInfo
class ReplaceMeMixin:
-
def replace_me_with_a_child(self, req, client, replace):
# a new file is being uploaded in our place.
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
def replace_me_with_a_childcap(self, req, client, replace):
req.content.seek(0)
childcap = req.content.read()
- childnode = client.create_node_from_uri(childcap, childcap+"readonly")
- if isinstance(childnode, UnknownNode):
- # don't be willing to pack unknown nodes: we might accidentally
- # put some write-authority into the rocap slot because we don't
- # know how to diminish the URI they gave us. We don't even know
- # if they gave us a readcap or a writecap.
- msg = "cannot attach unknown node as child %s" % str(self.name)
- raise CannotPackUnknownNodeError(msg)
+ childnode = client.create_node_from_uri(childcap, None, name=self.name)
d = self.parentnode.set_node(self.name, childnode, overwrite=replace)
d.addCallback(lambda res: childnode.get_uri())
return d
def FileJSONMetadata(ctx, filenode, edge_metadata):
- if filenode.is_readonly():
- rw_uri = None
- ro_uri = filenode.get_uri()
- else:
- rw_uri = filenode.get_uri()
- ro_uri = filenode.get_readonly_uri()
+ rw_uri = filenode.get_write_uri()
+ ro_uri = filenode.get_readonly_uri()
data = ("filenode", {})
data[1]['size'] = filenode.get_size()
if ro_uri:
def get_type(self):
node = self.original
if IDirectoryNode.providedBy(node):
+ if not node.is_mutable():
+ return "immutable directory"
return "directory"
if IFileNode.providedBy(node):
si = node.get_storage_index()
if node.is_mutable():
return "mutable file"
return "immutable file"
- return "LIT file"
+ return "immutable LIT file"
return "unknown"
def render_title(self, ctx, data):
def render_directory_writecap(self, ctx, data):
node = self.original
- if node.is_readonly():
- return ""
if not IDirectoryNode.providedBy(node):
return ""
+ if node.is_readonly():
+ return ""
return ctx.tag[node.get_uri()]
def render_directory_readcap(self, ctx, data):
return ""
return ctx.tag[node.get_verify_cap().to_string()]
-
def render_file_writecap(self, ctx, data):
node = self.original
if IDirectoryNode.providedBy(node):
node = node._node
- if ((IDirectoryNode.providedBy(node) or IFileNode.providedBy(node))
- and node.is_readonly()):
- return ""
- writecap = node.get_uri()
- if not writecap:
+ write_uri = node.get_write_uri()
+ if not write_uri:
return ""
- return ctx.tag[writecap]
+ return ctx.tag[write_uri]
def render_file_readcap(self, ctx, data):
node = self.original
if IDirectoryNode.providedBy(node):
node = node._node
- readcap = node.get_readonly_uri()
- if not readcap:
+ read_uri = node.get_readonly_uri()
+ if not read_uri:
return ""
- return ctx.tag[readcap]
+ return ctx.tag[read_uri]
def render_file_verifycap(self, ctx, data):
node = self.original
from allmydata import get_package_versions_string
from allmydata import provisioning
from allmydata.util import idlib, log
-from allmydata.interfaces import IFileNode, UnhandledCapTypeError
+from allmydata.interfaces import IFileNode
from allmydata.web import filenode, directory, unlinked, status, operations
from allmydata.web import reliability, storage
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
try:
node = self.client.create_node_from_uri(name)
return directory.make_handler_for(node, self.client)
- except (TypeError, UnhandledCapTypeError, AssertionError):
+ except (TypeError, AssertionError):
raise WebError("'%s' is not a valid file- or directory- cap"
% name)
# 'name' must be a file URI
try:
node = self.client.create_node_from_uri(name)
- except (TypeError, UnhandledCapTypeError, AssertionError):
+ except (TypeError, AssertionError):
# I think this can no longer be reached
raise WebError("'%s' is not a valid file- or directory- cap"
% name)