| |
| |
| |
| |
|
|
| #include <emscripten/threading.h> |
| #include <stdlib.h> |
|
|
| #include "backend.h" |
| #include "file.h" |
| #include "opfs_backend.h" |
| #include "support.h" |
| #include "thread_utils.h" |
| #include "wasmfs.h" |
|
|
| using namespace wasmfs; |
|
|
| namespace { |
|
|
| using ProxyWorker = emscripten::ProxyWorker; |
| using ProxyingQueue = emscripten::ProxyingQueue; |
|
|
| class Worker { |
| public: |
| #ifdef __EMSCRIPTEN_PTHREADS__ |
| ProxyWorker proxy; |
|
|
| template<typename T> void operator()(T func) { proxy(func); } |
| #else |
| |
| |
| template<typename T> void operator()(T func) { |
| if constexpr (std::is_invocable_v<T&, ProxyingQueue::ProxyingCtx>) { |
| |
| ProxyingQueue::ProxyingCtx p; |
| func(p); |
| } else { |
| func(); |
| } |
| } |
| #endif |
| }; |
|
|
| 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: |
| |
| proxy( |
| [&](auto ctx) { _wasmfs_opfs_open_access(ctx.ctx, fileID, &id); }); |
| |
| if (id < 0) { |
| return id; |
| } |
| kind = Access; |
| break; |
| case O_RDONLY: |
| |
| 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)) { |
| |
| int newID; |
| proxy( |
| [&](auto ctx) { _wasmfs_opfs_open_access(ctx.ctx, fileID, &newID); }); |
| if (newID < 0) { |
| return newID; |
| } |
| |
| proxy([&]() { _wasmfs_opfs_close_blob(getBlobID()); }); |
| id = newID; |
| kind = Access; |
| } |
| ++openCount; |
| return 0; |
| } |
|
|
| int close(Worker& proxy) { |
| |
| |
| 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: |
| |
| |
| |
| |
| |
| |
| 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 { |
| |
| 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); |
| |
| 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; |
|
|
| |
| int dirID = 0; |
|
|
| OPFSDirectory(mode_t mode, backend_t backend, int dirID, Worker& proxy) |
| : Directory(mode, backend), proxy(proxy), dirID(dirID) {} |
|
|
| ~OPFSDirectory() override { |
| |
| 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) { |
| |
| 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) { |
| |
| 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) { |
| |
| 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 { |
| |
| |
| 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 { |
| |
| |
| |
| 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 { |
| |
| |
| |
| 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 { |
| |
| return nullptr; |
| } |
| }; |
|
|
| } |
|
|
| extern "C" { |
|
|
| backend_t wasmfs_create_opfs_backend() { |
| |
| |
| 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}); |
| } |
|
|
| } |
|
|