Path: blob/main/system/lib/wasmfs/backends/opfs_backend.cpp
6175 views
// Copyright 2022 The Emscripten Authors. All rights reserved.1// Emscripten is available under two separate licenses, the MIT license and the2// University of Illinois/NCSA Open Source License. Both these licenses can be3// found in the LICENSE file.45#include <emscripten/threading.h>6#include <stdlib.h>78#include "backend.h"9#include "file.h"10#include "opfs_backend.h"11#include "support.h"12#include "thread_utils.h"13#include "wasmfs.h"1415using namespace wasmfs;1617namespace {1819using ProxyWorker = emscripten::ProxyWorker;20using ProxyingQueue = emscripten::ProxyingQueue;2122class Worker {23public:24#ifdef __EMSCRIPTEN_PTHREADS__25ProxyWorker proxy;2627template<typename T> void operator()(T func) { proxy(func); }28#else29// When used with JSPI on the main thread the various wasmfs_opfs_* functions30// can be directly executed since they are all async.31template<typename T> void operator()(T func) {32if constexpr (std::is_invocable_v<T&, ProxyingQueue::ProxyingCtx>) {33// TODO: Find a way to remove this, since it's unused.34ProxyingQueue::ProxyingCtx p;35func(p);36} else {37func();38}39}40#endif41};4243class OpenState {44public:45enum Kind { None, Access, Blob };4647private:48Kind kind = None;49int id = -1;50size_t openCount = 0;5152public:53Kind getKind() { return kind; }5455int open(Worker& proxy, int fileID, oflags_t flags) {56if (kind == None) {57assert(openCount == 0);58switch (flags) {59case O_RDWR:60case O_WRONLY:61// If we need write access, try to open an AccessHandle.62proxy(63[&](auto ctx) { _wasmfs_opfs_open_access(ctx.ctx, fileID, &id); });64// TODO: Fall back to open as a blob instead.65if (id < 0) {66return id;67}68kind = Access;69break;70case O_RDONLY:71// We only need read access, so open as a Blob72proxy(73[&](auto ctx) { _wasmfs_opfs_open_blob(ctx.ctx, fileID, &id); });74if (id < 0) {75return id;76}77kind = Blob;78break;79default:80WASMFS_UNREACHABLE("Unexpected open access mode");81}82} else if (kind == Blob && (flags == O_WRONLY || flags == O_RDWR)) {83// Try to upgrade to an AccessHandle.84int newID;85proxy(86[&](auto ctx) { _wasmfs_opfs_open_access(ctx.ctx, fileID, &newID); });87if (newID < 0) {88return newID;89}90// We have an AccessHandle, so close the blob.91proxy([&]() { _wasmfs_opfs_close_blob(getBlobID()); });92id = newID;93kind = Access;94}95++openCount;96return 0;97}9899int close(Worker& proxy) {100// TODO: Downgrade to Blob access once the last writable file descriptor has101// been closed.102int err = 0;103if (--openCount == 0) {104switch (kind) {105case Access:106proxy(107[&](auto ctx) { _wasmfs_opfs_close_access(ctx.ctx, id, &err); });108break;109case Blob:110proxy([&]() { _wasmfs_opfs_close_blob(id); });111break;112case None:113WASMFS_UNREACHABLE("Open file should have kind");114}115kind = None;116id = -1;117}118return err;119}120121int getAccessID() {122assert(openCount > 0);123assert(id >= 0);124assert(kind == Access);125return id;126}127128int getBlobID() {129assert(openCount > 0);130assert(id >= 0);131assert(kind == Blob);132return id;133}134};135136class OPFSFile : public DataFile {137public:138Worker& proxy;139int fileID;140OpenState state;141142OPFSFile(mode_t mode, backend_t backend, int fileID, Worker& proxy)143: DataFile(mode, backend), proxy(proxy), fileID(fileID) {}144145~OPFSFile() override {146assert(state.getKind() == OpenState::None);147proxy([&]() { _wasmfs_opfs_free_file(fileID); });148}149150private:151off_t getSize() override {152off_t size;153switch (state.getKind()) {154case OpenState::None:155proxy([&](auto ctx) {156_wasmfs_opfs_get_size_file(ctx.ctx, fileID, &size);157});158break;159case OpenState::Access:160proxy([&](auto ctx) {161_wasmfs_opfs_get_size_access(ctx.ctx, state.getAccessID(), &size);162});163break;164case OpenState::Blob:165proxy([&]() { size = _wasmfs_opfs_get_size_blob(state.getBlobID()); });166break;167default:168WASMFS_UNREACHABLE("Unexpected open state");169}170return size;171}172173int setSize(off_t size) override {174int err = 0;175switch (state.getKind()) {176case OpenState::Access:177proxy([&](auto ctx) {178_wasmfs_opfs_set_size_access(179ctx.ctx, state.getAccessID(), size, &err);180});181break;182case OpenState::Blob:183// We don't support `truncate` in blob mode because the blob would184// become invalidated and refreshing it while ensuring other in-flight185// operations on the same file do not observe the invalidated blob would186// be extremely complicated.187// TODO: Can we assume there are no other in-flight operations on this188// file and do something better here?189return -EIO;190case OpenState::None: {191proxy([&](auto ctx) {192_wasmfs_opfs_set_size_file(ctx.ctx, fileID, size, &err);193});194break;195}196default:197WASMFS_UNREACHABLE("Unexpected open state");198}199return err;200}201202int open(oflags_t flags) override { return state.open(proxy, fileID, flags); }203204int close() override { return state.close(proxy); }205206ssize_t read(uint8_t* buf, size_t len, off_t offset) override {207// TODO: use an i64 here.208int32_t nread;209switch (state.getKind()) {210case OpenState::Access:211proxy([&]() {212nread =213_wasmfs_opfs_read_access(state.getAccessID(), buf, len, offset);214});215break;216case OpenState::Blob:217proxy([&](auto ctx) {218_wasmfs_opfs_read_blob(219ctx.ctx, state.getBlobID(), buf, len, offset, &nread);220});221break;222case OpenState::None:223default:224WASMFS_UNREACHABLE("Unexpected open state");225}226return nread;227}228229ssize_t write(const uint8_t* buf, size_t len, off_t offset) override {230assert(state.getKind() == OpenState::Access);231// TODO: use an i64 here.232int32_t nwritten;233proxy([&]() {234nwritten =235_wasmfs_opfs_write_access(state.getAccessID(), buf, len, offset);236});237return nwritten;238}239240int flush() override {241int err = 0;242switch (state.getKind()) {243case OpenState::Access:244proxy([&](auto ctx) {245_wasmfs_opfs_flush_access(ctx.ctx, state.getAccessID(), &err);246});247break;248case OpenState::Blob:249case OpenState::None:250default:251break;252}253return err;254}255};256257class OPFSDirectory : public Directory {258public:259Worker& proxy;260261// The ID of this directory in the JS library.262int dirID = 0;263264OPFSDirectory(mode_t mode, backend_t backend, int dirID, Worker& proxy)265: Directory(mode, backend), proxy(proxy), dirID(dirID) {}266267~OPFSDirectory() override {268// Never free the root directory ID.269if (dirID != 0) {270proxy([&]() { _wasmfs_opfs_free_directory(dirID); });271}272}273274private:275std::shared_ptr<File> getChild(const std::string& name) override {276int childType = 0, childID = 0;277proxy([&](auto ctx) {278_wasmfs_opfs_get_child(279ctx.ctx, dirID, name.c_str(), &childType, &childID);280});281if (childID < 0) {282// TODO: More fine-grained error reporting.283return NULL;284}285if (childType == 1) {286return std::make_shared<OPFSFile>(0777, getBackend(), childID, proxy);287} else if (childType == 2) {288return std::make_shared<OPFSDirectory>(2890777, getBackend(), childID, proxy);290} else {291WASMFS_UNREACHABLE("Unexpected child type");292}293}294295std::shared_ptr<DataFile> insertDataFile(const std::string& name,296mode_t mode) override {297int childID = 0;298proxy([&](auto ctx) {299_wasmfs_opfs_insert_file(ctx.ctx, dirID, name.c_str(), &childID);300});301if (childID < 0) {302// TODO: Propagate specific errors.303return nullptr;304}305return std::make_shared<OPFSFile>(mode, getBackend(), childID, proxy);306}307308std::shared_ptr<Directory> insertDirectory(const std::string& name,309mode_t mode) override {310int childID = 0;311proxy([&](auto ctx) {312_wasmfs_opfs_insert_directory(ctx.ctx, dirID, name.c_str(), &childID);313});314if (childID < 0) {315// TODO: Propagate specific errors.316return nullptr;317}318return std::make_shared<OPFSDirectory>(mode, getBackend(), childID, proxy);319}320321std::shared_ptr<Symlink> insertSymlink(const std::string& name,322const std::string& target) override {323// Symlinks not supported.324// TODO: Propagate EPERM specifically.325return nullptr;326}327328int insertMove(const std::string& name, std::shared_ptr<File> file) override {329int err = 0;330if (file->is<DataFile>()) {331auto opfsFile = std::static_pointer_cast<OPFSFile>(file);332proxy([&](auto ctx) {333_wasmfs_opfs_move_file(334ctx.ctx, opfsFile->fileID, dirID, name.c_str(), &err);335});336} else {337// TODO: Support moving directories once OPFS supports that.338// EBUSY can be returned when the directory is "in use by the system,"339// which can mean whatever we want.340err = -EBUSY;341}342return err;343}344345int removeChild(const std::string& name) override {346int err = 0;347proxy([&](auto ctx) {348_wasmfs_opfs_remove_child(ctx.ctx, dirID, name.c_str(), &err);349});350return err;351}352353ssize_t getNumEntries() override {354auto entries = getEntries();355if (int err = entries.getError()) {356return err;357}358return entries->size();359}360361Directory::MaybeEntries getEntries() override {362std::vector<Directory::Entry> entries;363int err = 0;364proxy([&](auto ctx) {365_wasmfs_opfs_get_entries(ctx.ctx, dirID, &entries, &err);366});367if (err) {368assert(err < 0);369return {err};370}371return {entries};372}373};374375class OPFSBackend : public Backend {376public:377Worker proxy;378379std::shared_ptr<DataFile> createFile(mode_t mode) override {380// No way to support a raw file without a parent directory.381// TODO: update the core system to document this as a possible result of382// `createFile` and to handle it gracefully.383return nullptr;384}385386std::shared_ptr<Directory> createDirectory(mode_t mode) override {387proxy([](auto ctx) { _wasmfs_opfs_init_root_directory(ctx.ctx); });388return std::make_shared<OPFSDirectory>(mode, this, 1, proxy);389}390391std::shared_ptr<Symlink> createSymlink(std::string target) override {392// Symlinks not supported.393return nullptr;394}395};396397} // anonymous namespace398399extern "C" {400401backend_t wasmfs_create_opfs_backend() {402// ProxyWorker cannot safely be synchronously spawned from the main browser403// thread. See comment in thread_utils.h for more details.404assert(405!emscripten_is_main_browser_thread() ||406emscripten_has_asyncify() == 2 &&407"Cannot safely create OPFS backend on main browser thread without JSPI");408409return wasmFS.addBackend(std::make_unique<OPFSBackend>());410}411412void EMSCRIPTEN_KEEPALIVE _wasmfs_opfs_record_entry(413std::vector<Directory::Entry>* entries, const char* name, int type) {414entries->push_back({name, File::FileKind(type), 0});415}416417} // extern "C"418419420