import os, sha, stat, time
from foolscap import Referenceable, SturdyRef
from zope.interface import implements
-from allmydata.interfaces import RIClient
-from allmydata import node
+from allmydata.interfaces import RIClient, IDirectoryNode
+from allmydata import node, vdrive, uri
from twisted.internet import defer, reactor
from twisted.application.internet import TimerService
from allmydata.storageserver import StorageServer
from allmydata.upload import Uploader
from allmydata.download import Downloader
-from allmydata.vdrive import DirectoryNode
from allmydata.webish import WebishServer
from allmydata.control import ControlServer
from allmydata.introducer import IntroducerClient
GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl"
MY_FURL_FILE = "myself.furl"
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
- MY_VDRIVE_FURL_FILE = "my_vdrive.furl"
+ MY_VDRIVE_URI_FILE = "my_vdrive.uri"
# we're pretty narrow-minded right now
OLDEST_SUPPORTED_VERSION = allmydata.__version__
def _got_vdrive(self, vdrive_server):
# vdrive_server implements RIVirtualDriveServer
self.log("connected to vdrive server")
- d = vdrive_server.callRemote("get_public_root_furl")
- d.addCallback(self._got_vdrive_root_furl, vdrive_server)
- d.addCallback(self._create_my_vdrive)
+ d = vdrive_server.callRemote("get_public_root_uri")
+ d.addCallback(self._got_vdrive_uri)
+ d.addCallback(self._got_vdrive_rootnode)
+ d.addCallback(self._create_my_vdrive, vdrive_server)
d.addCallback(self._got_my_vdrive)
- def _got_vdrive_root_furl(self, vdrive_root_furl, vdrive_server):
- root = DirectoryNode(vdrive_root_furl, self)
+ def _got_vdrive_uri(self, root_uri):
+ furl, wk = uri.unpack_dirnode_uri(root_uri)
+ self._vdrive_furl = furl
+ return vdrive.create_directory_node(self, root_uri)
+
+ def _got_vdrive_rootnode(self, rootnode):
self.log("got vdrive root")
- self._vdrive_server = vdrive_server
- self._vdrive_root = root
+ self._vdrive_root = rootnode
self._connected_to_vdrive = True
#vdrive = self.getServiceNamed("vdrive")
if "webish" in self.namedServices:
webish = self.getServiceNamed("webish")
- webish.set_vdrive_root(root)
+ webish.set_vdrive_rootnode(rootnode)
- def _create_my_vdrive(self, ignored=None):
- MY_VDRIVE_FURL_FILE = os.path.join(self.basedir,
- self.MY_VDRIVE_FURL_FILE)
+ def _create_my_vdrive(self, ignored, vdrive_server):
+ MY_VDRIVE_URI_FILE = os.path.join(self.basedir,
+ self.MY_VDRIVE_URI_FILE)
try:
- f = open(MY_VDRIVE_FURL_FILE, "r")
- my_vdrive_furl = f.read().strip()
+ f = open(MY_VDRIVE_URI_FILE, "r")
+ my_vdrive_uri = f.read().strip()
f.close()
- return defer.succeed(DirectoryNode(my_vdrive_furl, self))
+ return vdrive.create_directory_node(self, my_vdrive_uri)
except EnvironmentError:
- d = self._vdrive_server.callRemote("create_directory")
+ assert self._vdrive_furl
+ d = vdrive.create_directory(self, self._vdrive_furl)
def _got_directory(dirnode):
- f = open(MY_VDRIVE_FURL_FILE, "w")
- f.write(dirnode.furl + "\n")
+ f = open(MY_VDRIVE_URI_FILE, "w")
+ f.write(dirnode.get_uri() + "\n")
f.close()
- dirnode._set_client(self)
return dirnode
d.addCallback(_got_directory)
return d
def _got_my_vdrive(self, my_vdrive):
- assert isinstance(my_vdrive, DirectoryNode), my_vdrive
+ IDirectoryNode(my_vdrive)
self._my_vdrive = my_vdrive
if "webish" in self.namedServices:
webish = self.getServiceNamed("webish")
- webish.set_my_vdrive_root(my_vdrive)
+ webish.set_my_vdrive_rootnode(my_vdrive)
def remote_get_versions(self):
import os
from zope.interface import implements
-from foolscap import Referenceable
-from allmydata.interfaces import RIVirtualDriveServer, RIMutableDirectoryNode
-from allmydata.vdrive import FileNode, DirectoryNode
-from allmydata.util import bencode, idlib
from twisted.application import service
-from twisted.python import log
-
-class BadNameError(Exception):
- """Bad filename component"""
+from foolscap import Referenceable
+from allmydata.interfaces import RIVirtualDriveServer
+from allmydata.util import bencode, idlib, hashutil, fileutil
+from allmydata import uri
-class BadFileError(Exception):
+class BadWriteEnablerError(Exception):
pass
-
-class BadDirectoryError(Exception):
+class ChildAlreadyPresentError(Exception):
pass
-class MutableDirectoryNode(Referenceable):
- """I represent a single directory.
-
- I am associated with a file on disk, using a randomly-generated (and
- hopefully unique) name. This file contains a serialized dictionary which
- maps child names to 'child specifications'. These specifications are
- tuples, either of ('file', URI), or ('subdir', FURL).
- """
-
- implements(RIMutableDirectoryNode)
-
- def __init__(self, basedir, name=None):
- self._basedir = basedir
- if name:
- self._name = name
- # for well-known nodes, make sure they exist
- try:
- ignored = self._read_from_file()
- except EnvironmentError:
- self._write_to_file({})
- else:
- self._name = self.create_filename()
- self._write_to_file({}) # start out empty
-
- def _read_from_file(self):
- data = open(os.path.join(self._basedir, self._name), "rb").read()
- children = bencode.bdecode(data)
- child_nodes = {}
- for k,v in children.iteritems():
- if v[0] == "file":
- child_nodes[k] = FileNode(v[1])
- elif v[0] == "subdir":
- child_nodes[k] = DirectoryNode(v[1])
- else:
- raise RuntimeError("unknown child spec '%s'" % (v[0],))
- return child_nodes
-
- def _write_to_file(self, children):
- child_nodes = {}
- for k,v in children.iteritems():
- if isinstance(v, FileNode):
- child_nodes[k] = ("file", v.uri)
- elif isinstance(v, DirectoryNode):
- child_nodes[k] = ("subdir", v.furl)
- else:
- raise RuntimeError("unknown child[%s] node '%s'" % (k,v))
- data = bencode.bencode(child_nodes)
- f = open(os.path.join(self._basedir, self._name), "wb")
- f.write(data)
- f.close()
-
-
- def create_filename(self):
- return idlib.b2a(os.urandom(8))
-
- def validate_name(self, name):
- if name == "." or name == ".." or "/" in name:
- raise BadNameError("'%s' is not cool" % name)
-
- # these are the public methods, available to anyone who holds a reference
-
- def list(self):
- log.msg("Dir(%s).list()" % self._name)
- children = self._read_from_file()
- results = list(children.items())
- return sorted(results)
- remote_list = list
-
- def get(self, name):
- log.msg("Dir(%s).get(%s)" % (self._name, name))
- self.validate_name(name)
- children = self._read_from_file()
- if name not in children:
- raise BadFileError("no such child")
- return children[name]
- remote_get = get
-
- def add(self, name, child):
- self.validate_name(name)
- children = self._read_from_file()
- if name in children:
- raise BadNameError("the child already existed")
- children[name] = child
- self._write_to_file(children)
- return child
- remote_add = add
-
- def remove(self, name):
- self.validate_name(name)
- children = self._read_from_file()
- if name not in children:
- raise BadFileError("cannot remove non-existent child")
- child = children[name]
- del children[name]
- self._write_to_file(children)
- return child
- remote_remove = remove
-
-
class NoPublicRootError(Exception):
pass
class VirtualDriveServer(service.MultiService, Referenceable):
implements(RIVirtualDriveServer)
name = "filetable"
- VDRIVEDIR = "vdrive"
- def __init__(self, basedir=".", offer_public_root=True):
+ def __init__(self, basedir, offer_public_root=True):
service.MultiService.__init__(self)
- vdrive_dir = os.path.join(basedir, self.VDRIVEDIR)
- if not os.path.exists(vdrive_dir):
- os.mkdir(vdrive_dir)
- self._vdrive_dir = vdrive_dir
+ self._basedir = os.path.abspath(basedir)
+ fileutil.make_dirs(self._basedir)
self._root = None
if offer_public_root:
- self._root = MutableDirectoryNode(vdrive_dir, "root")
-
- def startService(self):
- service.MultiService.startService(self)
- # _register_all_dirnodes exists to avoid hacking our Tub to
- # automatically translate inbound your-reference names
- # (Tub.getReferenceForName) into MutableDirectoryNode instances by
- # looking in our basedir for them. Without that hack, we have to
- # register all nodes at startup to make sure they'll be available to
- # all callers. In addition, we must register any new ones that we
- # create later on.
- tub = self.parent.tub
- self._root_furl = tub.registerReference(self._root, "root")
- self._register_all_dirnodes(tub)
-
- def _register_all_dirnodes(self, tub):
- for name in os.listdir(self._vdrive_dir):
- node = MutableDirectoryNode(self._vdrive_dir, name)
- ignored_furl = tub.registerReference(node, name)
-
- def get_public_root_furl(self):
+ rootfile = os.path.join(self._basedir, "root")
+ if not os.path.exists(rootfile):
+ write_key = hashutil.random_key()
+ (wk, we, rk, index) = \
+ hashutil.generate_dirnode_keys_from_writekey(write_key)
+ self.create_directory(index, we)
+ f = open(rootfile, "wb")
+ f.write(wk)
+ f.close()
+ self._root = wk
+ else:
+ f = open(rootfile, "rb")
+ self._root = f.read()
+
+ def set_furl(self, myfurl):
+ self._myfurl = myfurl
+
+ def get_public_root_uri(self):
if self._root:
- return self._root_furl
+ return uri.pack_dirnode_uri(self._myfurl, self._root)
raise NoPublicRootError
- remote_get_public_root_furl = get_public_root_furl
+ remote_get_public_root_uri = get_public_root_uri
- def create_directory(self):
- node = MutableDirectoryNode(self._vdrive_dir)
- furl = self.parent.tub.registerReference(node, node._name)
- return DirectoryNode(furl)
+ def create_directory(self, index, write_enabler):
+ data = [write_enabler, []]
+ self._write_to_file(index, data)
+ return index
remote_create_directory = create_directory
+
+ # the file on disk consists of the write_enabler token and a list of
+ # (H(name), E(name), E(write), E(read)) tuples.
+
+ def _read_from_file(self, index):
+ name = idlib.b2a(index)
+ data = open(os.path.join(self._basedir, name), "rb").read()
+ return bencode.bdecode(data)
+
+ def _write_to_file(self, index, data):
+ name = idlib.b2a(index)
+ f = open(os.path.join(self._basedir, name), "wb")
+ f.write(bencode.bencode(data))
+ f.close()
+
+
+ def get(self, index, key):
+ data = self._read_from_file(index)
+ for (H_key, E_key, E_write, E_read) in data[1]:
+ if H_key == key:
+ return (E_write, E_read)
+ raise IndexError("unable to find key %s" % idlib.b2a(key))
+ remote_get = get
+
+ def list(self, index):
+ data = self._read_from_file(index)
+ response = [ (E_key, E_write, E_read)
+ for (H_key, E_key, E_write, E_read) in data[1] ]
+ return response
+ remote_list = list
+
+ def delete(self, index, write_enabler, key):
+ data = self._read_from_file(index)
+ if data[0] != write_enabler:
+ raise BadWriteEnablerError
+ for i,(H_key, E_key, E_write, E_read) in enumerate(data[1]):
+ if H_key == key:
+ del data[1][i]
+ self._write_to_file(index, data)
+ return
+ raise IndexError("unable to find key %s" % idlib.b2a(key))
+ remote_delete = delete
+
+ def set(self, index, write_enabler, key, name, write, read):
+ data = self._read_from_file(index)
+ if data[0] != write_enabler:
+ raise BadWriteEnablerError
+ # first, see if the key is already present
+ for i,(H_key, E_key, E_write, E_read) in enumerate(data[1]):
+ if H_key == key:
+ raise ChildAlreadyPresentError
+ # now just append the data
+ data[1].append( (key, name, write, read) )
+ self._write_to_file(index, data)
+ remote_set = set
FileNode_ = Any() # TODO: foolscap needs constraints on copyables
DirectoryNode_ = Any() # TODO: same
AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
+EncryptedThing = str
-class RIMutableDirectoryNode(RemoteInterface):
- def list():
- return ListOf( TupleOf(str, # name, relative to directory
- AnyNode_,
- ),
- maxLength=100,
- )
+class RIVirtualDriveServer(RemoteInterface):
+ def get_public_root_uri():
+ """Obtain the URI for this server's global publically-writable root
+ directory. This returns a read-write directory URI.
+
+ If this vdrive server does not offer a public root, this will
+ raise an exception."""
+ return URI
- def get(name=str):
- return AnyNode_
+ def create_directory(index=Hash, write_enabler=Hash):
+ """Create a new (empty) directory, unattached to anything else.
- def add(name=str, what=AnyNode_):
- return AnyNode_
+ This returns the same index that was passed in.
+ """
+ return Hash
- def remove(name=str):
- return AnyNode_
+ def get(index=Hash, key=Hash):
+ """Retrieve a named child of the given directory. 'index' specifies
+ which directory is being accessed, and is generally the hash of the
+ read key. 'key' is the hash of the read key and the child name.
+ This operation returns a pair of encrypted strings. The first string
+ is meant to be decrypted by the Write Key and provides read-write
+ access to the child. If this directory holds read-only access to the
+ child, this first string will be an empty string. The second string
+ is meant to be decrypted by the Read Key and provides read-only
+ access to the child.
-class RIVirtualDriveServer(RemoteInterface):
- def get_public_root_furl():
- """If this vdrive server does not offer a public root, this will
- raise an exception."""
- return FURL
+ When the child is a read-write directory, the encrypted URI:DIR-RO
+ will be in the read slot, and the encrypted URI:DIR will be in the
+ write slot. When the child is a read-only directory, the encrypted
+ URI:DIR-RO will be in the read slot and the write slot will be empty.
+ When the child is a CHK file, the encrypted URI:CHK will be in the
+ read slot, and the write slot will be empty.
+
+ This might raise IndexError if there is no child by the desired name.
+ """
+ return (EncryptedThing, EncryptedThing)
+
+ def list(index=Hash):
+ """List the contents of a directory.
+
+ This returns a list of (NAME, WRITE, READ) tuples. Each value is an
+ encrypted string (although the WRITE value may sometimes be an empty
+ string).
+
+ NAME: the child name, encrypted with the Read Key
+ WRITE: the child write URI, encrypted with the Write Key, or an
+ empty string if this child is read-only
+ READ: the child read URI, encrypted with the Read Key
+ """
+ return ListOf((EncryptedThing, EncryptedThing, EncryptedThing),
+ maxLength=100,
+ )
+
+ def set(index=Hash, write_enabler=Hash, key=Hash,
+ name=EncryptedThing, write=EncryptedThing, read=EncryptedThing):
+ """Set a child object.
+
+ This will raise IndexError if a child with the given name already
+ exists.
+ """
+ pass
+
+ def delete(index=Hash, write_enabler=Hash, key=Hash):
+ """Delete a specific child.
+
+ This uses the hashed key to locate a specific child, and deletes it.
+ """
+ pass
- def create_directory():
- return DirectoryNode_
class IFileNode(Interface):
def download(target):
pass
class IDirectoryNode(Interface):
+ def is_mutable():
+ """Return True if this directory is mutable, False if it is read-only.
+ """
+ def get_uri():
+ """Return the directory URI that can be used by others to get access
+ to this directory 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 acess.
+
+ If you have read-write access to a directory and wish to share merely
+ read-only access with others, use get_immutable_uri().
+
+ The dirnode ('1') URI returned by this method can be used in set() on
+ a different directory ('2') to 'mount' a reference to this directory
+ ('1') under the other ('2'). This URI is just a string, so it can be
+ passed around through email or other out-of-band protocol.
+ """
+
+ def get_immutable_uri():
+ """Return the directory URI that can be used by others to get
+ read-only access to this directory node. The result is a read-only
+ URI, regardless of whether this dirnode is read-only or read-write.
+
+ If you have merely read-only access to this dirnode,
+ get_immutable_uri() will return the same thing as get_uri().
+ """
+
def list():
- """I return a Deferred that fires with a"""
- pass
+ """I return a Deferred that fires with a dictionary mapping child
+ name to an IFileNode or IDirectoryNode."""
def get(name):
- """I return a Deferred that fires with a specific named child."""
- pass
+ """I return a Deferred that fires with a specific named child node,
+ either an IFileNode or an IDirectoryNode."""
+
+ def set_uri(name, child_uri):
+ """I add a child (by URI) at the specific name. I return a Deferred
+ that fires when the operation finishes.
- def add(name, child):
+ The child_uri could be for a file, or for a directory (either
+ read-write or read-only, using a URI that came from get_uri() ).
+
+ If this directory node is read-only, the Deferred will errback with a
+ NotMutableError."""
+
+ def set_node(name, child):
"""I add a child at the specific name. I return a Deferred that fires
- when the operation finishes."""
+ when the operation finishes. This Deferred will fire with the child
+ node that was just added.
+
+ If this directory node is read-only, the Deferred will errback with a
+ NotMutableError."""
def add_file(name, uploadable):
"""I upload a file (using the given IUploadable), then attach the
- resulting FileNode to the directory at the given name."""
+ resulting FileNode to the directory at the given name. I return a
+ Deferred that fires (with the IFileNode of the uploaded file) when
+ the operation completes."""
- def remove(name):
+ def delete(name):
"""I remove the child at the specific name. I return a Deferred that
fires when the operation finishes."""
"""I create and attach an empty directory at the given name. I return
a Deferred that fires when the operation finishes."""
- def attach_shared_directory(name, furl):
- """I attach a directory that was shared (possibly by someone else)
- with IDirectoryNode.get_furl to this parent at the given name. I
- return a Deferred that fires when the operation finishes."""
-
- def get_shared_directory_furl():
- """I return a FURL that can be used to attach this directory to
- somewhere else. The FURL is just a string, so it can be passed
- through email or other out-of-band protocol. Use it by passing it in
- to attach_shared_directory(). I return a Deferred that fires when the
- operation finishes."""
-
def move_child_to(current_child_name, new_parent, new_child_name=None):
"""I take one of my children and move them to a new parent. The child
is referenced by name. On the new parent, the child will live under
class IntroducerAndVdrive(node.Node):
PORTNUMFILE = "introducer.port"
NODETYPE = "introducer"
+ VDRIVEDIR = "vdrive"
def __init__(self, basedir="."):
node.Node.__init__(self, basedir)
f.write(self.urls["introducer"] + "\n")
f.close()
- vds = self.add_service(VirtualDriveServer(self.basedir))
- self.urls["vdrive"] = self.tub.registerReference(vds, "vdrive")
+ vdrive_dir = os.path.join(self.basedir, self.VDRIVEDIR)
+ vds = self.add_service(VirtualDriveServer(vdrive_dir))
+ vds_furl = self.tub.registerReference(vds, "vdrive")
+ vds.set_furl(vds_furl)
+ self.urls["vdrive"] = vds_furl
self.log(" vdrive is at %s" % self.urls["vdrive"])
f = open(os.path.join(self.basedir, "vdrive.furl"), "w")
f.write(self.urls["vdrive"] + "\n")
if not self['filename']:
raise usage.UsageError("<filename> parameter is required")
+class DumpRootDirnodeOptions(BasedirMixin, usage.Options):
+ optParameters = [
+ ["basedir", "C", None, "the vdrive-server's base directory"],
+ ]
+
+class DumpDirnodeOptions(BasedirMixin, usage.Options):
+ optParameters = [
+ ["uri", "u", None, "the URI of the dirnode to dump."],
+ ["basedir", "C", None, "which directory to create the introducer in"],
+ ]
+ optFlags = [
+ ["verbose", "v", "be extra noisy (show encrypted data)"],
+ ]
+ def parseArgs(self, *args):
+ if len(args) == 1:
+ self['uri'] = args[-1]
+ args = args[:-1]
+ BasedirMixin.parseArgs(self, *args)
+
+ def postOptions(self):
+ BasedirMixin.postOptions(self)
+ if not self['uri']:
+ raise usage.UsageError("<uri> parameter is required")
+
client_tac = """
# -*- python -*-
["restart", None, RestartOptions, "Restart a node."],
["dump-uri-extension", None, DumpOptions,
"Unpack and display the contents of a uri_extension file."],
- ["dump-directory-node", None, DumpOptions,
+ ["dump-root-dirnode", None, DumpRootDirnodeOptions,
+ "Compute most of the URI for the vdrive server's root dirnode."],
+ ["dump-dirnode", None, DumpDirnodeOptions,
"Unpack and display the contents of a vdrive DirectoryNode."],
]
rc = start(basedir, so) or rc
elif command == "dump-uri-extension":
rc = dump_uri_extension(so)
- elif command == "dump-directory-node":
- rc = dump_directory_node(so)
+ elif command == "dump-root-dirnode":
+ rc = dump_root_dirnode(so.basedirs[0], so)
+ elif command == "dump-dirnode":
+ rc = dump_directory_node(so.basedirs[0], so)
return rc
def run():
print
return 0
-def dump_directory_node(config):
- from allmydata import filetable, vdrive
- filename = config['filename']
+def dump_root_dirnode(basedir, config):
+ from allmydata import uri
- basedir, name = os.path.split(filename)
- dirnode = filetable.MutableDirectoryNode(basedir, name)
+ root_dirnode_file = os.path.join(basedir, "vdrive", "root")
+ try:
+ f = open(root_dirnode_file, "rb")
+ key = f.read()
+ rooturi = uri.pack_dirnode_uri("fakeFURL", key)
+ print rooturi
+ return 0
+ except EnvironmentError:
+ print "unable to read root dirnode file from %s" % root_dirnode_file
+ return 1
+
+def dump_directory_node(basedir, config):
+ from allmydata import filetable, vdrive, uri
+ from allmydata.util import hashutil, idlib
+ dir_uri = config['uri']
+ verbose = config['verbose']
+
+ furl, key = uri.unpack_dirnode_uri(dir_uri)
+ if uri.is_mutable_dirnode_uri(dir_uri):
+ wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
+ else:
+ wk, we, rk, index = hashutil.generate_dirnode_keys_from_readkey(key)
+
+ filename = os.path.join(basedir, "vdrive", idlib.b2a(index))
print
- print "DirectoryNode at %s" % name
- print
+ print "dirnode uri: %s" % dir_uri
+ print "filename : %s" % filename
+ print "index : %s" % idlib.b2a(index)
+ if wk:
+ print "writekey : %s" % idlib.b2a(wk)
+ print "write_enabler: %s" % idlib.b2a(we)
+ else:
+ print "writekey : None"
+ print "write_enabler: None"
+ print "readkey : %s" % idlib.b2a(rk)
- children = dirnode._read_from_file()
- names = sorted(children.keys())
- for name in names:
- v = children[name]
- if isinstance(v, vdrive.FileNode):
- value = "File (uri=%s...)" % v.uri[:40]
- elif isinstance(v, vdrive.DirectoryNode):
- lastslash = v.furl.rindex("/")
- furlname = v.furl[lastslash+1:lastslash+1+15]
- value = "Directory (furl=%s.../%s...)" % (v.furl[:15], furlname)
- else:
- value = "weird: %s" % (v,)
- print "%20s: %s" % (name, value)
print
- return 0
+ vds = filetable.VirtualDriveServer(os.path.join(basedir, "vdrive"), False)
+ data = vds._read_from_file(index)
+ if we:
+ if we != data[0]:
+ print "ERROR: write_enabler does not match"
+
+ for (H_key, E_key, E_write, E_read) in data[1]:
+ if verbose:
+ print " H_key %s" % idlib.b2a(H_key)
+ print " E_key %s" % idlib.b2a(E_key)
+ print " E_write %s" % idlib.b2a(E_write)
+ print " E_read %s" % idlib.b2a(E_read)
+ key = vdrive.decrypt(rk, E_key)
+ print " key %s" % key
+ if hashutil.dir_name_hash(rk, key) != H_key:
+ print " ERROR: H_key does not match"
+ if wk and E_write:
+ if len(E_write) < 14:
+ print " ERROR: write data is short:", idlib.b2a(E_write)
+ write = vdrive.decrypt(wk, E_write)
+ print " write: %s" % write
+ read = vdrive.decrypt(rk, E_read)
+ print " read: %s" % read
+ print
+
+ return 0
-import os
from twisted.trial import unittest
-from allmydata.filetable import (MutableDirectoryNode,
- BadFileError, BadNameError)
-from allmydata.vdrive import FileNode, DirectoryNode
+from allmydata import filetable, uri
+from allmydata.util import hashutil
class FileTable(unittest.TestCase):
- def test_files(self):
- os.mkdir("filetable")
- basedir = os.path.abspath("filetable")
- root = MutableDirectoryNode(basedir, "root")
- self.failUnlessEqual(root.list(), [])
- root.add("one", FileNode("vid-one"))
- root.add("two", FileNode("vid-two"))
- self.failUnlessEqual(root.list(), [("one", FileNode("vid-one")),
- ("two", FileNode("vid-two"))])
- root.remove("two")
- self.failUnlessEqual(root.list(), [("one", FileNode("vid-one"))])
- self.failUnlessRaises(BadFileError, root.remove, "two")
- self.failUnlessRaises(BadFileError, root.remove, "three")
-
- self.failUnlessEqual(root.get("one"), FileNode("vid-one"))
- self.failUnlessRaises(BadFileError, root.get, "missing")
- self.failUnlessRaises(BadNameError, root.get, "/etc/passwd") # evil
- self.failUnlessRaises(BadNameError, root.get, "..") # sneaky
- self.failUnlessRaises(BadNameError, root.get, ".") # dumb
-
- # now play with directories
- subdir1 = root.add("subdir1", DirectoryNode("subdir1.furl"))
- self.failUnless(isinstance(subdir1, DirectoryNode))
- subdir1a = root.get("subdir1")
- self.failUnless(isinstance(subdir1a, DirectoryNode))
- self.failUnlessEqual(subdir1a, subdir1)
- entries = root.list()
- self.failUnlessEqual(len(entries), 2)
- one_index = entries.index( ("one", FileNode("vid-one")) )
- subdir_index = 1 - one_index
- self.failUnlessEqual(entries[subdir_index][0], "subdir1")
- subdir2 = entries[subdir_index][1]
- self.failUnless(isinstance(subdir2, DirectoryNode))
-
- self.failUnlessEqual(len(root.list()), 2)
-
- self.failUnlessRaises(BadNameError, # replacing an existing child
- root.add,
- "subdir1", DirectoryNode("subdir1.furl"))
-
- root.remove("subdir1")
- self.failUnlessEqual(root.list(), [("one", FileNode("vid-one"))])
+ def test_vdrive_server(self):
+ basedir = "filetable/FileTable/test_vdrive_server"
+ vds = filetable.VirtualDriveServer(basedir)
+ vds.set_furl("myFURL")
+ root_uri = vds.get_public_root_uri()
+ self.failUnless(uri.is_dirnode_uri(root_uri))
+ self.failUnless(uri.is_mutable_dirnode_uri(root_uri))
+ furl, key = uri.unpack_dirnode_uri(root_uri)
+ self.failUnlessEqual(furl, "myFURL")
+ self.failUnlessEqual(len(key), hashutil.KEYLEN)
+ wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
+ empty_list = vds.list(index)
+ self.failUnlessEqual(empty_list, [])
+ vds.set(index, we, "key1", "name1", "write1", "read1")
+ vds.set(index, we, "key2", "name2", "", "read2")
+
+ self.failUnlessRaises(filetable.ChildAlreadyPresentError,
+ vds.set,
+ index, we, "key2", "name2", "write2", "read2")
+
+ self.failUnlessRaises(filetable.BadWriteEnablerError,
+ vds.set,
+ index, "not the write enabler",
+ "key2", "name2", "write2", "read2")
+
+ self.failUnlessEqual(vds.get(index, "key1"),
+ ("write1", "read1"))
+ self.failUnlessEqual(vds.get(index, "key2"),
+ ("", "read2"))
+ self.failUnlessRaises(IndexError,
+ vds.get, index, "key3")
+
+ self.failUnlessEqual(sorted(vds.list(index)),
+ [ ("name1", "write1", "read1"),
+ ("name2", "", "read2"),
+ ])
+
+ self.failUnlessRaises(filetable.BadWriteEnablerError,
+ vds.delete,
+ index, "not the write enabler", "name1")
+ self.failUnlessEqual(sorted(vds.list(index)),
+ [ ("name1", "write1", "read1"),
+ ("name2", "", "read2"),
+ ])
+ self.failUnlessRaises(IndexError,
+ vds.delete,
+ index, we, "key3")
+
+ vds.delete(index, we, "key1")
+ self.failUnlessEqual(sorted(vds.list(index)),
+ [ ("name2", "", "read2"),
+ ])
+ self.failUnlessRaises(IndexError,
+ vds.get, index, "key1")
+ self.failUnlessEqual(vds.get(index, "key2"),
+ ("", "read2"))
+
+
+ vds2 = filetable.VirtualDriveServer(basedir)
+ vds2.set_furl("myFURL")
+ root_uri2 = vds.get_public_root_uri()
+ self.failUnless(uri.is_mutable_dirnode_uri(root_uri2))
+ furl2, key2 = uri.unpack_dirnode_uri(root_uri2)
+ (wk2, we2, rk2, index2) = \
+ hashutil.generate_dirnode_keys_from_writekey(key2)
+ self.failUnlessEqual(sorted(vds2.list(index2)),
+ [ ("name2", "", "read2"),
+ ])
+
+ def test_no_root(self):
+ basedir = "FileTable/test_no_root"
+ vds = filetable.VirtualDriveServer(basedir, offer_public_root=False)
+ vds.set_furl("myFURL")
+
+ self.failUnlessRaises(filetable.NoPublicRootError,
+ vds.get_public_root_uri)
d1.addCallback(lambda subdir1_node:
subdir1_node.add_file("mydata567", ut))
def _stash_uri(filenode):
- self.uri = filenode.uri
+ self.uri = filenode.get_uri()
return filenode
d1.addCallback(_stash_uri)
return d1
-import os
from twisted.trial import unittest
from twisted.internet import defer
-from allmydata import vdrive, filetable
+from twisted.python import failure
+from allmydata import vdrive, filetable, uri
+from allmydata.interfaces import IDirectoryNode
+from foolscap import eventual
-class LocalDirNode(filetable.MutableDirectoryNode):
+class LocalReference:
+ def __init__(self, target):
+ self.target = target
def callRemote(self, methname, *args, **kwargs):
- def _call():
- meth = getattr(self, methname)
+ def _call(ignored):
+ meth = getattr(self.target, methname)
return meth(*args, **kwargs)
- return defer.maybeDeferred(_call)
+ d = eventual.fireEventually(None)
+ d.addCallback(_call)
+ return d
+
+class MyTub:
+ def __init__(self, vds, myfurl):
+ self.vds = vds
+ self.myfurl = myfurl
+ def getReference(self, furl):
+ assert furl == self.myfurl
+ return eventual.fireEventually(LocalReference(self.vds))
+
+class MyClient:
+ def __init__(self, vds, myfurl):
+ self.tub = MyTub(vds, myfurl)
+
+class Test(unittest.TestCase):
+ def test_create_directory(self):
+ basedir = "vdrive/test_create_directory"
+ vds = filetable.VirtualDriveServer(basedir)
+ vds.set_furl("myFURL")
+ self.client = client = MyClient(vds, "myFURL")
+ d = vdrive.create_directory(client, "myFURL")
+ def _created(node):
+ self.failUnless(IDirectoryNode.providedBy(node))
+ self.failUnless(node.is_mutable())
+ d.addCallback(_created)
+ return d
+
+ def test_one(self):
+ basedir = "vdrive/test_one"
+ vds = filetable.VirtualDriveServer(basedir)
+ vds.set_furl("myFURL")
+ root_uri = vds.get_public_root_uri()
+
+ self.client = client = MyClient(vds, "myFURL")
+ d1 = vdrive.create_directory_node(client, root_uri)
+ d2 = vdrive.create_directory_node(client, root_uri)
+ d = defer.gatherResults( [d1,d2] )
+ d.addCallback(self._test_one_1)
+ return d
+
+ def _test_one_1(self, (rootnode1, rootnode2) ):
+ self.failUnlessEqual(rootnode1, rootnode2)
+ self.failIfEqual(rootnode1, "not")
+
+ self.rootnode = rootnode = rootnode1
+ self.failUnless(rootnode.is_mutable())
+ self.readonly_uri = rootnode.get_immutable_uri()
+ d = vdrive.create_directory_node(self.client, self.readonly_uri)
+ d.addCallback(self._test_one_2)
+ return d
+
+ def _test_one_2(self, ro_rootnode):
+ self.ro_rootnode = ro_rootnode
+ self.failIf(ro_rootnode.is_mutable())
+ self.failUnlessEqual(ro_rootnode.get_immutable_uri(),
+ self.readonly_uri)
+
+ rootnode = self.rootnode
+
+ ignored = rootnode.dump()
+
+ # root/
+ d = rootnode.list()
+ def _listed(res):
+ self.failUnlessEqual(res, {})
+ d.addCallback(_listed)
+
+ file1 = uri.pack_uri("i"*32, "k"*16, "e"*32, 25, 100, 12345)
+ file2 = uri.pack_uri("i"*31 + "2", "k"*16, "e"*32, 25, 100, 12345)
+ file2_node = vdrive.FileNode(file2, None)
+ d.addCallback(lambda res: rootnode.set_uri("foo", file1))
+ # root/
+ # root/foo =file1
+
+ d.addCallback(lambda res: rootnode.list())
+ def _listed2(res):
+ self.failUnlessEqual(res.keys(), ["foo"])
+ file1_node = res["foo"]
+ self.failUnless(isinstance(file1_node, vdrive.FileNode))
+ self.failUnlessEqual(file1_node.uri, file1)
+ d.addCallback(_listed2)
+
+ d.addCallback(lambda res: rootnode.get("foo"))
+ def _got_foo(res):
+ self.failUnless(isinstance(res, vdrive.FileNode))
+ self.failUnlessEqual(res.uri, file1)
+ d.addCallback(_got_foo)
+
+ d.addCallback(lambda res: rootnode.get("missing"))
+ # this should raise an exception
+ d.addBoth(self.shouldFail, IndexError, "get('missing')",
+ "unable to find child named 'missing'")
+
+ d.addCallback(lambda res: rootnode.create_empty_directory("bar"))
+ # root/
+ # root/foo =file1
+ # root/bar/
+
+ d.addCallback(lambda res: rootnode.list())
+ d.addCallback(self.failUnlessKeysMatch, ["foo", "bar"])
+ def _listed3(res):
+ self.failIfEqual(res["foo"], res["bar"])
+ self.failIfEqual(res["bar"], res["foo"])
+ self.failIfEqual(res["foo"], "not")
+ self.failIfEqual(res["bar"], self.rootnode)
+ self.failUnlessEqual(res["foo"], res["foo"])
+ # make sure the objects can be used as dict keys
+ testdict = {res["foo"]: 1, res["bar"]: 2}
+ bar_node = res["bar"]
+ self.failUnless(isinstance(bar_node, vdrive.MutableDirectoryNode))
+ self.bar_node = bar_node
+ bar_ro_uri = bar_node.get_immutable_uri()
+ return rootnode.set_uri("bar-ro", bar_ro_uri)
+ d.addCallback(_listed3)
+ # root/
+ # root/foo =file1
+ # root/bar/
+ # root/bar-ro/ (read-only)
+
+ d.addCallback(lambda res: rootnode.list())
+ d.addCallback(self.failUnlessKeysMatch, ["foo", "bar", "bar-ro"])
+ def _listed4(res):
+ self.failIf(res["bar-ro"].is_mutable())
+ self.bar_node_readonly = res["bar-ro"]
+
+ # add another file to bar/
+ bar = res["bar"]
+ return bar.set_node("file2", file2_node)
+ d.addCallback(_listed4)
+ d.addCallback(self.failUnlessIdentical, file2_node)
+ # and a directory
+ d.addCallback(lambda res: self.bar_node.create_empty_directory("baz"))
+ # root/
+ # root/foo =file1
+ # root/bar/
+ # root/bar/file2 =file2
+ # root/bar/baz/
+ # root/bar-ro/ (read-only)
+ # root/bar-ro/file2 =file2
+ # root/bar-ro/baz/
+
+ d.addCallback(lambda res: self.bar_node.list())
+ d.addCallback(self.failUnlessKeysMatch, ["file2", "baz"])
+ d.addCallback(lambda res:
+ self.failUnless(res["baz"].is_mutable()))
+
+ d.addCallback(lambda res: self.bar_node_readonly.list())
+ d.addCallback(self.failUnlessKeysMatch, ["file2", "baz"])
+ d.addCallback(lambda res:
+ self.failIf(res["baz"].is_mutable()))
+
+ # try to add a file to bar-ro, should get exception
+ d.addCallback(lambda res:
+ self.bar_node_readonly.set_uri("file3", file2))
+ d.addBoth(self.shouldFail, vdrive.NotMutableError,
+ "bar-ro.set('file3')")
+
+ # try to delete a file from bar-ro, should get exception
+ d.addCallback(lambda res: self.bar_node_readonly.delete("file2"))
+ d.addBoth(self.shouldFail, vdrive.NotMutableError,
+ "bar-ro.delete('file2')")
+
+ # try to mkdir in bar-ro, should get exception
+ d.addCallback(lambda res:
+ self.bar_node_readonly.create_empty_directory("boffo"))
+ d.addBoth(self.shouldFail, vdrive.NotMutableError,
+ "bar-ro.mkdir('boffo')")
+
+ d.addCallback(lambda res: rootnode.delete("foo"))
+ # root/
+ # root/bar/
+ # root/bar/file2 =file2
+ # root/bar/baz/
+ # root/bar-ro/ (read-only)
+ # root/bar-ro/file2 =file2
+ # root/bar-ro/baz/
+
+ d.addCallback(lambda res: rootnode.list())
+ d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro"])
+
+ d.addCallback(lambda res:
+ self.bar_node.move_child_to("file2",
+ self.rootnode, "file4"))
+ # root/
+ # root/file4 = file4
+ # root/bar/
+ # root/bar/baz/
+ # root/bar-ro/ (read-only)
+ # root/bar-ro/baz/
+
+ d.addCallback(lambda res: rootnode.list())
+ d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro", "file4"])
+ d.addCallback(lambda res:self.bar_node.list())
+ d.addCallback(self.failUnlessKeysMatch, ["baz"])
+ d.addCallback(lambda res:self.bar_node_readonly.list())
+ d.addCallback(self.failUnlessKeysMatch, ["baz"])
+
+
+ d.addCallback(lambda res:
+ rootnode.move_child_to("file4",
+ self.bar_node_readonly, "boffo"))
+ d.addBoth(self.shouldFail, vdrive.NotMutableError,
+ "mv root/file4 root/bar-ro/boffo")
+
+ d.addCallback(lambda res: rootnode.list())
+ d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro", "file4"])
+ d.addCallback(lambda res:self.bar_node.list())
+ d.addCallback(self.failUnlessKeysMatch, ["baz"])
+ d.addCallback(lambda res:self.bar_node_readonly.list())
+ d.addCallback(self.failUnlessKeysMatch, ["baz"])
+
+
+ d.addCallback(lambda res:
+ rootnode.move_child_to("file4", self.bar_node))
+
+ d.addCallback(lambda res: rootnode.list())
+ d.addCallback(self.failUnlessKeysMatch, ["bar", "bar-ro"])
+ d.addCallback(lambda res:self.bar_node.list())
+ d.addCallback(self.failUnlessKeysMatch, ["baz", "file4"])
+ d.addCallback(lambda res:self.bar_node_readonly.list())
+ d.addCallback(self.failUnlessKeysMatch, ["baz", "file4"])
+
+ return d
+
+ def shouldFail(self, res, expected_failure, which, substring=None):
+ if isinstance(res, failure.Failure):
+ res.trap(expected_failure)
+ if substring:
+ self.failUnless(substring in str(res),
+ "substring '%s' not in '%s'"
+ % (substring, str(res)))
+ else:
+ self.fail("%s was supposed to raise %s, not get '%s'" %
+ (which, expected_failure, res))
+
+ def failUnlessKeysMatch(self, res, expected_keys):
+ self.failUnlessEqual(sorted(res.keys()),
+ sorted(expected_keys))
+ return res
+"""
class Traverse(unittest.TestCase):
def make_tree(self, basedir):
os.makedirs(basedir)
["2.a", "2.b", "d2.1"]))
return d
del Traverse
+"""
if "hash" in k:
unpacked[k] = idlib.b2a(unpacked[k])
return unpacked
+
+def is_dirnode_uri(uri):
+ return uri.startswith("URI:DIR:") or uri.startswith("URI:DIR-RO:")
+def is_mutable_dirnode_uri(uri):
+ return uri.startswith("URI:DIR:")
+def unpack_dirnode_uri(uri):
+ assert is_dirnode_uri(uri)
+ # URI:DIR:furl:key
+ # but note that the furl contains colons
+ for prefix in ("URI:DIR:", "URI:DIR-RO:"):
+ if uri.startswith(prefix):
+ uri = uri[len(prefix):]
+ break
+ else:
+ assert 0
+ colon = uri.rindex(":")
+ furl = uri[:colon]
+ key = uri[colon+1:]
+ return furl, idlib.a2b(key)
+
+def make_immutable_dirnode_uri(mutable_uri):
+ assert is_mutable_dirnode_uri(mutable_uri)
+ furl, writekey = unpack_dirnode_uri(mutable_uri)
+ readkey = hashutil.dir_read_key_hash(writekey)
+ return "URI:DIR-RO:%s:%s" % (furl, idlib.b2a(readkey))
+
+def pack_dirnode_uri(furl, writekey):
+ return "URI:DIR:%s:%s" % (furl, idlib.b2a(writekey))
from allmydata.Crypto.Hash import SHA256
+import os
def netstring(s):
return "%d:%s," % (len(s), s,)
def key_hasher():
return tagged_hasher("allmydata_encryption_key_v1")
+KEYLEN = 16
+def random_key():
+ return os.urandom(KEYLEN)
+
+def dir_write_enabler_hash(write_key):
+ return tagged_hash("allmydata_dir_write_enabler_v1", write_key)
+def dir_read_key_hash(write_key):
+ return tagged_hash("allmydata_dir_read_key_v1", write_key)[:KEYLEN]
+def dir_index_hash(read_key):
+ return tagged_hash("allmydata_dir_index_v1", read_key)
+def dir_name_hash(readkey, name):
+ return tagged_pair_hash("allmydata_dir_name_v1", readkey, name)
+
+def generate_dirnode_keys_from_writekey(write_key):
+ readkey = dir_read_key_hash(write_key)
+ write_enabler = dir_write_enabler_hash(write_key)
+ index = dir_index_hash(readkey)
+ return write_key, write_enabler, readkey, index
+
+def generate_dirnode_keys_from_readkey(read_key):
+ index = dir_index_hash(read_key)
+ return None, None, read_key, index
"""This is the client-side facility to manipulate virtual drives."""
+import os.path
+from zope.interface import implements
from twisted.application import service
from twisted.internet import defer
from twisted.python import log
-from allmydata import upload, download
-from foolscap import Copyable, RemoteCopy
+from allmydata import upload, download, uri
+from allmydata.Crypto.Cipher import AES
+from allmydata.util import hashutil, idlib
+from allmydata.interfaces import IDirectoryNode, IFileNode
+
+class NotMutableError(Exception):
+ pass
class VDrive(service.MultiService):
name = "vdrive"
return self.get_file(from_where, download.FileHandle(filehandle))
-class DirectoryNode(Copyable, RemoteCopy):
- """I have either a .furl attribute or a .get(tub) method."""
- typeToCopy = "allmydata.com/tahoe/interfaces/DirectoryNode/v1"
- copytype = typeToCopy
- def __init__(self, furl=None, client=None):
- # RemoteCopy subclasses are always called without arguments
- self.furl = furl
- self._set_client(client)
- def _set_client(self, client):
+def create_directory_node(client, diruri):
+ assert uri.is_dirnode_uri(diruri)
+ if uri.is_mutable_dirnode_uri(diruri):
+ dirnode_class = MutableDirectoryNode
+ else:
+ dirnode_class = ImmutableDirectoryNode
+ (furl, key) = uri.unpack_dirnode_uri(diruri)
+ d = client.tub.getReference(furl)
+ def _got(rref):
+ dirnode = dirnode_class(diruri, client, rref, key)
+ return dirnode
+ d.addCallback(_got)
+ return d
+
+def encrypt(key, data):
+ # TODO: add the hmac
+ IV = os.urandom(14)
+ counterstart = IV + "\x00"*2
+ assert len(counterstart) == 16, len(counterstart)
+ cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
+ crypttext = cryptor.encrypt(data)
+ return IV + crypttext
+
+def decrypt(key, data):
+ # TODO: validate the hmac
+ assert len(data) >= 14, len(data)
+ IV = data[:14]
+ counterstart = IV + "\x00"*2
+ assert len(counterstart) == 16, len(counterstart)
+ cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
+ plaintext = cryptor.decrypt(data[14:])
+ return plaintext
+
+
+class ImmutableDirectoryNode:
+ implements(IDirectoryNode)
+
+ def __init__(self, myuri, client, rref, readkey):
+ self._uri = myuri
self._client = client
- return self
- def getStateToCopy(self):
- return {"furl": self.furl }
- def setCopyableState(self, state):
- self.furl = state['furl']
+ self._tub = client.tub
+ self._rref = rref
+ self._readkey = readkey
+ self._writekey = None
+ self._write_enabler = None
+ self._index = hashutil.dir_index_hash(self._readkey)
+ self._mutable = False
+
+ def dump(self):
+ return ["URI: %s" % self._uri,
+ "rk: %s" % idlib.b2a(self._readkey),
+ "index: %s" % idlib.b2a(self._index),
+ ]
+
+ def is_mutable(self):
+ return self._mutable
+
+ def get_uri(self):
+ return self._uri
+
+ def get_immutable_uri(self):
+ # return the dirnode URI for a read-only form of this directory
+ if self._mutable:
+ return uri.make_immutable_dirnode_uri(self._uri)
+ else:
+ return self._uri
+
def __hash__(self):
- return hash((self.__class__, self.furl))
+ return hash((self.__class__, self._uri))
def __cmp__(self, them):
if cmp(type(self), type(them)):
return cmp(type(self), type(them))
if cmp(self.__class__, them.__class__):
return cmp(self.__class__, them.__class__)
- return cmp(self.furl, them.furl)
+ return cmp(self._uri, them._uri)
+
+ def _encrypt(self, key, data):
+ return encrypt(key, data)
+
+ def _decrypt(self, key, data):
+ return decrypt(key, data)
+
+ def _decrypt_child(self, E_write, E_read):
+ if E_write and self._writekey:
+ # we prefer read-write children when we can get them
+ return self._decrypt(self._writekey, E_write)
+ else:
+ return self._decrypt(self._readkey, E_read)
def list(self):
- d = self._client.tub.getReference(self.furl)
- d.addCallback(lambda node: node.callRemote("list"))
- d.addCallback(lambda children:
- [(name,child._set_client(self._client))
- for name,child in children])
+ d = self._rref.callRemote("list", self._index)
+ entries = {}
+ def _got(res):
+ dl = []
+ for (E_name, E_write, E_read) in res:
+ name = self._decrypt(self._readkey, E_name)
+ child_uri = self._decrypt_child(E_write, E_read)
+ d2 = self._create_node(child_uri)
+ def _created(node, name):
+ entries[name] = node
+ d2.addCallback(_created, name)
+ dl.append(d2)
+ return defer.DeferredList(dl)
+ d.addCallback(_got)
+ d.addCallback(lambda res: entries)
return d
+ def _hash_name(self, name):
+ return hashutil.dir_name_hash(self._readkey, name)
+
def get(self, name):
- d = self._client.tub.getReference(self.furl)
- d.addCallback(lambda node: node.callRemote("get", name))
- d.addCallback(lambda child: child._set_client(self._client))
+ H_name = self._hash_name(name)
+ d = self._rref.callRemote("get", self._index, H_name)
+ def _check_index_error(f):
+ f.trap(IndexError)
+ raise IndexError("get(index=%s): unable to find child named '%s'"
+ % (idlib.b2a(self._index), name))
+ d.addErrback(_check_index_error)
+ d.addCallback(lambda (E_write, E_read):
+ self._decrypt_child(E_write, E_read))
+ d.addCallback(self._create_node)
return d
- def add(self, name, child):
- d = self._client.tub.getReference(self.furl)
- d.addCallback(lambda node: node.callRemote("add", name, child))
- d.addCallback(lambda newnode: newnode._set_client(self._client))
+ def _set(self, name, write_child, read_child):
+ if not self._mutable:
+ return defer.fail(NotMutableError())
+ H_name = self._hash_name(name)
+ E_name = self._encrypt(self._readkey, name)
+ E_write = ""
+ if self._writekey and write_child:
+ E_write = self._encrypt(self._writekey, write_child)
+ E_read = self._encrypt(self._readkey, read_child)
+ d = self._rref.callRemote("set", self._index, self._write_enabler,
+ H_name, E_name, E_write, E_read)
return d
- def add_file(self, name, uploadable):
- uploader = self._client.getServiceNamed("uploader")
- d = uploader.upload(uploadable)
- d.addCallback(lambda uri: self.add(name, FileNode(uri, self._client)))
+ def set_uri(self, name, child_uri):
+ write, read = self._split_uri(child_uri)
+ return self._set(name, write, read)
+
+ def set_node(self, name, child):
+ d = self.set_uri(name, child.get_uri())
+ d.addCallback(lambda res: child)
return d
- def remove(self, name):
- d = self._client.tub.getReference(self.furl)
- d.addCallback(lambda node: node.callRemote("remove", name))
- d.addCallback(lambda newnode: newnode._set_client(self._client))
+ def delete(self, name):
+ if not self._mutable:
+ return defer.fail(NotMutableError())
+ H_name = self._hash_name(name)
+ d = self._rref.callRemote("delete", self._index, self._write_enabler,
+ H_name)
return d
+ def _create_node(self, child_uri):
+ if uri.is_dirnode_uri(child_uri):
+ return create_directory_node(self._client, child_uri)
+ else:
+ return defer.succeed(FileNode(child_uri, self._client))
+
+ def _split_uri(self, child_uri):
+ if uri.is_dirnode_uri(child_uri):
+ if uri.is_mutable_dirnode_uri(child_uri):
+ write = child_uri
+ read = uri.make_immutable_dirnode_uri(child_uri)
+ else:
+ write = None
+ read = child_uri
+ return (write, read)
+ return (None, child_uri) # file
+
def create_empty_directory(self, name):
- vdrive_server = self._client._vdrive_server
- d = vdrive_server.callRemote("create_directory")
- d.addCallback(lambda node: self.add(name, node))
+ if not self._mutable:
+ return defer.fail(NotMutableError())
+ child_writekey = hashutil.random_key()
+ my_furl, parent_writekey = uri.unpack_dirnode_uri(self._uri)
+ child_uri = uri.pack_dirnode_uri(my_furl, child_writekey)
+ child = MutableDirectoryNode(child_uri, self._client, self._rref,
+ child_writekey)
+ d = self._rref.callRemote("create_directory",
+ child._index, child._write_enabler)
+ d.addCallback(lambda index: self.set_node(name, child))
return d
- def attach_shared_directory(self, name, furl):
- d = self.add(name, DirectoryNode(furl))
+ def add_file(self, name, uploadable):
+ if not self._mutable:
+ return defer.fail(NotMutableError())
+ uploader = self._client.getServiceNamed("uploader")
+ d = uploader.upload(uploadable)
+ d.addCallback(lambda uri: self.set_node(name,
+ FileNode(uri, self._client)))
return d
- def get_shared_directory_furl(self):
- return defer.succeed(self.furl)
-
def move_child_to(self, current_child_name,
new_parent, new_child_name=None):
+ if not (self._mutable and new_parent.is_mutable()):
+ return defer.fail(NotMutableError())
if new_child_name is None:
new_child_name = current_child_name
d = self.get(current_child_name)
- d.addCallback(lambda child: new_parent.add(new_child_name, child))
- d.addCallback(lambda child: self.remove(current_child_name))
+ d.addCallback(lambda child: new_parent.set_node(new_child_name, child))
+ d.addCallback(lambda child: self.delete(current_child_name))
return d
-class FileNode(Copyable, RemoteCopy):
- """I have a .uri attribute."""
- typeToCopy = "allmydata.com/tahoe/interfaces/FileNode/v1"
- copytype = typeToCopy
- def __init__(self, uri=None, client=None):
- # RemoteCopy subclasses are always called without arguments
+class MutableDirectoryNode(ImmutableDirectoryNode):
+ implements(IDirectoryNode)
+
+ def __init__(self, myuri, client, rref, writekey):
+ readkey = hashutil.dir_read_key_hash(writekey)
+ ImmutableDirectoryNode.__init__(self, myuri, client, rref, readkey)
+ self._writekey = writekey
+ self._write_enabler = hashutil.dir_write_enabler_hash(writekey)
+ self._mutable = True
+
+def create_directory(client, furl):
+ write_key = hashutil.random_key()
+ (wk, we, rk, index) = \
+ hashutil.generate_dirnode_keys_from_writekey(write_key)
+ myuri = uri.pack_dirnode_uri(furl, wk)
+ d = client.tub.getReference(furl)
+ def _got_vdrive_server(vdrive_server):
+ node = MutableDirectoryNode(myuri, client, vdrive_server, wk)
+ d2 = vdrive_server.callRemote("create_directory", index, we)
+ d2.addCallback(lambda res: node)
+ return d2
+ d.addCallback(_got_vdrive_server)
+ return d
+
+class FileNode:
+ implements(IFileNode)
+
+ def __init__(self, uri, client):
self.uri = uri
- self._set_client(client)
- def _set_client(self, client):
self._client = client
- return self
- def getStateToCopy(self):
- return {"uri": self.uri }
- def setCopyableState(self, state):
- self.uri = state['uri']
+
+ def get_uri(self):
+ return self.uri
+
def __hash__(self):
return hash((self.__class__, self.uri))
def __cmp__(self, them):
<div><a href=".">Refresh this view</a></div>
<div><a href="..">Parent Directory</a></div>
-<div>To share this directory, paste the following FURL string into an
+<div>To share this directory, paste the following URI string into an
"Add Shared Directory" box:
- <pre class="overflow" n:render="string" n:data="share_url" /></div>
+ <pre class="overflow" n:render="string" n:data="share_uri" /></div>
+<div>To share a transitively read-only copy, use the following URI instead:
+ <pre class="overflow" n:render="string" n:data="share_readonly_uri" /></div>
<table n:render="sequence" n:data="children" border="1">
<tr n:pattern="header">
from nevow.static import File as nevow_File # TODO: merge with static.File?
from allmydata.util import idlib
from allmydata.uri import unpack_uri
-from allmydata.interfaces import IDownloadTarget
-from allmydata.vdrive import FileNode, DirectoryNode
+from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode
+from allmydata.vdrive import FileNode
from allmydata import upload
from zope.interface import implements, Interface
import urllib
dirname = self._dirname + "/" + name
d = self._dirnode.get(name)
def _got_child(res):
- if isinstance(res, FileNode):
+ if IFileNode.providedBy(res):
dl = get_downloader_service(ctx)
return Downloader(dl, name, res)
- elif isinstance(res, DirectoryNode):
+ elif IDirectoryNode.providedBy(res):
return Directory(res, dirname)
else:
raise RuntimeError("what is this %s" % res)
return ctx.tag["Directory of '%s':" % self._dirname]
def render_header(self, ctx, data):
- return "Directory of '%s':" % self._dirname
+ header = "Directory of '%s':" % self._dirname
+ if not self._dirnode.is_mutable():
+ header += " (readonly)"
+ return header
- def data_share_url(self, ctx, data):
- return self._dirnode.furl
+ def data_share_uri(self, ctx, data):
+ return self._dirnode.get_uri()
+ def data_share_readonly_uri(self, ctx, data):
+ return self._dirnode.get_immutable_uri()
def data_children(self, ctx, data):
d = self._dirnode.list()
+ d.addCallback(lambda dict: sorted(dict.items()))
return d
def render_row(self, ctx, data):
name, target = data
- if isinstance(target, FileNode):
+
+ if self._dirnode.is_mutable():
+ # this creates a button which will cause our child__delete method
+ # to be invoked, which deletes the file and then redirects the
+ # browser back to this directory
+ del_url = url.here.child("_delete")
+ #del_url = del_url.add("uri", target.uri)
+ del_url = del_url.add("name", name)
+ delete = T.form(action=del_url, method="post")[
+ T.input(type='submit', value='del', name="del"),
+ ]
+ else:
+ delete = "-"
+ ctx.fillSlots("delete", delete)
+
+ if IFileNode.providedBy(target):
# file
dlurl = urllib.quote(name)
ctx.fillSlots("filename",
#extract and display file size
ctx.fillSlots("size", unpack_uri(uri)['size'])
- # this creates a button which will cause our child__delete method
- # to be invoked, which deletes the file and then redirects the
- # browser back to this directory
- del_url = url.here.child("_delete")
- #del_url = del_url.add("uri", target.uri)
- del_url = del_url.add("name", name)
- delete = T.form(action=del_url, method="post")[
- T.input(type='submit', value='del', name="del"),
- ]
- ctx.fillSlots("delete", delete)
- elif isinstance(target, DirectoryNode):
+ elif IDirectoryNode.providedBy(target):
# directory
subdir_url = urllib.quote(name)
ctx.fillSlots("filename",
ctx.fillSlots("type", "DIR")
ctx.fillSlots("size", "-")
ctx.fillSlots("uri", "-")
-
- del_url = url.here.child("_delete")
- del_url = del_url.add("name", name)
- delete = T.form(action=del_url, method="post")[
- T.input(type='submit', value='del', name="del"),
- ]
- ctx.fillSlots("delete", delete)
else:
raise RuntimeError("unknown thing %s" % (target,))
return ctx.tag
def render_forms(self, ctx, data):
- return webform.renderForms()
+ if self._dirnode.is_mutable():
+ return webform.renderForms()
+ return T.div["No upload forms: directory is immutable"]
def render_results(self, ctx, data):
req = inevow.IRequest(ctx)
log.msg("starting webish upload")
uploader = get_uploader_service(ctx)
- d = uploader.upload(upload.FileHandle(contents.file))
+ uploadable = upload.FileHandle(contents.file)
name = contents.filename
- def _uploaded(uri):
- if privateupload:
- return self.uploadprivate(name, uri)
- else:
- return self._dirnode.add(name, FileNode(uri))
- d.addCallback(_uploaded)
+ if privateupload:
+ d = uploader.upload(uploadable)
+ d.addCallback(lambda uri: self.uploadprivate(name, uri))
+ else:
+ d = self._dirnode.add_file(name, uploadable)
def _done(res):
log.msg("webish upload complete")
return res
def bind_mount(self, ctx):
namearg = annotate.Argument("name",
annotate.String("Name to place incoming directory: "))
- furlarg = annotate.Argument("furl",
- annotate.String("FURL of Shared Directory"))
- meth = annotate.Method(arguments=[namearg, furlarg],
+ uriarg = annotate.Argument("uri",
+ annotate.String("URI of Shared Directory"))
+ meth = annotate.Method(arguments=[namearg, uriarg],
label="Add Shared Directory")
return annotate.MethodBinding("mount", meth,
action="Mount Shared Directory")
- def mount(self, name, furl):
- d = self._dirnode.attach_shared_directory(name, furl)
+ def mount(self, name, uri):
+ d = self._dirnode.set_uri(name, uri)
#d.addCallback(lambda done: url.here.child(name))
return d
# perform the delete, then redirect back to the directory page
args = inevow.IRequest(ctx).args
name = args["name"][0]
- d = self._dirnode.remove(name)
+ d = self._dirnode.delete(name)
d.addCallback(lambda done: url.here.up())
return d
def __init__(self, downloader, name, filenode):
self._downloader = downloader
self._name = name
- assert isinstance(filenode, FileNode)
+ IFileNode(filenode)
self._filenode = filenode
def render(self, ctx):
# apparently 'ISite' does not exist
#self.site._client = self.parent
- def set_vdrive_root(self, root):
+ def set_vdrive_rootnode(self, root):
self.root.putChild("global_vdrive", Directory(root, "/"))
self.root.child_welcome.has_global_vdrive = True
# I tried doing it this way and for some reason it didn't seem to work
#print "REMEMBERING", self.site, dl, IDownloader
#self.site.remember(dl, IDownloader)
- def set_my_vdrive_root(self, my_vdrive):
+ def set_my_vdrive_rootnode(self, my_vdrive):
self.root.putChild("my_vdrive", Directory(my_vdrive, "~"))
self.root.child_welcome.has_my_vdrive = True