From 409f5324091ec018b4d6c0187505650227a91200 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 10 May 2025 14:36:42 +1200 Subject: [PATCH] Works --- runner/runner.cpp | 122 +++++++++++++++++++++++++++++++++++++---- runner/runner_demo.cpp | 22 +++++++- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/runner/runner.cpp b/runner/runner.cpp index d9853ce..efa57de 100644 --- a/runner/runner.cpp +++ b/runner/runner.cpp @@ -14,6 +14,14 @@ namespace runner { namespace { +// Add working_dir to the forward declaration +ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map& env, std::string* error); +std::string ssh_build_remote_command(const std::string& command, const std::vector& args, const std::string& working_dir, const std::map& env); +std::string escape_shell_arg(const std::string& arg); +int ssh_interactive_shell_session(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, const std::string& command, std::string* output); +int ssh_exec_command(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, bool silent, std::string* output, const std::map& env, const std::string& working_dir); +int local_execute_cmd(const std::string& command, const std::vector& args, const std::string& working_dir, const std::map& env, bool silent, bool interactive, std::string* output); + ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map& env, std::string* error) { ssh_session session = ssh_new(); if (!session) { @@ -66,6 +74,30 @@ std::string ssh_build_remote_command(const std::string& command, const std::vect return remote_cmd.str(); } +// Utility function to escape special shell characters +std::string escape_shell_arg(const std::string& arg) { + std::ostringstream escaped; + escaped << '"'; + for (char c : arg) { + if (c == '"' || c == '\\' || c == '$' || c == '`') { + escaped << '\\'; + } + escaped << c; + } + escaped << '"'; + return escaped.str(); +} + +// For non-interactive SSH, just build the command with args +std::string ssh_build_command_only(const std::string& command, const std::vector& args) { + std::ostringstream remote_cmd; + remote_cmd << command; + for (const auto& arg : args) { + remote_cmd << " " << escape_shell_arg(arg); + } + return remote_cmd.str(); +} + int ssh_interactive_shell_session(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, const std::string& command, std::string* output) { int rc = ssh_channel_request_pty(channel); if (rc != SSH_OK) { @@ -122,36 +154,102 @@ int ssh_interactive_shell_session(ssh_session session, ssh_channel channel, cons return 0; } -int ssh_exec_command(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, bool silent, std::string* output, const std::map& env) { - // Build command with env assignments as prefix using 'env' +int ssh_exec_command(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, bool silent, std::string* output, const std::map& env, const std::string& working_dir) { + // Build complete command with env, working_dir, and the command itself std::ostringstream cmd_with_env; - cmd_with_env << "env "; - for (const auto& kv : env) { - if (kv.first == "SSHPASS") continue; - cmd_with_env << kv.first << "='" << kv.second << "' "; + + // Create a simple, flat command that will work reliably + // Format: env VAR=value bash -c 'cd /path && command args' + + // Start with env variables + if (!env.empty()) { + cmd_with_env << "env "; + for (const auto& kv : env) { + if (kv.first == "SSHPASS") continue; + cmd_with_env << kv.first << "='" << kv.second << "' "; + } } - cmd_with_env << remote_cmd_str; + + // Use a single bash -c with the entire command inside single quotes + cmd_with_env << "bash -c '"; + + // Add cd if working directory specified + if (!working_dir.empty()) { + cmd_with_env << "cd " << working_dir << " && "; + } + + // Add the command, but replace any single quotes with '\'' + std::string escaped_cmd = remote_cmd_str; + size_t pos = 0; + while ((pos = escaped_cmd.find('\'', pos)) != std::string::npos) { + escaped_cmd.replace(pos, 1, "'\\''"); + pos += 4; // Length of "'\\''" + } + cmd_with_env << escaped_cmd; + + // Close the single quote + cmd_with_env << "'"; + std::string final_cmd = cmd_with_env.str(); + + // Debug: Show the command being executed + std::cerr << "SSH exec command: " << final_cmd << std::endl; + int rc = ssh_channel_request_exec(channel, final_cmd.c_str()); if (rc != SSH_OK) { - if (output) *output = std::string("Failed to exec remote command: ") + ssh_get_error(session); + std::string error = std::string("Failed to exec remote command: ") + ssh_get_error(session); + std::cerr << "SSH exec error: " << error << std::endl; + if (output) *output = error; return -1; } + if (output) { std::ostringstream oss; char buffer[4096]; int nbytes; + + // Read from stdout while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + std::cerr << "Read " << nbytes << " bytes from stdout" << std::endl; oss.write(buffer, nbytes); } + if (nbytes < 0) { + std::cerr << "Error reading from stdout" << std::endl; + } + + // Read from stderr + while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) { + std::cerr << "Read " << nbytes << " bytes from stderr" << std::endl; + oss.write(buffer, nbytes); + } + if (nbytes < 0) { + std::cerr << "Error reading from stderr" << std::endl; + } + *output = oss.str(); } else if (!silent) { char buffer[4096]; int nbytes; + + // Read from stdout while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + std::cerr << "Read " << nbytes << " bytes from stdout (writing to fd 1)" << std::endl; write(1, buffer, nbytes); } + if (nbytes < 0) { + std::cerr << "Error reading from stdout" << std::endl; + } + + // Read from stderr + while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) { + std::cerr << "Read " << nbytes << " bytes from stderr (writing to fd 2)" << std::endl; + write(2, buffer, nbytes); + } + if (nbytes < 0) { + std::cerr << "Error reading from stderr" << std::endl; + } } + return 0; } @@ -264,13 +362,17 @@ int execute_cmd( ssh_free(session); return -1; } - std::string remote_cmd_str = ssh_build_remote_command(command, args, working_dir, {}); // Don't prefix env here + int ret = 0; if (interactive) { + std::string remote_cmd_str = ssh_build_remote_command(command, args, working_dir, {}); ret = ssh_interactive_shell_session(session, channel, remote_cmd_str, command, output); } else { - ret = ssh_exec_command(session, channel, remote_cmd_str, silent, output, env); + // For non-interactive, handle working directory in ssh_exec_command + std::string remote_cmd_str = ssh_build_command_only(command, args); + ret = ssh_exec_command(session, channel, remote_cmd_str, silent, output, env, working_dir); } + ssh_channel_send_eof(channel); ssh_channel_close(channel); ssh_channel_free(channel); diff --git a/runner/runner_demo.cpp b/runner/runner_demo.cpp index 50b5cf6..fa39d86 100644 --- a/runner/runner_demo.cpp +++ b/runner/runner_demo.cpp @@ -93,8 +93,9 @@ void test_ssh() { } void test_env() { - std::string command = "env"; - std::vector args = {}; + std::string command = "bash"; + // Simplify the command to avoid nested quoting issues + std::vector args = {"-c", "env | grep WHATSUP && echo The value is: $WHATSUP"}; std::string working_dir = "/home"; std::map env = {{"WHATSUP", "Waaaaattttsssuuuppppp!"}}; bool silent = false; @@ -104,7 +105,22 @@ void test_env() { ssh.user = "katie"; ssh.port = "22"; - runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh); + std::cerr << "Running test_env with SSH to " << ssh.user << "@" << ssh.host << std::endl; + + // Capture output explicitly + std::string output; + int ret = runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh, &output); + + std::cerr << "Command returned code: " << ret << std::endl; + std::cerr << "Output length: " << output.length() << " bytes" << std::endl; + + // Print the actual output + std::cout << "=== BEGIN OUTPUT ===" << std::endl; + std::cout << output; + if (!output.empty() && output[output.length()-1] != '\n') { + std::cout << std::endl; + } + std::cout << "=== END OUTPUT ===" << std::endl; }