1 | """ |
---|
2 | Create file nodes of various types. |
---|
3 | """ |
---|
4 | |
---|
5 | from __future__ import annotations |
---|
6 | |
---|
7 | import weakref |
---|
8 | from zope.interface import implementer |
---|
9 | from twisted.internet.defer import succeed |
---|
10 | from allmydata.util.assertutil import precondition |
---|
11 | from allmydata.interfaces import INodeMaker |
---|
12 | from allmydata.immutable.literal import LiteralFileNode |
---|
13 | from allmydata.immutable.filenode import ImmutableFileNode, CiphertextFileNode |
---|
14 | from allmydata.immutable.upload import Data |
---|
15 | from allmydata.mutable.filenode import MutableFileNode |
---|
16 | from allmydata.mutable.publish import MutableData |
---|
17 | from allmydata.dirnode import DirectoryNode, pack_children |
---|
18 | from allmydata.unknown import UnknownNode |
---|
19 | from allmydata.blacklist import ProhibitedNode |
---|
20 | from allmydata.crypto.rsa import PublicKey, PrivateKey |
---|
21 | from allmydata import uri |
---|
22 | |
---|
23 | |
---|
24 | @implementer(INodeMaker) |
---|
25 | class NodeMaker(object): |
---|
26 | |
---|
27 | def __init__(self, storage_broker, secret_holder, history, |
---|
28 | uploader, terminator, |
---|
29 | default_encoding_parameters, mutable_file_default, |
---|
30 | key_generator, blacklist=None): |
---|
31 | self.storage_broker = storage_broker |
---|
32 | self.secret_holder = secret_holder |
---|
33 | self.history = history |
---|
34 | self.uploader = uploader |
---|
35 | self.terminator = terminator |
---|
36 | self.default_encoding_parameters = default_encoding_parameters |
---|
37 | self.mutable_file_default = mutable_file_default |
---|
38 | self.key_generator = key_generator |
---|
39 | self.blacklist = blacklist |
---|
40 | |
---|
41 | self._node_cache = weakref.WeakValueDictionary() # uri -> node |
---|
42 | |
---|
43 | def _create_lit(self, cap): |
---|
44 | return LiteralFileNode(cap) |
---|
45 | def _create_immutable(self, cap): |
---|
46 | return ImmutableFileNode(cap, self.storage_broker, self.secret_holder, |
---|
47 | self.terminator, self.history) |
---|
48 | def _create_immutable_verifier(self, cap): |
---|
49 | return CiphertextFileNode(cap, self.storage_broker, self.secret_holder, |
---|
50 | self.terminator, self.history) |
---|
51 | def _create_mutable(self, cap): |
---|
52 | n = MutableFileNode(self.storage_broker, self.secret_holder, |
---|
53 | self.default_encoding_parameters, |
---|
54 | self.history) |
---|
55 | return n.init_from_cap(cap) |
---|
56 | def _create_dirnode(self, filenode): |
---|
57 | return DirectoryNode(filenode, self, self.uploader) |
---|
58 | |
---|
59 | def create_from_cap(self, writecap, readcap=None, deep_immutable=False, name=u"<unknown name>"): |
---|
60 | # this returns synchronously. It starts with a "cap string". |
---|
61 | assert isinstance(writecap, (bytes, type(None))), type(writecap) |
---|
62 | assert isinstance(readcap, (bytes, type(None))), type(readcap) |
---|
63 | |
---|
64 | bigcap = writecap or readcap |
---|
65 | if not bigcap: |
---|
66 | # maybe the writecap was hidden because we're in a readonly |
---|
67 | # directory, and the future cap format doesn't have a readcap, or |
---|
68 | # something. |
---|
69 | return UnknownNode(None, None) # deep_immutable and name not needed |
---|
70 | |
---|
71 | # The name doesn't matter for caching since it's only used in the error |
---|
72 | # attribute of an UnknownNode, and we don't cache those. |
---|
73 | if deep_immutable: |
---|
74 | memokey = b"I" + bigcap |
---|
75 | else: |
---|
76 | memokey = b"M" + bigcap |
---|
77 | try: |
---|
78 | node = self._node_cache[memokey] |
---|
79 | except KeyError: |
---|
80 | cap = uri.from_string(bigcap, deep_immutable=deep_immutable, |
---|
81 | name=name) |
---|
82 | node = self._create_from_single_cap(cap) |
---|
83 | |
---|
84 | # node is None for an unknown URI, otherwise it is a type for which |
---|
85 | # is_mutable() is known. We avoid cacheing mutable nodes due to |
---|
86 | # ticket #1679. |
---|
87 | if node is None: |
---|
88 | # don't cache UnknownNode |
---|
89 | node = UnknownNode(writecap, readcap, |
---|
90 | deep_immutable=deep_immutable, name=name) |
---|
91 | elif node.is_mutable(): |
---|
92 | self._node_cache[memokey] = node # note: WeakValueDictionary |
---|
93 | |
---|
94 | if self.blacklist: |
---|
95 | si = node.get_storage_index() |
---|
96 | # if this node is blacklisted, return the reason, otherwise return None |
---|
97 | reason = self.blacklist.check_storageindex(si) |
---|
98 | if reason is not None: |
---|
99 | # The original node object is cached above, not the ProhibitedNode wrapper. |
---|
100 | # This ensures that removing the blacklist entry will make the node |
---|
101 | # accessible if create_from_cap is called again. |
---|
102 | node = ProhibitedNode(node, reason) |
---|
103 | return node |
---|
104 | |
---|
105 | def _create_from_single_cap(self, cap): |
---|
106 | if isinstance(cap, uri.LiteralFileURI): |
---|
107 | return self._create_lit(cap) |
---|
108 | if isinstance(cap, uri.CHKFileURI): |
---|
109 | return self._create_immutable(cap) |
---|
110 | if isinstance(cap, uri.CHKFileVerifierURI): |
---|
111 | return self._create_immutable_verifier(cap) |
---|
112 | if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI, |
---|
113 | uri.WriteableMDMFFileURI, uri.ReadonlyMDMFFileURI)): |
---|
114 | return self._create_mutable(cap) |
---|
115 | if isinstance(cap, (uri.DirectoryURI, |
---|
116 | uri.ReadonlyDirectoryURI, |
---|
117 | uri.ImmutableDirectoryURI, |
---|
118 | uri.LiteralDirectoryURI, |
---|
119 | uri.MDMFDirectoryURI, |
---|
120 | uri.ReadonlyMDMFDirectoryURI)): |
---|
121 | filenode = self._create_from_single_cap(cap.get_filenode_cap()) |
---|
122 | return self._create_dirnode(filenode) |
---|
123 | return None |
---|
124 | |
---|
125 | def create_mutable_file(self, contents=None, version=None, keypair: tuple[PublicKey, PrivateKey] | None = None): |
---|
126 | if version is None: |
---|
127 | version = self.mutable_file_default |
---|
128 | n = MutableFileNode(self.storage_broker, self.secret_holder, |
---|
129 | self.default_encoding_parameters, self.history) |
---|
130 | if keypair is None: |
---|
131 | d = self.key_generator.generate() |
---|
132 | else: |
---|
133 | d = succeed(keypair) |
---|
134 | d.addCallback(n.create_with_keys, contents, version=version) |
---|
135 | d.addCallback(lambda res: n) |
---|
136 | return d |
---|
137 | |
---|
138 | def create_new_mutable_directory( |
---|
139 | self, |
---|
140 | initial_children=None, |
---|
141 | version=None, |
---|
142 | *, |
---|
143 | keypair: tuple[PublicKey, PrivateKey] | None = None, |
---|
144 | ): |
---|
145 | if initial_children is None: |
---|
146 | initial_children = {} |
---|
147 | for (name, (node, metadata)) in initial_children.items(): |
---|
148 | precondition(isinstance(metadata, dict), |
---|
149 | "create_new_mutable_directory requires metadata to be a dict, not None", metadata) |
---|
150 | node.raise_error() |
---|
151 | d = self.create_mutable_file(lambda n: |
---|
152 | MutableData(pack_children(initial_children, |
---|
153 | n.get_writekey())), |
---|
154 | version=version, |
---|
155 | keypair=keypair) |
---|
156 | d.addCallback(self._create_dirnode) |
---|
157 | return d |
---|
158 | |
---|
159 | def create_immutable_directory(self, children, convergence=None): |
---|
160 | if convergence is None: |
---|
161 | convergence = self.secret_holder.get_convergence_secret() |
---|
162 | packed = pack_children(children, None, deep_immutable=True) |
---|
163 | uploadable = Data(packed, convergence) |
---|
164 | # XXX should pass reactor arg |
---|
165 | d = self.uploader.upload(uploadable) |
---|
166 | d.addCallback(lambda results: |
---|
167 | self.create_from_cap(None, results.get_uri())) |
---|
168 | d.addCallback(self._create_dirnode) |
---|
169 | return d |
---|