vdrive: switch to URI:DIR and URI:DIR-RO, providing transitive readonlyness

This commit is contained in:
Brian Warner
2007-06-25 13:23:51 -07:00
parent 2cf7cfbe4a
commit fb02488a8e
13 changed files with 978 additions and 393 deletions

View File

@ -2,8 +2,8 @@
import os, sha, stat, time import os, sha, stat, time
from foolscap import Referenceable, SturdyRef from foolscap import Referenceable, SturdyRef
from zope.interface import implements from zope.interface import implements
from allmydata.interfaces import RIClient from allmydata.interfaces import RIClient, IDirectoryNode
from allmydata import node from allmydata import node, vdrive, uri
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from twisted.application.internet import TimerService from twisted.application.internet import TimerService
@ -13,7 +13,6 @@ from allmydata.Crypto.Util.number import bytes_to_long
from allmydata.storageserver import StorageServer from allmydata.storageserver import StorageServer
from allmydata.upload import Uploader from allmydata.upload import Uploader
from allmydata.download import Downloader from allmydata.download import Downloader
from allmydata.vdrive import DirectoryNode
from allmydata.webish import WebishServer from allmydata.webish import WebishServer
from allmydata.control import ControlServer from allmydata.control import ControlServer
from allmydata.introducer import IntroducerClient from allmydata.introducer import IntroducerClient
@ -28,7 +27,7 @@ class Client(node.Node, Referenceable):
GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl" GLOBAL_VDRIVE_FURL_FILE = "vdrive.furl"
MY_FURL_FILE = "myself.furl" MY_FURL_FILE = "myself.furl"
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline" 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 # we're pretty narrow-minded right now
OLDEST_SUPPORTED_VERSION = allmydata.__version__ OLDEST_SUPPORTED_VERSION = allmydata.__version__
@ -115,16 +114,20 @@ class Client(node.Node, Referenceable):
def _got_vdrive(self, vdrive_server): def _got_vdrive(self, vdrive_server):
# vdrive_server implements RIVirtualDriveServer # vdrive_server implements RIVirtualDriveServer
self.log("connected to vdrive server") self.log("connected to vdrive server")
d = vdrive_server.callRemote("get_public_root_furl") d = vdrive_server.callRemote("get_public_root_uri")
d.addCallback(self._got_vdrive_root_furl, vdrive_server) d.addCallback(self._got_vdrive_uri)
d.addCallback(self._create_my_vdrive) d.addCallback(self._got_vdrive_rootnode)
d.addCallback(self._create_my_vdrive, vdrive_server)
d.addCallback(self._got_my_vdrive) d.addCallback(self._got_my_vdrive)
def _got_vdrive_root_furl(self, vdrive_root_furl, vdrive_server): def _got_vdrive_uri(self, root_uri):
root = DirectoryNode(vdrive_root_furl, self) 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.log("got vdrive root")
self._vdrive_server = vdrive_server self._vdrive_root = rootnode
self._vdrive_root = root
self._connected_to_vdrive = True self._connected_to_vdrive = True
#vdrive = self.getServiceNamed("vdrive") #vdrive = self.getServiceNamed("vdrive")
@ -133,34 +136,34 @@ class Client(node.Node, Referenceable):
if "webish" in self.namedServices: if "webish" in self.namedServices:
webish = self.getServiceNamed("webish") webish = self.getServiceNamed("webish")
webish.set_vdrive_root(root) webish.set_vdrive_rootnode(rootnode)
def _create_my_vdrive(self, ignored=None): def _create_my_vdrive(self, ignored, vdrive_server):
MY_VDRIVE_FURL_FILE = os.path.join(self.basedir, MY_VDRIVE_URI_FILE = os.path.join(self.basedir,
self.MY_VDRIVE_FURL_FILE) self.MY_VDRIVE_URI_FILE)
try: try:
f = open(MY_VDRIVE_FURL_FILE, "r") f = open(MY_VDRIVE_URI_FILE, "r")
my_vdrive_furl = f.read().strip() my_vdrive_uri = f.read().strip()
f.close() f.close()
return defer.succeed(DirectoryNode(my_vdrive_furl, self)) return vdrive.create_directory_node(self, my_vdrive_uri)
except EnvironmentError: 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): def _got_directory(dirnode):
f = open(MY_VDRIVE_FURL_FILE, "w") f = open(MY_VDRIVE_URI_FILE, "w")
f.write(dirnode.furl + "\n") f.write(dirnode.get_uri() + "\n")
f.close() f.close()
dirnode._set_client(self)
return dirnode return dirnode
d.addCallback(_got_directory) d.addCallback(_got_directory)
return d return d
def _got_my_vdrive(self, my_vdrive): def _got_my_vdrive(self, my_vdrive):
assert isinstance(my_vdrive, DirectoryNode), my_vdrive IDirectoryNode(my_vdrive)
self._my_vdrive = my_vdrive self._my_vdrive = my_vdrive
if "webish" in self.namedServices: if "webish" in self.namedServices:
webish = self.getServiceNamed("webish") webish = self.getServiceNamed("webish")
webish.set_my_vdrive_root(my_vdrive) webish.set_my_vdrive_rootnode(my_vdrive)
def remote_get_versions(self): def remote_get_versions(self):

View File

@ -1,165 +1,110 @@
import os import os
from zope.interface import implements 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.application import service
from twisted.python import log from foolscap import Referenceable
from allmydata.interfaces import RIVirtualDriveServer
from allmydata.util import bencode, idlib, hashutil, fileutil
from allmydata import uri
class BadNameError(Exception): class BadWriteEnablerError(Exception):
"""Bad filename component"""
class BadFileError(Exception):
pass pass
class ChildAlreadyPresentError(Exception):
class BadDirectoryError(Exception):
pass 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): class NoPublicRootError(Exception):
pass pass
class VirtualDriveServer(service.MultiService, Referenceable): class VirtualDriveServer(service.MultiService, Referenceable):
implements(RIVirtualDriveServer) implements(RIVirtualDriveServer)
name = "filetable" name = "filetable"
VDRIVEDIR = "vdrive"
def __init__(self, basedir=".", offer_public_root=True): def __init__(self, basedir, offer_public_root=True):
service.MultiService.__init__(self) service.MultiService.__init__(self)
vdrive_dir = os.path.join(basedir, self.VDRIVEDIR) self._basedir = os.path.abspath(basedir)
if not os.path.exists(vdrive_dir): fileutil.make_dirs(self._basedir)
os.mkdir(vdrive_dir)
self._vdrive_dir = vdrive_dir
self._root = None self._root = None
if offer_public_root: if offer_public_root:
self._root = MutableDirectoryNode(vdrive_dir, "root") 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 startService(self): def set_furl(self, myfurl):
service.MultiService.startService(self) self._myfurl = myfurl
# _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): def get_public_root_uri(self):
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):
if self._root: if self._root:
return self._root_furl return uri.pack_dirnode_uri(self._myfurl, self._root)
raise NoPublicRootError raise NoPublicRootError
remote_get_public_root_furl = get_public_root_furl remote_get_public_root_uri = get_public_root_uri
def create_directory(self): def create_directory(self, index, write_enabler):
node = MutableDirectoryNode(self._vdrive_dir) data = [write_enabler, []]
furl = self.parent.tub.registerReference(node, node._name) self._write_to_file(index, data)
return DirectoryNode(furl) return index
remote_create_directory = create_directory 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

View File

@ -126,33 +126,79 @@ RIMutableDirectoryNode_ = Any() # TODO: how can we avoid this?
FileNode_ = Any() # TODO: foolscap needs constraints on copyables FileNode_ = Any() # TODO: foolscap needs constraints on copyables
DirectoryNode_ = Any() # TODO: same DirectoryNode_ = Any() # TODO: same
AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_) AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
EncryptedThing = str
class RIMutableDirectoryNode(RemoteInterface): class RIVirtualDriveServer(RemoteInterface):
def list(): def get_public_root_uri():
return ListOf( TupleOf(str, # name, relative to directory """Obtain the URI for this server's global publically-writable root
AnyNode_, 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 create_directory(index=Hash, write_enabler=Hash):
"""Create a new (empty) directory, unattached to anything else.
This returns the same index that was passed in.
"""
return Hash
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.
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, maxLength=100,
) )
def get(name=str): def set(index=Hash, write_enabler=Hash, key=Hash,
return AnyNode_ name=EncryptedThing, write=EncryptedThing, read=EncryptedThing):
"""Set a child object.
def add(name=str, what=AnyNode_): This will raise IndexError if a child with the given name already
return AnyNode_ exists.
"""
pass
def remove(name=str): def delete(index=Hash, write_enabler=Hash, key=Hash):
return AnyNode_ """Delete a specific child.
This uses the hashed key to locate a specific child, and deletes it.
"""
pass
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
def create_directory():
return DirectoryNode_
class IFileNode(Interface): class IFileNode(Interface):
def download(target): def download(target):
@ -161,23 +207,66 @@ class IFileNode(Interface):
pass pass
class IDirectoryNode(Interface): 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(): def list():
"""I return a Deferred that fires with a""" """I return a Deferred that fires with a dictionary mapping child
pass name to an IFileNode or IDirectoryNode."""
def get(name): def get(name):
"""I return a Deferred that fires with a specific named child.""" """I return a Deferred that fires with a specific named child node,
pass either an IFileNode or an IDirectoryNode."""
def add(name, child): 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.
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 """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): def add_file(name, uploadable):
"""I upload a file (using the given IUploadable), then attach the """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 """I remove the child at the specific name. I return a Deferred that
fires when the operation finishes.""" fires when the operation finishes."""
@ -185,18 +274,6 @@ class IDirectoryNode(Interface):
"""I create and attach an empty directory at the given name. I return """I create and attach an empty directory at the given name. I return
a Deferred that fires when the operation finishes.""" 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): 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 """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 is referenced by name. On the new parent, the child will live under

View File

@ -8,6 +8,7 @@ from allmydata.introducer import Introducer
class IntroducerAndVdrive(node.Node): class IntroducerAndVdrive(node.Node):
PORTNUMFILE = "introducer.port" PORTNUMFILE = "introducer.port"
NODETYPE = "introducer" NODETYPE = "introducer"
VDRIVEDIR = "vdrive"
def __init__(self, basedir="."): def __init__(self, basedir="."):
node.Node.__init__(self, basedir) node.Node.__init__(self, basedir)
@ -21,8 +22,11 @@ class IntroducerAndVdrive(node.Node):
f.write(self.urls["introducer"] + "\n") f.write(self.urls["introducer"] + "\n")
f.close() f.close()
vds = self.add_service(VirtualDriveServer(self.basedir)) vdrive_dir = os.path.join(self.basedir, self.VDRIVEDIR)
self.urls["vdrive"] = self.tub.registerReference(vds, "vdrive") 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"]) self.log(" vdrive is at %s" % self.urls["vdrive"])
f = open(os.path.join(self.basedir, "vdrive.furl"), "w") f = open(os.path.join(self.basedir, "vdrive.furl"), "w")
f.write(self.urls["vdrive"] + "\n") f.write(self.urls["vdrive"] + "\n")

View File

@ -129,6 +129,30 @@ class DumpOptions(usage.Options):
if not self['filename']: if not self['filename']:
raise usage.UsageError("<filename> parameter is required") 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 = """ client_tac = """
# -*- python -*- # -*- python -*-
@ -164,7 +188,9 @@ class Options(usage.Options):
["restart", None, RestartOptions, "Restart a node."], ["restart", None, RestartOptions, "Restart a node."],
["dump-uri-extension", None, DumpOptions, ["dump-uri-extension", None, DumpOptions,
"Unpack and display the contents of a uri_extension file."], "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."], "Unpack and display the contents of a vdrive DirectoryNode."],
] ]
@ -211,8 +237,10 @@ def runner(argv, run_by_human=True):
rc = start(basedir, so) or rc rc = start(basedir, so) or rc
elif command == "dump-uri-extension": elif command == "dump-uri-extension":
rc = dump_uri_extension(so) rc = dump_uri_extension(so)
elif command == "dump-directory-node": elif command == "dump-root-dirnode":
rc = dump_directory_node(so) rc = dump_root_dirnode(so.basedirs[0], so)
elif command == "dump-dirnode":
rc = dump_directory_node(so.basedirs[0], so)
return rc return rc
def run(): def run():
@ -329,30 +357,71 @@ def dump_uri_extension(config):
print print
return 0 return 0
def dump_directory_node(config): def dump_root_dirnode(basedir, config):
from allmydata import filetable, vdrive from allmydata import uri
filename = config['filename']
basedir, name = os.path.split(filename) root_dirnode_file = os.path.join(basedir, "vdrive", "root")
dirnode = filetable.MutableDirectoryNode(basedir, name) try:
f = open(root_dirnode_file, "rb")
print key = f.read()
print "DirectoryNode at %s" % name rooturi = uri.pack_dirnode_uri("fakeFURL", key)
print print rooturi
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 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 "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)
print
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

View File

@ -1,54 +1,86 @@
import os
from twisted.trial import unittest from twisted.trial import unittest
from allmydata.filetable import (MutableDirectoryNode, from allmydata import filetable, uri
BadFileError, BadNameError) from allmydata.util import hashutil
from allmydata.vdrive import FileNode, DirectoryNode
class FileTable(unittest.TestCase): class FileTable(unittest.TestCase):
def test_files(self): def test_vdrive_server(self):
os.mkdir("filetable") basedir = "filetable/FileTable/test_vdrive_server"
basedir = os.path.abspath("filetable") vds = filetable.VirtualDriveServer(basedir)
root = MutableDirectoryNode(basedir, "root") vds.set_furl("myFURL")
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")) root_uri = vds.get_public_root_uri()
self.failUnlessRaises(BadFileError, root.get, "missing") self.failUnless(uri.is_dirnode_uri(root_uri))
self.failUnlessRaises(BadNameError, root.get, "/etc/passwd") # evil self.failUnless(uri.is_mutable_dirnode_uri(root_uri))
self.failUnlessRaises(BadNameError, root.get, "..") # sneaky furl, key = uri.unpack_dirnode_uri(root_uri)
self.failUnlessRaises(BadNameError, root.get, ".") # dumb self.failUnlessEqual(furl, "myFURL")
self.failUnlessEqual(len(key), hashutil.KEYLEN)
# now play with directories wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
subdir1 = root.add("subdir1", DirectoryNode("subdir1.furl")) empty_list = vds.list(index)
self.failUnless(isinstance(subdir1, DirectoryNode)) self.failUnlessEqual(empty_list, [])
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) vds.set(index, we, "key1", "name1", "write1", "read1")
vds.set(index, we, "key2", "name2", "", "read2")
self.failUnlessRaises(BadNameError, # replacing an existing child self.failUnlessRaises(filetable.ChildAlreadyPresentError,
root.add, vds.set,
"subdir1", DirectoryNode("subdir1.furl")) index, we, "key2", "name2", "write2", "read2")
root.remove("subdir1") self.failUnlessRaises(filetable.BadWriteEnablerError,
self.failUnlessEqual(root.list(), [("one", FileNode("vid-one"))]) 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)

View File

@ -240,7 +240,7 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
d1.addCallback(lambda subdir1_node: d1.addCallback(lambda subdir1_node:
subdir1_node.add_file("mydata567", ut)) subdir1_node.add_file("mydata567", ut))
def _stash_uri(filenode): def _stash_uri(filenode):
self.uri = filenode.uri self.uri = filenode.get_uri()
return filenode return filenode
d1.addCallback(_stash_uri) d1.addCallback(_stash_uri)
return d1 return d1

View File

@ -1,17 +1,262 @@
import os
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer 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 callRemote(self, methname, *args, **kwargs):
def _call(): def _call(ignored):
meth = getattr(self, methname) meth = getattr(self.target, methname)
return meth(*args, **kwargs) 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): class Traverse(unittest.TestCase):
def make_tree(self, basedir): def make_tree(self, basedir):
os.makedirs(basedir) os.makedirs(basedir)
@ -67,3 +312,4 @@ class Traverse(unittest.TestCase):
["2.a", "2.b", "d2.1"])) ["2.a", "2.b", "d2.1"]))
return d return d
del Traverse del Traverse
"""

View File

@ -86,3 +86,31 @@ def unpack_extension_readable(data):
if "hash" in k: if "hash" in k:
unpacked[k] = idlib.b2a(unpacked[k]) unpacked[k] = idlib.b2a(unpacked[k])
return unpacked 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))

View File

@ -1,4 +1,5 @@
from allmydata.Crypto.Hash import SHA256 from allmydata.Crypto.Hash import SHA256
import os
def netstring(s): def netstring(s):
return "%d:%s," % (len(s), s,) return "%d:%s," % (len(s), s,)
@ -56,3 +57,25 @@ def key_hash(data):
def key_hasher(): def key_hasher():
return tagged_hasher("allmydata_encryption_key_v1") 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

View File

@ -1,11 +1,18 @@
"""This is the client-side facility to manipulate virtual drives.""" """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.application import service
from twisted.internet import defer from twisted.internet import defer
from twisted.python import log from twisted.python import log
from allmydata import upload, download from allmydata import upload, download, uri
from foolscap import Copyable, RemoteCopy 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): class VDrive(service.MultiService):
name = "vdrive" name = "vdrive"
@ -181,99 +188,243 @@ class VDrive(service.MultiService):
return self.get_file(from_where, download.FileHandle(filehandle)) return self.get_file(from_where, download.FileHandle(filehandle))
class DirectoryNode(Copyable, RemoteCopy): def create_directory_node(client, diruri):
"""I have either a .furl attribute or a .get(tub) method.""" assert uri.is_dirnode_uri(diruri)
typeToCopy = "allmydata.com/tahoe/interfaces/DirectoryNode/v1" if uri.is_mutable_dirnode_uri(diruri):
copytype = typeToCopy dirnode_class = MutableDirectoryNode
def __init__(self, furl=None, client=None): else:
# RemoteCopy subclasses are always called without arguments dirnode_class = ImmutableDirectoryNode
self.furl = furl (furl, key) = uri.unpack_dirnode_uri(diruri)
self._set_client(client) d = client.tub.getReference(furl)
def _set_client(self, client): 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 self._client = client
return self self._tub = client.tub
def getStateToCopy(self): self._rref = rref
return {"furl": self.furl } self._readkey = readkey
def setCopyableState(self, state): self._writekey = None
self.furl = state['furl'] 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): def __hash__(self):
return hash((self.__class__, self.furl)) return hash((self.__class__, self._uri))
def __cmp__(self, them): def __cmp__(self, them):
if cmp(type(self), type(them)): if cmp(type(self), type(them)):
return cmp(type(self), type(them)) return cmp(type(self), type(them))
if cmp(self.__class__, them.__class__): if cmp(self.__class__, them.__class__):
return 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): def list(self):
d = self._client.tub.getReference(self.furl) d = self._rref.callRemote("list", self._index)
d.addCallback(lambda node: node.callRemote("list")) entries = {}
d.addCallback(lambda children: def _got(res):
[(name,child._set_client(self._client)) dl = []
for name,child in children]) 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 return d
def _hash_name(self, name):
return hashutil.dir_name_hash(self._readkey, name)
def get(self, name): def get(self, name):
d = self._client.tub.getReference(self.furl) H_name = self._hash_name(name)
d.addCallback(lambda node: node.callRemote("get", name)) d = self._rref.callRemote("get", self._index, H_name)
d.addCallback(lambda child: child._set_client(self._client)) 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 return d
def add(self, name, child): def _set(self, name, write_child, read_child):
d = self._client.tub.getReference(self.furl) if not self._mutable:
d.addCallback(lambda node: node.callRemote("add", name, child)) return defer.fail(NotMutableError())
d.addCallback(lambda newnode: newnode._set_client(self._client)) 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 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 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):
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 return d
def add_file(self, name, uploadable): def add_file(self, name, uploadable):
if not self._mutable:
return defer.fail(NotMutableError())
uploader = self._client.getServiceNamed("uploader") uploader = self._client.getServiceNamed("uploader")
d = uploader.upload(uploadable) d = uploader.upload(uploadable)
d.addCallback(lambda uri: self.add(name, FileNode(uri, self._client))) d.addCallback(lambda uri: self.set_node(name,
FileNode(uri, self._client)))
return d 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))
return d
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))
return d
def attach_shared_directory(self, name, furl):
d = self.add(name, DirectoryNode(furl))
return d
def get_shared_directory_furl(self):
return defer.succeed(self.furl)
def move_child_to(self, current_child_name, def move_child_to(self, current_child_name,
new_parent, new_child_name=None): 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: if new_child_name is None:
new_child_name = current_child_name new_child_name = current_child_name
d = self.get(current_child_name) d = self.get(current_child_name)
d.addCallback(lambda child: new_parent.add(new_child_name, child)) d.addCallback(lambda child: new_parent.set_node(new_child_name, child))
d.addCallback(lambda child: self.remove(current_child_name)) d.addCallback(lambda child: self.delete(current_child_name))
return d return d
class FileNode(Copyable, RemoteCopy): class MutableDirectoryNode(ImmutableDirectoryNode):
"""I have a .uri attribute.""" implements(IDirectoryNode)
typeToCopy = "allmydata.com/tahoe/interfaces/FileNode/v1"
copytype = typeToCopy def __init__(self, myuri, client, rref, writekey):
def __init__(self, uri=None, client=None): readkey = hashutil.dir_read_key_hash(writekey)
# RemoteCopy subclasses are always called without arguments 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.uri = uri
self._set_client(client)
def _set_client(self, client):
self._client = client self._client = client
return self
def getStateToCopy(self): def get_uri(self):
return {"uri": self.uri } return self.uri
def setCopyableState(self, state):
self.uri = state['uri']
def __hash__(self): def __hash__(self):
return hash((self.__class__, self.uri)) return hash((self.__class__, self.uri))
def __cmp__(self, them): def __cmp__(self, them):

View File

@ -13,9 +13,11 @@
<div><a href=".">Refresh this view</a></div> <div><a href=".">Refresh this view</a></div>
<div><a href="..">Parent Directory</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: "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"> <table n:render="sequence" n:data="children" border="1">
<tr n:pattern="header"> <tr n:pattern="header">

View File

@ -6,8 +6,8 @@ from nevow import inevow, rend, loaders, appserver, url, tags as T
from nevow.static import File as nevow_File # TODO: merge with static.File? from nevow.static import File as nevow_File # TODO: merge with static.File?
from allmydata.util import idlib from allmydata.util import idlib
from allmydata.uri import unpack_uri from allmydata.uri import unpack_uri
from allmydata.interfaces import IDownloadTarget from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode
from allmydata.vdrive import FileNode, DirectoryNode from allmydata.vdrive import FileNode
from allmydata import upload from allmydata import upload
from zope.interface import implements, Interface from zope.interface import implements, Interface
import urllib import urllib
@ -120,10 +120,10 @@ class Directory(rend.Page):
dirname = self._dirname + "/" + name dirname = self._dirname + "/" + name
d = self._dirnode.get(name) d = self._dirnode.get(name)
def _got_child(res): def _got_child(res):
if isinstance(res, FileNode): if IFileNode.providedBy(res):
dl = get_downloader_service(ctx) dl = get_downloader_service(ctx)
return Downloader(dl, name, res) return Downloader(dl, name, res)
elif isinstance(res, DirectoryNode): elif IDirectoryNode.providedBy(res):
return Directory(res, dirname) return Directory(res, dirname)
else: else:
raise RuntimeError("what is this %s" % res) raise RuntimeError("what is this %s" % res)
@ -134,18 +134,39 @@ class Directory(rend.Page):
return ctx.tag["Directory of '%s':" % self._dirname] return ctx.tag["Directory of '%s':" % self._dirname]
def render_header(self, ctx, data): 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): def data_share_uri(self, ctx, data):
return self._dirnode.furl 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): def data_children(self, ctx, data):
d = self._dirnode.list() d = self._dirnode.list()
d.addCallback(lambda dict: sorted(dict.items()))
return d return d
def render_row(self, ctx, data): def render_row(self, ctx, data):
name, target = 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 # file
dlurl = urllib.quote(name) dlurl = urllib.quote(name)
ctx.fillSlots("filename", ctx.fillSlots("filename",
@ -160,17 +181,7 @@ class Directory(rend.Page):
#extract and display file size #extract and display file size
ctx.fillSlots("size", unpack_uri(uri)['size']) ctx.fillSlots("size", unpack_uri(uri)['size'])
# this creates a button which will cause our child__delete method elif IDirectoryNode.providedBy(target):
# 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):
# directory # directory
subdir_url = urllib.quote(name) subdir_url = urllib.quote(name)
ctx.fillSlots("filename", ctx.fillSlots("filename",
@ -178,19 +189,14 @@ class Directory(rend.Page):
ctx.fillSlots("type", "DIR") ctx.fillSlots("type", "DIR")
ctx.fillSlots("size", "-") ctx.fillSlots("size", "-")
ctx.fillSlots("uri", "-") 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: else:
raise RuntimeError("unknown thing %s" % (target,)) raise RuntimeError("unknown thing %s" % (target,))
return ctx.tag return ctx.tag
def render_forms(self, ctx, data): def render_forms(self, ctx, data):
if self._dirnode.is_mutable():
return webform.renderForms() return webform.renderForms()
return T.div["No upload forms: directory is immutable"]
def render_results(self, ctx, data): def render_results(self, ctx, data):
req = inevow.IRequest(ctx) req = inevow.IRequest(ctx)
@ -235,14 +241,13 @@ class Directory(rend.Page):
log.msg("starting webish upload") log.msg("starting webish upload")
uploader = get_uploader_service(ctx) uploader = get_uploader_service(ctx)
d = uploader.upload(upload.FileHandle(contents.file)) uploadable = upload.FileHandle(contents.file)
name = contents.filename name = contents.filename
def _uploaded(uri):
if privateupload: if privateupload:
return self.uploadprivate(name, uri) d = uploader.upload(uploadable)
d.addCallback(lambda uri: self.uploadprivate(name, uri))
else: else:
return self._dirnode.add(name, FileNode(uri)) d = self._dirnode.add_file(name, uploadable)
d.addCallback(_uploaded)
def _done(res): def _done(res):
log.msg("webish upload complete") log.msg("webish upload complete")
return res return res
@ -271,15 +276,15 @@ class Directory(rend.Page):
def bind_mount(self, ctx): def bind_mount(self, ctx):
namearg = annotate.Argument("name", namearg = annotate.Argument("name",
annotate.String("Name to place incoming directory: ")) annotate.String("Name to place incoming directory: "))
furlarg = annotate.Argument("furl", uriarg = annotate.Argument("uri",
annotate.String("FURL of Shared Directory")) annotate.String("URI of Shared Directory"))
meth = annotate.Method(arguments=[namearg, furlarg], meth = annotate.Method(arguments=[namearg, uriarg],
label="Add Shared Directory") label="Add Shared Directory")
return annotate.MethodBinding("mount", meth, return annotate.MethodBinding("mount", meth,
action="Mount Shared Directory") action="Mount Shared Directory")
def mount(self, name, furl): def mount(self, name, uri):
d = self._dirnode.attach_shared_directory(name, furl) d = self._dirnode.set_uri(name, uri)
#d.addCallback(lambda done: url.here.child(name)) #d.addCallback(lambda done: url.here.child(name))
return d return d
@ -287,7 +292,7 @@ class Directory(rend.Page):
# perform the delete, then redirect back to the directory page # perform the delete, then redirect back to the directory page
args = inevow.IRequest(ctx).args args = inevow.IRequest(ctx).args
name = args["name"][0] name = args["name"][0]
d = self._dirnode.remove(name) d = self._dirnode.delete(name)
d.addCallback(lambda done: url.here.up()) d.addCallback(lambda done: url.here.up())
return d return d
@ -324,7 +329,7 @@ class Downloader(resource.Resource):
def __init__(self, downloader, name, filenode): def __init__(self, downloader, name, filenode):
self._downloader = downloader self._downloader = downloader
self._name = name self._name = name
assert isinstance(filenode, FileNode) IFileNode(filenode)
self._filenode = filenode self._filenode = filenode
def render(self, ctx): def render(self, ctx):
@ -397,14 +402,14 @@ class WebishServer(service.MultiService):
# apparently 'ISite' does not exist # apparently 'ISite' does not exist
#self.site._client = self.parent #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.putChild("global_vdrive", Directory(root, "/"))
self.root.child_welcome.has_global_vdrive = True self.root.child_welcome.has_global_vdrive = True
# I tried doing it this way and for some reason it didn't seem to work # I tried doing it this way and for some reason it didn't seem to work
#print "REMEMBERING", self.site, dl, IDownloader #print "REMEMBERING", self.site, dl, IDownloader
#self.site.remember(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.putChild("my_vdrive", Directory(my_vdrive, "~"))
self.root.child_welcome.has_my_vdrive = True self.root.child_welcome.has_my_vdrive = True