[Refactor] Implement in-memory VFS cache.
Refactors VFS to use a cetralised, in memory caching on the server. This resolves performance and state sync issues from the previous implementation.
This commit is contained in:
		
							parent
							
								
									9d770ef9b2
								
							
						
					
					
						commit
						4f5436f376
					
				@ -3,10 +3,9 @@
 | 
			
		||||
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include "db/database_manager.h"
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
#include "i_network_bridge.h"
 | 
			
		||||
#include "lua_api.h"
 | 
			
		||||
#include "lua_processor.h"
 | 
			
		||||
#include "machine_manager.h"
 | 
			
		||||
#include "machine.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,20 +23,19 @@ vfs_node* find_node_by_id(vfs_node* root, long long id) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CommandProcessor::CommandProcessor(Machine* home_machine,
 | 
			
		||||
                                   std::map<std::string, Machine*>& world_machines,
 | 
			
		||||
                                   DatabaseManager* db_manager,
 | 
			
		||||
                                   MachineManager* machine_manager) :
 | 
			
		||||
                                   MachineManager* machine_manager,
 | 
			
		||||
                                   INetworkBridge* network_bridge) :
 | 
			
		||||
    _machine_manager(machine_manager),
 | 
			
		||||
    _db_manager(db_manager),
 | 
			
		||||
    _network_bridge(network_bridge),
 | 
			
		||||
    _home_machine(home_machine),
 | 
			
		||||
    _session_machine(home_machine),
 | 
			
		||||
    _world_machines(world_machines) {
 | 
			
		||||
  _lua = new LuaProcessor();
 | 
			
		||||
  _current_dir = _session_machine->vfs_root;
 | 
			
		||||
    _session_machine(home_machine) {
 | 
			
		||||
 | 
			
		||||
  if(home_machine) _current_dir = home_machine->vfs_root;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CommandProcessor::~CommandProcessor(void) {
 | 
			
		||||
  delete _lua;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vfs_node* CommandProcessor::get_current_dir(void) {
 | 
			
		||||
@ -47,11 +45,6 @@ vfs_node* CommandProcessor::get_current_dir(void) {
 | 
			
		||||
Machine* CommandProcessor::get_home_machine(void)     { return _home_machine; }
 | 
			
		||||
Machine* CommandProcessor::get_session_machine(void)  { return _session_machine; }
 | 
			
		||||
 | 
			
		||||
std::map<std::string, Machine*>&
 | 
			
		||||
CommandProcessor::get_world_machines(void) {
 | 
			
		||||
  return _world_machines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DatabaseManager* CommandProcessor::get_db_manager(void) {
 | 
			
		||||
  return _db_manager;
 | 
			
		||||
}
 | 
			
		||||
@ -60,6 +53,10 @@ MachineManager* CommandProcessor::get_machine_manager(void) {
 | 
			
		||||
  return _machine_manager;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
INetworkBridge* CommandProcessor::get_network_bridge(void) {
 | 
			
		||||
  return _network_bridge;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CommandProcessor::set_current_dir(vfs_node* node) {
 | 
			
		||||
  _current_dir = node;
 | 
			
		||||
}
 | 
			
		||||
@ -72,6 +69,12 @@ void CommandProcessor::set_session_machine(Machine* machine) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string CommandProcessor::process_command(const std::string& command) {
 | 
			
		||||
  /*
 | 
			
		||||
   * Creating the Lua processor on-demand to ensure it exists on the same
 | 
			
		||||
   * thread that will execute the script.
 | 
			
		||||
   */
 | 
			
		||||
  LuaProcessor lua(*this);
 | 
			
		||||
 | 
			
		||||
  /* Trim trailing whitespace. */
 | 
			
		||||
  std::string cmd = command;
 | 
			
		||||
  size_t end = cmd.find_last_not_of(" \t\n\r");
 | 
			
		||||
@ -104,7 +107,7 @@ std::string CommandProcessor::process_command(const std::string& command) {
 | 
			
		||||
 | 
			
		||||
  if(script_id > 0) {
 | 
			
		||||
    bool is_remote = (_session_machine != _home_machine);
 | 
			
		||||
    sol::object result = _lua->execute(script_content, *this, args, is_remote);
 | 
			
		||||
    sol::object result = lua.execute(script_content, *this, args, is_remote);
 | 
			
		||||
    return result.is<std::string>() ? result.as<std::string>() : "[Script returned an unexpected type]";
 | 
			
		||||
  }
 | 
			
		||||
  return "Unknown command: " + command_name + "\n";
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <lua_processor.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#include "db/database_manager.h"
 | 
			
		||||
#include "i_network_bridge.h"
 | 
			
		||||
#include "machine.h"
 | 
			
		||||
#include "machine_manager.h"
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
class LuaProcessor;
 | 
			
		||||
 | 
			
		||||
class CommandProcessor {
 | 
			
		||||
public:
 | 
			
		||||
  CommandProcessor(Machine* home_machine, std::map<std::string, Machine*>& world_machines,
 | 
			
		||||
                   DatabaseManager* db_manager, MachineManager* machine_manager);
 | 
			
		||||
  CommandProcessor(Machine* home_machine, DatabaseManager* db_manager,
 | 
			
		||||
                   MachineManager* machine_manager, INetworkBridge* network_bridge);
 | 
			
		||||
  ~CommandProcessor(void);
 | 
			
		||||
 | 
			
		||||
  std::string process_command(const std::string& command);
 | 
			
		||||
@ -23,9 +23,9 @@ public:
 | 
			
		||||
  vfs_node* get_current_dir(void);
 | 
			
		||||
  Machine* get_home_machine(void);
 | 
			
		||||
  Machine* get_session_machine(void);
 | 
			
		||||
  std::map<std::string, Machine*>& get_world_machines(void);
 | 
			
		||||
  DatabaseManager* get_db_manager(void);
 | 
			
		||||
  MachineManager* get_machine_manager(void);
 | 
			
		||||
  INetworkBridge* get_network_bridge(void);
 | 
			
		||||
 
 | 
			
		||||
  void set_current_dir(vfs_node* node);
 | 
			
		||||
  void set_session_machine(Machine* machine);
 | 
			
		||||
@ -33,9 +33,9 @@ public:
 | 
			
		||||
private:
 | 
			
		||||
  MachineManager* _machine_manager;
 | 
			
		||||
  DatabaseManager* _db_manager;
 | 
			
		||||
  INetworkBridge* _network_bridge;
 | 
			
		||||
  Machine* _home_machine;
 | 
			
		||||
  Machine* _session_machine;
 | 
			
		||||
  vfs_node* _current_dir;
 | 
			
		||||
  std::map<std::string, Machine*>& _world_machines;
 | 
			
		||||
  LuaProcessor* _lua;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -8,13 +8,7 @@ DatabaseManager::DatabaseManager(const std::string& db_path) :
 | 
			
		||||
  _machine_repository = std::make_unique<MachineRepository>(_db);
 | 
			
		||||
  _service_repository = std::make_unique<ServiceRepository>(_db);
 | 
			
		||||
  _vfs_repository     = std::make_unique<VFSRepository>(_db);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DatabaseManager::~DatabaseManager(void) {
 | 
			
		||||
  /* db is auto closed when _db goes out of scope. */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DatabaseManager::init(void) {
 | 
			
		||||
  _db <<  "CREATE TABLE IF NOT EXISTS players("
 | 
			
		||||
          "id INTEGER PRIMARY KEY AUTOINCREMENT,"
 | 
			
		||||
          "username TEXT NOT NULL UNIQUE,"
 | 
			
		||||
@ -46,6 +40,10 @@ void DatabaseManager::init(void) {
 | 
			
		||||
          ");";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DatabaseManager::~DatabaseManager(void) {
 | 
			
		||||
  /* db is auto closed when _db goes out of scope. */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DatabaseManager::create_player(const std::string& username, const std::string& password,
 | 
			
		||||
                                    const std::string& hostname, vfs_node* vfs_template) {
 | 
			
		||||
  long long player_id   = 0;
 | 
			
		||||
 | 
			
		||||
@ -15,8 +15,6 @@ public:
 | 
			
		||||
  DatabaseManager(const std::string& db_path);
 | 
			
		||||
  ~DatabaseManager(void);
 | 
			
		||||
 | 
			
		||||
  void init(void);
 | 
			
		||||
 | 
			
		||||
  /* Return true on success, false if user already exists. */
 | 
			
		||||
  bool create_player(const std::string& username, const std::string& password,
 | 
			
		||||
                     const std::string& hostname, vfs_node* vfs_template);
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,15 @@ std::vector<MachineData> MachineRepository::get_all_npcs(void) {
 | 
			
		||||
  return machines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<MachineData> MachineRepository::get_all(void) {
 | 
			
		||||
  std::vector<MachineData> machines;
 | 
			
		||||
  _db << "SELECT id, ip_address FROM machines;"
 | 
			
		||||
      >> [&](long long id, std::string ip_address) {
 | 
			
		||||
      machines.push_back({id, ip_address});
 | 
			
		||||
    };
 | 
			
		||||
  return machines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string MachineRepository::get_hostname(long long machine_id) {
 | 
			
		||||
  std::string hostname;
 | 
			
		||||
  _db << "SELECT hostname FROM machines WHERE id = ?;"
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ public:
 | 
			
		||||
                   const std::string& ip_address);
 | 
			
		||||
  int get_npc_count(void);
 | 
			
		||||
  std::vector<MachineData> get_all_npcs(void);
 | 
			
		||||
  std::vector<MachineData> get_all(void);
 | 
			
		||||
  std::string get_hostname(long long machine_id);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								common/src/i_network_bridge.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								common/src/i_network_bridge.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
class Machine;
 | 
			
		||||
 | 
			
		||||
class INetworkBridge {
 | 
			
		||||
public:
 | 
			
		||||
  virtual Machine* get_machine_by_ip(const std::string& ip) = 0;
 | 
			
		||||
  virtual void release_machine(long long machine_id) = 0;
 | 
			
		||||
};
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
#include <sol/types.hpp>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#include "i_network_bridge.h"
 | 
			
		||||
#include "lua_api.h"
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include "db/database_manager.h"
 | 
			
		||||
#include "machine.h"
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
@ -15,45 +15,34 @@ vfs_node* get_current_dir(CommandProcessor& context) {
 | 
			
		||||
 | 
			
		||||
std::string rm(CommandProcessor& context, const std::string& filename) {
 | 
			
		||||
  vfs_node* current_dir = context.get_current_dir();
 | 
			
		||||
  DatabaseManager* db = context.get_db_manager();
 | 
			
		||||
  auto it = current_dir->children.find(filename);
 | 
			
		||||
 | 
			
		||||
  /* find the file in the database. */
 | 
			
		||||
  long long file_id = 0;
 | 
			
		||||
  int file_type = 0;
 | 
			
		||||
  db->_db << "SELECT id, type FROM vfs_nodes WHERE parent_id = ? AND name = ?;"
 | 
			
		||||
          << current_dir->id << filename
 | 
			
		||||
          >> std::tie(file_id, file_type);
 | 
			
		||||
 | 
			
		||||
  if(file_id == 0) {
 | 
			
		||||
  if(it == current_dir->children.end()) {
 | 
			
		||||
    return "rm: cannot remove '" + filename + "': No such file or directory.";
 | 
			
		||||
  }
 | 
			
		||||
  if(file_type == DIR_NODE) {
 | 
			
		||||
  if(it->second->type == DIR_NODE) {
 | 
			
		||||
    return "rm: cannot remove '" + filename + "': Is a directory.";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  db->_db << "DELETE FROM vfs_nodes WHERE id = ?;" << file_id;
 | 
			
		||||
  /* Also remove from in-mem VFS. */
 | 
			
		||||
  delete current_dir->children[filename];
 | 
			
		||||
  current_dir->children.erase(filename);
 | 
			
		||||
 | 
			
		||||
  delete it->second; /* Free the memory for the node. */
 | 
			
		||||
  current_dir->children.erase(it); /* Remove from map. */
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string write_file(CommandProcessor& context, const std::string& filename,
 | 
			
		||||
                       const std::string& content) {
 | 
			
		||||
  vfs_node* current_dir = context.get_current_dir();
 | 
			
		||||
  DatabaseManager* db = context.get_db_manager();
 | 
			
		||||
  auto it = current_dir->children.find(filename);
 | 
			
		||||
 | 
			
		||||
  if(current_dir->children.count(filename)) {
 | 
			
		||||
    vfs_node* target_node = current_dir->children[filename];
 | 
			
		||||
    db->_db << "UPDATE vfs_nodes SET content = ? WHERE id = ?;"
 | 
			
		||||
            << content << target_node->id;
 | 
			
		||||
    target_node->content = content;
 | 
			
		||||
  if(it != current_dir->children.end()) {
 | 
			
		||||
    /* File exists, update content. */
 | 
			
		||||
    if(it->second->type == DIR_NODE) {
 | 
			
		||||
      return "write: " + filename + ": Is a directory.";
 | 
			
		||||
    }
 | 
			
		||||
    it->second->content = content;
 | 
			
		||||
  } else {
 | 
			
		||||
    db->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) VALUES (?, ?, ?, ?, ?);"
 | 
			
		||||
            << context.get_session_machine()->id << current_dir->id << filename << FILE_NODE << content;
 | 
			
		||||
    /* File does not exist, create it. */
 | 
			
		||||
    vfs_node* new_file = new_node(filename, FILE_NODE, current_dir);
 | 
			
		||||
    new_file->id = db->_db.last_insert_rowid();
 | 
			
		||||
    new_file->content = content;
 | 
			
		||||
    current_dir->children[filename] = new_file;
 | 
			
		||||
  }
 | 
			
		||||
@ -62,24 +51,15 @@ std::string write_file(CommandProcessor& context, const std::string& filename,
 | 
			
		||||
 | 
			
		||||
std::string cd(CommandProcessor& context, const std::string& path) {
 | 
			
		||||
  vfs_node* current_dir = context.get_current_dir();
 | 
			
		||||
  DatabaseManager* db = context.get_db_manager();
 | 
			
		||||
 | 
			
		||||
  if(path == "..") {
 | 
			
		||||
    if(current_dir->parent) {
 | 
			
		||||
      context.set_current_dir(current_dir->parent);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    long long target_id = 0;
 | 
			
		||||
    db->_db << "SELECT id FROM vfs_nodes WHERE parent_id = ? AND name = ? AND type = 1;"
 | 
			
		||||
            << current_dir->id << path
 | 
			
		||||
            >> target_id;
 | 
			
		||||
 | 
			
		||||
    if(target_id > 0) {
 | 
			
		||||
      /* Inefficient, I know. */
 | 
			
		||||
      Machine* reloaded_machine =
 | 
			
		||||
        context.get_machine_manager()->load_machine(context.get_session_machine()->id, db);
 | 
			
		||||
      vfs_node* new_dir = find_node_by_id(reloaded_machine->vfs_root, target_id);
 | 
			
		||||
      context.set_current_dir(new_dir);
 | 
			
		||||
    auto it = current_dir->children.find(path);
 | 
			
		||||
    if(it != current_dir->children.end() && it->second->type == DIR_NODE) {
 | 
			
		||||
      context.set_current_dir(it->second);
 | 
			
		||||
    } else {
 | 
			
		||||
      return "cd: no such file or directory: " + path;
 | 
			
		||||
    }
 | 
			
		||||
@ -89,52 +69,59 @@ std::string cd(CommandProcessor& context, const std::string& path) {
 | 
			
		||||
 | 
			
		||||
std::string ls(CommandProcessor& context) {
 | 
			
		||||
  vfs_node* dir = context.get_current_dir();
 | 
			
		||||
  printf("DEBUG: ls command called for directory_id: %lld\n", dir->id);
 | 
			
		||||
  if(dir->type != DIR_NODE) {
 | 
			
		||||
    return "ls: not a directory";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::stringstream ss;
 | 
			
		||||
 | 
			
		||||
  context.get_db_manager()->_db << "SELECT name, type FROM vfs_nodes WHERE parent_id = ?;"
 | 
			
		||||
                                << dir->id
 | 
			
		||||
                                >> [&](std::string name, int type) {
 | 
			
		||||
  for(auto const& [name, node] : dir->children) {
 | 
			
		||||
    ss << name;
 | 
			
		||||
      if(type == DIR_NODE) ss << "/";
 | 
			
		||||
    if(node->type == DIR_NODE) {
 | 
			
		||||
      ss << "/";
 | 
			
		||||
    }
 | 
			
		||||
    ss << "   ";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  return ss.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ssh(CommandProcessor& context, const std::string& ip) {
 | 
			
		||||
  if(context.get_world_machines().count(ip)) {
 | 
			
		||||
    context.set_session_machine(context.get_world_machines().at(ip));
 | 
			
		||||
  INetworkBridge* bridge = context.get_network_bridge();
 | 
			
		||||
  Machine* target_machine = bridge->get_machine_by_ip(ip);
 | 
			
		||||
 | 
			
		||||
  if(target_machine) {
 | 
			
		||||
    context.set_session_machine(target_machine);
 | 
			
		||||
    return "Connected to " + ip;
 | 
			
		||||
  }
 | 
			
		||||
  } else {
 | 
			
		||||
    return "ssh: Could not resolve hostname " + ip + ": Name or service not found";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string nmap(CommandProcessor& context, const std::string& ip) {
 | 
			
		||||
  if(!context.get_world_machines().count(ip)) {
 | 
			
		||||
  long long machine_id = context.get_machine_manager()->get_machine_id_by_ip(ip);
 | 
			
		||||
  if(machine_id == 0) {
 | 
			
		||||
    return "nmap: Could not resolve host: " + ip;
 | 
			
		||||
  }
 | 
			
		||||
  auto services = context.get_db_manager()->services().get_for_machine(machine_id);
 | 
			
		||||
 | 
			
		||||
  Machine* target_machine = context.get_world_machines().at(ip);
 | 
			
		||||
  if(target_machine->services.empty()) {
 | 
			
		||||
  if(services.empty()) {
 | 
			
		||||
    return "No open ports for " + ip;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::stringstream ss;
 | 
			
		||||
  ss << "Host: " << ip << "\n";
 | 
			
		||||
  ss << "PORT\tSTATE\tSERVICE\n";
 | 
			
		||||
  for(auto const& [port, service_name] : target_machine->services) {
 | 
			
		||||
  for(auto const& [port, service_name] : services) {
 | 
			
		||||
    ss << port << "/tcp\t" << "open\t" << service_name << "\n";
 | 
			
		||||
  }
 | 
			
		||||
  return ss.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string disconnect(CommandProcessor& context) {
 | 
			
		||||
  Machine* current_machine = context.get_session_machine();
 | 
			
		||||
  if(current_machine != context.get_home_machine()) {
 | 
			
		||||
    INetworkBridge* bridge = context.get_network_bridge();
 | 
			
		||||
    bridge->release_machine(current_machine->id);
 | 
			
		||||
  }
 | 
			
		||||
  context.set_session_machine(context.get_home_machine());
 | 
			
		||||
  return "Connection closed.";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
class CommandProcessor;
 | 
			
		||||
class NetworkManager;
 | 
			
		||||
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,11 @@
 | 
			
		||||
#include <sol/raii.hpp>
 | 
			
		||||
 | 
			
		||||
#include "lua_processor.h"
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
#include "lua_api.h"
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
LuaProcessor::LuaProcessor(void) {
 | 
			
		||||
LuaProcessor::LuaProcessor(CommandProcessor& context) {
 | 
			
		||||
  _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io, sol::lib::table);
 | 
			
		||||
 | 
			
		||||
  /* Expose vfs_node struct members to Lua. */
 | 
			
		||||
@ -23,15 +23,15 @@ LuaProcessor::LuaProcessor(void) {
 | 
			
		||||
 | 
			
		||||
  /* Create the 'bettola' API table. */
 | 
			
		||||
  sol::table bettola_api = _lua.create_named_table("bettola");
 | 
			
		||||
  bettola_api["rm"]             = &api::rm;
 | 
			
		||||
  bettola_api["ls"]             = &api::ls;
 | 
			
		||||
  bettola_api["write_file"]     = &api::write_file;
 | 
			
		||||
  bettola_api["get_current_dir"]= &api::get_current_dir;
 | 
			
		||||
  bettola_api["cd"]             = &api::cd;
 | 
			
		||||
  bettola_api["ssh"]            = &api::ssh;
 | 
			
		||||
  bettola_api["nmap"]           = &api::nmap;
 | 
			
		||||
  bettola_api["disconnect"]     = &api::disconnect;
 | 
			
		||||
  bettola_api["close_terminal"] = &api::close_terminal;
 | 
			
		||||
  bettola_api.set_function("rm",              &api::rm);
 | 
			
		||||
  bettola_api.set_function("ls",              &api::ls);
 | 
			
		||||
  bettola_api.set_function("write_file",      &api::write_file);
 | 
			
		||||
  bettola_api.set_function("get_current_dir", &api::get_current_dir);
 | 
			
		||||
  bettola_api.set_function("cd",              &api::cd);
 | 
			
		||||
  bettola_api.set_function("close_terminal",  &api::close_terminal);
 | 
			
		||||
  bettola_api.set_function("ssh",             &api::ssh);
 | 
			
		||||
  bettola_api.set_function("nmap",            &api::nmap);
 | 
			
		||||
  bettola_api.set_function("disconnect",      &api::disconnect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LuaProcessor::~LuaProcessor(void) {}
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ class CommandProcessor;
 | 
			
		||||
 | 
			
		||||
class LuaProcessor {
 | 
			
		||||
public:
 | 
			
		||||
  LuaProcessor(void);
 | 
			
		||||
  LuaProcessor(CommandProcessor& context);
 | 
			
		||||
  ~LuaProcessor(void);
 | 
			
		||||
 | 
			
		||||
  /* Executes a string of lua code and returns result as a string. */
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,7 @@ void build_tree(vfs_node* parent, const std::map<long long, vfs_node*>& nodes) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_manager) {
 | 
			
		||||
Machine* MachineManager::load_machine(long long machine_id) {
 | 
			
		||||
  printf("DEBUG: load_machine called for machine_id: %lld\n", machine_id);
 | 
			
		||||
 | 
			
		||||
  std::string hostname = _db_manager->machines().get_hostname(machine_id);
 | 
			
		||||
@ -107,7 +107,7 @@ Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_
 | 
			
		||||
  std::map<long long, vfs_node*> node_map;
 | 
			
		||||
  vfs_node* root = nullptr;
 | 
			
		||||
 | 
			
		||||
  auto nodes = db_manager->vfs().get_nodes_for_machine(machine_id);
 | 
			
		||||
  auto nodes = _db_manager->vfs().get_nodes_for_machine(machine_id);
 | 
			
		||||
  for(vfs_node* node : nodes) {
 | 
			
		||||
    node_map[node->id] = node;
 | 
			
		||||
    if(node->name == "/") {
 | 
			
		||||
@ -123,3 +123,17 @@ Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_
 | 
			
		||||
  }
 | 
			
		||||
  return machine;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
long long MachineManager::get_machine_id_by_ip(const std::string& ip) {
 | 
			
		||||
  if(_ip_to_id_map.count(ip)) {
 | 
			
		||||
    return _ip_to_id_map[ip];
 | 
			
		||||
  }
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MachineManager::init(void) {
 | 
			
		||||
  auto all_machines = _db_manager->machines().get_all();
 | 
			
		||||
  for(const auto& machine_data : all_machines) {
 | 
			
		||||
    _ip_to_id_map[machine_data.ip_address] = machine_data.id;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "db/database_manager.h"
 | 
			
		||||
@ -16,12 +17,20 @@ public:
 | 
			
		||||
 | 
			
		||||
  Machine* create_machine(uint32_t id, const std::string& hostname,
 | 
			
		||||
                          const std::string& system_type);
 | 
			
		||||
  Machine* load_machine(long long machine_id, DatabaseManager* db_manager);
 | 
			
		||||
  Machine* load_machine(long long machine_id);
 | 
			
		||||
  void init(void);
 | 
			
		||||
 | 
			
		||||
  vfs_node* get_vfs_template(void) { return _vfs_template_root; }
 | 
			
		||||
 | 
			
		||||
  /* Machine lookup. */
 | 
			
		||||
  long long get_machine_id_by_ip(const std::string& ip);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  DatabaseManager* _db_manager;
 | 
			
		||||
  vfs_node* _vfs_template_root;
 | 
			
		||||
 | 
			
		||||
  /* In memory cache of all machines, loaded or not. */
 | 
			
		||||
  std::map<std::string, long long> _ip_to_id_map;
 | 
			
		||||
  std::map<long long, Machine*> _world_machines;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,26 +16,18 @@
 | 
			
		||||
#include "vfs.h"
 | 
			
		||||
 | 
			
		||||
NetworkManager::NetworkManager(const std::string& db_path) :
 | 
			
		||||
    _io_context(),
 | 
			
		||||
    _db_manager(std::make_unique<DatabaseManager>(db_path)),
 | 
			
		||||
    _acceptor(_io_context),
 | 
			
		||||
    _machine_manager(_db_manager.get()) {
 | 
			
		||||
 | 
			
		||||
  _db_manager->init();
 | 
			
		||||
  _seed_npc_machines();
 | 
			
		||||
 | 
			
		||||
  /* Load NPC machines from the database. */
 | 
			
		||||
  auto npc_machines = _db_manager->machines().get_all_npcs();
 | 
			
		||||
  for(const auto& machine_data : npc_machines) {
 | 
			
		||||
    _world_machines[machine_data.ip_address] =
 | 
			
		||||
      _machine_manager.load_machine(machine_data.id, _db_manager.get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fprintf(stderr, "Created world with %zu networks\n", _world_machines.size());
 | 
			
		||||
  _machine_manager.init();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NetworkManager::~NetworkManager(void) {
 | 
			
		||||
  for(auto const& [ip, machine] : _world_machines) {
 | 
			
		||||
    delete machine;
 | 
			
		||||
  for(auto& pair : _active_machines) {
 | 
			
		||||
    _save_machine_vfs(pair.second.machine.get());
 | 
			
		||||
  }
 | 
			
		||||
  stop();
 | 
			
		||||
}
 | 
			
		||||
@ -76,13 +68,10 @@ void NetworkManager::start_accept(void) {
 | 
			
		||||
        auto new_connection =
 | 
			
		||||
          std::make_shared<net::TcpConnection>(_io_context, std::move(socket));
 | 
			
		||||
 | 
			
		||||
        /* Create a new player for this connection. */
 | 
			
		||||
        /* Create a new player for this connection. pendnding authentication.*/
 | 
			
		||||
        uint32_t player_id = _next_player_id++;
 | 
			
		||||
        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, _db_manager.get(),
 | 
			
		||||
                                                   &_machine_manager);
 | 
			
		||||
        auto new_player = std::make_unique<Player>(player_id, nullptr, _db_manager.get(),
 | 
			
		||||
                                                  &_machine_manager, this);
 | 
			
		||||
        Player* new_player_ptr = new_player.get();
 | 
			
		||||
        _players[player_id] = std::move(new_player);
 | 
			
		||||
        new_connection->set_id(player_id);
 | 
			
		||||
@ -123,12 +112,11 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
 | 
			
		||||
        if(args.size() == 3) {
 | 
			
		||||
          if(_db_manager->create_player(args[0], args[1], args[2],
 | 
			
		||||
                                      _machine_manager.get_vfs_template())) {
 | 
			
		||||
          long long machine_id = _db_manager->players().get_home_machine_id(args[0]);
 | 
			
		||||
          Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get());
 | 
			
		||||
          long long home_machine_id = _db_manager->players().get_home_machine_id(args[0]);
 | 
			
		||||
          Machine* home_machine = _get_or_load_machine(home_machine_id);
 | 
			
		||||
          delete player->cmd_processor; /* Delete old command processor. */
 | 
			
		||||
          player->cmd_processor = new CommandProcessor(home_machine, _world_machines,
 | 
			
		||||
                                                       _db_manager.get(),
 | 
			
		||||
                                                       &_machine_manager);
 | 
			
		||||
          player->cmd_processor = new CommandProcessor(home_machine, _db_manager.get(),
 | 
			
		||||
                                                       &_machine_manager, this);
 | 
			
		||||
          player->state = PlayerState::ACTIVE;
 | 
			
		||||
          connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS));
 | 
			
		||||
          /* send initial prompt. */
 | 
			
		||||
@ -144,13 +132,11 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
 | 
			
		||||
      case net_protocol::Opcode::C2S_LOGIN:
 | 
			
		||||
        if(args.size() == 2) {
 | 
			
		||||
          if(_db_manager->players().authenticate(args[0], args[1])) {
 | 
			
		||||
            long long machine_id = _db_manager->players().get_home_machine_id(args[0]);
 | 
			
		||||
            printf("DEBUG: Loading machine %lld for player %s\n", machine_id, args[0].c_str());
 | 
			
		||||
          Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get());
 | 
			
		||||
            long long home_machine_id = _db_manager->players().get_home_machine_id(args[0]);
 | 
			
		||||
            Machine* home_machine = _get_or_load_machine(home_machine_id);
 | 
			
		||||
            delete player->cmd_processor; /* Delete old command processor. */
 | 
			
		||||
          player->cmd_processor = new CommandProcessor(home_machine, _world_machines,
 | 
			
		||||
                                                       _db_manager.get(), &_machine_manager);
 | 
			
		||||
 | 
			
		||||
            player->cmd_processor = new CommandProcessor(home_machine, _db_manager.get(),
 | 
			
		||||
                                                         &_machine_manager, this);
 | 
			
		||||
            player->state = PlayerState::ACTIVE;
 | 
			
		||||
            connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_SUCCESS));
 | 
			
		||||
            /* Send initial prompt. */
 | 
			
		||||
@ -214,6 +200,20 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
 | 
			
		||||
 | 
			
		||||
void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connection) {
 | 
			
		||||
  uint32_t player_id = connection->get_id();
 | 
			
		||||
  Player* player = _players[player_id].get();
 | 
			
		||||
 | 
			
		||||
  if(player && player->cmd_processor) {
 | 
			
		||||
    Machine* home_machine = player->cmd_processor->get_home_machine();
 | 
			
		||||
    Machine* session_machine = player->cmd_processor->get_session_machine();
 | 
			
		||||
 | 
			
		||||
    if(home_machine) {
 | 
			
		||||
      release_machine(home_machine->id);
 | 
			
		||||
    }
 | 
			
		||||
    if(session_machine && session_machine != home_machine) {
 | 
			
		||||
      release_machine(session_machine->id);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fprintf(stderr, "[Player %u] Disconnected.\n", player_id);
 | 
			
		||||
  _connections.erase(
 | 
			
		||||
    std::remove(_connections.begin(), _connections.end(), connection), _connections.end());
 | 
			
		||||
@ -221,6 +221,67 @@ void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connectio
 | 
			
		||||
  _players.erase(player_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Machine* NetworkManager::get_machine_by_ip(const std::string& ip) {
 | 
			
		||||
  long long machine_id = _machine_manager.get_machine_id_by_ip(ip);
 | 
			
		||||
  if(machine_id != 0) {
 | 
			
		||||
    return _get_or_load_machine(machine_id);
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Machine* NetworkManager::_get_or_load_machine(long long machine_id) {
 | 
			
		||||
  if(_active_machines.count(machine_id)) {
 | 
			
		||||
    _active_machines[machine_id].ref_count++;
 | 
			
		||||
    return _active_machines[machine_id].machine.get();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Machine not in cache, load it. */
 | 
			
		||||
  fprintf(stderr, "Loading machine %lld into cache.\n", machine_id);
 | 
			
		||||
  Machine* loaded_machine = _machine_manager.load_machine(machine_id);
 | 
			
		||||
  _active_machines[machine_id] = {
 | 
			
		||||
    std::unique_ptr<Machine>(loaded_machine), 1
 | 
			
		||||
  };
 | 
			
		||||
  return loaded_machine;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NetworkManager::release_machine(long long machine_id) {
 | 
			
		||||
  if(!_active_machines.count(machine_id)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _active_machines[machine_id].ref_count--;
 | 
			
		||||
  fprintf(stderr, "Releasing machine %lld, ref count now %d.\n", machine_id,
 | 
			
		||||
          _active_machines[machine_id].ref_count);
 | 
			
		||||
 | 
			
		||||
  if(_active_machines[machine_id].ref_count <= 0) {
 | 
			
		||||
    fprintf(stderr, "Machine %lld has no more references, saving and evicting from cache.\n",
 | 
			
		||||
            machine_id);
 | 
			
		||||
    _save_machine_vfs(_active_machines[machine_id].machine.get());
 | 
			
		||||
    _active_machines.erase(machine_id);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NetworkManager::_recursive_save_vfs(long long machine_id, vfs_node* node, long long* parent_id) {
 | 
			
		||||
  long long current_node_id = _db_manager->vfs().create_node(machine_id, parent_id,
 | 
			
		||||
                                                             node->name, node->type,
 | 
			
		||||
                                                             node->content);
 | 
			
		||||
 | 
			
		||||
  if(node->type == DIR_NODE) {
 | 
			
		||||
    for(auto const& [name, child] : node->children) {
 | 
			
		||||
      _recursive_save_vfs(machine_id, child, ¤t_node_id);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NetworkManager::_save_machine_vfs(Machine* machine) {
 | 
			
		||||
  if(!machine || !machine->vfs_root) return;
 | 
			
		||||
 | 
			
		||||
  _db_manager->_db << "BEGIN;";
 | 
			
		||||
  _db_manager->_db << "DELETE FROM vfs_nodes WHERE machine_id = ?;" << machine->id;
 | 
			
		||||
  _recursive_save_vfs(machine->id, machine->vfs_root, nullptr);
 | 
			
		||||
  _db_manager->_db << "COMMIT;";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NetworkManager::_seed_npc_machines(void) {
 | 
			
		||||
  int npc_count = _db_manager->machines().get_npc_count();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,11 +10,17 @@
 | 
			
		||||
#include "asio/io_context.hpp"
 | 
			
		||||
#include "net/tcp_connection.h"
 | 
			
		||||
#include "player.h"
 | 
			
		||||
#include "i_network_bridge.h"
 | 
			
		||||
#include "machine_manager.h"
 | 
			
		||||
 | 
			
		||||
class DatabaseManager;
 | 
			
		||||
 | 
			
		||||
class NetworkManager {
 | 
			
		||||
struct CachedMachine {
 | 
			
		||||
  std::unique_ptr<Machine> machine;
 | 
			
		||||
  int ref_count = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NetworkManager : public INetworkBridge {
 | 
			
		||||
public:
 | 
			
		||||
  NetworkManager(const std::string& db_path);
 | 
			
		||||
  ~NetworkManager(void);
 | 
			
		||||
@ -22,11 +28,17 @@ public:
 | 
			
		||||
  void start(uint16_t port);
 | 
			
		||||
  void stop(void);
 | 
			
		||||
 | 
			
		||||
  Machine* get_machine_by_ip(const std::string& ip) override;
 | 
			
		||||
  void release_machine(long long machine_id) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  void start_accept(void);
 | 
			
		||||
  void on_message(std::shared_ptr<net::TcpConnection> connection,
 | 
			
		||||
                  const std::string& message);
 | 
			
		||||
  void on_disconnect(std::shared_ptr<net::TcpConnection> connection);
 | 
			
		||||
  void _save_machine_vfs(Machine* machine);
 | 
			
		||||
  void _recursive_save_vfs(long long machine_id, vfs_node* node,
 | 
			
		||||
                           long long* parent_id);
 | 
			
		||||
 | 
			
		||||
  void _seed_npc_machines(void);
 | 
			
		||||
 | 
			
		||||
@ -36,9 +48,12 @@ private:
 | 
			
		||||
 | 
			
		||||
  std::deque<std::shared_ptr<net::TcpConnection>> _connections;
 | 
			
		||||
  std::unordered_map<uint32_t, std::unique_ptr<Player>> _players;
 | 
			
		||||
  std::map<long long, CachedMachine> _active_machines;
 | 
			
		||||
  uint32_t _next_player_id = 1;
 | 
			
		||||
 | 
			
		||||
  std::map<std::string, Machine*> _world_machines; /* For NPC's. */
 | 
			
		||||
  std::unique_ptr<DatabaseManager> _db_manager;
 | 
			
		||||
  MachineManager _machine_manager;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
  Machine* _get_or_load_machine(long long machine_id);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,20 @@
 | 
			
		||||
#include "player.h"
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include "db/database_manager.h"
 | 
			
		||||
#include "i_network_bridge.h"
 | 
			
		||||
#include "machine.h"
 | 
			
		||||
 | 
			
		||||
Player::Player(uint32_t new_id, Machine* home_machine,
 | 
			
		||||
               std::map<std::string, Machine*>& world_machines, DatabaseManager* db_manager,
 | 
			
		||||
               MachineManager* machine_manager) :
 | 
			
		||||
               DatabaseManager* db_manager,
 | 
			
		||||
               MachineManager* machine_manager,
 | 
			
		||||
               INetworkBridge* network_bridge) :
 | 
			
		||||
    id(new_id),
 | 
			
		||||
    state(PlayerState::AUTHENTICATING) {
 | 
			
		||||
 | 
			
		||||
  cmd_processor = new CommandProcessor(home_machine, world_machines, db_manager, machine_manager);
 | 
			
		||||
  cmd_processor = new CommandProcessor(home_machine, db_manager, machine_manager,
 | 
			
		||||
                                       network_bridge);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Player::~Player(void) {
 | 
			
		||||
  if(cmd_processor) {
 | 
			
		||||
    /*
 | 
			
		||||
     * Player is owner of their own machine. CommandProcessor
 | 
			
		||||
     * holds the most current pointer to it (in case of CoW).
 | 
			
		||||
     * Delete the machine first, then the processor point to it.
 | 
			
		||||
     */
 | 
			
		||||
    delete cmd_processor->get_home_machine();
 | 
			
		||||
  delete cmd_processor;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,10 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "db/database_manager.h"
 | 
			
		||||
#include "command_processor.h"
 | 
			
		||||
#include "i_network_bridge.h"
 | 
			
		||||
#include "machine.h"
 | 
			
		||||
#include "machine_manager.h"
 | 
			
		||||
 | 
			
		||||
@ -16,9 +15,8 @@ enum class PlayerState {
 | 
			
		||||
 | 
			
		||||
class Player {
 | 
			
		||||
public:
 | 
			
		||||
  Player(uint32_t id, Machine* home_machine, std::map<std::string,
 | 
			
		||||
         Machine*>& world_machines, DatabaseManager* db_manager,
 | 
			
		||||
         MachineManager* machine_manager);
 | 
			
		||||
  Player(uint32_t id, Machine* home_machine, DatabaseManager* db_manager,
 | 
			
		||||
         MachineManager* machine_manager, INetworkBridge* network_bridge);
 | 
			
		||||
  ~Player(void);
 | 
			
		||||
 | 
			
		||||
  uint32_t id;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user