Buckets:
arudradey/fullhtml-storage / emsdk /upstream /emscripten /system /lib /wasmfs /backends /opfs_backend.cpp
| // Copyright 2022 The Emscripten Authors. All rights reserved. | |
| // Emscripten is available under two separate licenses, the MIT license and the | |
| // University of Illinois/NCSA Open Source License. Both these licenses can be | |
| // found in the LICENSE file. | |
| using namespace wasmfs; | |
| namespace { | |
| using ProxyWorker = emscripten::ProxyWorker; | |
| using ProxyingQueue = emscripten::ProxyingQueue; | |
| class Worker { | |
| public: | |
| ProxyWorker proxy; | |
| template<typename T> void operator()(T func) { proxy(func); } | |
| // When used with JSPI on the main thread the various wasmfs_opfs_* functions | |
| // can be directly executed since they are all async. | |
| template<typename T> void operator()(T func) { | |
| if constexpr (std::is_invocable_v<T&, ProxyingQueue::ProxyingCtx>) { | |
| // TODO: Find a way to remove this, since it's unused. | |
| ProxyingQueue::ProxyingCtx p; | |
| func(p); | |
| } else { | |
| func(); | |
| } | |
| } | |
| }; | |
| class OpenState { | |
| public: | |
| enum Kind { None, Access, Blob }; | |
| private: | |
| Kind kind = None; | |
| int id = -1; | |
| size_t openCount = 0; | |
| public: | |
| Kind getKind() { return kind; } | |
| int open(Worker& proxy, int fileID, oflags_t flags) { | |
| if (kind == None) { | |
| assert(openCount == 0); | |
| switch (flags) { | |
| case O_RDWR: | |
| case O_WRONLY: | |
| // If we need write access, try to open an AccessHandle. | |
| proxy( | |
| [&](auto ctx) { _wasmfs_opfs_open_access(ctx.ctx, fileID, &id); }); | |
| // TODO: Fall back to open as a blob instead. | |
| if (id < 0) { | |
| return id; | |
| } | |
| kind = Access; | |
| break; | |
| case O_RDONLY: | |
| // We only need read access, so open as a Blob | |
| proxy( | |
| [&](auto ctx) { _wasmfs_opfs_open_blob(ctx.ctx, fileID, &id); }); | |
| if (id < 0) { | |
| return id; | |
| } | |
| kind = Blob; | |
| break; | |
| default: | |
| WASMFS_UNREACHABLE("Unexpected open access mode"); | |
| } | |
| } else if (kind == Blob && (flags == O_WRONLY || flags == O_RDWR)) { | |
| // Try to upgrade to an AccessHandle. | |
| int newID; | |
| proxy( | |
| [&](auto ctx) { _wasmfs_opfs_open_access(ctx.ctx, fileID, &newID); }); | |
| if (newID < 0) { | |
| return newID; | |
| } | |
| // We have an AccessHandle, so close the blob. | |
| proxy([&]() { _wasmfs_opfs_close_blob(getBlobID()); }); | |
| id = newID; | |
| kind = Access; | |
| } | |
| ++openCount; | |
| return 0; | |
| } | |
| int close(Worker& proxy) { | |
| // TODO: Downgrade to Blob access once the last writable file descriptor has | |
| // been closed. | |
| int err = 0; | |
| if (--openCount == 0) { | |
| switch (kind) { | |
| case Access: | |
| proxy( | |
| [&](auto ctx) { _wasmfs_opfs_close_access(ctx.ctx, id, &err); }); | |
| break; | |
| case Blob: | |
| proxy([&]() { _wasmfs_opfs_close_blob(id); }); | |
| break; | |
| case None: | |
| WASMFS_UNREACHABLE("Open file should have kind"); | |
| } | |
| kind = None; | |
| id = -1; | |
| } | |
| return err; | |
| } | |
| int getAccessID() { | |
| assert(openCount > 0); | |
| assert(id >= 0); | |
| assert(kind == Access); | |
| return id; | |
| } | |
| int getBlobID() { | |
| assert(openCount > 0); | |
| assert(id >= 0); | |
| assert(kind == Blob); | |
| return id; | |
| } | |
| }; | |
| class OPFSFile : public DataFile { | |
| public: | |
| Worker& proxy; | |
| int fileID; | |
| OpenState state; | |
| OPFSFile(mode_t mode, backend_t backend, int fileID, Worker& proxy) | |
| : DataFile(mode, backend), proxy(proxy), fileID(fileID) {} | |
| ~OPFSFile() override { | |
| assert(state.getKind() == OpenState::None); | |
| proxy([&]() { _wasmfs_opfs_free_file(fileID); }); | |
| } | |
| private: | |
| off_t getSize() override { | |
| off_t size; | |
| switch (state.getKind()) { | |
| case OpenState::None: | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_get_size_file(ctx.ctx, fileID, &size); | |
| }); | |
| break; | |
| case OpenState::Access: | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_get_size_access(ctx.ctx, state.getAccessID(), &size); | |
| }); | |
| break; | |
| case OpenState::Blob: | |
| proxy([&]() { size = _wasmfs_opfs_get_size_blob(state.getBlobID()); }); | |
| break; | |
| default: | |
| WASMFS_UNREACHABLE("Unexpected open state"); | |
| } | |
| return size; | |
| } | |
| int setSize(off_t size) override { | |
| int err = 0; | |
| switch (state.getKind()) { | |
| case OpenState::Access: | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_set_size_access( | |
| ctx.ctx, state.getAccessID(), size, &err); | |
| }); | |
| break; | |
| case OpenState::Blob: | |
| // We don't support `truncate` in blob mode because the blob would | |
| // become invalidated and refreshing it while ensuring other in-flight | |
| // operations on the same file do not observe the invalidated blob would | |
| // be extremely complicated. | |
| // TODO: Can we assume there are no other in-flight operations on this | |
| // file and do something better here? | |
| return -EIO; | |
| case OpenState::None: { | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_set_size_file(ctx.ctx, fileID, size, &err); | |
| }); | |
| break; | |
| } | |
| default: | |
| WASMFS_UNREACHABLE("Unexpected open state"); | |
| } | |
| return err; | |
| } | |
| int open(oflags_t flags) override { return state.open(proxy, fileID, flags); } | |
| int close() override { return state.close(proxy); } | |
| ssize_t read(uint8_t* buf, size_t len, off_t offset) override { | |
| // TODO: use an i64 here. | |
| int32_t nread; | |
| switch (state.getKind()) { | |
| case OpenState::Access: | |
| proxy([&]() { | |
| nread = | |
| _wasmfs_opfs_read_access(state.getAccessID(), buf, len, offset); | |
| }); | |
| break; | |
| case OpenState::Blob: | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_read_blob( | |
| ctx.ctx, state.getBlobID(), buf, len, offset, &nread); | |
| }); | |
| break; | |
| case OpenState::None: | |
| default: | |
| WASMFS_UNREACHABLE("Unexpected open state"); | |
| } | |
| return nread; | |
| } | |
| ssize_t write(const uint8_t* buf, size_t len, off_t offset) override { | |
| assert(state.getKind() == OpenState::Access); | |
| // TODO: use an i64 here. | |
| int32_t nwritten; | |
| proxy([&]() { | |
| nwritten = | |
| _wasmfs_opfs_write_access(state.getAccessID(), buf, len, offset); | |
| }); | |
| return nwritten; | |
| } | |
| int flush() override { | |
| int err = 0; | |
| switch (state.getKind()) { | |
| case OpenState::Access: | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_flush_access(ctx.ctx, state.getAccessID(), &err); | |
| }); | |
| break; | |
| case OpenState::Blob: | |
| case OpenState::None: | |
| default: | |
| break; | |
| } | |
| return err; | |
| } | |
| }; | |
| class OPFSDirectory : public Directory { | |
| public: | |
| Worker& proxy; | |
| // The ID of this directory in the JS library. | |
| int dirID = 0; | |
| OPFSDirectory(mode_t mode, backend_t backend, int dirID, Worker& proxy) | |
| : Directory(mode, backend), proxy(proxy), dirID(dirID) {} | |
| ~OPFSDirectory() override { | |
| // Never free the root directory ID. | |
| if (dirID != 0) { | |
| proxy([&]() { _wasmfs_opfs_free_directory(dirID); }); | |
| } | |
| } | |
| private: | |
| std::shared_ptr<File> getChild(const std::string& name) override { | |
| int childType = 0, childID = 0; | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_get_child( | |
| ctx.ctx, dirID, name.c_str(), &childType, &childID); | |
| }); | |
| if (childID < 0) { | |
| // TODO: More fine-grained error reporting. | |
| return NULL; | |
| } | |
| if (childType == 1) { | |
| return std::make_shared<OPFSFile>(0777, getBackend(), childID, proxy); | |
| } else if (childType == 2) { | |
| return std::make_shared<OPFSDirectory>( | |
| 0777, getBackend(), childID, proxy); | |
| } else { | |
| WASMFS_UNREACHABLE("Unexpected child type"); | |
| } | |
| } | |
| std::shared_ptr<DataFile> insertDataFile(const std::string& name, | |
| mode_t mode) override { | |
| int childID = 0; | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_insert_file(ctx.ctx, dirID, name.c_str(), &childID); | |
| }); | |
| if (childID < 0) { | |
| // TODO: Propagate specific errors. | |
| return nullptr; | |
| } | |
| return std::make_shared<OPFSFile>(mode, getBackend(), childID, proxy); | |
| } | |
| std::shared_ptr<Directory> insertDirectory(const std::string& name, | |
| mode_t mode) override { | |
| int childID = 0; | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_insert_directory(ctx.ctx, dirID, name.c_str(), &childID); | |
| }); | |
| if (childID < 0) { | |
| // TODO: Propagate specific errors. | |
| return nullptr; | |
| } | |
| return std::make_shared<OPFSDirectory>(mode, getBackend(), childID, proxy); | |
| } | |
| std::shared_ptr<Symlink> insertSymlink(const std::string& name, | |
| const std::string& target) override { | |
| // Symlinks not supported. | |
| // TODO: Propagate EPERM specifically. | |
| return nullptr; | |
| } | |
| int insertMove(const std::string& name, std::shared_ptr<File> file) override { | |
| int err = 0; | |
| if (file->is<DataFile>()) { | |
| auto opfsFile = std::static_pointer_cast<OPFSFile>(file); | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_move_file( | |
| ctx.ctx, opfsFile->fileID, dirID, name.c_str(), &err); | |
| }); | |
| } else { | |
| // TODO: Support moving directories once OPFS supports that. | |
| // EBUSY can be returned when the directory is "in use by the system," | |
| // which can mean whatever we want. | |
| err = -EBUSY; | |
| } | |
| return err; | |
| } | |
| int removeChild(const std::string& name) override { | |
| int err = 0; | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_remove_child(ctx.ctx, dirID, name.c_str(), &err); | |
| }); | |
| return err; | |
| } | |
| ssize_t getNumEntries() override { | |
| auto entries = getEntries(); | |
| if (int err = entries.getError()) { | |
| return err; | |
| } | |
| return entries->size(); | |
| } | |
| Directory::MaybeEntries getEntries() override { | |
| std::vector<Directory::Entry> entries; | |
| int err = 0; | |
| proxy([&](auto ctx) { | |
| _wasmfs_opfs_get_entries(ctx.ctx, dirID, &entries, &err); | |
| }); | |
| if (err) { | |
| assert(err < 0); | |
| return {err}; | |
| } | |
| return {entries}; | |
| } | |
| }; | |
| class OPFSBackend : public Backend { | |
| public: | |
| Worker proxy; | |
| std::shared_ptr<DataFile> createFile(mode_t mode) override { | |
| // No way to support a raw file without a parent directory. | |
| // TODO: update the core system to document this as a possible result of | |
| // `createFile` and to handle it gracefully. | |
| return nullptr; | |
| } | |
| std::shared_ptr<Directory> createDirectory(mode_t mode) override { | |
| proxy([](auto ctx) { _wasmfs_opfs_init_root_directory(ctx.ctx); }); | |
| return std::make_shared<OPFSDirectory>(mode, this, 1, proxy); | |
| } | |
| std::shared_ptr<Symlink> createSymlink(std::string target) override { | |
| // Symlinks not supported. | |
| return nullptr; | |
| } | |
| }; | |
| } // anonymous namespace | |
| extern "C" { | |
| backend_t wasmfs_create_opfs_backend() { | |
| // ProxyWorker cannot safely be synchronously spawned from the main browser | |
| // thread. See comment in thread_utils.h for more details. | |
| assert( | |
| !emscripten_is_main_browser_thread() || | |
| emscripten_has_asyncify() && | |
| "Cannot safely create OPFS backend on main browser thread without Asyncify or JSPI"); | |
| return wasmFS.addBackend(std::make_unique<OPFSBackend>()); | |
| } | |
| void EMSCRIPTEN_KEEPALIVE _wasmfs_opfs_record_entry( | |
| std::vector<Directory::Entry>* entries, const char* name, int type) { | |
| entries->push_back({name, File::FileKind(type), 0}); | |
| } | |
| } // extern "C" | |
Xet Storage Details
- Size:
- 11.6 kB
- Xet hash:
- 746abdd70b534ebb9954020fa7418a2f16974897884e9f55d03c54483e6c0f40
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.