// Copyright 2021 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. // This file defines the file object. #include "file.h" #include "wasmfs.h" #include "wasmfs_internal.h" #include namespace wasmfs { // // DataFile // void DataFile::Handle::preloadFromJS(int index) { // TODO: Each Datafile type could have its own impl of file preloading. // Create a buffer with the required file size. std::vector buffer(_wasmfs_get_preloaded_file_size(index)); // Ensure that files are preloaded from the main thread. assert(emscripten_is_main_runtime_thread()); // Load data into the in-memory buffer. _wasmfs_copy_preloaded_file_data(index, buffer.data()); write((const uint8_t*)buffer.data(), buffer.size(), 0); } // // Directory // void Directory::Handle::cacheChild(const std::string& name, std::shared_ptr child, DCacheKind kind) { // Update the dcache if the backend hasn't opted out of using the dcache or if // this is a mount point, in which case it is not under the control of the // backend. if (kind == DCacheKind::Mount || !getDir()->maintainsFileIdentity()) { auto& dcache = getDir()->dcache; [[maybe_unused]] auto [_, inserted] = dcache.insert({name, {kind, child}}); assert(inserted && "inserted child already existed!"); } // Set the child's parent. assert(child->locked().getParent() == nullptr || child->locked().getParent() == getDir()); child->locked().setParent(getDir()); } std::shared_ptr Directory::Handle::getChild(const std::string& name) { // Unlinked directories must be empty, without even "." or ".." if (!getParent()) { return nullptr; } if (name == ".") { return file; } if (name == "..") { return getParent(); } // Check whether the cache already contains this child. auto& dcache = getDir()->dcache; if (auto it = dcache.find(name); it != dcache.end()) { return it->second.file; } // Otherwise check whether the backend contains an underlying file we don't // know about. auto child = getDir()->getChild(name); if (!child) { return nullptr; } cacheChild(name, child, DCacheKind::Normal); return child; } bool Directory::Handle::mountChild(const std::string& name, std::shared_ptr child) { assert(child); // Cannot insert into an unlinked directory. if (!getParent()) { return false; } cacheChild(name, child, DCacheKind::Mount); return true; } std::shared_ptr Directory::Handle::insertDataFile(const std::string& name, mode_t mode) { // Cannot insert into an unlinked directory. if (!getParent()) { return nullptr; } auto child = getDir()->insertDataFile(name, mode); if (!child) { return nullptr; } cacheChild(name, child, DCacheKind::Normal); updateMTime(); return child; } std::shared_ptr Directory::Handle::insertDirectory(const std::string& name, mode_t mode) { // Cannot insert into an unlinked directory. if (!getParent()) { return nullptr; } auto child = getDir()->insertDirectory(name, mode); if (!child) { return nullptr; } cacheChild(name, child, DCacheKind::Normal); updateMTime(); return child; } std::shared_ptr Directory::Handle::insertSymlink(const std::string& name, const std::string& target) { // Cannot insert into an unlinked directory. if (!getParent()) { return nullptr; } auto child = getDir()->insertSymlink(name, target); if (!child) { return nullptr; } cacheChild(name, child, DCacheKind::Normal); updateMTime(); return child; } // TODO: consider moving this to be `Backend::move` to avoid asymmetry between // the source and destination directories and/or taking `Directory::Handle` // arguments to prove that the directories have already been locked. int Directory::Handle::insertMove(const std::string& name, std::shared_ptr file) { // Cannot insert into an unlinked directory. if (!getParent()) { return -EPERM; } // Look up the file in its old parent's cache. auto oldParent = file->locked().getParent(); auto& oldCache = oldParent->dcache; auto oldIt = std::find_if(oldCache.begin(), oldCache.end(), [&](auto& kv) { return kv.second.file == file; }); // TODO: Handle moving mount points correctly by only updating caches without // involving the backend. // Attempt the move. if (auto err = getDir()->insertMove(name, file)) { return err; } if (oldIt != oldCache.end()) { // Do the move and update the caches. auto [oldName, entry] = *oldIt; assert(oldName.size()); // Update parent pointers and caches to reflect the successful move. oldCache.erase(oldIt); auto& newCache = getDir()->dcache; auto [it, inserted] = newCache.insert({name, entry}); if (!inserted) { // Update and overwrite the overwritten file. it->second.file->locked().setParent(nullptr); it->second = entry; } } else { // This backend doesn't use the dcache. assert(getDir()->maintainsFileIdentity()); } file->locked().setParent(getDir()); // TODO: Moving mount points probably shouldn't update the mtime. oldParent->locked().updateMTime(); updateMTime(); return 0; } int Directory::Handle::removeChild(const std::string& name) { auto& dcache = getDir()->dcache; auto entry = dcache.find(name); // If this is a mount, we don't need to call into the backend. if (entry != dcache.end() && entry->second.kind == DCacheKind::Mount) { dcache.erase(entry); return 0; } if (auto err = getDir()->removeChild(name)) { assert(err < 0); return err; } if (entry != dcache.end()) { entry->second.file->locked().setParent(nullptr); dcache.erase(entry); } updateMTime(); return 0; } std::string Directory::Handle::getName(std::shared_ptr file) { if (getDir()->maintainsFileIdentity()) { return getDir()->getName(file); } auto& dcache = getDir()->dcache; for (auto it = dcache.begin(); it != dcache.end(); ++it) { if (it->second.file == file) { return it->first; } } return ""; } ssize_t Directory::Handle::getNumEntries() { size_t mounts = 0; auto& dcache = getDir()->dcache; for (auto it = dcache.begin(); it != dcache.end(); ++it) { if (it->second.kind == DCacheKind::Mount) { ++mounts; } } auto numReal = getDir()->getNumEntries(); if (numReal < 0) { return numReal; } return numReal + mounts; } Directory::MaybeEntries Directory::Handle::getEntries() { auto entries = getDir()->getEntries(); if (entries.getError()) { return entries; } auto& dcache = getDir()->dcache; for (auto it = dcache.begin(); it != dcache.end(); ++it) { auto& [name, entry] = *it; if (entry.kind == DCacheKind::Mount) { entries->push_back({name, entry.file->kind, entry.file->getIno()}); } } return entries; } } // namespace wasmfs