1 //
2 // inmemory_filesystem.hpp
3 //
4 // Copyright © 2024 Apple Inc. All rights reserved.
5 //
6 // Please refer to the license found in the LICENSE file in the root directory of the source tree.
7
8 #pragma once
9
10 #include <functional>
11 #include <memory>
12 #include <optional>
13 #include <stdio.h>
14 #include <string>
15 #include <system_error>
16
17 #include "inmemory_filesystem_metadata.hpp"
18 #include "memory_buffer.hpp"
19
20 namespace inmemoryfs {
21
22 /// A class representing an in-memory file system.
23 class InMemoryFileSystem final {
24 public:
25 /// Error codes for `InMemoryFileSystem`.
26 enum class ErrorCode: int8_t {
27 DirectoryExists = 1, // If the path already exists.
28 ItemNotFound, // If the path does not exist.
29 ItemExists, // If an item at the path already exists.
30 DirectoryExpected, // If path is not a directory.
31 FileExpected, // If the path is not a file.
32 };
33
34 /// Options for loading file content.
35 enum class FileLoadOption: int8_t {
36 Malloc = 1, // Copy file contents into memory.
37 MMap, // Memory map file contents.
38 LazyMMap // Memory map file contents but lazily.
39 };
40
41 /// The error category for `InMemoryFileSystem`.
42 struct ErrorCategory final: public std::error_category {
43 public:
nameinmemoryfs::InMemoryFileSystem::ErrorCategory44 inline const char* name() const noexcept override {
45 return "InMemoryFileSystem";
46 }
47
48 std::string message(int code) const override;
49 };
50
51 struct Attributes {
52 time_t modificationTime;
53
Attributesinmemoryfs::InMemoryFileSystem::Attributes54 inline Attributes() noexcept:
55 modificationTime(time(0))
56 {}
57 };
58
59 using MetadataWriter = std::function<bool(const InMemoryFileSystemMetadata&, std::ostream&)>;
60 using MetadataWriterInMemory = std::function<size_t(const InMemoryFileSystemMetadata&, void *)>;
61 using MetadataReader = std::function<std::optional<InMemoryFileSystemMetadata>(std::istream&)>;
62
63 /// A class representing an in-memory node. This could either be a file node or a directory node.
64 class InMemoryNode {
65 public:
66 /// The node kind.
67 enum class Kind: uint8_t {
68 File = 0, /// Node is a File.
69 Directory /// Node is a Directory.
70 };
71
72 /// Constructs an in-memory node instance.
73 ///
74 /// @param name The name of the Node. It must be unique in the enclosing Directory.
75 /// @param attributes The node attributes.
76 /// @param kind The node kind.
InMemoryNode(std::string name,Attributes attributes,Kind kind)77 inline InMemoryNode(std::string name, Attributes attributes, Kind kind) noexcept:
78 name_(std::move(name)),
79 attributes_(std::move(attributes)),
80 kind_(kind)
81 {}
82
83 InMemoryNode(InMemoryNode const&) = delete;
84 InMemoryNode& operator=(InMemoryNode const&) = delete;
85
~InMemoryNode()86 inline virtual ~InMemoryNode() {}
87
88 /// Returns the node attributes.
attributes() const89 inline Attributes attributes() const noexcept {
90 return attributes_;
91 }
92
93 /// Sets the node attributes.
94 ///
95 /// @param attributes The node attributes.
set_attributes(Attributes attributes)96 inline void set_attributes(Attributes attributes) noexcept {
97 attributes_ = std::move(attributes);
98 }
99
100 /// Returns the node kind, possible values are `File` and `Directory`.
kind() const101 inline Kind kind() const noexcept {
102 return kind_;
103 }
104
105 /// Returns the name of the node.
name() const106 inline const std::string& name() const noexcept {
107 return name_;
108 }
109
set_name(std::string name)110 inline void set_name(std::string name) noexcept {
111 std::swap(name_, name);
112 }
113
114 /// Returns `true` if the node is a directory otherwise `false`.
isDirectory() const115 inline bool isDirectory() const noexcept {
116 switch (kind_) {
117 case InMemoryFileSystem::InMemoryNode::Kind::Directory:
118 return true;
119 default:
120 return false;
121 }
122 }
123
124 /// Returns `true` if the node is a file otherwise `false`.
isFile() const125 inline bool isFile() const noexcept {
126 return !isDirectory();
127 }
128
129 private:
130 std::string name_;
131 InMemoryFileSystem::Attributes attributes_;
132 const Kind kind_;
133 };
134
135 /// Constructs an`InMemoryFileSystem` instance with an empty root and the specified name.
136 ///
137 /// @param rootName The name of the root node.
138 explicit InMemoryFileSystem(std::string rootName = "root") noexcept;
139
140 /// Constructs an`InMemoryFileSystem` instance with the specified root.
141 ///
142 /// @param root The root node.
InMemoryFileSystem(std::unique_ptr<InMemoryNode> root)143 explicit InMemoryFileSystem(std::unique_ptr<InMemoryNode> root) noexcept
144 :root_(std::move(root))
145 {}
146
147 InMemoryFileSystem(InMemoryFileSystem const&) = delete;
148 InMemoryFileSystem& operator=(InMemoryFileSystem const&) = delete;
149
~InMemoryFileSystem()150 virtual ~InMemoryFileSystem() {}
151
152 /// Returns the root.
root() const153 InMemoryNode *root() const noexcept {
154 return root_.get();
155 }
156
157 /// Checks if the node at the specified path is a directory.
158 ///
159 /// @param canonical_path The path components from the root.
160 /// @retval `true` if the node at the specified path is a directory otherwise `false`.
161 bool is_directory(const std::vector<std::string>& canonical_path) noexcept;
162
163 /// Checks if the node at the specified path is a file.
164 ///
165 /// @param canonical_path The path components from the root.
166 /// @retval `true` if the node at the specified path is a file otherwise `false`.
167 bool is_file(const std::vector<std::string>& canonical_path) noexcept;
168
169 /// Checks if the node at the specified path exists.
170 ///
171 /// @param canonical_path The path components from the root.
172 /// @retval `true` if the node at the specified path exists.
173 bool exists(const std::vector<std::string>& canonical_path) const noexcept;
174
175 /// Retrieves the canonical path of all the child nodes at the specified path. The node
176 /// at the specified path must be a directory otherwise it returns an empty vector with the `error`
177 /// populated.
178 ///
179 /// @param canonical_path The path components from the root.
180 /// @param error On failure, error is populated with the failure reason.
181 /// @retval paths to all the items at the specified path.
182 std::vector<std::vector<std::string>> get_item_paths(const std::vector<std::string>& canonical_path,
183 std::error_code& error) const noexcept;
184
185 /// Retrieves the attributes of the item at the specified path.
186 ///
187 /// @param canonical_path The path components from the root.
188 /// @param error On failure, error is populated with the failure reason.
189 /// @retval The item attributes at the specified path.
190 std::optional<Attributes> get_attributes(const std::vector<std::string>& canonical_path,
191 std::error_code& error) const noexcept;
192
193 /// Retrieves the contents of the file at the specified path.
194 ///
195 /// @param canonical_path The path components from the root.
196 /// @param error On failure, error is populated with the failure reason.
197 /// @retval The file contents or `nullptr` if the item at the specified path is not a file.
198 std::shared_ptr<MemoryBuffer> get_file_content(const std::vector<std::string>& canonical_path,
199 std::error_code& error) const noexcept;
200
201 /// Creates an in-memory directory at the specified path.
202 ///
203 /// @param canonical_path The path components from the root.
204 /// @param attributes The directory attributes.
205 /// @param create_intermediate_directories If this is `true` then the method will also create intermediate directories if not present.
206 /// @param error On failure, error is populated with the failure reason.
207 /// @retval `true` if the directory is created otherwise `false`.
208 bool make_directory(const std::vector<std::string>& canonical_path,
209 Attributes attributes,
210 bool create_intermediate_directories,
211 std::error_code& error) noexcept;
212
213 /// Creates an in-memory file at the specified path.
214 ///
215 /// @param canonical_path The path components from the root.
216 /// @param buffer The file contents.
217 /// @param attributes The file attributes.
218 /// @param overwrite If this is `true` then the the method will overwrite the contents at the specified path.
219 /// @param error On failure, error is populated with the failure reason.
220 /// @retval `true` if the file is created otherwise `false`.
221 bool make_file(const std::vector<std::string>& canonical_path,
222 std::shared_ptr<MemoryBuffer> buffer,
223 Attributes attributes,
224 bool overwrite,
225 std::error_code& error) noexcept;
226
227 /// Removes the item at the specified path.
228 ///
229 /// @param canonical_path The path components from the root.
230 /// @param error On failure, error is populated with the failure reason.
231 /// @retval `true` if the item is removed otherwise `false`.
232 bool remove_item(const std::vector<std::string>& canonical_path,
233 std::error_code& error) noexcept;
234
235 /// Sets the attributes at the specified path.
236 ///
237 /// @param canonical_path The path components from the root.
238 /// @param error On failure, error is populated with the failure reason.
239 /// @retval `true` if the attributes are updated otherwise `false`.
240 bool set_attributes(const std::vector<std::string>& canonical_path,
241 Attributes attributes,
242 std::error_code& error) noexcept;
243
244 /// Writes the item at the specified path to the filesystem.
245 ///
246 /// @param canonical_path The path components from the root.
247 /// @param dst_path The filesystem path where the item contents will be saved.
248 /// @param recursive If this is `true` then the the method will recursively write the contents of nested directory items.
249 /// @param error On failure, error is populated with the failure reason.
250 /// @retval `true` if the write succeeded otherwise `false`.
251 bool write_item_to_disk(const std::vector<std::string>& canonical_path,
252 const std::string& dst_path,
253 bool recursive,
254 std::error_code& error) const noexcept;
255
256 /// Renames the item at the specified path, if there is already an item with the same name then
257 /// the rename would fail.
258 ///
259 /// @param canonical_path The path components from the root.
260 /// @param name The new name,
261 /// @param error On failure, error is populated with the failure reason.
262 /// @retval `true` if the write succeeded otherwise `false`.
263 bool rename_item(const std::vector<std::string>& canonical_path,
264 const std::string& name,
265 std::error_code& error) noexcept;
266
267 /// Creates an`InMemoryFileSystem` from the filesystem path.
268 ///
269 /// The structure of the `InMemoryFileSystem` is identical to the structure of the filesystem at the
270 /// specified path.
271 ///
272 /// @param path The filesystem path.
273 /// @param option The loading option.
274 /// @param error On failure, error is populated with the failure reason.
275 /// @retval The `InMemoryFileSystem` instance if the construction succeeded otherwise `nullptr`.
276 static std::unique_ptr<InMemoryFileSystem> make_from_directory(const std::string& path,
277 FileLoadOption option,
278 std::error_code& error) noexcept;
279
280 /// Serializes the item at the specified path and writes it to the stream.
281 ///
282 /// The structure of the `InMemoryFileSystem` is identical to the structure of the filesystem at the
283 /// specified path.
284 ///
285 /// @param canonical_path The path components from the root.
286 /// @param alignment The alignment of the offset where an item is written to the stream.
287 /// @param metadata_writer The function to use when serializing the filesystem metadata.
288 /// @param ostream The output stream.
289 /// @param error On failure, error is populated with the failure reason.
290 /// @retval `true` if the serialized bytes were written to `ostream` otherwise `false`.
291 bool serialize(const std::vector<std::string>& canonical_path,
292 size_t alignment,
293 const MetadataWriter& metadata_writer,
294 std::ostream& ostream,
295 std::error_code& error) const noexcept;
296
297 /// Serializes the item at the specified path and writes it to the stream.
298 ///
299 /// The structure of the `InMemoryFileSystem` is identical to the structure of the filesystem at the
300 /// specified path.
301 ///
302 /// @param canonical_path The path components from the root.
303 /// @param alignment The alignment of the offset where an item is written to the stream.
304 /// @param metadata_writer The function to use when serializing the filesystem metadata.
305 /// @param dst The destination pointer.
306 /// @param error On failure, error is populated with the failure reason.
307 /// @retval `true` if the serialized bytes were written to `ostream` otherwise `false`.
308 bool serialize(const std::vector<std::string>& canonical_path,
309 size_t alignment,
310 const MetadataWriterInMemory& metadata_writer,
311 void *dst,
312 std::error_code& error) const noexcept;
313
314 /// Computes the size of the buffer that would be needed to serialized the item at the specified path.
315 ///
316 /// @param canonical_path The path components from the root.
317 /// @param alignment The offset alignment where an item is written to the stream.
318 /// @param metadata_writer The function to use when serializing the filesystem metadata.
319 /// @retval The size of the buffer that will be needed to write the item at the specified path.
320 size_t get_buffer_size_for_serialization(const std::vector<std::string>& canonical_path,
321 size_t alignment,
322 const MetadataWriter& metadata_writer) const noexcept;
323
324 /// Constructs an `InMemoryFileSystem` instance from the buffer contents.
325 ///
326 /// @param buffer The memory buffer.
327 /// @param metadata_reader The function to use when deserializing the filesystem metadata.
328 /// @retval The constructed `InMemoryFileSystem` or `nullptr` if the deserialization failed.
329 static std::unique_ptr<InMemoryFileSystem> make_from_buffer(const std::shared_ptr<MemoryBuffer>& buffer,
330 const MetadataReader& metadata_reader) noexcept;
331
332 private:
333 const std::unique_ptr<InMemoryNode> root_;
334 };
335
336 /// Constructs an `error_code` from a `InMemoryFileSystem::ErrorCode`.
make_error_code(InMemoryFileSystem::ErrorCode code)337 inline std::error_code make_error_code(InMemoryFileSystem::ErrorCode code) {
338 static InMemoryFileSystem::ErrorCategory errorCategory;
339 return {static_cast<int>(code), errorCategory};
340 }
341
342 }; // namespace inmemoryfs
343
344 namespace std {
345
346 template <> struct is_error_code_enum<inmemoryfs::InMemoryFileSystem::ErrorCode> : true_type {};
347
348 } // namespace std
349