diff --git a/src/server_env_manager.cpp b/src/server_env_manager.cpp index dce24d5..38bd9d3 100644 --- a/src/server_env_manager.cpp +++ b/src/server_env_manager.cpp @@ -134,11 +134,13 @@ std::string server_env_manager::get_variable(const std::string& name) const { bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const { - return 0==runner::execute_cmd("test",{"-d", quote(dir_path)}, {}, {}, true, false, get_SSH_INFO()); + runner::runner_ssh test_runner(get_SSH_INFO(),"test",{"-d", quote(dir_path)}); + return test_runner.execute(); } bool server_env_manager::check_remote_file_exists(const std::string& file_path) const { - return 0==runner::execute_cmd("test",{"-f",quote(file_path)}, {}, {}, true, false, get_SSH_INFO()); + runner::runner_ssh test_runner(get_SSH_INFO(),"test",{"-f",quote(file_path)}); + return test_runner.execute(); } bool server_env_manager::check_remote_items_exist(const std::vector &file_paths) const @@ -149,8 +151,8 @@ bool server_env_manager::check_remote_items_exist(const std::vector file_paths_str += quote(file_path) + " "; } // check if all items in the vector exist on the remote server, in a single command. - - return 0==runner::execute_cmd("bash",{"-c","for item in " + file_paths_str + "; do test -f $item; done"}, {}, {}, true, false, get_SSH_INFO()); + runner::runner_ssh test_runner(get_SSH_INFO(),"bash",{"-c","for item in " + file_paths_str + "; do test -f $item; done"}); + return test_runner.execute(); } bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector args, bool silent, std::map extra_env_vars, std::string * output) const @@ -177,7 +179,13 @@ bool server_env_manager::run_remote_template_command(const std::string &service_ ASSERT(!output || !silent); // if output is captured, silent must be false ASSERT(!interactive || !silent); // if command is ssh, silent must be false - return 0==runner::execute_cmd("bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars, silent, interactive, get_SSH_INFO(), output); + if (interactive) { + runner::runner_ssh_interactive bash_runner(get_SSH_INFO(),"bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars); + return bash_runner.execute(); + } else { + runner::runner_ssh bash_runner(get_SSH_INFO(),"bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars, silent); + return bash_runner.execute(); + } } // base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh" diff --git a/src/service_runner.cpp b/src/service_runner.cpp index e78ca7b..caed78a 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -52,18 +52,23 @@ bool service_runner::install(bool silent) { if (!tinfo.is_set()) return false; - // Create service directory - if (0!=runner::execute_cmd("mkdir",{"-p",remotepath::service(mServer, mService)},"",{},true,false,mServerEnv.get_SSH_INFO())) - { - std::cerr << "Failed to create service directory " << remotepath::service(mServer, mService) << std::endl; - return false; + + { // Create service directory + runner::runner_ssh mkdir_runner(mServerEnv.get_SSH_INFO(),"mkdir",{"-p",remotepath::service(mServer, mService)}); + if (!mkdir_runner.execute()) + { + std::cerr << "Failed to create service directory " << remotepath::service(mServer, mService) << std::endl; + return false; + } } - - // Check if rsync is installed on remote host - if (0!=runner::execute_cmd("which",{"rsync"},"",{},true,false,mServerEnv.get_SSH_INFO())) - { - std::cerr << "rsync is not installed on the remote host" << std::endl; - return false; + + { // Check if rsync is installed on remote host + runner::runner_ssh which_runner(mServerEnv.get_SSH_INFO(),"which",{"rsync"}); + if (!which_runner.execute()) + { + std::cerr << "rsync is not installed on the remote host" << std::endl; + return false; + } } // make sure all shell files are executable @@ -125,8 +130,11 @@ bool service_runner::uninstall(bool silent) { } // 4. Remove the service directory from the server - if (0!=runner::execute_cmd("rm -rf " + quote(remotepath::service(mServer, mService)), {}, "", {}, true, false, mServerEnv.get_SSH_INFO())) - std::cerr << "Failed to remove remote service directory at " << remotepath::service(mServer, mService) << std::endl; + { + runner::runner_ssh rm_runner(mServerEnv.get_SSH_INFO(),"rm",{"-rf",remotepath::service(mServer, mService)}); + if (!rm_runner.execute()) + std::cerr << "Failed to remove remote service directory at " << remotepath::service(mServer, mService) << std::endl; + } std::cout << "Service " << mService << " successfully uninstalled from " << mServer << std::endl; return true; @@ -170,8 +178,11 @@ bool service_runner::fullnuke() return false; } - if (0!=runner::execute_cmd("rm -rf " + quote(local_service_path), {}, "", {}, true, false, mServerEnv.get_SSH_INFO())) - std::cerr << "Failed to remove local service directory at " << local_service_path << std::endl; + { + runner::runner_local rm_runner("rm",{"-rf",quote(local_service_path)}); + if (!rm_runner.execute()) + std::cerr << "Failed to remove local service directory at " << local_service_path << std::endl; + } std::cout << "Service " << mService << " successfully fully nuked from " << mServer << std::endl; return true; @@ -265,7 +276,6 @@ std::map service_runner::get_all_services_status(std { std::map status; - std::string command = "_allservicesstatus"; std::string service_name = "dropshell-agent"; @@ -275,12 +285,15 @@ std::map service_runner::get_all_services_status(std return status; } - std::string cmd_path = remotepath::service_template(server_name,service_name) + "/shared/"; + std::string cmd_path = remotepath::service_template(server_name,service_name) + "/shared/_allservicesstatus.sh"; std::string output; - if (0!=runner::execute_cmd("bash",{cmd_path+command+".sh"}, cmd_path, {}, true, false, env.get_SSH_INFO(), &output)) { - std::cerr << "Error: Failed to run command script at " << cmd_path << std::endl; - return status; + runner::runner_ssh_capture bash_runner(env.get_SSH_INFO(),output,cmd_path); + if (!bash_runner.execute()) + { + std::cerr << "Error: Failed to run command script at " << cmd_path << std::endl; + return status; + } } std::stringstream ss(output); @@ -390,7 +403,8 @@ bool service_runner::interactive_ssh(const std::string & server_name, const std: std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; return false; } - return 0==runner::execute_cmd("", {}, "", {}, false, true, env.get_SSH_INFO()); + runner::runner_ssh_interactive ssh_runner(env.get_SSH_INFO(),command); + return ssh_runner.execute(); } void service_runner::edit_server(const std::string &server_name) @@ -438,7 +452,8 @@ bool service_runner::edit_file(const std::string &file_path) } std::cout << "Editing file: " << file_path << std::endl; - return 0==runner::execute_cmd(editor_cmd, {file_path}, "", {}, false, true, nullptr); + runner::runner_local_interactive editor_runner(editor_cmd,{file_path}); + return editor_runner.execute(); } bool service_runner::interactive_ssh_service() @@ -544,7 +559,8 @@ bool service_runner::restore(std::string backup_file, bool silent) std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file; // Copy backup file from local to server - if (0!=runner::execute_cmd("scp", {quote(local_backup_file_path), mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path)}, "", {}, false, false, mServerEnv.get_SSH_INFO())) + runner::runner_local scp_runner("scp",{quote(local_backup_file_path), mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path)}); + if (!scp_runner.execute()) { std::cerr << "Failed to copy backup file from server" << std::endl; return false; @@ -621,11 +637,14 @@ bool service_runner::backup(bool silent) { // Create backups directory on server if it doesn't exist std::string remote_backups_dir = remotepath::backups(mServer); - if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl; - if (0!=runner::execute_cmd("mkdir", {"-p",quote(remote_backups_dir)}, "", {}, true, false, mServerEnv.get_SSH_INFO())) + if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl; { - std::cerr << "Failed to create backups directory on server" << std::endl; - return false; + runner::runner_ssh mkdir_runner(mServerEnv.get_SSH_INFO(),"mkdir",{"-p",quote(remote_backups_dir)}); + if (!mkdir_runner.execute()) + { + std::cerr << "Failed to create backups directory on server" << std::endl; + return false; + } } // Create backups directory locally if it doesn't exist @@ -664,7 +683,8 @@ bool service_runner::backup(bool silent) { } // Copy backup file from server to local - if (0!=runner::execute_cmd("scp", {quote(remote_backup_file_path), quote(local_backup_file_path)}, "", {}, silent, false, mServerEnv.get_SSH_INFO())) + runner::runner_local scp_runner("scp",{"-P", mServerEnv.get_SSH_PORT(), mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path), quote(local_backup_file_path)}); + if (!scp_runner.execute()) { std::cerr << "Failed to copy backup file from server" << std::endl; return false; @@ -681,15 +701,19 @@ bool service_runner::backup(bool silent) { cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env) { std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10); - if (0!=runner::execute_cmd("mkdir", {"-p",quote(p)}, "", {}, true, false, server_env.get_SSH_INFO())) - std::cerr << "Failed to create temp directory on server" << std::endl; - else - mPath = p; + { + runner::runner_ssh mkdir_runner(server_env.get_SSH_INFO(),"mkdir",{"-p",quote(p)}); + if (!mkdir_runner.execute()) + std::cerr << "Failed to create temp directory on server" << std::endl; + else + mPath = p; + } } cRemoteTempFolder::~cRemoteTempFolder() { - if (0!=runner::execute_cmd("rm", {"-rf",quote(mPath)}, "", {}, true, false, mServerEnv.get_SSH_INFO())) + runner::runner_ssh rm_runner(mServerEnv.get_SSH_INFO(),"rm",{"-rf",quote(mPath)}); + if (!rm_runner.execute()) std::cerr << "Failed to remove temp directory on server" << std::endl; } @@ -748,11 +772,10 @@ std::string service_runner::get_latest_backup_file(const std::string& server, co bool service_runner::rsync_copy(const std::string& local_path, const std::string& remote_path, bool silent) { std::cout << "Copying: [LOCAL] " << local_path << std::endl << std::string(8,' ')<<"[REMOTE] " << remote_path << std::endl; - if (0 != runner::execute_cmd( - "rsync", - {"--delete","--mkpath","-zrpc","-e",quote("ssh -p " + mServerEnv.get_SSH_PORT()),local_path, - mServerEnv.get_SSH_USER()+"@"+mServerEnv.get_SSH_HOST()+":"+remote_path}, - "", {}, true, false)) { + runner::runner_local rsync_runner("rsync", {"--delete","--mkpath","-zrpc","-e",quote("ssh -p " + mServerEnv.get_SSH_PORT()),local_path, + mServerEnv.get_SSH_USER()+"@"+mServerEnv.get_SSH_HOST()+":"+remote_path}, "", {}, true); + + if (!rsync_runner.execute()) { std::cerr << "Failed to copy files using rsync" << std::endl; return false; } diff --git a/src/utils/runner.cpp b/src/utils/runner.cpp index 1761460..ff7f33e 100644 --- a/src/utils/runner.cpp +++ b/src/utils/runner.cpp @@ -77,364 +77,281 @@ ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map& args, const std::string& working_dir, const std::map& env) { - std::ostringstream remote_cmd; - for (const auto& kv : env) { - if (kv.first == "SSHPASS") continue; - remote_cmd << kv.first << "='" << kv.second << "' "; - } - if (!working_dir.empty()) { - remote_cmd << "cd '" << working_dir << "' && "; - } - remote_cmd << command; - for (const auto& arg : args) { - remote_cmd << " " << arg; - } - return remote_cmd.str(); -} +} // namespace runner -// // 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 << " " << 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) { - // Request a PTY with xterm-256color type for color support - // First try using default terminal settings - should work with libssh 0.9.0+ - int rc = ssh_channel_request_pty(channel); - if (rc != SSH_OK) { - if (output) *output = std::string("Failed to request pty: ") + ssh_get_error(session); - return -1; - } - - // Try to explicitly tell the server we want colors through additional means - // 1. Set TERM environment variable - rc = ssh_channel_request_env(channel, "TERM", "xterm-256color"); - // Ignore errors - this is optional - - // 2. Request a shell - rc = ssh_channel_request_shell(channel); - if (rc != SSH_OK) { - if (output) *output = std::string("Failed to request shell: ") + ssh_get_error(session); - return -1; - } - - struct termios orig_termios, raw_termios; - tcgetattr(STDIN_FILENO, &orig_termios); - raw_termios = orig_termios; - cfmakeraw(&raw_termios); - tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios); - - if (!command.empty()) { - ssh_channel_write(channel, remote_cmd_str.c_str(), remote_cmd_str.size()); - ssh_channel_write(channel, "\n", 1); - } else { - // Initialize bash with color support if no specific command - std::string init_cmd = "export TERM=xterm-256color && if [ -f ~/.bashrc ]; then source ~/.bashrc; fi"; - ssh_channel_write(channel, init_cmd.c_str(), init_cmd.size()); - ssh_channel_write(channel, "\n", 1); - } - - int maxfd = STDIN_FILENO > STDOUT_FILENO ? STDIN_FILENO : STDOUT_FILENO; - maxfd = maxfd > ssh_get_fd(session) ? maxfd : ssh_get_fd(session); - char buffer[4096]; - bool done = false; - - while (!done) { - fd_set fds_read; - FD_ZERO(&fds_read); - FD_SET(STDIN_FILENO, &fds_read); - FD_SET(ssh_get_fd(session), &fds_read); - - int ret = select(maxfd + 1, &fds_read, nullptr, nullptr, nullptr); - if (ret < 0) break; - - if (FD_ISSET(STDIN_FILENO, &fds_read)) { - ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer)); - if (n > 0) { - ssh_channel_write(channel, buffer, n); - } else { - ssh_channel_send_eof(channel); - done = true; - } - } - - if (FD_ISSET(ssh_get_fd(session), &fds_read)) { - int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0); - if (n > 0) { - write(STDOUT_FILENO, buffer, n); - } else if (n == 0) { - done = true; - } - } - - if (ssh_channel_is_closed(channel) || ssh_channel_is_eof(channel)) { - done = true; - } - } - - tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); - 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, const std::string& working_dir) { - // Build complete command with env, working_dir, and the command itself - std::ostringstream cmd_with_env; - - // 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 << "\" "; - } - } - - // 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 << " && "; - } - cmd_with_env << remote_cmd_str; - - // Close the single quote - cmd_with_env << "'"; - - std::string final_cmd = cmd_with_env.str(); - - std::cout << "Final remote command: " << final_cmd << std::endl; - - int rc = ssh_channel_request_exec(channel, final_cmd.c_str()); - if (rc != SSH_OK) { - 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) { - 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) { - 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) { - 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) { - write(2, buffer, nbytes); - } - if (nbytes < 0) { - std::cerr << "Error reading from stderr" << std::endl; - } - } - - return 0; -} - -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 -) { - std::cerr << "Local command: " << command << std::endl; - - int pipefd[2]; - bool use_pipe = output && !interactive; - if (use_pipe && pipe(pipefd) == -1) { - perror("pipe"); - std::cerr << "Pipe error: " << strerror(errno) << std::endl; - return -1; - } +// runner_local: non-interactive local command execution +bool runner_local::execute() { pid_t pid = fork(); if (pid == -1) { - perror("fork"); - std::cerr << "Fork error: " << strerror(errno) << std::endl; - return -1; + std::cerr << "Failed to fork" << std::endl; + mReturnCode = -1; + return false; } if (pid == 0) { - if (!working_dir.empty()) { - if (chdir(working_dir.c_str()) != 0) { + // Child process + if (!mWorkingDir.empty()) { + if (chdir(mWorkingDir.c_str()) != 0) { perror("chdir"); - std::cerr << "Chdir error: " << strerror(errno) << std::endl; - exit(-1); + exit(127); } } - - for (const auto& kv : env) { - setenv(kv.first.c_str(), kv.second.c_str(), 1); - } - - if (use_pipe) { - close(pipefd[0]); - dup2(pipefd[1], STDOUT_FILENO); - dup2(pipefd[1], STDERR_FILENO); - close(pipefd[1]); - } else if (silent && !interactive) { - int devnull = open("/dev/null", O_WRONLY); - dup2(devnull, STDOUT_FILENO); - dup2(devnull, STDERR_FILENO); - close(devnull); - } - if (!interactive) { - setsid(); + // Set environment variables + for (const auto& [k, v] : mEnv) { + setenv(k.c_str(), v.c_str(), 1); } + // Build argv std::vector argv; - argv.push_back(const_cast(command.c_str())); - for (const auto& arg : args) { - argv.push_back(const_cast(arg.c_str())); - } + argv.push_back(const_cast(mCommand.c_str())); + for (auto& arg : mArgs) argv.push_back(const_cast(arg.c_str())); argv.push_back(nullptr); - - std::cerr << "Local command: " << command << std::endl; - for (const auto& arg : args) { - std::cerr << "Local arg: " << arg << std::endl; - } - - execvp(command.c_str(), argv.data()); - perror("execvp"); - exit(-1); - } else { - if (use_pipe) { - close(pipefd[1]); - std::ostringstream oss; - char buf[4096]; - ssize_t n; - while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) { - oss.write(buf, n); + if (mSilent) { + int fd = open("/dev/null", O_WRONLY); + if (fd != -1) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); } - close(pipefd[0]); - *output = oss.str(); } + execvp(mCommand.c_str(), argv.data()); + perror("execvp"); + exit(127); + } else { int status = 0; waitpid(pid, &status, 0); - std::cerr << "Waitpid status: " << status << std::endl; if (WIFEXITED(status)) { - return WEXITSTATUS(status); + mReturnCode = WEXITSTATUS(status); } else { - return -1; + mReturnCode = -1; } + return mReturnCode == 0; } } -} // anonymous namespace - -int execute_cmd( - const std::string& command, - const std::vector& args, - const std::string& working_dir, - const std::map& env, - const bool silent, - const bool interactive, - const copySSHPtr& sshinfo, - std::string* output -) { - if (sshinfo.valid()) { - std::string error; - ssh_session session = ssh_connect_and_auth(&sshinfo, env, &error); - if (!session) { - if (output) *output = error; - std::cerr << "SSH connection error: " << error << std::endl; - return -1; +// runner_local_interactive: interactive local command execution +bool runner_local_interactive::execute() { + pid_t pid = fork(); + if (pid == -1) { + std::cerr << "Failed to fork" << std::endl; + mReturnCode = -1; + return false; + } + if (pid == 0) { + if (!mWorkingDir.empty()) { + if (chdir(mWorkingDir.c_str()) != 0) { + perror("chdir"); + exit(127); + } } - ssh_channel channel = ssh_channel_new(session); - if (!channel) { - if (output) *output = "Failed to create SSH channel."; - ssh_disconnect(session); - ssh_free(session); - std::cerr << "SSH channel error: " << output->c_str() << std::endl; - return -1; + for (const auto& [k, v] : mEnv) { + setenv(k.c_str(), v.c_str(), 1); } - int rc = ssh_channel_open_session(channel); - if (rc != SSH_OK) { - if (output) *output = std::string("Failed to open SSH channel: ") + ssh_get_error(session); - ssh_channel_free(channel); - ssh_disconnect(session); - ssh_free(session); - std::cerr << "SSH channel open error: " << output->c_str() << std::endl; - return -1; - } - - int ret = 0; - std::string remote_cmd_str = ssh_build_remote_command(command, args, working_dir, {}); - if (interactive) { - ret = ssh_interactive_shell_session(session, channel, remote_cmd_str, command, output); + std::vector argv; + argv.push_back(const_cast(mCommand.c_str())); + for (auto& arg : mArgs) argv.push_back(const_cast(arg.c_str())); + argv.push_back(nullptr); + // Attach to terminal (no redirection) + execvp(mCommand.c_str(), argv.data()); + perror("execvp"); + exit(127); + } else { + int status = 0; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + mReturnCode = WEXITSTATUS(status); } else { - // For non-interactive, handle working directory in ssh_exec_command - ret = ssh_exec_command(session, channel, remote_cmd_str, silent, output, env, working_dir); + mReturnCode = -1; } - - ssh_channel_send_eof(channel); + return mReturnCode == 0; + } +} + +// runner_ssh: non-interactive SSH command execution +bool runner_ssh::execute() { + std::string error; + ssh_session session = ssh_connect_and_auth(&mSSHInfo, mEnv, &error); + if (!session) { + std::cerr << error << std::endl; + mReturnCode = -1; + return false; + } + ssh_channel channel = ssh_channel_new(session); + if (!channel) { + std::cerr << "Failed to create SSH channel." << std::endl; + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + if (ssh_channel_open_session(channel) != SSH_OK) { + std::cerr << "Failed to open SSH channel: " << ssh_get_error(session) << std::endl; + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + std::stringstream cmd; + cmd << mCommand; + for (const auto& arg : mArgs) { + cmd << " '" << arg << "'"; + } + int rc = ssh_channel_request_exec(channel, cmd.str().c_str()); + if (rc != SSH_OK) { + std::cerr << "SSH exec failed: " << ssh_get_error(session) << std::endl; ssh_channel_close(channel); ssh_channel_free(channel); ssh_disconnect(session); ssh_free(session); - - if (output) trim(output); - - std::cerr << "SSH command execution result: " << ret << std::endl; - return ret; - } else { - int ret=local_execute_cmd(command, args, working_dir, env, silent, interactive, output); - if (output) trim(output); - - std::cerr << "Local command execution result: " << ret << std::endl; - return ret; + mReturnCode = -1; + return false; } + char buffer[256]; + int nbytes; + while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + if (!mSilent) std::cout.write(buffer, nbytes); + } + mReturnCode = ssh_channel_get_exit_status(channel); + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + return mReturnCode == 0; } +// runner_ssh_interactive: interactive SSH command execution +bool runner_ssh_interactive::execute() { + std::string error; + ssh_session session = ssh_connect_and_auth(&mSSHInfo, mEnv, &error); + if (!session) { + std::cerr << error << std::endl; + mReturnCode = -1; + return false; + } + ssh_channel channel = ssh_channel_new(session); + if (!channel) { + std::cerr << "Failed to create SSH channel." << std::endl; + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + if (ssh_channel_open_session(channel) != SSH_OK) { + std::cerr << "Failed to open SSH channel: " << ssh_get_error(session) << std::endl; + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + std::stringstream cmd; + cmd << mCommand; + for (const auto& arg : mArgs) { + cmd << " '" << arg << "'"; + } + int rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) { + std::cerr << "Failed to request PTY: " << ssh_get_error(session) << std::endl; + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) { + std::cerr << "Failed to request shell: " << ssh_get_error(session) << std::endl; + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + // Forward input/output between user and SSH channel + fd_set fds; + char buffer[256]; + while (true) { + FD_ZERO(&fds); + FD_SET(0, &fds); // stdin + int ssh_fd = ssh_get_fd(session); + FD_SET(ssh_fd, &fds); + int maxfd = std::max(0, ssh_fd) + 1; + int ret = select(maxfd, &fds, nullptr, nullptr, nullptr); + if (ret < 0) break; + if (FD_ISSET(0, &fds)) { + int n = read(0, buffer, sizeof(buffer)); + if (n > 0) ssh_channel_write(channel, buffer, n); + } + if (FD_ISSET(ssh_fd, &fds)) { + int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (n > 0) write(1, buffer, n); + else if (n == 0) break; + } + if (ssh_channel_is_closed(channel)) break; + } + mReturnCode = ssh_channel_get_exit_status(channel); + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + return mReturnCode == 0; +} + +// runner_ssh_capture: SSH command execution with output capture +bool runner_ssh_capture::execute() { + std::string error; + ssh_session session = ssh_connect_and_auth(&mSSHInfo, mEnv, &error); + if (!session) { + std::cerr << error << std::endl; + mReturnCode = -1; + return false; + } + ssh_channel channel = ssh_channel_new(session); + if (!channel) { + std::cerr << "Failed to create SSH channel." << std::endl; + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + if (ssh_channel_open_session(channel) != SSH_OK) { + std::cerr << "Failed to open SSH channel: " << ssh_get_error(session) << std::endl; + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + std::stringstream cmd; + cmd << mCommand; + for (const auto& arg : mArgs) { + cmd << " '" << arg << "'"; + } + int rc = ssh_channel_request_exec(channel, cmd.str().c_str()); + if (rc != SSH_OK) { + std::cerr << "SSH exec failed: " << ssh_get_error(session) << std::endl; + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + mReturnCode = -1; + return false; + } + char buffer[256]; + int nbytes; + mOutput.clear(); + while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + mOutput.append(buffer, nbytes); + } + mReturnCode = ssh_channel_get_exit_status(channel); + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + return mReturnCode == 0; +} } // namespace runner \ No newline at end of file diff --git a/src/utils/runner.hpp b/src/utils/runner.hpp index ce908bb..c3effc6 100644 --- a/src/utils/runner.hpp +++ b/src/utils/runner.hpp @@ -14,26 +14,67 @@ struct sSSHInfo { std::string port; }; -class copySSHPtr { +class runner { public: - copySSHPtr(const sSSHInfo* sshinfo) : mSSHInfo(sshinfo ? *sshinfo : sSSHInfo()) {} - copySSHPtr(const sSSHInfo& sshinfo) : mSSHInfo(sshinfo) {} - bool valid() const { return !mSSHInfo.host.empty(); } - const sSSHInfo * operator&() const { return (valid() ? &mSSHInfo : nullptr); } - private: - sSSHInfo mSSHInfo; -}; + runner(std::string command, std::vector args={}, std::string working_dir="", std::map env={}, bool silent=false) : + mCommand(command), + mArgs(args), + mWorkingDir(working_dir), + mEnv(env), + mSilent(silent), + mReturnCode(0) {} + virtual ~runner() {} -int execute_cmd( - const std::string& command, - const std::vector& args, - const std::string& working_dir, - const std::map& env, - const bool silent, - const bool interactive, - const copySSHPtr& sshinfo = nullptr, - std::string* output = nullptr -); + virtual bool execute() = 0; + + int return_code() const { return mReturnCode; } + protected: + std::string mCommand; + std::vector mArgs; + std::string mWorkingDir; + std::map mEnv; + bool mSilent; + int mReturnCode; +}; + +class runner_local : public runner { + public: + runner_local(std::string command, std::vector args={}, std::string working_dir="", std::map env={}, bool silent=false) : + runner(command, args, working_dir, env, silent) {} + bool execute() override; +}; + +class runner_local_interactive : public runner { + public: + runner_local_interactive(std::string command, std::vector args={}, std::string working_dir="", std::map env={}) : + runner(command, args, working_dir, env, false) {} + bool execute() override; +}; + +class runner_ssh : public runner { + public: + runner_ssh(const sSSHInfo sshinfo, std::string command, std::vector args={}, std::string working_dir="", std::map env={}, bool silent=false) : + runner(command, args, working_dir, env, silent), mSSHInfo(sshinfo) {} + bool execute() override; + protected: + const sSSHInfo mSSHInfo; +}; + +class runner_ssh_interactive : public runner_ssh { + public: + runner_ssh_interactive(const sSSHInfo sshinfo, std::string command, std::vector args={}, std::string working_dir="", std::map env={}, bool silent=true) : + runner_ssh(sshinfo, command, args, working_dir, env, silent) {} + bool execute() override; +}; + +class runner_ssh_capture : public runner_ssh { + public: + runner_ssh_capture(const sSSHInfo sshinfo, std::string output, std::string command, std::vector args={}, std::string working_dir="", std::map env={}) : + runner_ssh(sshinfo, command, args, working_dir, env, true), mOutput(output) {} + bool execute() override; + protected: + std::string & mOutput; +}; } // namespace runner