[Refactor] :O Machines now walk the earth!!

Old vfs_node was getting a bit big for its boots, trying to be a
filesystem, network interface and the whole damn computer all at once.

This server-side refactor introduces a 'Machine' class that rightfully
owns the VFS, network services and other machine-state sh.t.
This commit is contained in:
Ritchie Cunningham 2025-09-27 17:30:14 +01:00
parent 28804998d7
commit 59783d2408
12 changed files with 140 additions and 95 deletions

View File

@ -62,7 +62,7 @@ void ClientNetwork::disconnect(void) {
_context_thread.join();
}
/* Should be safe to close the socket no that thread is stoped <questionmark> */
/* Should be safe to close the socket now that thread is stoped <questionmark> */
if(is_connected()) {
/* Close the socket. Causes outstanding async operations
* in TcpConnection to compete with an error, which in return

View File

@ -1,18 +1,19 @@
#include "command_processor.h"
#include <sol/types.hpp>
#include <sstream>
#include "command_processor.h"
#include "vfs.h"
#include "lua_processor.h"
#include "vfs_manager.h"
#include "machine_manager.h"
#include "machine.h"
CommandProcessor::CommandProcessor(vfs_node* home_vfs,
std::map<std::string, vfs_node*>& world_vfs) :
_world_vfs(world_vfs) {
CommandProcessor::CommandProcessor(Machine* home_machine,
std::map<std::string, Machine*>& world_machines) :
_home_machine(home_machine),
_session_machine(home_machine),
_world_machines(world_machines) {
_lua = new LuaProcessor();
home_vfs_root = home_vfs;
session_vfs_root = home_vfs_root;
_current_dir = session_vfs_root;
_current_dir = _session_machine->vfs_root;
}
CommandProcessor::~CommandProcessor(void) {
@ -41,15 +42,12 @@ std::string CommandProcessor::process_command(const std::string& command) {
args.push_back(arg);
}
/* Search for script in the /bin directory. */
/* Search for script in the /bin directory of the current session machine. */
std::string script_filename = command_name + ".lua";
vfs_node* root = _current_dir;
while(root->parent != nullptr) {
root = root->parent;
}
vfs_node* root = _session_machine->vfs_root;
if(root->children.count("bin") && root->children["bin"]->children.count(script_filename)) {
vfs_node* script_node = root->children["bin"]->children[script_filename];
bool is_remote = (session_vfs_root != home_vfs_root);
bool is_remote = (_session_machine != _home_machine);
sol::object result = _lua->execute(script_node->content, _current_dir, args, is_remote);
if(result.is<std::string>()) {
return result.as<std::string>();
@ -101,9 +99,9 @@ std::string CommandProcessor::_handle_vfs_action(sol::table action) {
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) {
if(!_session_machine->is_vfs_a_copy) {
for(auto const& [ip, machine] : _world_machines) {
if(machine == _session_machine) {
remote_ip = ip;
break;
}
@ -111,19 +109,29 @@ std::string CommandProcessor::_handle_vfs_action(sol::table action) {
}
if(!remote_ip.empty()) {
/* CoW for a remote system. */
/* This machine is using a shared VFS. Copy it! ;) */
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. */
/* Copy the sh.t out of the VFS. */
vfs_node* new_vfs_root = copy_vfs_node(_session_machine->vfs_root, nullptr);
make_vfs_writable(new_vfs_root);
/* Create the new machine that will hold the copied VFS. */
auto* new_machine = new Machine(_session_machine->id, _session_machine->hostname);
new_machine->vfs_root = new_vfs_root;
new_machine->services = _session_machine->services;
new_machine->is_vfs_a_copy = true; /* Mark as copy. */
/* Update the world map and the current session. */
_world_machines[remote_ip] = new_machine;
_session_machine = new_machine;
/* Update _current_dir to point to the equivalent dir in the new VFS. */
_current_dir = find_node_by_path(_session_machine->vfs_root, original_path);
if(!_current_dir) { _current_dir = _session_machine->vfs_root; }
} else {
/* If not a known remote system, i.e, player's own /bin, deny. */
return "Permission denied: Filesystem is read-only.";
return "Permission denied: Filesystem is read-only and not part of the world.";
}
}
}
@ -163,9 +171,9 @@ std::string CommandProcessor::_handle_vfs_action(sol::table action) {
return ""; /* Success. */
} else if(action_name == "ssh") {
std::string target_ip = action["target"].get_or<std::string>("");
if(_world_vfs.count(target_ip)) {
session_vfs_root = _world_vfs[target_ip];
_current_dir = session_vfs_root;
if(_world_machines.count(target_ip)) {
_session_machine = _world_machines[target_ip];
_current_dir = _session_machine->vfs_root;
return "Connected to " + target_ip;
}
return "ssh: Could not resolve hostname " + target_ip + ": Name or service not known";
@ -187,26 +195,26 @@ std::string CommandProcessor::_handle_vfs_action(sol::table action) {
}
return ""; /* Success. */
} else if(action_name == "disconnect") {
session_vfs_root = home_vfs_root;
_current_dir = home_vfs_root;
_session_machine = _home_machine;
_current_dir = _home_machine->vfs_root;
return "Connection closed.";
} else if(action_name == "close_terminal") {
return "__CLOSE_CONNECTION__";
} else if(action_name == "scan") {
std::string target_ip = action["target"].get_or<std::string>("");
if(!_world_vfs.count(target_ip)) {
if(!_world_machines.count(target_ip)) {
return std::string("nmap: Could not resolve host: ") + target_ip;
}
vfs_node* target_root = _world_vfs[target_ip];
if(target_root->services.empty()) {
Machine* target_machine = _world_machines[target_ip];
if(target_machine->services.empty()) {
return std::string("No open ports for ") + target_ip;
}
std::stringstream ss;
ss<<"Host: "<<target_ip<<"\n";
ss<<"PORT\tSTATE\tSERVICE\n";
for(auto const& [port, service_name] : target_root->services) {
for(auto const& [port, service_name] : target_machine->services) {
ss<<port<<"/tcp\t"<<"open\t"<<service_name<<"\n";
}
return ss.str();

View File

@ -4,11 +4,12 @@
#include <string>
#include <map>
#include "machine.h"
#include "vfs.h"
class CommandProcessor {
public:
CommandProcessor(vfs_node* home_vfs, std::map<std::string, vfs_node*>& world_vfs);
CommandProcessor(Machine* home_machine, std::map<std::string, Machine*>& world_machines);
~CommandProcessor(void);
std::string process_command(const std::string& command);
@ -16,9 +17,9 @@ public:
private:
std::string _handle_vfs_action(sol::table action);
vfs_node* home_vfs_root;
vfs_node* session_vfs_root;
Machine* _home_machine;
Machine* _session_machine;
vfs_node* _current_dir;
std::map<std::string, vfs_node*>& _world_vfs;
std::map<std::string, Machine*>& _world_machines;
LuaProcessor* _lua;
};

28
common/src/machine.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <map>
#include <cstdint>
#include "vfs.h"
class Machine {
public:
Machine(uint32_t id, std::string hostname) :
id(id),
hostname(std::move(hostname)),
vfs_root(nullptr),
is_vfs_a_copy(false) {}
/* TODO: Implement recursive deletion of vfs_root. */
~Machine(void); /* Clean up VFS tree. */
uint32_t id;
std::string hostname;
vfs_node* vfs_root;
std::map<int, std::string> services;
bool is_vfs_a_copy; /* Flag for CoW mechanism. */
/* TODO: We'll add hardware and sh.t here. */
};

View File

@ -1,8 +1,10 @@
#include <filesystem>
#include <fstream>
#include <sstream>
#include <utility>
#include "vfs_manager.h"
#include "machine_manager.h"
#include "machine.h"
#include "vfs.h"
/* Create a new node. */
@ -33,13 +35,13 @@ vfs_node* copy_vfs_node(vfs_node* original, vfs_node* new_parent) {
return new_copy;
}
VFSManager::VFSManager(void) {
MachineManager::MachineManager(void) {
/* Create template VFS that holds shared, read-only directories. */
_vfs_root = new_node("/", DIR_NODE, nullptr);
vfs_node* bin = new_node("bin", DIR_NODE, _vfs_root);
_vfs_root->children["bin"] = bin;
_vfs_template_root = new_node("/", DIR_NODE, nullptr);
vfs_node* bin = new_node("bin", DIR_NODE, _vfs_template_root);
_vfs_template_root->children["bin"] = bin;
_vfs_root->read_only = true;
_vfs_template_root->read_only = true;
bin->read_only = true;
/* Load all scripts from assets/scripts/bin into the VFS. */
@ -58,12 +60,15 @@ VFSManager::VFSManager(void) {
}
}
VFSManager::~VFSManager(void) {
MachineManager::~MachineManager(void) {
/* TODO: Implement recursive deletion of all created VFS nodes.*/
//delete _vfs_root;
}
vfs_node* VFSManager::create_vfs(const std::string& system_type) {
Machine* MachineManager::create_machine(uint32_t id, const std::string& hostname,
const std::string& system_type) {
auto* new_machine = new Machine(id, hostname);
vfs_node* root = new_node("/", DIR_NODE, nullptr);
/* Create directories for this specific VFS. */
@ -75,10 +80,13 @@ vfs_node* VFSManager::create_vfs(const std::string& system_type) {
user->children["readme.txt"] = readme;
/* Link to the shared directories from the template. */
root->children["bin"] = copy_vfs_node(_vfs_root->children["bin"], root);
root->children["bin"] = copy_vfs_node(_vfs_template_root->children["bin"], root);
/* Assign the VFS to the new machine. */
new_machine->vfs_root = root;
/* Default services. All machines have SSH currently. */
root->services[22] = "SSH v1.2";
new_machine->services[22] = "SSH v1.2";
if(system_type == "npc") {
vfs_node* npc_file = new_node("npc_system.txt", FILE_NODE, root);
@ -87,5 +95,5 @@ vfs_node* VFSManager::create_vfs(const std::string& system_type) {
}
return root;
return new_machine;
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include "machine.h"
#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 MachineManager {
public:
MachineManager(void);
~MachineManager(void); /* TODO: Implement recursive VFS deletion. */
Machine* create_machine(uint32_t id, const std::string& hostname,
const std::string& system_type);
private:
vfs_node* _vfs_template_root;
};

View File

@ -24,9 +24,6 @@ struct vfs_node {
/* Directories. */
vfs_child_map children;
vfs_node* parent;
/* Services (for root nodes). */
std::map<int, std::string> services;
};
std::string get_full_path(vfs_node* node);

View File

@ -1,19 +0,0 @@
#pragma once
#include <string>
#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);
~VFSManager(void); /* TODO: Implement recursive VFS deletion. */
vfs_node* create_vfs(const std::string& system_type);
private:
vfs_node* _vfs_root;
};

View File

@ -1,6 +1,7 @@
#include <cstdio>
#include <exception>
#include <functional>
#include <memory>
#include "network_manager.h"
@ -9,19 +10,20 @@
#include "asio/ip/tcp.hpp"
#include "command_processor.h"
#include "player.h"
#include "machine.h"
#include "net/tcp_connection.h"
#include "vfs.h"
NetworkManager::NetworkManager(void) : _acceptor(_io_context) {
/* VFS setup. */
_world_vfs["8.8.8.8"] = _vfs_manager.create_vfs("npc");
_world_vfs["10.0.2.15"] = _vfs_manager.create_vfs("npc");
/* World setup. */
_world_machines["8.8.8.8"] = _machine_manager.create_machine(1000, "dns.google", "npc");
_world_machines["10.0.2.15"] = _machine_manager.create_machine(1001, "corp.internal", "npc");
/* Specific npc services. */
_world_vfs["8.8.8.8"]->services[80] = "HTTPD v2.4";
_world_vfs["10.0.2.15"]->services[21] = "FTPd v3.0";
_world_machines["8.8.8.8"]->services[80] = "HTTPD v2.4";
_world_machines["10.0.2.15"]->services[21] = "FTPd v3.0";
fprintf(stderr, "Created world with %zu networks\n", _world_vfs.size());
fprintf(stderr, "Created world with %zu networks\n", _world_machines.size());
}
NetworkManager::~NetworkManager(void) { stop(); }
@ -64,7 +66,9 @@ void NetworkManager::start_accept(void) {
/* Create a new player for this connection. */
uint32_t player_id = _next_player_id++;
auto new_player = std::make_unique<Player>(player_id, _vfs_manager, _world_vfs);
Machine* player_machine = _machine_manager.create_machine(player_id, "player.home",
"player");
auto new_player = std::make_unique<Player>(player_id, player_machine, _world_machines);
Player* new_player_ptr = new_player.get();
_players[player_id] = std::move(new_player);
new_connection->set_id(player_id);

View File

@ -10,7 +10,7 @@
#include "asio/io_context.hpp"
#include "net/tcp_connection.h"
#include "player.h"
#include "vfs_manager.h"
#include "machine_manager.h"
class NetworkManager {
public:
@ -34,6 +34,6 @@ private:
std::unordered_map<uint32_t, std::unique_ptr<Player>> _players;
uint32_t _next_player_id = 1;
std::map<std::string, vfs_node*> _world_vfs; /* For NPC's. */
VFSManager _vfs_manager;
std::map<std::string, Machine*> _world_machines; /* For NPC's. */
MachineManager _machine_manager;
};

View File

@ -1,17 +1,15 @@
#include "player.h"
#include "command_processor.h"
#include "machine.h"
Player::Player(uint32_t new_id, VFSManager& vfs_manager,
std::map<std::string, vfs_node*>& world_vfs) :
Player::Player(uint32_t new_id, Machine* home_machine,
std::map<std::string, Machine*>& world_machines) :
id(new_id) {
vfs_node* player_vfs = vfs_manager.create_vfs("player");
cmd_processor = new CommandProcessor(player_vfs, world_vfs);
cmd_processor = new CommandProcessor(home_machine, world_machines);
}
Player::~Player(void) {
if(cmd_processor) {
delete cmd_processor;
}
/* TODO: The VFSManager should handle deleting the player_vfs_root. */
}

View File

@ -1,16 +1,15 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>
#include "command_processor.h"
#include "vfs.h"
#include "vfs_manager.h"
class CommandProcessor;
#include "machine.h"
class Player {
public:
Player(uint32_t id, VFSManager& vfs_manager, std::map<std::string, vfs_node*>& world_vfs);
Player(uint32_t id, Machine* home_machine, std::map<std::string, Machine*>& world_machines);
~Player(void);
uint32_t id;