From 23ee18e4543dc5392850102e576ac611d9b095df Mon Sep 17 00:00:00 2001 From: supunvindula Date: Tue, 31 Oct 2023 10:02:42 +0530 Subject: [PATCH] included the basic architecture for hpsh implementation --- src/hpsh/hpsh.cpp | 288 ++++++++++++++++++++++++++++++++++++---------- src/hpsh/hpsh.hpp | 52 ++++++++- src/hpsh/util.cpp | 45 ++++++++ src/hpsh/util.hpp | 30 +++++ src/usr/usr.cpp | 8 +- test/bin/hpsh | Bin 19592 -> 34032 bytes 6 files changed, 352 insertions(+), 71 deletions(-) create mode 100644 src/hpsh/util.cpp create mode 100644 src/hpsh/util.hpp diff --git a/src/hpsh/hpsh.cpp b/src/hpsh/hpsh.cpp index e7b5e30b..e6994e59 100644 --- a/src/hpsh/hpsh.cpp +++ b/src/hpsh/hpsh.cpp @@ -1,92 +1,254 @@ -#include "../conf.hpp" -#include "../util/util.hpp" +#include "hpsh.hpp" namespace hpsh { - pid_t hpsh_pid; - static int fd1[2]; - static int fd2[2]; + constexpr const char *HPSH_EXEC_PATH = "./executable/hpsh"; + constexpr int MAX_BUFFER_LEN = 1024; + constexpr int POLL_TIMEOUT = 1000; + constexpr uint32_t READ_BUFFER_SIZE = 128 * 1024; - int deinit() + hpsh_context ctx; + + int init() { - //kill(hpsh_pid, SIGTERM); - close(fd1[0]); - close(fd1[1]); - close(fd2[0]); - close(fd1[1]); + std::cout << "Initializing hpsh process.." << std::endl; - LOG_INFO << "HPSH stopped."; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, ctx.control_fds) == -1) + { + std::cerr << errno << "Error initializing socket pair." << std::endl; + return -1; + } + + ctx.hpsh_pid = fork(); + if (ctx.hpsh_pid == -1) + { + std::cerr << errno << "Error forking." << std::endl; + close(ctx.control_fds[0]); + close(ctx.control_fds[1]); + return -1; + } + else if (ctx.hpsh_pid > 0) + { + close(ctx.control_fds[0]); + + ctx.watcher_thread = std::thread(response_watcher); + } + else if (ctx.hpsh_pid == 0) + { + util::fork_detach(); + + close(ctx.control_fds[1]); + + std::cout << "Starting hpsh process... " << std::endl; + + std::string fd_str; + fd_str.resize(10); + snprintf(fd_str.data(), sizeof(fd_str), "%d", ctx.control_fds[0]); + + char *argv[] = {(char *)HPSH_EXEC_PATH, (char *)("-s1"), fd_str.data(), NULL}; + execv(argv[0], argv); + + std::cerr << errno << "Error executing hpfs." << std::endl; + + close(ctx.control_fds[0]); + exit(EXIT_FAILURE); + } return 0; } - int init() + + void deinit() { - LOG_INFO << "Initializing HPSH"; + std::cout << "De-initializing hpsh process.." << std::endl; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd1) == -1 || socketpair(AF_UNIX, SOCK_STREAM, 0, fd2) == -1) + ctx.is_shutting_down = true; + + if (ctx.hpsh_pid > 0) + kill(ctx.hpsh_pid, SIGTERM); + + // Joining consensus processing thread. + if (ctx.watcher_thread.joinable()) + ctx.watcher_thread.join(); + + // close sockets. + close(ctx.control_fds[0]); + for (const auto &command : ctx.commands) { - return -1; + close(command.child_fds[0]); + close(command.child_fds[1]); } - pid_t pid = fork(); - if (pid == -1) + if (ctx.hpsh_pid > 0) { - return -1; + // Check if the hpsh has exited voluntarily. + if (check_hpsh_exited(false) == 0) + { + // Issue kill signal to kill the hpsh process. + kill(ctx.hpsh_pid, SIGKILL); + check_hpsh_exited(true); // Blocking wait until exit. + } } - if (pid == 0) - { - hpsh_pid = getpid(); - char cfd1[10], cfd2[10]; - snprintf(cfd1, 10, "%d", fd1[0]); - snprintf(cfd2, 10, "%d", fd2[1]); + std::cout << "Stopped hpsh process.." << std::endl; + } - char *argv[] = {const_cast(conf::ctx.hpsh_exe_path.c_str()), (char *)("-s1"), cfd1, (char *)("-s2"), cfd2, NULL}; - LOG_DEBUG << "Starting HPSH Executable"; - execv(argv[0], argv); - LOG_DEBUG << "Failed to execute hpsh"; - exit(EXIT_FAILURE); - } - else - { - close(fd1[0]); - close(fd2[1]); + int check_hpsh_exited(const bool block) + { + int scstatus = 0; + const int wait_res = waitpid(ctx.hpsh_pid, &scstatus, block ? 0 : WNOHANG); + if (wait_res == 0) // Child still running. + { return 0; } + if (wait_res == -1) + { + std::cerr << errno << ": hpsh process waitpid error. pid:" << ctx.hpsh_pid << std::endl; + ctx.hpsh_pid = 0; + return -1; + } + else // Child has exited + { + ctx.hpsh_pid = 0; + + if (WIFEXITED(scstatus)) + { + std::cerr << "Contract process ended normally." << std::endl; + return 1; + } + else + { + std::cerr << "Contract process ended prematurely. Exit code " << WEXITSTATUS(scstatus) << std::endl; + return -1; + } + } } - std::string serve(const char *message) + int execute(std::string_view pubkey, std::string_view message) { - char buffer[1024]; + if (ctx.is_shutting_down) + return -1; - ssize_t bytes_written = write(fd1[1], message, strlen(message)); - if (bytes_written == -1) { - perror("Error when writing to HPSH socket"); - } - - LOG_DEBUG << "\nMessage sent from hpcore: " << message; - - while (true) + int child_fds[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, child_fds) == -1) { - int bytesRead; - bytesRead = read(fd2[0], buffer, sizeof(buffer)); - if (bytesRead < 0) - { - // Handle read error - perror("read"); - return "error when reading"; - } - - - buffer[bytesRead] = '\0'; // Null-terminate the buffer - - LOG_DEBUG << "\nMessage received in hpcore: " << buffer; - if(bytesRead<1024){ - break; - } - + std::cerr << errno << "Error initializing socket pair." << std::endl; + return -1; + } + + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + char iobuf[1]; + struct iovec io = { + .iov_base = iobuf, + .iov_len = sizeof(iobuf)}; + union + { /* Ancillary data buffer, wrapped in a union + in order to ensure it is suitably aligned */ + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), child_fds, sizeof(int)); + + if (sendmsg(ctx.control_fds[1], &msg, 0) < 0) + { + std::cerr << errno << "Error writing to control fd." << std::endl; + close(child_fds[0]); + close(child_fds[1]); + return -1; + } + + if (write(child_fds[1], message.data(), sizeof(message)) < 0) + { + std::cerr << errno << "Error writing to child fd." << std::endl; + close(child_fds[0]); + close(child_fds[1]); + return -1; + } + + { + std::scoped_lock lock(ctx.command_mutex); + ctx.commands.push_back(command_context{std::string(pubkey), {child_fds[0], child_fds[1]}, std::string(), false}); + } + + return 0; + } + + void response_watcher() + { + util::mask_signal(); + + while (!ctx.is_shutting_down) + { + if (ctx.commands.size() > 0) + { + auto itr = ctx.commands.begin(); + while (itr != ctx.commands.end()) + { + if (ctx.is_shutting_down) + break; + + struct pollfd pfd; + pfd.fd = itr->child_fds[1]; + pfd.events = POLLIN; + + if (poll(&pfd, 1, POLL_TIMEOUT) == -1) + { + std::cerr << errno << "Error in poll" << std::endl; + continue; + } + else if (pfd.revents & POLLIN) + { + itr->response.resize(READ_BUFFER_SIZE); + const int res = read(pfd.fd, itr->response.data(), READ_BUFFER_SIZE); + + if (res > 0) + itr->response.resize(res); // Resize back to the actual bytes read. + else if (res == -1) + { + // Assuming that EPIPE or ECONNRESET resulted from contract termination, consider this as a neutral read. + if (errno == EPIPE || errno == ECONNRESET) + itr->read_completed = true; + else + std::cerr << errno << "Error reading from fd" << std::endl; + } + } + else + { + itr->read_completed = true; + } + + // Send command back to user; + if (itr->read_completed) + { + std::cout << "Received full output for user " << itr->pubkey << std::endl; + std::cout << itr->response.data() << std::endl; + + std::list collected_commands; + { + std::scoped_lock lock(ctx.command_mutex); + itr = ctx.commands.erase(itr); + } + } + else + { + itr++; + } + } + } + else + { + util::sleep(1000); + } } - return buffer; } } \ No newline at end of file diff --git a/src/hpsh/hpsh.hpp b/src/hpsh/hpsh.hpp index 4d9bad19..db41dfd3 100644 --- a/src/hpsh/hpsh.hpp +++ b/src/hpsh/hpsh.hpp @@ -1,14 +1,54 @@ -#ifndef HPSH_H -#define HPSH_H +#ifndef _HP_HPSH_ +#define _HP_HPSH_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.hpp" -#include "../conf.hpp" -#include "../util/util.hpp" namespace hpsh { - int deinit(); + struct command_context + { + std::string pubkey; + int child_fds[2]; + std::string response; + bool read_completed = false; + }; + + struct hpsh_context + { + std::mutex command_mutex; + std::list commands; + int control_fds[2]; + int hpsh_pid; + std::thread watcher_thread; + bool is_shutting_down; + }; + + extern hpsh_context ctx; + int init(); - std::string serve(const char* command); + + void deinit(); + + int check_hpsh_exited(const bool block); + + int execute(std::string_view pubkey, std::string_view message); + + void response_watcher(); } #endif \ No newline at end of file diff --git a/src/hpsh/util.cpp b/src/hpsh/util.cpp new file mode 100644 index 00000000..2e9d97a0 --- /dev/null +++ b/src/hpsh/util.cpp @@ -0,0 +1,45 @@ +#include "util.hpp" + +namespace util +{ + constexpr mode_t DIR_PERMS = 0755; + + /** + * Sleeps the current thread for specified no. of milliseconds. + */ + void sleep(const uint64_t milliseconds) + { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); + } + + // Applies signal mask to the calling thread. + void mask_signal() + { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &mask, NULL); + } + + /** + * Clears signal mask and signal handlers from the caller. + * Called by other processes forked from hpcore threads so they get detatched from + * the hpcore signal setup. + */ + void fork_detach() + { + // Restore signal handlers to defaults. + signal(SIGINT, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + signal(SIGABRT, SIG_DFL); + + // Remove any signal masks applied by hpcore. + sigset_t mask; + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + + // Set process group id (so the terminal doesn't send kill signals to forked children). + setpgrp(); + } +} // namespace util diff --git a/src/hpsh/util.hpp b/src/hpsh/util.hpp new file mode 100644 index 00000000..beb9833f --- /dev/null +++ b/src/hpsh/util.hpp @@ -0,0 +1,30 @@ +#ifndef _HP_UTIL_UTIL_ +#define _HP_UTIL_UTIL_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Contains helper functions and data structures used by multiple other subsystems. + */ + +namespace util +{ + void sleep(const uint64_t milliseconds); + + void mask_signal(); + + void fork_detach(); + +} // namespace util + +#endif diff --git a/src/usr/usr.cpp b/src/usr/usr.cpp index 87655008..6871841c 100644 --- a/src/usr/usr.cpp +++ b/src/usr/usr.cpp @@ -280,8 +280,12 @@ namespace usr { LOG_INFO << "shell input received:" << content; - std::string response = hpsh::serve(content.c_str()); - LOG_INFO << "response: " << response; + // std::string response = hpsh::serve(content.c_str()); + if (hpsh::execute(std::string("user_").append(std::to_string(1)), content.c_str()) == -1) + { + std::cout << "\nError sending message:" << content.c_str() << std::endl; + } + // LOG_INFO << "response: " << response; return 0; } diff --git a/test/bin/hpsh b/test/bin/hpsh index d58f320b1776018bb9269b44b6390f3965c3abaa..cf4cd294fed5b325afc51a1f7a4991fb72754f66 100755 GIT binary patch literal 34032 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 literal 19592 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