From 30bff884bf0fd0bca97ad5126cfd4a48508ddc5b Mon Sep 17 00:00:00 2001 From: chalith Date: Thu, 2 Nov 2023 13:29:53 +0530 Subject: [PATCH] Disconnect handler --- src/hpsh/hpsh.cpp | 111 ++++++++++++++++++++++------------------------ src/hpsh/hpsh.hpp | 2 +- test/bin/hpsh | Bin 18792 -> 28240 bytes 3 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/hpsh/hpsh.cpp b/src/hpsh/hpsh.cpp index d3a9ea96..bad45c07 100644 --- a/src/hpsh/hpsh.cpp +++ b/src/hpsh/hpsh.cpp @@ -2,8 +2,8 @@ namespace hpsh { - constexpr const char *HPSH_CTR_SH = "sh"; - constexpr const char *HPSH_CTR_TERMINATE = "terminate"; + constexpr uint8_t HPSH_CTRL_TERMINATE = 0; + constexpr uint8_t HPSH_CTRL_SH = 1; constexpr uint32_t POLL_TIMEOUT = 1000; constexpr uint32_t READ_BUFFER_SIZE = 128 * 1024; @@ -94,8 +94,7 @@ namespace hpsh close(ctx.control_fds[0]); for (const auto &command : ctx.commands) { - close(command.child_fds[0]); - close(command.child_fds[1]); + close(command.out_fd); } if (ctx.hpsh_pid > 0) @@ -146,7 +145,7 @@ namespace hpsh int send_terminate_message() { - return (write(ctx.control_fds[1], HPSH_CTR_TERMINATE, 10) < 0) ? -1 : 0; + return (write(ctx.control_fds[1], &HPSH_CTRL_TERMINATE, 1) < 0) ? -1 : 0; } void remove_user_commands(std::string_view user_pubkey) @@ -159,7 +158,7 @@ namespace hpsh if (itr->user_pubkey == user_pubkey) { // Close the file descriptor and remove the command from context. - close(itr->child_fds[1]); + close(itr->out_fd); itr = ctx.commands.erase(itr); } else @@ -180,69 +179,49 @@ namespace hpsh return -2; } + std::string buffer; + buffer.resize(message.size() + 1); + buffer[0] = HPSH_CTRL_SH; + memcpy(buffer.data() + 1, message.data(), message.size()); + // Send the hpsh request header. - if (write(ctx.control_fds[1], HPSH_CTR_SH, 3) < 0) + if (write(ctx.control_fds[1], buffer.data(), message.size() + 1) < 0) { LOG_ERROR << errno << ": Error writing header message to control fd."; return -1; } - // Create a socket pair to communicate for the hpsh request. - int child_fds[2]; - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, child_fds) == -1) + // Read the control message which will contain the socket file descriptor. + struct msghdr child_msg = {0}; + memset(&child_msg, 0, sizeof(child_msg)); + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + child_msg.msg_control = cmsgbuf; + child_msg.msg_controllen = sizeof(cmsgbuf); + + recvmsg(ctx.control_fds[1], &child_msg, 0); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&child_msg); + + // Skip if the message does not has file descriptor scm rights. + if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS) { - LOG_ERROR << errno << ": Error initializing socket pair."; + LOG_ERROR << "Message sent on control line from hpsh has non-scm_rights."; return -1; } - // Prepare and send the child socket file descriptor with scm rights. - 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; + int out_fd = -1; + memcpy(&out_fd, CMSG_DATA(cmsg), sizeof(out_fd)); - 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) + if (out_fd <= 0) { - LOG_ERROR << errno << ": Error writing to control fd."; - close(child_fds[0]); - close(child_fds[1]); + LOG_ERROR << "Invalid file descriptor receives on control line from hpsh"; return -1; } - // Write the request message to the child socket. - if (write(child_fds[1], message.data(), message.size()) < 0) - { - LOG_ERROR << errno << ": Error writing to child fd."; - close(child_fds[0]); - close(child_fds[1]); - return -1; - } - - // Close the child end of the socket. - close(child_fds[0]); - // Add the command to the context. { std::scoped_lock lock(ctx.command_mutex); - ctx.commands.push_back(command_context{std::string(id), std::string(user_pubkey), {child_fds[0], child_fds[1]}}); + ctx.commands.push_back(command_context{std::string(id), std::string(user_pubkey), out_fd}); } return 0; @@ -266,16 +245,19 @@ namespace hpsh break; struct pollfd pfd; - pfd.fd = itr->child_fds[1]; + pfd.fd = itr->out_fd; pfd.events = POLLIN; + bool remove = false; + // If child fd has data to read handle them. - if (poll(&pfd, 1, POLL_TIMEOUT) == -1) + const int poll_res = poll(&pfd, 1, POLL_TIMEOUT); + if (poll_res == -1) { LOG_ERROR << errno << ": Error in poll"; - continue; + remove = true; } - else if (pfd.revents & POLLIN) + else if (poll_res > 0 && (pfd.revents & POLLIN)) { // Read the response and send to the user. std::string response; @@ -303,11 +285,26 @@ namespace hpsh } else if (res == -1) { - LOG_ERROR << errno << ": Error reading from fd"; + LOG_ERROR << errno << ": Error reading from fd."; + remove = true; + } + else + { + LOG_DEBUG << "Hpsh has closed the connection."; + remove = true; } } - itr++; + if (remove) + { + // Close the file descriptor and remove the command from context. + close(itr->out_fd); + itr = ctx.commands.erase(itr); + } + else + { + itr++; + } } } util::sleep(100); diff --git a/src/hpsh/hpsh.hpp b/src/hpsh/hpsh.hpp index 6c859897..ef9ad675 100644 --- a/src/hpsh/hpsh.hpp +++ b/src/hpsh/hpsh.hpp @@ -12,7 +12,7 @@ namespace hpsh { std::string id; std::string user_pubkey; - int child_fds[2]; + int out_fd; }; struct hpsh_context diff --git a/test/bin/hpsh b/test/bin/hpsh index ce2524706564058544078a3d87568ee1162cd1fc..1c76d10b66d9a39e498d978bcbddd1b472ed53a6 100755 GIT binary patch literal 28240 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 literal 18792 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 b)?AnjUe+sR={tQn{T9C=ahXlQ#