4 from zope.interface import implements
5 from twisted.internet import defer
6 from twisted.python import log
8 from allmydata.mutable import NotMutableError
9 from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\
10 INewDirectoryURI, IURI, IFileNode, \
12 from allmydata.util import hashutil
13 from allmydata.util.hashutil import netstring
14 from allmydata.uri import NewDirectoryURI
15 from allmydata.Crypto.Cipher import AES
17 from allmydata.mutable import MutableFileNode
19 def split_netstring(data, numstrings, allow_leftover=False):
20 """like string.split(), but extracts netstrings. If allow_leftover=False,
21 returns numstrings elements, and throws ValueError if there was leftover
22 data. If allow_leftover=True, returns numstrings+1 elements, in which the
23 last element is the leftover data (possibly an empty string)"""
25 assert numstrings >= 0
27 colon = data.index(":")
28 length = int(data[:colon])
29 string = data[colon+1:colon+1+length]
30 assert len(string) == length
31 elements.append(string)
32 assert data[colon+1+length] == ","
33 data = data[colon+1+length+1:]
34 if len(elements) == numstrings:
36 if len(elements) < numstrings:
37 raise ValueError("ran out of netstrings")
39 return tuple(elements + [data])
41 raise ValueError("leftover data in netstrings")
42 return tuple(elements)
44 class NewDirectoryNode:
45 implements(IDirectoryNode)
46 filenode_class = MutableFileNode
48 def __init__(self, client):
51 return "<%s %s %s>" % (self.__class__.__name__, self.is_readonly() and "RO" or "RW", hasattr(self, '_uri') and self._uri.abbrev())
52 def init_from_uri(self, myuri):
53 self._uri = IURI(myuri)
54 self._node = self.filenode_class(self._client)
55 self._node.init_from_uri(self._uri.get_filenode_uri())
58 def create(self, wait_for_numpeers=None):
60 Returns a deferred that eventually fires with self once the directory
61 has been created (distributed across a set of storage servers).
63 # first we create a MutableFileNode with empty_contents, then use its
64 # URI to create our own.
65 self._node = self.filenode_class(self._client)
66 empty_contents = self._pack_contents({})
67 d = self._node.create(empty_contents, wait_for_numpeers=wait_for_numpeers)
68 d.addCallback(self._filenode_created)
70 def _filenode_created(self, res):
71 self._uri = NewDirectoryURI(self._node._uri)
75 d = self._node.download_to_data()
76 d.addCallback(self._unpack_contents)
79 def _encrypt_rwcap(self, rwcap):
80 assert isinstance(rwcap, str)
82 key = hashutil.mutable_rwcap_key_hash(IV, self._node.get_writekey())
83 counterstart = "\x00"*16
84 cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
85 crypttext = cryptor.encrypt(rwcap)
86 mac = hashutil.hmac(key, IV + crypttext)
88 return IV + crypttext + mac
90 def _decrypt_rwcapdata(self, encwrcap):
92 crypttext = encwrcap[16:-32]
94 key = hashutil.mutable_rwcap_key_hash(IV, self._node.get_writekey())
95 if mac != hashutil.hmac(key, IV+crypttext):
96 raise hashutil.IntegrityCheckError("HMAC does not match, crypttext is corrupted")
97 counterstart = "\x00"*16
98 cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
99 plaintext = cryptor.decrypt(crypttext)
102 def _create_node(self, child_uri):
103 return self._client.create_node_from_uri(child_uri)
105 def _unpack_contents(self, data):
106 # the directory is serialized as a list of netstrings, one per child.
107 # Each child is serialized as a list of four netstrings: (name,
108 # rocap, rwcap, metadata), in which the name,rocap,metadata are in
109 # cleartext. The rwcap is formatted as:
110 # pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac)
111 assert isinstance(data, str)
112 # an empty directory is serialized as an empty string
115 writeable = not self.is_readonly()
118 entry, data = split_netstring(data, 1, True)
119 name, rocap, rwcapdata, metadata_s = split_netstring(entry, 4)
121 rwcap = self._decrypt_rwcapdata(rwcapdata)
122 child = self._create_node(rwcap)
124 child = self._create_node(rocap)
125 metadata = simplejson.loads(metadata_s)
126 assert isinstance(metadata, dict)
127 children[name] = (child, metadata)
130 def _pack_contents(self, children):
131 # expects children in the same format as _unpack_contents
132 assert isinstance(children, dict)
134 for name in sorted(children.keys()):
135 child, metadata = children[name]
136 assert (IFileNode.providedBy(child)
137 or IMutableFileNode.providedBy(child)
138 or IDirectoryNode.providedBy(child)), children
139 assert isinstance(metadata, dict)
140 rwcap = child.get_uri() # might be RO if the child is not writeable
141 rocap = child.get_readonly_uri()
142 entry = "".join([netstring(name),
144 netstring(self._encrypt_rwcap(rwcap)),
145 netstring(simplejson.dumps(metadata))])
146 entries.append(netstring(entry))
147 return "".join(entries)
149 def is_readonly(self):
150 return self._node.is_readonly()
151 def is_mutable(self):
152 return self._node.is_mutable()
155 return self._uri.to_string()
157 def get_readonly_uri(self):
158 return self._uri.get_readonly().to_string()
160 def get_verifier(self):
161 return self._uri.get_verifier().to_string()
164 """Perform a file check. See IChecker.check for details."""
168 """I return a Deferred that fires with a dictionary mapping child
169 name to a tuple of (IFileNode or IDirectoryNode, metadata)."""
172 def has_child(self, name):
173 """I return a Deferred that fires with a boolean, True if there
174 exists a child of the given name, False if not."""
176 d.addCallback(lambda children: children.has_key(name))
179 def _get(self, children, name):
180 child = children.get(name)
186 """I return a Deferred that fires with the named child node,
187 which is either an IFileNode or an IDirectoryNode."""
189 d.addCallback(self._get, name)
192 def get_metadata_for(self, name):
194 d.addCallback(lambda children: children[name][1])
197 def get_child_at_path(self, path):
198 """Transform a child path into an IDirectoryNode or IFileNode.
200 I perform a recursive series of 'get' operations to find the named
201 descendant node. I return a Deferred that fires with the node, or
202 errbacks with IndexError if the node could not be found.
204 The path can be either a single string (slash-separated) or a list of
209 return defer.succeed(self)
210 if isinstance(path, (str, unicode)):
211 path = path.split("/")
213 remaining_path = path[1:]
214 d = self.get(childname)
217 return node.get_child_at_path(remaining_path)
221 def set_uri(self, name, child_uri, metadata={}, wait_for_numpeers=None):
222 """I add a child (by URI) at the specific name. I return a Deferred
223 that fires with the child node when the operation finishes. I will
224 replace any existing child of the same name.
226 The child_uri could be for a file, or for a directory (either
227 read-write or read-only, using a URI that came from get_uri() ).
229 If this directory node is read-only, the Deferred will errback with a
231 return self.set_node(name, self._create_node(child_uri), metadata, wait_for_numpeers=wait_for_numpeers)
233 def set_node(self, name, child, metadata={}, wait_for_numpeers=None):
234 """I add a child at the specific name. I return a Deferred that fires
235 when the operation finishes. This Deferred will fire with the child
236 node that was just added. I will replace any existing child of the
239 If this directory node is read-only, the Deferred will errback with a
241 if self.is_readonly():
242 return defer.fail(NotMutableError())
245 children[name] = (child, metadata)
246 new_contents = self._pack_contents(children)
247 return self._node.replace(new_contents, wait_for_numpeers=wait_for_numpeers)
249 d.addCallback(lambda res: child)
252 def add_file(self, name, uploadable, wait_for_numpeers=None):
253 """I upload a file (using the given IUploadable), then attach the
254 resulting FileNode to the directory at the given name. I return a
255 Deferred that fires (with the IFileNode of the uploaded file) when
256 the operation completes."""
257 if self.is_readonly():
258 return defer.fail(NotMutableError())
259 d = self._client.upload(uploadable, wait_for_numpeers=wait_for_numpeers)
260 d.addCallback(self._client.create_node_from_uri)
261 d.addCallback(lambda node: self.set_node(name, node, wait_for_numpeers=wait_for_numpeers))
264 def delete(self, name):
265 """I remove the child at the specific name. I return a Deferred that
266 fires (with the node just removed) when the operation finishes."""
267 if self.is_readonly():
268 return defer.fail(NotMutableError())
270 def _delete(children):
271 old_child, metadata = children[name]
273 new_contents = self._pack_contents(children)
274 d = self._node.replace(new_contents)
279 d.addCallback(_delete)
282 def create_empty_directory(self, name, wait_for_numpeers=None):
283 """I create and attach an empty directory at the given name. I return
284 a Deferred that fires (with the new directory node) when the
285 operation finishes."""
286 if self.is_readonly():
287 return defer.fail(NotMutableError())
288 d = self._client.create_empty_dirnode(wait_for_numpeers=wait_for_numpeers)
290 d = self.set_node(name, child, wait_for_numpeers=wait_for_numpeers)
291 d.addCallback(lambda res: child)
293 d.addCallback(_created)
296 def move_child_to(self, current_child_name, new_parent,
297 new_child_name=None, wait_for_numpeers=None):
298 """I take one of my children and move them to a new parent. The child
299 is referenced by name. On the new parent, the child will live under
300 'new_child_name', which defaults to 'current_child_name'. I return a
301 Deferred that fires when the operation finishes."""
302 if self.is_readonly() or new_parent.is_readonly():
303 return defer.fail(NotMutableError())
304 if new_child_name is None:
305 new_child_name = current_child_name
306 d = self.get(current_child_name)
308 return new_parent.set_node(new_child_name, child,
309 wait_for_numpeers=wait_for_numpeers)
311 d.addCallback(lambda child: self.delete(current_child_name))
314 def build_manifest(self):
315 """Return a frozenset of verifier-capability strings for all nodes
316 (directories and files) reachable from this one."""
318 # this is just a tree-walker, except that following each edge
319 # requires a Deferred.
322 manifest.add(self.get_verifier())
324 d = self._build_manifest_from_node(self, manifest)
326 # LIT nodes have no verifier-capability: their data is stored
327 # inside the URI itself, so there is no need to refresh anything.
328 # They indicate this by returning None from their get_verifier
329 # method. We need to remove any such Nones from our set. We also
330 # want to convert all these caps into strings.
331 return frozenset([IVerifierURI(cap).to_string()
337 def _build_manifest_from_node(self, node, manifest):
341 for name, (child, metadata) in res.iteritems():
342 verifier = child.get_verifier()
343 if verifier not in manifest:
344 manifest.add(verifier)
345 if IDirectoryNode.providedBy(child):
346 dl.append(self._build_manifest_from_node(child,
349 return defer.DeferredList(dl)
350 d.addCallback(_got_list)
353 # use client.create_dirnode() to make one of these