[Add] Implement file I/O and remote CoW.
This commit is contained in:
		
							parent
							
								
									a80764a70e
								
							
						
					
					
						commit
						5f648440c5
					
				
							
								
								
									
										25
									
								
								assets/scripts/bin/echo.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								assets/scripts/bin/echo.lua
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
-- /bin/echo - Display contents of a text file.
 | 
			
		||||
local content_parts = {}
 | 
			
		||||
local filename = nil
 | 
			
		||||
local found_redirect = false
 | 
			
		||||
 | 
			
		||||
for i, v in ipairs(arg) do
 | 
			
		||||
  if v == ">" then
 | 
			
		||||
    found_redirect = true
 | 
			
		||||
    filename = arg[i+1]
 | 
			
		||||
    break
 | 
			
		||||
  else
 | 
			
		||||
    table.insert(content_parts, v)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
if found_redirect then
 | 
			
		||||
  if not filename then
 | 
			
		||||
    return "echo: syntax error: expected filename after '>'"
 | 
			
		||||
  end
 | 
			
		||||
  local content = table.concat(content_parts, " ")
 | 
			
		||||
  return { action = "write_file", target = filename, content = content }
 | 
			
		||||
else
 | 
			
		||||
  return table.concat(arg, " ")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include <sol/types.hpp>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
@ -60,54 +61,106 @@ std::string CommandProcessor::process_command(const std::string& command) {
 | 
			
		||||
  return "Unknown command: " + command_name + "\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Find a VFS node by it's absolute path. */
 | 
			
		||||
vfs_node* find_node_by_path(vfs_node* root, const std::string& path) {
 | 
			
		||||
  if(path == "/") {
 | 
			
		||||
    return root;
 | 
			
		||||
  }
 | 
			
		||||
  vfs_node* current = root;
 | 
			
		||||
  std::stringstream ss(path);
 | 
			
		||||
  std::string segment;
 | 
			
		||||
 | 
			
		||||
  /* Discard the first empty segment that comes form the leading '/'. */
 | 
			
		||||
  if(path[0] == '/') {
 | 
			
		||||
    std::getline(ss, segment, '/');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  while(std::getline(ss, segment, '/')) {
 | 
			
		||||
    if(current->type == DIR_NODE && current->children.count(segment)) {
 | 
			
		||||
      current = current->children[segment];
 | 
			
		||||
    } else {
 | 
			
		||||
      return nullptr; /* Path segmenet not found. */
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return current;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Recursively set all nodes in a VFS tree to writeable. */
 | 
			
		||||
void make_vfs_writable(vfs_node* node) {
 | 
			
		||||
  if(!node) return;
 | 
			
		||||
  node->read_only = false;
 | 
			
		||||
  for(auto const& [key, child] : node->children) {
 | 
			
		||||
    make_vfs_writable(child);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string CommandProcessor::_handle_vfs_action(sol::table action) {
 | 
			
		||||
  std::string action_name = action["action"].get_or<std::string>("");
 | 
			
		||||
  /* Make the CoW check universal for any write operation. */
 | 
			
		||||
  if(action_name == "rm" || action_name == "write_file") {
 | 
			
		||||
    if(_current_dir->read_only) {
 | 
			
		||||
      std::string remote_ip = "";
 | 
			
		||||
      /* Check if we are on a known remote system. */
 | 
			
		||||
      if(session_vfs_root != home_vfs_root) {
 | 
			
		||||
        for(auto const& [ip, root_node] : _world_vfs) {
 | 
			
		||||
          if(root_node == session_vfs_root) {
 | 
			
		||||
            remote_ip = ip;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if(!remote_ip.empty()) {
 | 
			
		||||
        /* CoW for a remote system. */
 | 
			
		||||
        fprintf(stderr, "CoW: Write attempt on read-only remote system '%s'."
 | 
			
		||||
                "Creating persistant copy.\n", remote_ip.c_str());
 | 
			
		||||
        std::string original_path = get_full_path(_current_dir);
 | 
			
		||||
        vfs_node* new_root = copy_vfs_node(session_vfs_root, nullptr);
 | 
			
		||||
        make_vfs_writable(new_root);
 | 
			
		||||
        _world_vfs[remote_ip] = new_root;
 | 
			
		||||
        session_vfs_root = new_root;
 | 
			
		||||
        _current_dir = find_node_by_path(session_vfs_root, original_path);
 | 
			
		||||
        if(!_current_dir) { _current_dir = session_vfs_root; } /* Just in case. */
 | 
			
		||||
      } else {
 | 
			
		||||
        /* If not a known remote system, i.e, player's own /bin, deny. */
 | 
			
		||||
        return "Permission denied: Filesystem is read-only.";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  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. */
 | 
			
		||||
    }
 | 
			
		||||
    if(!_current_dir->children.count(path)) {
 | 
			
		||||
      return std::string("rm: cannot remove '") + path + "': No such file or directory.";
 | 
			
		||||
    }
 | 
			
		||||
    vfs_node* target_node = _current_dir->children[path];
 | 
			
		||||
    if(target_node->type == DIR_NODE) {
 | 
			
		||||
      return std::string("rm: cannot remove '") + path + "': Is a directory.";
 | 
			
		||||
    }
 | 
			
		||||
    _current_dir->children.erase(path);
 | 
			
		||||
    delete target_node;
 | 
			
		||||
  } else if(action_name == "write_file") {
 | 
			
		||||
    std::string filename  = action["target"].get_or<std::string>("");
 | 
			
		||||
    std::string content   = action["content"].get_or<std::string>("");
 | 
			
		||||
    if(filename.empty()) {
 | 
			
		||||
      return "write_file: missing filename";
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
    if(_current_dir->children.count(filename)) {
 | 
			
		||||
      vfs_node* target_node = _current_dir->children[filename];
 | 
			
		||||
      if(target_node->type == DIR_NODE) {
 | 
			
		||||
        return std::string("cannot write to '") + filename + "': Is a directory.";
 | 
			
		||||
      }
 | 
			
		||||
      target_node->content = content;
 | 
			
		||||
    } else {
 | 
			
		||||
      vfs_node* new_file = new vfs_node {
 | 
			
		||||
        .name = filename, .type = FILE_NODE, .read_only = false, .content = content,
 | 
			
		||||
        .parent = _current_dir
 | 
			
		||||
      };
 | 
			
		||||
      _current_dir->children[filename] = new_file;
 | 
			
		||||
    }
 | 
			
		||||
    return ""; /* Success. */
 | 
			
		||||
  } else if(action_name == "ssh") {
 | 
			
		||||
    std::string target_ip = action["target"].get_or<std::string>("");
 | 
			
		||||
    if(_world_vfs.count(target_ip)) {
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
LuaProcessor::LuaProcessor(void) {
 | 
			
		||||
  _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io);
 | 
			
		||||
  _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io, sol::lib::table);
 | 
			
		||||
 | 
			
		||||
  /* Expose vfs_node struct members to Lua. */
 | 
			
		||||
  _lua.new_usertype<vfs_node>("vfs_node",
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@ vfs_node* copy_vfs_node(vfs_node* original, vfs_node* new_parent) {
 | 
			
		||||
 | 
			
		||||
  /* 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->read_only = original->read_only; /* Preserver read-only status. */
 | 
			
		||||
  new_copy->content = original->content;
 | 
			
		||||
 | 
			
		||||
  /* Recursively copy all children. */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user