dirnode.py: Fix #1034 (MetadataSetter does not enforce restriction on setting 'tahoe' subkeys), and expose the metadata updater for use by SFTP. Also, support diminishing a child cap to read-only if 'no-write' is set in the metadata.

This commit is contained in:
david-sarah 2010-05-31 21:54:28 -07:00
parent 027e7701bd
commit 53f7d2c7fe
2 changed files with 86 additions and 51 deletions

View File

@ -24,6 +24,50 @@ from pycryptopp.cipher.aes import AES
from allmydata.util.dictutil import AuxValueDict
def update_metadata(metadata, new_metadata, now):
"""Updates 'metadata' in-place with the information in 'new_metadata'.
Timestamps are set according to the time 'now'."""
if metadata is None:
metadata = {"ctime": now,
"mtime": now,
"tahoe": {
"linkcrtime": now,
"linkmotime": now,
}
}
if new_metadata is not None:
# Overwrite all metadata.
newmd = new_metadata.copy()
# Except 'tahoe'.
if newmd.has_key('tahoe'):
del newmd['tahoe']
if metadata.has_key('tahoe'):
newmd['tahoe'] = metadata['tahoe']
metadata = newmd
else:
# For backwards compatibility with Tahoe < 1.4.0:
if "ctime" not in metadata:
metadata["ctime"] = now
metadata["mtime"] = now
# update timestamps
sysmd = metadata.get('tahoe', {})
if not 'linkcrtime' in sysmd:
if "ctime" in metadata:
# In Tahoe < 1.4.0 we used the word "ctime" to mean what Tahoe >= 1.4.0
# calls "linkcrtime".
sysmd["linkcrtime"] = metadata["ctime"]
else:
sysmd["linkcrtime"] = now
sysmd["linkmotime"] = now
return metadata
# TODO: {Deleter,MetadataSetter,Adder}.modify all start by unpacking the
# contents and end by repacking them. It might be better to apply them to
# the unpacked contents.
@ -47,28 +91,38 @@ class Deleter:
class MetadataSetter:
def __init__(self, node, name, metadata):
def __init__(self, node, name, metadata, create_readonly_node=None):
self.node = node
self.name = name
self.metadata = metadata
self.create_readonly_node = create_readonly_node
def modify(self, old_contents, servermap, first_time):
children = self.node._unpack_contents(old_contents)
if self.name not in children:
raise NoSuchChildError(self.name)
children[self.name] = (children[self.name][0], self.metadata)
name = self.name
if name not in children:
raise NoSuchChildError(name)
now = time.time()
metadata = update_metadata(children[name][1].copy(), self.metadata, now)
child = children[name][0]
if self.create_readonly_node and metadata and metadata.get('no-write', False):
child = self.create_readonly_node(child, name)
children[name] = (child, metadata)
new_contents = self.node._pack_contents(children)
return new_contents
class Adder:
def __init__(self, node, entries=None, overwrite=True):
def __init__(self, node, entries=None, overwrite=True, create_readonly_node=None):
self.node = node
if entries is None:
entries = {}
precondition(isinstance(entries, dict), entries)
self.entries = entries
self.overwrite = overwrite
self.create_readonly_node = create_readonly_node
def set_node(self, name, node, metadata):
precondition(isinstance(name, unicode), name)
@ -86,6 +140,7 @@ class Adder:
# error again in pack_children.
child.raise_error()
metadata = None
if name in children:
if not self.overwrite:
raise ExistingChildError("child '%s' already exists" % name)
@ -93,44 +148,11 @@ class Adder:
if self.overwrite == "only-files" and IDirectoryNode.providedBy(children[name][0]):
raise ExistingChildError("child '%s' already exists" % name)
metadata = children[name][1].copy()
else:
metadata = {"ctime": now,
"mtime": now,
"tahoe": {
"linkcrtime": now,
"linkmotime": now,
}
}
if new_metadata is not None:
# Overwrite all metadata.
newmd = new_metadata.copy()
if self.create_readonly_node and metadata and metadata.get('no-write', False):
child = self.create_readonly_node(child, name)
# Except 'tahoe'.
if newmd.has_key('tahoe'):
del newmd['tahoe']
if metadata.has_key('tahoe'):
newmd['tahoe'] = metadata['tahoe']
metadata = newmd
else:
# For backwards compatibility with Tahoe < 1.4.0:
if "ctime" not in metadata:
metadata["ctime"] = now
metadata["mtime"] = now
# update timestamps
sysmd = metadata.get('tahoe', {})
if not 'linkcrtime' in sysmd:
if "ctime" in metadata:
# In Tahoe < 1.4.0 we used the word "ctime" to mean what Tahoe >= 1.4.0
# calls "linkcrtime".
sysmd["linkcrtime"] = metadata["ctime"]
else:
sysmd["linkcrtime"] = now
sysmd["linkmotime"] = now
children[name] = (child, metadata)
children[name] = (child, update_metadata(metadata, new_metadata, now))
new_contents = self.node._pack_contents(children)
return new_contents
@ -248,6 +270,11 @@ class DirectoryNode:
node.raise_error()
return node
def _create_readonly_node(self, node, name):
if not node.is_unknown() and node.is_readonly():
return node
return self._create_and_validate_node(None, node.get_readonly_uri(), name=name)
def _unpack_contents(self, data):
# the directory is serialized as a list of netstrings, one per child.
# Each child is serialized as a list of four netstrings: (name, ro_uri,
@ -407,7 +434,8 @@ class DirectoryNode:
if self.is_readonly():
return defer.fail(NotWriteableError())
assert isinstance(metadata, dict)
s = MetadataSetter(self, name, metadata)
s = MetadataSetter(self, name, metadata,
create_readonly_node=self._create_readonly_node)
d = self._node.modify(s.modify)
d.addCallback(lambda res: self)
return d
@ -453,7 +481,7 @@ class DirectoryNode:
precondition(isinstance(name, unicode), name)
precondition(isinstance(writecap, (str,type(None))), writecap)
precondition(isinstance(readcap, (str,type(None))), readcap)
# We now allow packing unknown nodes, provided they are valid
# for this type of directory.
child_node = self._create_and_validate_node(writecap, readcap, name)
@ -463,7 +491,8 @@ class DirectoryNode:
def set_children(self, entries, overwrite=True):
# this takes URIs
a = Adder(self, overwrite=overwrite)
a = Adder(self, overwrite=overwrite,
create_readonly_node=self._create_readonly_node)
for (name, e) in entries.iteritems():
assert isinstance(name, unicode)
if len(e) == 2:
@ -498,7 +527,8 @@ class DirectoryNode:
return defer.fail(NotWriteableError())
assert isinstance(name, unicode)
assert IFilesystemNode.providedBy(child), child
a = Adder(self, overwrite=overwrite)
a = Adder(self, overwrite=overwrite,
create_readonly_node=self._create_readonly_node)
a.set_node(name, child, metadata)
d = self._node.modify(a.modify)
d.addCallback(lambda res: child)
@ -508,7 +538,8 @@ class DirectoryNode:
precondition(isinstance(entries, dict), entries)
if self.is_readonly():
return defer.fail(NotWriteableError())
a = Adder(self, entries, overwrite=overwrite)
a = Adder(self, entries, overwrite=overwrite,
create_readonly_node=self._create_readonly_node)
d = self._node.modify(a.modify)
d.addCallback(lambda res: self)
return d
@ -551,7 +582,8 @@ class DirectoryNode:
d = self._nodemaker.create_immutable_directory(initial_children)
def _created(child):
entries = {name: (child, None)}
a = Adder(self, entries, overwrite=overwrite)
a = Adder(self, entries, overwrite=overwrite,
create_readonly_node=self._create_readonly_node)
d = self._node.modify(a.modify)
d.addCallback(lambda res: child)
return d

View File

@ -894,11 +894,14 @@ class Dirnode(GridTestMixin, unittest.TestCase,
d.addCallback(lambda res:
n.set_metadata_for(u"child",
{"tags": ["web2.0-compatible"]}))
{"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
d.addCallback(lambda metadata:
self.failUnlessEqual(metadata,
{"tags": ["web2.0-compatible"]}))
def _check_metadata(md):
self.failUnless("tags" in md, md)
self.failUnlessEqual(md["tags"], ["web2.0-compatible"])
self.failUnless("tahoe" in md, md)
self.failIf("bad" in md["tahoe"], md)
d.addCallback(_check_metadata)
def _start(res):
self._start_timestamp = time.time()