Path: blob/main/system/lib/wasmfs/wasmfs.cpp
6174 views
// Copyright 2021 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// This file defines the global state.67#include <emscripten/threading.h>89#include "memory_backend.h"10#include "paths.h"11#include "special_files.h"12#include "wasmfs.h"13#include "wasmfs_internal.h"1415namespace wasmfs {1617#ifdef WASMFS_CASE_INSENSITIVE18backend_t createIgnoreCaseBackend(std::function<backend_t()> createBackend);19#endif2021// The below lines are included to make the compiler believe that the global22// constructor is part of a system header, which is necessary to work around a23// compilation error about using a reserved init priority less than 101. This24// ensures that the global state of the file system is constructed before25// anything else. ATTENTION: No other static global objects should be defined26// besides wasmFS. Due to # define _LIBCPP_INIT_PRIORITY_MAX27// __attribute__((init_priority(101))), we must use init priority 100 (reserved28// system priority) since wasmFS is a system level component.29// TODO: consider instead adding this in libc's startup code.30// WARNING: Maintain # n + 1 "wasmfs.cpp" 3 where n = line number.31# 29 "wasmfs.cpp" 332__attribute__((init_priority(100))) WasmFS wasmFS;33# 31 "wasmfs.cpp"3435// If the user does not implement this hook, do nothing.36__attribute__((weak)) extern "C" void wasmfs_before_preload(void) {}3738// Set up global data structures and preload files.39WasmFS::WasmFS() : rootDirectory(initRootDirectory()), cwd(rootDirectory) {40wasmfs_before_preload();41preloadFiles();42}4344// Manual integration with LSan. LSan installs itself during startup at the45// first allocation, which happens inside WasmFS code (since the WasmFS global46// object creates some data structures). As a result LSan's atexit() destructor47// will be called last, after WasmFS is cleaned up, since atexit() calls work48// are LIFO (like a stack). But that is a problem, since if WasmFS has shut49// down and deallocated itself then the leak code cannot actually print any of50// its findings, if it has any. To avoid that, define the LSan entry point as a51// weak symbol, and call it; if LSan is not enabled this can be optimized out,52// and if LSan is enabled then we'll check for leaks right at the start of the53// WasmFS destructor, which is the last time during which it is valid to print.54// (Note that this means we can't find leaks inside WasmFS code itself, but that55// seems fundamentally impossible for the above reasons, unless we made LSan log56// its findings in a way that does not depend on normal file I/O.)57__attribute__((weak)) extern "C" void __lsan_do_leak_check(void) {}5859extern "C" void wasmfs_flush(void) {60// Flush musl libc streams.61fflush(0);6263// Flush our own streams. TODO: flush all backends.64(void)SpecialFiles::getStdout()->locked().flush();65(void)SpecialFiles::getStderr()->locked().flush();66}6768WasmFS::~WasmFS() {69// See comment above on this function.70//71// Note that it is ok for us to call it here, as LSan internally makes all72// calls after the first into no-ops. That is, calling it here makes the one73// time that leak checks are run be done here, or potentially earlier, but not74// later; and as mentioned in the comment above, this is the latest possible75// time for the checks to run (since right after this nothing can be printed).76__lsan_do_leak_check();7778// TODO: Integrate musl exit() which would flush the libc part for us. That79// might also help with destructor priority - we need to happen last.80// (But we would still need to flush the internal WasmFS buffers, see81// wasmfs_flush() and the comment on it in the header.)82wasmfs_flush();8384// Break the reference cycle caused by the root directory being its own85// parent.86rootDirectory->locked().setParent(nullptr);87}8889// Special backends that want to install themselves as the root use this hook.90// Otherwise, we use the default backends.91__attribute__((weak)) extern "C" backend_t wasmfs_create_root_dir(void) {92#ifdef WASMFS_CASE_INSENSITIVE93return createIgnoreCaseBackend([]() { return createMemoryBackend(); });94#else95return createMemoryBackend();96#endif97}9899std::shared_ptr<Directory> WasmFS::initRootDirectory() {100auto rootBackend = wasmfs_create_root_dir();101auto rootDirectory =102rootBackend->createDirectory(S_IRUGO | S_IXUGO | S_IWUGO);103auto lockedRoot = rootDirectory->locked();104105// The root directory is its own parent.106lockedRoot.setParent(rootDirectory);107108// If the /dev/ directory does not already exist, create it. (It may already109// exist in NODERAWFS mode, or if those files have been preloaded.)110auto devDir = lockedRoot.insertDirectory("dev", S_IRUGO | S_IXUGO);111if (devDir) {112auto lockedDev = devDir->locked();113lockedDev.mountChild("null", SpecialFiles::getNull());114lockedDev.mountChild("stdin", SpecialFiles::getStdin());115lockedDev.mountChild("stdout", SpecialFiles::getStdout());116lockedDev.mountChild("stderr", SpecialFiles::getStderr());117lockedDev.mountChild("random", SpecialFiles::getRandom());118lockedDev.mountChild("urandom", SpecialFiles::getURandom());119}120121// As with the /dev/ directory, it is not an error for /tmp/ to already122// exist.123lockedRoot.insertDirectory("tmp", S_IRWXUGO);124125return rootDirectory;126}127128// Initialize files specified by the --preload-file option.129// Set up directories and files in wasmFS$preloadedDirs and130// wasmFS$preloadedFiles from JS. This function will be called before any file131// operation to ensure any preloaded files are eagerly available for use.132void WasmFS::preloadFiles() {133// Debug builds only: add check to ensure preloadFiles() is called once.134#ifndef NDEBUG135static std::atomic<int> timesCalled;136timesCalled++;137assert(timesCalled == 1);138#endif139140// Ensure that files are preloaded from the main thread.141assert(emscripten_is_main_runtime_thread());142143auto numFiles = _wasmfs_get_num_preloaded_files();144auto numDirs = _wasmfs_get_num_preloaded_dirs();145146// If there are no preloaded files, exit early.147if (numDirs == 0 && numFiles == 0) {148return;149}150151// Iterate through wasmFS$preloadedDirs to obtain a parent and child pair.152// Ex. Module['FS_createPath']("/foo/parent", "child", true, true);153for (int i = 0; i < numDirs; i++) {154char parentPath[PATH_MAX] = {};155_wasmfs_get_preloaded_parent_path(i, parentPath);156157auto parsed = path::parseFile(parentPath);158std::shared_ptr<Directory> parentDir;159if (parsed.getError() ||160!(parentDir = parsed.getFile()->dynCast<Directory>())) {161emscripten_err(162"Fatal error during directory creation in file preloading.");163abort();164}165166char childName[PATH_MAX] = {};167_wasmfs_get_preloaded_child_path(i, childName);168169auto lockedParentDir = parentDir->locked();170if (lockedParentDir.getChild(childName)) {171// The child already exists, so we don't need to do anything here.172continue;173}174175auto inserted =176lockedParentDir.insertDirectory(childName, S_IRUGO | S_IXUGO);177assert(inserted && "TODO: handle preload insertion errors");178}179180for (int i = 0; i < numFiles; i++) {181char fileName[PATH_MAX] = {};182_wasmfs_get_preloaded_path_name(i, fileName);183184auto mode = _wasmfs_get_preloaded_file_mode(i);185186auto parsed = path::parseParent(fileName);187if (parsed.getError()) {188emscripten_err("Fatal error during file preloading");189abort();190}191auto& [parent, childName] = parsed.getParentChild();192auto created =193parent->locked().insertDataFile(std::string(childName), (mode_t)mode);194assert(created && "TODO: handle preload insertion errors");195created->locked().preloadFromJS(i);196}197}198199} // namespace wasmfs200201202