From 3fbacd4a998ac4ae64f4387af6a97110d2fad654 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sun, 12 Oct 2025 15:21:32 +0100 Subject: [PATCH] [Add] Implment scp for remote file copy. --- assets/scripts/bin/scp.lua | 8 +++ common/src/command_processor.cpp | 24 --------- common/src/lua_api.cpp | 91 ++++++++++++++++++++++++++++++++ common/src/lua_api.h | 2 + common/src/lua_processor.cpp | 1 + common/src/vfs.cpp | 26 +++++++++ common/src/vfs.h | 1 + 7 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 assets/scripts/bin/scp.lua diff --git a/assets/scripts/bin/scp.lua b/assets/scripts/bin/scp.lua new file mode 100644 index 0000000..30bf3f4 --- /dev/null +++ b/assets/scripts/bin/scp.lua @@ -0,0 +1,8 @@ +local source = arg[1] +local destination = arg[2] + +if not source or not destination then + return "usage: scp source destination" +end + +return bettola.scp(context, source, destination) diff --git a/common/src/command_processor.cpp b/common/src/command_processor.cpp index e7174d0..95a7551 100644 --- a/common/src/command_processor.cpp +++ b/common/src/command_processor.cpp @@ -117,30 +117,6 @@ std::string CommandProcessor::write_file(const std::string& path, const std::str return api::write_file(*this, path, content); } -/* 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; -} - std::string CommandProcessor::read_file(const std::string& path) { vfs_node* root = get_session_machine()->vfs_root; vfs_node* node = find_node_by_path(root, path); diff --git a/common/src/lua_api.cpp b/common/src/lua_api.cpp index 8355ab0..d546851 100644 --- a/common/src/lua_api.cpp +++ b/common/src/lua_api.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -130,4 +131,94 @@ std::string close_terminal(CommandProcessor& context) { return "__CLOSE_CONNECTION__"; } +struct ScpPath { + std::string host; + std::string path; +}; + +ScpPath parse_scp_path(const std::string& arg) { + size_t colon_pos = arg.find(':'); + if(colon_pos != std::string::npos) { + return {arg.substr(0, colon_pos), arg.substr(colon_pos+1)}; + } else { + return {"", arg}; + } +} + +std::string scp(CommandProcessor& context, const std::string& source_arg, + const std::string& dest_arg) { + ScpPath source_path = parse_scp_path(source_arg); + ScpPath dest_path = parse_scp_path(dest_arg); + Machine* session_machine = context.get_session_machine(); + INetworkBridge* bridge = context.get_network_bridge(); + + Machine* source_machine = source_path.host.empty() + ? session_machine : bridge->get_machine_by_ip(source_path.host); + + Machine* dest_machine = dest_path.host.empty() + ? session_machine : bridge->get_machine_by_ip(dest_path.host); + + if(!source_machine) { + return "scp: " + source_path.host + ": Name or service not known"; + } + if(!dest_machine) { + if(source_machine != session_machine) bridge->release_machine(source_machine->id); + return "scp: " + dest_path.host + ": Name or service not known"; + } + + /* Simplified to use only absolute paths for now. */ + vfs_node* source_node = find_node_by_path(source_machine->vfs_root, source_path.path); + + if(!source_node || source_node->type != FILE_NODE) { + if(source_machine != session_machine) bridge->release_machine(source_machine->id); + if(dest_machine != session_machine) bridge->release_machine(dest_machine->id); + return "scp: " + source_path.path + ": No such file or directory"; + } + + vfs_node* dest_node = find_node_by_path(dest_machine->vfs_root, dest_path.path); + vfs_node* dest_dir = nullptr; + std::string dest_filename; + + if(dest_node && dest_node->type == DIR_NODE) { + dest_dir = dest_node; + dest_filename = source_node->name; + } else { + size_t last_slash = dest_path.path.find_last_of('/'); + if(last_slash != std::string::npos) { + std::string parent_path = dest_path.path.substr(0, last_slash); + if(parent_path.empty()) parent_path = "/"; + dest_dir = find_node_by_path(dest_machine->vfs_root, parent_path); + dest_filename = dest_path.path.substr(last_slash+1); + } else { + dest_dir = dest_machine->vfs_root; + dest_filename = dest_path.path; + } + } + + if(!dest_dir) { + if(source_machine != session_machine) bridge->release_machine(source_machine->id); + if(dest_machine != session_machine) bridge->release_machine(dest_machine->id); + return "scp: " + dest_path.path + ": No such file or directory"; + } + + if(dest_dir->children.count(dest_filename)) { + vfs_node* existing_node = dest_dir->children[dest_filename]; + if(existing_node->type == DIR_NODE) { + if(source_machine != session_machine) bridge->release_machine(source_machine->id); + if(dest_machine != session_machine) bridge->release_machine(dest_machine->id); + return "scp: " + dest_path.path + ": Is a directory"; + } + existing_node->content = source_node->content; + } else { + vfs_node* new_file = new_node(dest_filename, FILE_NODE, dest_dir); + new_file->content = source_node->content; + dest_dir->children[dest_filename] = new_file; + } + + if(source_machine != session_machine) bridge->release_machine(source_machine->id); + if(dest_machine != session_machine) bridge->release_machine(dest_machine->id); + + return ""; +} + } /* namespace api */ diff --git a/common/src/lua_api.h b/common/src/lua_api.h index 5113c7d..d469695 100644 --- a/common/src/lua_api.h +++ b/common/src/lua_api.h @@ -15,6 +15,8 @@ std::string write_file(CommandProcessor& context, const std::string& filename, const std::string& content); std::string ls(CommandProcessor& context); std::string cd(CommandProcessor& context, const std::string& path); +std::string scp(CommandProcessor& context, const std::string& source, + const std::string& destination); /* NETWORK ACTIONS. */ std::string ssh(CommandProcessor& context, const std::string& ip); diff --git a/common/src/lua_processor.cpp b/common/src/lua_processor.cpp index a7e2853..a991b2b 100644 --- a/common/src/lua_processor.cpp +++ b/common/src/lua_processor.cpp @@ -28,6 +28,7 @@ LuaProcessor::LuaProcessor(CommandProcessor& context) { 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("scp", &api::scp); bettola_api.set_function("close_terminal", &api::close_terminal); bettola_api.set_function("ssh", &api::ssh); bettola_api.set_function("nmap", &api::nmap); diff --git a/common/src/vfs.cpp b/common/src/vfs.cpp index b8724f8..90cd6e2 100644 --- a/common/src/vfs.cpp +++ b/common/src/vfs.cpp @@ -1,3 +1,5 @@ +#include + #include "vfs.h" /* Create a new node. */ @@ -31,3 +33,27 @@ void delete_vfs_tree(vfs_node* node) { } delete node; } + +/* 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; +} diff --git a/common/src/vfs.h b/common/src/vfs.h index e601d38..558061c 100644 --- a/common/src/vfs.h +++ b/common/src/vfs.h @@ -30,4 +30,5 @@ struct vfs_node { vfs_node* new_node(std::string name, vfs_node_type type, vfs_node* parent); vfs_node* find_node_by_id(vfs_node* root, long long id); std::string get_full_path(vfs_node* node); +vfs_node* find_node_by_path(vfs_node* root, const std::string& path); void delete_vfs_tree(vfs_node* node);