[Add] Implemented Copy-on-Write and scriptable rm.
OK, this commit finally implements the Copy-on-Write architecure I spoke
about in previous commits.. It also refactors command execution to be
safer and more extensible.
To enable CoW and centralise state-changing, command scripts no longer
modify the VFS directly. Instead, they return a table describing their
intended action '{ action = "rm", target = "file.txt" }'. The C++
CommandProcessor is then responsible for interpreting this and executing
it safely.
			
			
This commit is contained in:
		
							parent
							
								
									b30c497769
								
							
						
					
					
						commit
						267d2477de
					
				@ -1,7 +1,9 @@
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
#include "lua_processor.h"
 | 
			
		||||
#include "vfs_manager.h"
 | 
			
		||||
 | 
			
		||||
CommandProcessor::CommandProcessor(vfs_node* starting_dir) {
 | 
			
		||||
  _current_dir = starting_dir;
 | 
			
		||||
@ -42,7 +44,13 @@ std::string CommandProcessor::process_command(const std::string& command) {
 | 
			
		||||
  }
 | 
			
		||||
  if(root->children.count("bin") && root->children["bin"]->children.count(script_filename)) {
 | 
			
		||||
    vfs_node* script_node = root->children["bin"]->children[script_filename];
 | 
			
		||||
    return _lua->execute(script_node->content, _current_dir, args);
 | 
			
		||||
    sol::object result = _lua->execute(script_node->content, _current_dir, args);
 | 
			
		||||
    if(result.is<std::string>()) {
 | 
			
		||||
      return result.as<std::string>();
 | 
			
		||||
    } else if(result.is<sol::table>()) {
 | 
			
		||||
      return _handle_vfs_action(result.as<sol::table>());
 | 
			
		||||
    }
 | 
			
		||||
    return "[Script returned an unexpected type]";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -68,3 +76,59 @@ std::string CommandProcessor::process_command(const std::string& command) {
 | 
			
		||||
 | 
			
		||||
  return "Unknown command: " + command + "\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string CommandProcessor::_handle_vfs_action(sol::table action) {
 | 
			
		||||
  std::string action_name = action["action"].get_or<std::string>("");
 | 
			
		||||
  if(action_name == "rm") {
 | 
			
		||||
    std::string path = action["target"].get_or<std::string>("");
 | 
			
		||||
    if(path.empty()) return "rm: missing operand";
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * std::map::count was failing weirdly..
 | 
			
		||||
     * Instead, iterate manually to find the node.
 | 
			
		||||
     */
 | 
			
		||||
    vfs_node* target_node = nullptr;
 | 
			
		||||
    for(auto const& [key, node] : _current_dir->children) {
 | 
			
		||||
      if(key == path) {
 | 
			
		||||
        target_node = node;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(target_node != nullptr) {
 | 
			
		||||
      if(_current_dir->read_only) {
 | 
			
		||||
        /* === Copy on Write === */
 | 
			
		||||
        fprintf(stderr, "CoW: Write attempt on read-only dir '%s'. "
 | 
			
		||||
                "Creating private copy.\n",
 | 
			
		||||
                _current_dir->name.c_str());
 | 
			
		||||
 | 
			
		||||
        vfs_node* grand_parent = _current_dir->parent;
 | 
			
		||||
        if(!grand_parent) {
 | 
			
		||||
          return "rm: Permission denied. Cannot modify the root filesystem.";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Copy the directory. */
 | 
			
		||||
        vfs_node* private_copy = copy_vfs_node(_current_dir, grand_parent);
 | 
			
		||||
 | 
			
		||||
        /* Link the new private copy into the VFS tree. */
 | 
			
		||||
        grand_parent->children[_current_dir->name] = private_copy;
 | 
			
		||||
 | 
			
		||||
        /* Update our own state to point to the new writable dir. */
 | 
			
		||||
        _current_dir = private_copy;
 | 
			
		||||
 | 
			
		||||
        /* Find the target node again within our new private copy. */
 | 
			
		||||
        target_node = _current_dir->children[path];
 | 
			
		||||
      }
 | 
			
		||||
      /* Go ahead and delete, you have a writable target now :) */
 | 
			
		||||
      if(target_node->type == DIR_NODE) {
 | 
			
		||||
        return std::string("rm: cannot remove '") + path + "': Is a directory.";
 | 
			
		||||
      }
 | 
			
		||||
      _current_dir->children.erase(path);
 | 
			
		||||
      delete target_node;
 | 
			
		||||
      return ""; /* Success. */
 | 
			
		||||
    }
 | 
			
		||||
      return std::string("rm: cannot remove '") + path + "': No such file or directory.";
 | 
			
		||||
  }
 | 
			
		||||
  return "Error: Unknown VFS action '" + action_name + "'";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ public:
 | 
			
		||||
  vfs_node* get_current_dir(void);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  std::string _handle_vfs_action(sol::table action);
 | 
			
		||||
  vfs_node* _current_dir;
 | 
			
		||||
  LuaProcessor* _lua;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
#include "lua_processor.h"
 | 
			
		||||
#include <sol/forward.hpp>
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
LuaProcessor::LuaProcessor(void) {
 | 
			
		||||
@ -9,51 +10,27 @@ LuaProcessor::LuaProcessor(void) {
 | 
			
		||||
                              "name",       &vfs_node::name,
 | 
			
		||||
                              "type",       &vfs_node::type,
 | 
			
		||||
                              "children",   &vfs_node::children,
 | 
			
		||||
                              "read_only",  &vfs_node::read_only);
 | 
			
		||||
 | 
			
		||||
  /* Create the 'vfs' table for our API. */
 | 
			
		||||
  sol::table vfs_api = _lua.create_table("vfs");
 | 
			
		||||
  vfs_api.set_function("rm", [](std::string path, vfs_node* current_dir) {
 | 
			
		||||
    /* for now, we'll just support deleting from the current directory. */
 | 
			
		||||
    if(current_dir->children.count(path)) {
 | 
			
		||||
      vfs_node* target_node = current_dir->children[path];
 | 
			
		||||
      if(target_node->read_only || current_dir->read_only) {
 | 
			
		||||
        return std::string("rm: Permission denied. File or directory is read-only.");
 | 
			
		||||
      }
 | 
			
		||||
      if(target_node->type == DIR_NODE) {
 | 
			
		||||
        return std::string("rm: cannot remove '" + path + "': Is a directory.");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current_dir->children.erase(path);
 | 
			
		||||
      delete target_node; /* Clean up memry. */
 | 
			
		||||
      return std::string(""); /* Success. */
 | 
			
		||||
    }
 | 
			
		||||
    return std::string("rm: cannot remove '" + path + "': No such file or directory.");
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
                              "read_only",  &vfs_node::read_only); }
 | 
			
		||||
 | 
			
		||||
LuaProcessor::~LuaProcessor(void) {}
 | 
			
		||||
 | 
			
		||||
std::string LuaProcessor::execute(const std::string& script, vfs_node* current_dir,
 | 
			
		||||
sol::object LuaProcessor::execute(const std::string& script, vfs_node* current_dir,
 | 
			
		||||
                                  const std::vector<std::string>& args) {
 | 
			
		||||
  try {
 | 
			
		||||
    /* Pass C++ objects/points into the Lua env. */
 | 
			
		||||
    _lua["current_dir"] = current_dir;
 | 
			
		||||
 | 
			
		||||
    /* Create and populate the 'arg' table for the script. */
 | 
			
		||||
    sol::table arg_table = _lua.create_table();;;;
 | 
			
		||||
    sol::table arg_table = _lua.create_table();
 | 
			
		||||
    for(size_t i = 0; i < args.size(); ++i) {
 | 
			
		||||
      arg_table[i+1] = args[i]; /* Lua array are 1-indexed. Not that Skuzzy would know. */
 | 
			
		||||
      arg_table[i+1] = args[i]; /* Lua arrays 1-indexed. */
 | 
			
		||||
    }
 | 
			
		||||
    _lua["arg"] = arg_table;
 | 
			
		||||
 | 
			
		||||
    sol::object result = _lua.script(script);
 | 
			
		||||
    if(result.is<std::string>()) {
 | 
			
		||||
      return result.as<std::string>();
 | 
			
		||||
    }
 | 
			
		||||
    return _lua.script(script);
 | 
			
		||||
 | 
			
		||||
  } catch(const sol::error& e) {
 | 
			
		||||
    return e.what();
 | 
			
		||||
    /* Return the error message as a string in a sol::object. */
 | 
			
		||||
    return sol::make_object(_lua, e.what());
 | 
			
		||||
  }
 | 
			
		||||
  /* Shouldn't reach this, just shutting the compiler up. */
 | 
			
		||||
  return "[Unknown error in LuaProcessor::execute]";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ public:
 | 
			
		||||
  ~LuaProcessor(void);
 | 
			
		||||
 | 
			
		||||
  /* Executes a string of lua code and returns result as a string. */
 | 
			
		||||
  std::string execute(const std::string& script, vfs_node* current_dir,
 | 
			
		||||
  sol::object execute(const std::string& script, vfs_node* current_dir,
 | 
			
		||||
                      const std::vector<std::string>& args);
 | 
			
		||||
private:
 | 
			
		||||
  sol::state _lua;
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,24 @@ vfs_node* new_node(std::string name, vfs_node_type type, vfs_node* parent) {
 | 
			
		||||
  return node;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vfs_node* copy_vfs_node(vfs_node* original, vfs_node* new_parent) {
 | 
			
		||||
  if(!original) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Create the new node and copy its properties. */
 | 
			
		||||
  vfs_node* new_copy = new_node(original->name, original->type, new_parent);
 | 
			
		||||
  new_copy->read_only = false; /* The new copy is *always* writeable. */
 | 
			
		||||
  new_copy->content = original->content;
 | 
			
		||||
 | 
			
		||||
  /* Recursively copy all children. */
 | 
			
		||||
  for(auto const& [key, child_node] : original->children) {
 | 
			
		||||
    new_copy->children[key] = copy_vfs_node(child_node, new_copy);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new_copy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VFSManager::VFSManager(void) {
 | 
			
		||||
  /* Create template VFS that holds shared, read-only directories. */
 | 
			
		||||
  _vfs_root = new_node("/", DIR_NODE, nullptr);
 | 
			
		||||
@ -46,7 +64,7 @@ VFSManager::VFSManager(void) {
 | 
			
		||||
  rm_script->content = R"lua(-- /bin/rm.lua - Removes a file.
 | 
			
		||||
                             local file_to_remove = arg[1]
 | 
			
		||||
                             if not file_to_remove then return "rm: missing operand" end
 | 
			
		||||
                             return vfs.rm(file_to_remove, current_dir)
 | 
			
		||||
                             return { action = "rm", target = file_to_remove }
 | 
			
		||||
                             )lua";
 | 
			
		||||
  bin->children["rm.lua"] = rm_script;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,9 @@
 | 
			
		||||
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
/* Recursive copy function for our Copy-on-Write behaviour. */
 | 
			
		||||
vfs_node* copy_vfs_node(vfs_node* original, vfs_node* new_parent);
 | 
			
		||||
 | 
			
		||||
class VFSManager {
 | 
			
		||||
public:
 | 
			
		||||
  VFSManager(void);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user