[Add] Implment scp for remote file copy.

This commit is contained in:
Ritchie Cunningham 2025-10-12 15:21:32 +01:00
parent 4f5436f376
commit 3fbacd4a99
7 changed files with 129 additions and 24 deletions

View File

@ -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)

View File

@ -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);

View File

@ -1,3 +1,4 @@
#include <sol/call.hpp>
#include <sol/types.hpp>
#include <sstream>
@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -1,3 +1,5 @@
#include <sstream>
#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;
}

View File

@ -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);