1 /* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #include "tensorflow/c/experimental/filesystem/modular_filesystem_registration.h"
16
17 #include "tensorflow/c/experimental/filesystem/modular_filesystem.h"
18 #include "tensorflow/c/tf_status_internal.h"
19 #include "tensorflow/core/platform/env.h"
20 #include "tensorflow/core/platform/errors.h"
21 #include "tensorflow/core/util/ptr_util.h"
22
23 namespace tensorflow {
24
25 // Checks that all schemes provided by a plugin are valid.
26 // TODO(b/139060984): More validation could be done here, based on supported
27 // charset, maximum length, etc. Punting it for later.
ValidateScheme(const char * scheme)28 static Status ValidateScheme(const char* scheme) {
29 if (scheme == nullptr)
30 return errors::InvalidArgument(
31 "Attempted to register filesystem with `nullptr` URI scheme");
32 return OkStatus();
33 }
34
35 // Checks if the plugin and core ABI numbers match.
36 //
37 // If the numbers don't match, plugin cannot be loaded.
CheckABI(int pluginABI,int coreABI,StringPiece where)38 static Status CheckABI(int pluginABI, int coreABI, StringPiece where) {
39 if (pluginABI != coreABI)
40 return errors::FailedPrecondition(
41 strings::StrCat("Plugin ABI (", pluginABI, ") for ", where,
42 " operations doesn't match expected core ABI (",
43 coreABI, "). Plugin cannot be loaded."));
44 return OkStatus();
45 }
46
47 // Checks if the plugin and core ABI numbers match, for all operations.
48 //
49 // If the numbers don't match, plugin cannot be loaded.
50 //
51 // Uses the simpler `CheckABI(int, int, StringPiece)`.
ValidateABI(const TF_FilesystemPluginOps * ops)52 static Status ValidateABI(const TF_FilesystemPluginOps* ops) {
53 TF_RETURN_IF_ERROR(
54 CheckABI(ops->filesystem_ops_abi, TF_FILESYSTEM_OPS_ABI, "filesystem"));
55
56 if (ops->random_access_file_ops != nullptr)
57 TF_RETURN_IF_ERROR(CheckABI(ops->random_access_file_ops_abi,
58 TF_RANDOM_ACCESS_FILE_OPS_ABI,
59 "random access file"));
60
61 if (ops->writable_file_ops != nullptr)
62 TF_RETURN_IF_ERROR(CheckABI(ops->writable_file_ops_abi,
63 TF_WRITABLE_FILE_OPS_ABI, "writable file"));
64
65 if (ops->read_only_memory_region_ops != nullptr)
66 TF_RETURN_IF_ERROR(CheckABI(ops->read_only_memory_region_ops_abi,
67 TF_READ_ONLY_MEMORY_REGION_OPS_ABI,
68 "read only memory region"));
69
70 return OkStatus();
71 }
72
73 // Checks if the plugin and core API numbers match, logging mismatches.
CheckAPI(int plugin_API,int core_API,StringPiece where)74 static void CheckAPI(int plugin_API, int core_API, StringPiece where) {
75 if (plugin_API != core_API) {
76 VLOG(0) << "Plugin API (" << plugin_API << ") for " << where
77 << " operations doesn't match expected core API (" << core_API
78 << "). Plugin will be loaded but functionality might be missing.";
79 }
80 }
81
82 // Checks if the plugin and core API numbers match, for all operations.
83 //
84 // Uses the simpler `CheckAPIHelper(int, int, StringPiece)`.
ValidateAPI(const TF_FilesystemPluginOps * ops)85 static void ValidateAPI(const TF_FilesystemPluginOps* ops) {
86 CheckAPI(ops->filesystem_ops_api, TF_FILESYSTEM_OPS_API, "filesystem");
87
88 if (ops->random_access_file_ops != nullptr)
89 CheckAPI(ops->random_access_file_ops_api, TF_RANDOM_ACCESS_FILE_OPS_API,
90 "random access file");
91
92 if (ops->writable_file_ops != nullptr)
93 CheckAPI(ops->writable_file_ops_api, TF_WRITABLE_FILE_OPS_API,
94 "writable file");
95
96 if (ops->read_only_memory_region_ops != nullptr)
97 CheckAPI(ops->read_only_memory_region_ops_api,
98 TF_READ_ONLY_MEMORY_REGION_OPS_API, "read only memory region");
99 }
100
101 // Validates the filesystem operations supplied by the plugin.
ValidateHelper(const TF_FilesystemOps * ops)102 static Status ValidateHelper(const TF_FilesystemOps* ops) {
103 if (ops == nullptr)
104 return errors::FailedPrecondition(
105 "Trying to register filesystem without operations");
106
107 if (ops->init == nullptr)
108 return errors::FailedPrecondition(
109 "Trying to register filesystem without `init` operation");
110
111 if (ops->cleanup == nullptr)
112 return errors::FailedPrecondition(
113 "Trying to register filesystem without `cleanup` operation");
114
115 return OkStatus();
116 }
117
118 // Validates the random access file operations supplied by the plugin.
ValidateHelper(const TF_RandomAccessFileOps * ops)119 static Status ValidateHelper(const TF_RandomAccessFileOps* ops) {
120 if (ops == nullptr) {
121 // We allow filesystems where files can only be written to (from TF code)
122 return OkStatus();
123 }
124
125 if (ops->cleanup == nullptr)
126 return errors::FailedPrecondition(
127 "Trying to register filesystem without `cleanup` operation on random "
128 "access files");
129
130 return OkStatus();
131 }
132
133 // Validates the writable file operations supplied by the plugin.
ValidateHelper(const TF_WritableFileOps * ops)134 static Status ValidateHelper(const TF_WritableFileOps* ops) {
135 if (ops == nullptr) {
136 // We allow read-only filesystems
137 return OkStatus();
138 }
139
140 if (ops->cleanup == nullptr)
141 return errors::FailedPrecondition(
142 "Trying to register filesystem without `cleanup` operation on writable "
143 "files");
144
145 return OkStatus();
146 }
147
148 // Validates the read only memory region operations given by the plugin.
ValidateHelper(const TF_ReadOnlyMemoryRegionOps * ops)149 static Status ValidateHelper(const TF_ReadOnlyMemoryRegionOps* ops) {
150 if (ops == nullptr) {
151 // read only memory region support is always optional
152 return OkStatus();
153 }
154
155 if (ops->cleanup == nullptr)
156 return errors::FailedPrecondition(
157 "Trying to register filesystem without `cleanup` operation on read "
158 "only memory regions");
159
160 if (ops->data == nullptr)
161 return errors::FailedPrecondition(
162 "Trying to register filesystem without `data` operation on read only "
163 "memory regions");
164
165 if (ops->length == nullptr)
166 return errors::FailedPrecondition(
167 "Trying to register filesystem without `length` operation on read only "
168 "memory regions");
169
170 return OkStatus();
171 }
172
173 // Validates the operations supplied by the plugin.
174 //
175 // Uses the 4 simpler `ValidateHelper(const TF_...*)` to validate each
176 // individual function table and then checks that the function table for a
177 // specific file type exists if the plugin offers support for creating that
178 // type of files.
ValidateOperations(const TF_FilesystemPluginOps * ops)179 static Status ValidateOperations(const TF_FilesystemPluginOps* ops) {
180 TF_RETURN_IF_ERROR(ValidateHelper(ops->filesystem_ops));
181 TF_RETURN_IF_ERROR(ValidateHelper(ops->random_access_file_ops));
182 TF_RETURN_IF_ERROR(ValidateHelper(ops->writable_file_ops));
183 TF_RETURN_IF_ERROR(ValidateHelper(ops->read_only_memory_region_ops));
184
185 if (ops->filesystem_ops->new_random_access_file != nullptr &&
186 ops->random_access_file_ops == nullptr)
187 return errors::FailedPrecondition(
188 "Filesystem allows creation of random access files but no "
189 "operations on them have been supplied.");
190
191 if ((ops->filesystem_ops->new_writable_file != nullptr ||
192 ops->filesystem_ops->new_appendable_file != nullptr) &&
193 ops->writable_file_ops == nullptr)
194 return errors::FailedPrecondition(
195 "Filesystem allows creation of writable files but no "
196 "operations on them have been supplied.");
197
198 if (ops->filesystem_ops->new_read_only_memory_region_from_file != nullptr &&
199 ops->read_only_memory_region_ops == nullptr)
200 return errors::FailedPrecondition(
201 "Filesystem allows creation of readonly memory regions but no "
202 "operations on them have been supplied.");
203
204 return OkStatus();
205 }
206
207 // Copies a function table from plugin memory space to core memory space.
208 //
209 // This has three benefits:
210 // * allows having newer plugins than the current core TensorFlow: the
211 // additional entries in the plugin's table are just discarded;
212 // * allows having older plugins than the current core TensorFlow (though
213 // we are still warning users): the entries that core TensorFlow expects
214 // but plugins didn't provide will be set to `nullptr` values and core
215 // TensorFlow will know to not call these on behalf of users;
216 // * increased security as plugins will not be able to alter function table
217 // after loading up. Thus, malicious plugins can't alter functionality to
218 // probe for gadgets inside core TensorFlow. We can even protect the area
219 // of memory where the copies reside to not allow any more writes to it
220 // after all copies are created.
221 template <typename T>
CopyToCore(const T * plugin_ops,size_t plugin_size)222 static std::unique_ptr<const T> CopyToCore(const T* plugin_ops,
223 size_t plugin_size) {
224 if (plugin_ops == nullptr) return nullptr;
225
226 size_t copy_size = std::min(plugin_size, sizeof(T));
227 auto core_ops = tensorflow::MakeUnique<T>();
228 memset(core_ops.get(), 0, sizeof(T));
229 memcpy(core_ops.get(), plugin_ops, copy_size);
230 return core_ops;
231 }
232
233 // Registers one filesystem from the plugin.
234 //
235 // Must be called only with `index` a valid index in `info->ops`.
RegisterFileSystem(const TF_FilesystemPluginInfo * info,int index)236 static Status RegisterFileSystem(const TF_FilesystemPluginInfo* info,
237 int index) {
238 // Step 1: Copy all the function tables to core TensorFlow memory space
239 auto core_filesystem_ops = CopyToCore<TF_FilesystemOps>(
240 info->ops[index].filesystem_ops, info->ops[index].filesystem_ops_size);
241 auto core_random_access_file_ops = CopyToCore<TF_RandomAccessFileOps>(
242 info->ops[index].random_access_file_ops,
243 info->ops[index].random_access_file_ops_size);
244 auto core_writable_file_ops =
245 CopyToCore<TF_WritableFileOps>(info->ops[index].writable_file_ops,
246 info->ops[index].writable_file_ops_size);
247 auto core_read_only_memory_region_ops =
248 CopyToCore<TF_ReadOnlyMemoryRegionOps>(
249 info->ops[index].read_only_memory_region_ops,
250 info->ops[index].read_only_memory_region_ops_size);
251
252 // Step 2: Initialize the opaque filesystem structure
253 auto filesystem = tensorflow::MakeUnique<TF_Filesystem>();
254 TF_Status* c_status = TF_NewStatus();
255 Status status = OkStatus();
256 core_filesystem_ops->init(filesystem.get(), c_status);
257 status = Status(c_status->status);
258 TF_DeleteStatus(c_status);
259 if (!status.ok()) return status;
260
261 // Step 3: Actual registration
262 return Env::Default()->RegisterFileSystem(
263 info->ops[index].scheme,
264 tensorflow::MakeUnique<tensorflow::ModularFileSystem>(
265 std::move(filesystem), std::move(core_filesystem_ops),
266 std::move(core_random_access_file_ops),
267 std::move(core_writable_file_ops),
268 std::move(core_read_only_memory_region_ops),
269 info->plugin_memory_allocate, info->plugin_memory_free));
270 }
271
272 // Registers filesystem at `index`, if plugin is providing valid information.
273 //
274 // Extracted to a separate function so that pointers inside `info` are freed
275 // by the caller regardless of whether validation/registration failed or not.
276 //
277 // Must be called only with `index` a valid index in `info->ops`.
ValidateAndRegisterFilesystems(const TF_FilesystemPluginInfo * info,int index)278 static Status ValidateAndRegisterFilesystems(
279 const TF_FilesystemPluginInfo* info, int index) {
280 TF_RETURN_IF_ERROR(ValidateScheme(info->ops[index].scheme));
281 TF_RETURN_IF_ERROR(ValidateABI(&info->ops[index]));
282 ValidateAPI(&info->ops[index]); // we just warn on API number mismatch
283 TF_RETURN_IF_ERROR(ValidateOperations(&info->ops[index]));
284 TF_RETURN_IF_ERROR(RegisterFileSystem(info, index));
285 return OkStatus();
286 }
287
288 // Ensures that the plugin provides the required memory management operations.
ValidatePluginMemoryRoutines(const TF_FilesystemPluginInfo * info)289 static Status ValidatePluginMemoryRoutines(
290 const TF_FilesystemPluginInfo* info) {
291 if (info->plugin_memory_allocate == nullptr)
292 return errors::FailedPrecondition(
293 "Cannot load filesystem plugin which does not provide "
294 "`plugin_memory_allocate`");
295
296 if (info->plugin_memory_free == nullptr)
297 return errors::FailedPrecondition(
298 "Cannot load filesystem plugin which does not provide "
299 "`plugin_memory_free`");
300
301 return OkStatus();
302 }
303
304 namespace filesystem_registration {
305
RegisterFilesystemPluginImpl(const TF_FilesystemPluginInfo * info)306 Status RegisterFilesystemPluginImpl(const TF_FilesystemPluginInfo* info) {
307 TF_RETURN_IF_ERROR(ValidatePluginMemoryRoutines(info));
308
309 // Validate and register all filesystems
310 // Try to register as many filesystems as possible.
311 // Free memory once we no longer need it
312 Status status;
313 for (int i = 0; i < info->num_schemes; i++) {
314 status.Update(ValidateAndRegisterFilesystems(info, i));
315 info->plugin_memory_free(info->ops[i].scheme);
316 info->plugin_memory_free(info->ops[i].filesystem_ops);
317 info->plugin_memory_free(info->ops[i].random_access_file_ops);
318 info->plugin_memory_free(info->ops[i].writable_file_ops);
319 info->plugin_memory_free(info->ops[i].read_only_memory_region_ops);
320 }
321 info->plugin_memory_free(info->ops);
322 return status;
323 }
324
325 } // namespace filesystem_registration
326
327 } // namespace tensorflow
328