From 64c247ba6ff925023de1fabc2df6e48f64a4d878 Mon Sep 17 00:00:00 2001 From: faraphel Date: Thu, 22 Aug 2024 19:39:32 +0200 Subject: [PATCH] replaced the virtual file system back by the original custom one --- .gitmodules | 3 - CMakeLists.txt | 24 +++-- external/vfs/CMakeLists.txt | 18 ++++ external/vfs/README.md | 10 +++ external/vfs/include/VirtualFileSystem.hpp | 49 +++++++++++ .../exception/FileNotFoundException.hpp | 14 +++ external/vfs/source/VirtualFileSystem.cpp | 88 +++++++++++++++++++ .../exception/FileNotFoundException.cpp | 9 ++ external/vfs/tests/CMakeLists.txt | 25 ++++++ external/vfs/tests/_assets/content.txt | 1 + external/vfs/tests/test-restriction.cpp | 38 ++++++++ external/vfspp | 1 - source/main.cpp | 34 ++++++- source/script/engine/Engine.cpp | 13 +-- source/script/engine/Engine.hpp | 3 +- .../module/debug/{load.cpp => _load.cpp} | 3 +- .../module/debug/{load.hpp => _load.hpp} | 4 + source/script/module/debug/log.hpp | 6 ++ source/script/module/filesystem/Path.cpp | 8 ++ source/script/module/filesystem/Path.hpp | 17 ++++ source/script/module/filesystem/_load.cpp | 23 +++++ source/script/module/filesystem/_load.hpp | 13 +++ 22 files changed, 373 insertions(+), 31 deletions(-) create mode 100644 external/vfs/CMakeLists.txt create mode 100644 external/vfs/README.md create mode 100644 external/vfs/include/VirtualFileSystem.hpp create mode 100644 external/vfs/include/exception/FileNotFoundException.hpp create mode 100644 external/vfs/source/VirtualFileSystem.cpp create mode 100644 external/vfs/source/exception/FileNotFoundException.cpp create mode 100644 external/vfs/tests/CMakeLists.txt create mode 100644 external/vfs/tests/_assets/content.txt create mode 100644 external/vfs/tests/test-restriction.cpp delete mode 160000 external/vfspp rename source/script/module/debug/{load.cpp => _load.cpp} (95%) rename source/script/module/debug/{load.hpp => _load.hpp} (60%) create mode 100644 source/script/module/filesystem/Path.cpp create mode 100644 source/script/module/filesystem/Path.hpp create mode 100644 source/script/module/filesystem/_load.cpp create mode 100644 source/script/module/filesystem/_load.hpp diff --git a/.gitmodules b/.gitmodules index 68145ec..00850b9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,3 @@ [submodule "external/wit"] path = external/wit url = https://git.faraphel.fr/Atlas/library-wit -[submodule "external/vfspp"] - path = external/vfspp - url = https://github.com/nextgeniuspro/vfspp diff --git a/CMakeLists.txt b/CMakeLists.txt index 590322a..c278a7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,28 +23,34 @@ add_executable(Atlas-Launcher # Source source/main.cpp - source/script/module/debug/load.cpp - source/script/module/debug/load.hpp + source/script/module/debug/_load.cpp + source/script/module/debug/_load.hpp source/script/module/debug/log.cpp source/script/module/debug/log.hpp source/script/engine/Engine.cpp source/script/engine/Engine.hpp + source/script/module/filesystem/_load.cpp + source/script/module/filesystem/_load.hpp + source/script/module/filesystem/Path.cpp + source/script/module/filesystem/Path.hpp ) target_include_directories(Atlas-Launcher PRIVATE - source/ - # AngelScript external/angelscript/add_on + + # Source + source/ ) target_link_libraries(Atlas-Launcher PRIVATE # Libraries angelscript - vfspp - - # Tools - # SZS + VFS ) + +# Copy the mods +file(COPY .mods DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + add_subdirectory(external/angelscript/angelscript/projects/cmake) -add_subdirectory(external/vfspp) +add_subdirectory(external/vfs) # add_subdirectory(external/szs) diff --git a/external/vfs/CMakeLists.txt b/external/vfs/CMakeLists.txt new file mode 100644 index 0000000..a675ee1 --- /dev/null +++ b/external/vfs/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.28) +project(VFS LANGUAGES CXX) + + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +add_library(VFS STATIC + source/VirtualFileSystem.cpp + source/exception/FileNotFoundException.cpp + include/exception/FileNotFoundException.hpp +) +target_include_directories(VFS PUBLIC + include +) + +add_subdirectory(tests) \ No newline at end of file diff --git a/external/vfs/README.md b/external/vfs/README.md new file mode 100644 index 0000000..8c0106e --- /dev/null +++ b/external/vfs/README.md @@ -0,0 +1,10 @@ +# Restricted Virtual File System +This is a micro-library used to create a virtual file system to restrict access +to the host file system while still being able to mount directories that can be accessed. + +As of right now, this is an incredibly simple implementation of this system that +work by mapping virtual directory path to a corresponding real directory path, replacing +them while resolving the path and reject all others paths that have not been mounted +to avoid access to other files. + +It does not emulate a real file system nor allow control over permissions of the files. diff --git a/external/vfs/include/VirtualFileSystem.hpp b/external/vfs/include/VirtualFileSystem.hpp new file mode 100644 index 0000000..38d68ab --- /dev/null +++ b/external/vfs/include/VirtualFileSystem.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + + +namespace vfs { + +/** + * Represent a virtual file system. + * Real directory can be mounted to safely interact with the real file system. + */ +class VirtualFileSystem { + +public: + explicit VirtualFileSystem(); + ~VirtualFileSystem(); + + std::filesystem::path getWorkingDirectory() const; + + void setWorkingDirectory(const std::filesystem::path& virtualPath); + + /** + * Mount a real directory into the virtual file system + * @param sourcePath the path to the directory in the real file system + * @param virtualDestinationPath the path to the directory in the virtual file system + */ + void mount(const std::filesystem::path& sourcePath, const std::filesystem::path& virtualDestinationPath) const; + + /** + * Unmount a directory from the virtual file system + * @param virtualPath the path to the directory in the virtual file system + */ + void unmount(const std::filesystem::path& virtualPath) const; + + /** + * Get the real path on the host file system from its virtual path + * @param virtualPath the virtual path + * @return the real path + */ + std::filesystem::path resolve(const std::filesystem::path& virtualPath) const; + +private: + /// the internal temporary directory + std::filesystem::path rootDirectoryPath; + /// the virtual current working directory. Always relative. + std::filesystem::path virtualWorkingDirectory; +}; + +} diff --git a/external/vfs/include/exception/FileNotFoundException.hpp b/external/vfs/include/exception/FileNotFoundException.hpp new file mode 100644 index 0000000..f995fdf --- /dev/null +++ b/external/vfs/include/exception/FileNotFoundException.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + + +namespace vfs::exception { + +class FileNotFoundException final : public std::runtime_error { +public: + explicit FileNotFoundException(const std::filesystem::path& path); +}; + +} diff --git a/external/vfs/source/VirtualFileSystem.cpp b/external/vfs/source/VirtualFileSystem.cpp new file mode 100644 index 0000000..947ee29 --- /dev/null +++ b/external/vfs/source/VirtualFileSystem.cpp @@ -0,0 +1,88 @@ +#include "VirtualFileSystem.hpp" + +#include + +#include "exception/FileNotFoundException.hpp" + + +namespace vfs { + +VirtualFileSystem::VirtualFileSystem() { + // create the temporary directory + char temporaryDirectoryPathTemplate[] = "/tmp/vfs-XXXXXX"; + const char* temporaryDirectoryPath = mkdtemp(temporaryDirectoryPathTemplate); + // check for any issue while creating the directory + if (temporaryDirectoryPath == nullptr) + throw std::runtime_error("Could not create a temporary directory for the virtual file system."); + + // store it as a standard path + this->rootDirectoryPath = std::filesystem::path(temporaryDirectoryPath); + + // set the virtual working directory at the root + this->setWorkingDirectory("./"); +} + +VirtualFileSystem::~VirtualFileSystem() { + // delete everything in the temporary directory + std::error_code error; + remove_all(this->rootDirectoryPath, error); + + // check for any error + if (error) + std::cerr << "Could not clean the VFS : " + error.message() << std::endl; +} + +std::filesystem::path VirtualFileSystem::getWorkingDirectory() const { + return this->virtualWorkingDirectory; +} + +void VirtualFileSystem::setWorkingDirectory(const std::filesystem::path& virtualPath) { + if (!virtualPath.is_relative()) + throw std::runtime_error("The working directory need to be a relative path (for the virtual file system root)."); + + // set the new working directory + this->virtualWorkingDirectory = virtualPath; +} + +void VirtualFileSystem::mount(const std::filesystem::path& sourcePath, const std::filesystem::path& virtualDestinationPath) const { + // create a symlink from the directory to our temporary directory + const std::filesystem::path realDestinationPath = this->resolve(virtualDestinationPath); + + // create a symlink to the directory to mount in our virtual file system directory + std::error_code error; + create_directory_symlink(absolute(sourcePath), realDestinationPath, error); + // check for any error while creating the symlink + if (error) + throw std::runtime_error("Could not mount the directory in the virtual file system. Reason: " + error.message()); +} + +void VirtualFileSystem::unmount(const std::filesystem::path& virtualPath) const { + // get the real path of the given virtual path + const std::filesystem::path realPath = this->resolve(virtualPath); + + // check if it is a symbolic link + if (!is_symlink(realPath)) + throw std::runtime_error("Not a mounted directory !"); + + // delete the symlink + remove(realPath); +} + +std::filesystem::path VirtualFileSystem::resolve(const std::filesystem::path& virtualPath) const { + // normalize the path by solving the special symbols first. + std::filesystem::path normalizedVirtualPath = virtualPath.lexically_normal(); + + if (virtualPath.is_relative()) { + // if the path is relative, prepend the working directory + normalizedVirtualPath = this->virtualWorkingDirectory / normalizedVirtualPath; + } else { + // convert it into a relative path for the root + normalizedVirtualPath = relative(normalizedVirtualPath.lexically_normal(), "/"); + } + + // get the real path by prepending the real root directory path + return this->rootDirectoryPath / normalizedVirtualPath; +} + + +} diff --git a/external/vfs/source/exception/FileNotFoundException.cpp b/external/vfs/source/exception/FileNotFoundException.cpp new file mode 100644 index 0000000..4134a63 --- /dev/null +++ b/external/vfs/source/exception/FileNotFoundException.cpp @@ -0,0 +1,9 @@ +#include "exception/FileNotFoundException.hpp" + + +namespace vfs::exception { + +FileNotFoundException::FileNotFoundException(const std::filesystem::path& path) : + std::runtime_error("Could not find the file : " + path.string()) {} + +} diff --git a/external/vfs/tests/CMakeLists.txt b/external/vfs/tests/CMakeLists.txt new file mode 100644 index 0000000..3ef0ddf --- /dev/null +++ b/external/vfs/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.28) +project(Test-VFS LANGUAGES CXX) + + +enable_testing() + + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +# Test executable +add_executable(Test-VFS + test-restriction.cpp +) +# Libraries +target_link_libraries(Test-VFS PRIVATE + # Code to test + VFS +) +# Copy the assets to run the tests +file(COPY _assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +# Test +add_test(NAME Test-VFS COMMAND Test-VFS) diff --git a/external/vfs/tests/_assets/content.txt b/external/vfs/tests/_assets/content.txt new file mode 100644 index 0000000..8a03e0e --- /dev/null +++ b/external/vfs/tests/_assets/content.txt @@ -0,0 +1 @@ +This is a simple text file. diff --git a/external/vfs/tests/test-restriction.cpp b/external/vfs/tests/test-restriction.cpp new file mode 100644 index 0000000..348c5c5 --- /dev/null +++ b/external/vfs/tests/test-restriction.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +int main() { + // TODO(Faraphel): use a real unit test framework ? + + // create a basic virtual file system + vfs::VirtualFileSystem fs; + + // mount a directory into our system + fs.mount("_assets", "/assets"); + + // TEST: check if a file outside of an allowed mounted directory can't be resolved + try { + const std::filesystem::path path = fs.resolve("/content.txt"); + throw std::runtime_error("[Test 1] - Impossible path should not be resolved !"); + } catch (const std::runtime_error& exception) {} + + // TEST: check if a file in a mounted directory can be resolved + try { + const std::filesystem::path path = fs.resolve("/assets/content.txt"); + + // auto file = QFile(path); + // file.open(QIODeviceBase::Text | QIODevice::ReadOnly); + // std::cout << file.readAll().toStdString() << std::endl; + + } catch (const std::runtime_error& exception) { + throw std::runtime_error("[Test 2] - Path could not be accessed !"); + } + + // TEST: cannot bypass security with the ".." parent directory syntax + try { + // const std::filesystem::path path = fs.resolve("/assets/../content.txt"); + // throw std::runtime_error("[Test 3] - Resolved impossible path"); + } catch (const std::runtime_error& exception) { + } +} diff --git a/external/vfspp b/external/vfspp deleted file mode 160000 index f22a0b2..0000000 --- a/external/vfspp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f22a0b23827e0ff3faa8379dc0bad450db9ac17a diff --git a/source/main.cpp b/source/main.cpp index 2ab3a8d..39b91af 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,16 +1,16 @@ #include +#include #include -#include -#include #include +#include #include #include "script/engine/Engine.hpp" -#include "script/module/debug/load.hpp" +#include "script/module/debug/_load.hpp" -int main(int argc, char* argv[]) { +int main_AngelScript(int argc, char* argv[]) { int error; // engine @@ -64,3 +64,29 @@ int main(int argc, char* argv[]) { return error; } + +int main_vfspp(int argc, char* argv[]) { + int error; + + // create a virtual file system + const auto vfs = std::make_unique(); + vfs->mount(".mods/Atlas/assets", "assets"); + + // get an example file + auto file = vfs->resolve("assets/message.txt"); + + // open and read it + auto stream = std::ifstream(file); + if (!stream.is_open()) + throw std::runtime_error("Could not open the file."); + + std::string line; + std::getline(stream, line); + std::cout << line; + + return 0; +} + +int main(int argc, char* argv[]) { + main_vfspp(argc, argv); +} diff --git a/source/script/engine/Engine.cpp b/source/script/engine/Engine.cpp index 9f1aeb0..ce4ecb2 100644 --- a/source/script/engine/Engine.cpp +++ b/source/script/engine/Engine.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,7 +13,6 @@ #include #include #include -#include #include @@ -38,15 +38,6 @@ Engine::Engine() { RegisterScriptWeakRef(this->asEngine); RegisterExceptionRoutines(this->asEngine); RegisterScriptMath(this->asEngine); - - // initialise the file system - this->fileSystem = std::make_unique(); - - // TODO(Faraphel): is the memory fs - auto memoryFileSystem = std::make_unique(); - memoryFileSystem->Initialize(); - - this->fileSystem->AddFileSystem("/tmp", std::move(memoryFileSystem)); } void Engine::asCallback(const asSMessageInfo *message, void *args) { @@ -84,7 +75,7 @@ Engine::~Engine() { this->asEngine->ShutDownAndRelease(); } -asIScriptEngine *Engine::getAsEngine() const { +asIScriptEngine* Engine::getAsEngine() const { return this->asEngine; } diff --git a/source/script/engine/Engine.hpp b/source/script/engine/Engine.hpp index d74513d..84cb720 100644 --- a/source/script/engine/Engine.hpp +++ b/source/script/engine/Engine.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include namespace atlas::script::engine { @@ -27,9 +26,9 @@ public: * @return the AngelScript engine */ [[nodiscard]] asIScriptEngine* getAsEngine() const; + private: asIScriptEngine* asEngine; - std::unique_ptr fileSystem; }; } diff --git a/source/script/module/debug/load.cpp b/source/script/module/debug/_load.cpp similarity index 95% rename from source/script/module/debug/load.cpp rename to source/script/module/debug/_load.cpp index e63a857..c71bb09 100644 --- a/source/script/module/debug/load.cpp +++ b/source/script/module/debug/_load.cpp @@ -1,5 +1,6 @@ -#include "load.hpp" +#include "_load.hpp" +#include #include #include "log.hpp" diff --git a/source/script/module/debug/load.hpp b/source/script/module/debug/_load.hpp similarity index 60% rename from source/script/module/debug/load.hpp rename to source/script/module/debug/_load.hpp index 2a47569..6ce3a93 100644 --- a/source/script/module/debug/load.hpp +++ b/source/script/module/debug/_load.hpp @@ -5,6 +5,10 @@ namespace atlas::script::module::debug { +/** + * Load the debug features into an Atlas engine + * @param atlasEngine the Atlas engine + */ void load(const engine::Engine* atlasEngine); } diff --git a/source/script/module/debug/log.hpp b/source/script/module/debug/log.hpp index e822a7e..d75a54c 100644 --- a/source/script/module/debug/log.hpp +++ b/source/script/module/debug/log.hpp @@ -5,6 +5,12 @@ namespace atlas::script::module::debug { +/** + * @brief Log a message + * @details Display a message in the execution logs + * @param category the category of the message + * @param message the message + */ void log(const std::string& category, const std::string& message); } diff --git a/source/script/module/filesystem/Path.cpp b/source/script/module/filesystem/Path.cpp new file mode 100644 index 0000000..3bd542d --- /dev/null +++ b/source/script/module/filesystem/Path.cpp @@ -0,0 +1,8 @@ +#include "Path.hpp" + +namespace atlas::script::module::filesystem { + +Path::Path(const engine::Engine* atlasEngine, const std::string &path) { +} + +} \ No newline at end of file diff --git a/source/script/module/filesystem/Path.hpp b/source/script/module/filesystem/Path.hpp new file mode 100644 index 0000000..e6f8c45 --- /dev/null +++ b/source/script/module/filesystem/Path.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +#include "script/engine/Engine.hpp" + + +namespace atlas::script::module::filesystem { + +/** + * Represent a path in the virtual file system + */ +class Path { +public: + explicit Path(const engine::Engine* atlasEngine, const std::string& path); +}; + +} diff --git a/source/script/module/filesystem/_load.cpp b/source/script/module/filesystem/_load.cpp new file mode 100644 index 0000000..178ee28 --- /dev/null +++ b/source/script/module/filesystem/_load.cpp @@ -0,0 +1,23 @@ +#include "_load.hpp" + +#include + + +namespace atlas::script::module::filesystem { + +void load(const engine::Engine* atlasEngine) { + asIScriptEngine* asEngine = atlasEngine->getAsEngine(); + int error; + + // start namespace + error = asEngine->SetDefaultNamespace("atlas::filesystem"); + if (error < 0) + throw std::runtime_error("Could not enter the \"atlas::filesystem\" namespace."); + + // end namespace + error = asEngine->SetDefaultNamespace(""); + if (error < 0) + throw std::runtime_error("Could not reset the namespace."); +} + +} diff --git a/source/script/module/filesystem/_load.hpp b/source/script/module/filesystem/_load.hpp new file mode 100644 index 0000000..e28b093 --- /dev/null +++ b/source/script/module/filesystem/_load.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "script/engine/Engine.hpp" + + +namespace atlas::script::module::filesystem { + +/** + * Load the file system features into an Atlas engine + * @param atlasEngine the Atlas engine + */ +void load(const engine::Engine* atlasEngine); + +}