From a37ace9b47f7c2e144ea27aafe92cd4e90a69b73 Mon Sep 17 00:00:00 2001 From: Sebastien Sikora Date: Fri, 3 Dec 2021 21:26:26 +0000 Subject: [PATCH] Attempted another refactoring. Base Port class is now subclassed into seperate Server and Client Port classes. Inbound and Outbound fifos are now not handled seperately, instead each Port has both an in and out fifo. This moves a little bit more complexity out of the main Server and Client classes and moves it into the Port classes. --- demos/client_demo.cpp | 4 +- demos/server_demo.cpp | 6 +- src/satellite_terminal.h | 27 ++-- src/satterm_agent.cpp | 67 +++----- src/satterm_client.cpp | 52 +++--- src/satterm_port.cpp | 313 +++++++++++++----------------------- src/satterm_port.h | 75 ++++----- src/satterm_port_client.cpp | 61 +++++++ src/satterm_port_server.cpp | 109 +++++++++++++ src/satterm_server.cpp | 78 +++++---- src/satterm_struct.h | 53 ++++++ 11 files changed, 470 insertions(+), 375 deletions(-) create mode 100644 src/satterm_port_client.cpp create mode 100644 src/satterm_port_server.cpp create mode 100644 src/satterm_struct.h diff --git a/demos/client_demo.cpp b/demos/client_demo.cpp index e1b54eb..2ef1768 100644 --- a/demos/client_demo.cpp +++ b/demos/client_demo.cpp @@ -26,10 +26,10 @@ int main(int argc, char *argv[]) { } usleep(1000); } - std::cerr << "On termination error code = " << stc.GetErrorCode().err_no << " Error detail = " << stc.GetErrorCode().detail << std::endl; + std::cerr << "On termination error code = " << stc.GetErrorCode().err_no << " Error detail = " << stc.GetErrorCode().err_detail << std::endl; sleep(5); // Delay to read the message before terminal emulator window closes. } else { - std::cerr << "On termination error code = " << stc.GetErrorCode().err_no << " Error detail = " << stc.GetErrorCode().detail << std::endl; + std::cerr << "On termination error code = " << stc.GetErrorCode().err_no << " Error detail = " << stc.GetErrorCode().err_detail << std::endl; sleep(5); // Delay to read the message before terminal emulator window closes. } return 0; diff --git a/demos/server_demo.cpp b/demos/server_demo.cpp index c15e78f..80cc86d 100644 --- a/demos/server_demo.cpp +++ b/demos/server_demo.cpp @@ -7,7 +7,7 @@ int main (void) { - SatTerm_Server sts("test_server", "./client_demo"); + SatTerm_Server sts("test_server", "./client_demo", true, {"com_1", "com_2"}); if (sts.IsConnected()) { size_t message_count = 10; @@ -27,11 +27,11 @@ int main (void) { usleep(1000); } - std::cerr << "On termination error code = " << sts.GetErrorCode().err_no << " Error detail = " << sts.GetErrorCode().detail << std::endl; + std::cerr << "On termination error code = " << sts.GetErrorCode().err_no << " Error detail = " << sts.GetErrorCode().err_detail << std::endl; } else { - std::cerr << "On termination error code = " << sts.GetErrorCode().err_no << " Error detail = " << sts.GetErrorCode().detail << std::endl; + std::cerr << "On termination error code = " << sts.GetErrorCode().err_no << " Error detail = " << sts.GetErrorCode().err_detail << std::endl; } return 0; diff --git a/src/satellite_terminal.h b/src/satellite_terminal.h index 8e73f65..c9fde14 100644 --- a/src/satellite_terminal.h +++ b/src/satellite_terminal.h @@ -12,8 +12,8 @@ #include // std::string. #include // std::vector. -#include -#include +#include // std::map. +#include // std::unique_ptr. #include "satterm_port.h" @@ -36,15 +36,12 @@ class SatTerm_Agent { void SetConnectedFlag(bool is_connected); protected: - bool CreatePorts(bool is_server, bool is_in_port, std::string const& working_path, std::vector port_identifiers, - bool display_messages, char end_char, std::map>& ports); bool OpenPorts(std::map>& ports, unsigned long timeout_seconds = 5); error_descriptor m_error_code = {0, ""}; bool m_display_messages = false; - std::map> m_in_ports = {}; - std::map> m_out_ports = {}; - default_port m_default_port = {"", ""}; + std::map> m_ports = {}; + std::string m_default_port_identifier = ""; std::string m_stop_port_identifier = ""; std::string m_working_path = ""; std::string m_identifier = ""; @@ -56,26 +53,28 @@ class SatTerm_Agent { class SatTerm_Server : public SatTerm_Agent { public: SatTerm_Server(std::string const& identifier, std::string const& path_to_client_binary, bool display_messages = true, + std::vector port_identifiers = {"comms"}, std::string const& stop_message = "q", std::string const& path_to_terminal_emulator_paths = "./terminal_emulator_paths.txt", - std::vector out_port_identifiers = {"server_out"}, std::vector in_port_identifiers = {"server_in"}, - char end_char = 3, std::string const& stop_port_identifier = "", std::string const& stop_message = "q"); + char end_char = 3, std::string const& stop_port_identifier = "", unsigned long timeout_seconds = 5); ~SatTerm_Server(); - + private: std::string GetWorkingPath(void); + bool CreateServerPorts(std::string const& working_path, std::vector port_identifiers, + bool display_messages, char end_char, std::map>& ports); std::vector LoadTerminalEmulatorPaths(std::string const& file_path); pid_t StartClient(std::string const& path_to_terminal_emulator_paths, std::string const& path_to_client_binary, std::string const& working_path, - char end_char, std::string const& stop_message, std::vector in_port_identifiers, - std::vector out_port_identifiers); - + char end_char, std::string const& stop_message, std::vector port_identifiers); }; class SatTerm_Client : public SatTerm_Agent { public: SatTerm_Client(std::string const& identifier, int argc, char* argv[], bool display_messages = true); ~SatTerm_Client(); - + private: size_t GetArgStartIndex(std::string const& arg_start_delimiter, int argc, char* argv[]); std::vector ParseFifoPaths(size_t argv_start_index, size_t argv_count, char* argv[]); + void CreateClientPorts(std::string const& working_path, std::vector port_identifiers, + bool display_messages, char end_char, std::map>& ports); }; diff --git a/src/satterm_agent.cpp b/src/satterm_agent.cpp index 35f2e92..7fc04a6 100644 --- a/src/satterm_agent.cpp +++ b/src/satterm_agent.cpp @@ -10,37 +10,20 @@ // For a copy, see . // ----------------------------------------------------------------------------------------------------- -#include +#include // std::cout, std::cerr, std::endl. #include // std::ifstream. -#include -#include -#include -#include -#include +#include // std::out_of_range. +#include // std::string, std::to_string. +#include // std::map. +#include // std::vector. +#include // std::unique_ptr. #include "satellite_terminal.h" -bool SatTerm_Agent::CreatePorts(bool is_server, bool is_in_port, std::string const& working_path, std::vector port_identifiers, - bool display_messages, char end_char, std::map>& ports) { - bool success = true; - for (auto const& port_identifier : port_identifiers) { - ports.emplace(port_identifier, std::make_unique(is_server, is_in_port, working_path, port_identifier, display_messages, end_char)); - if (is_server) { - if (!(ports.at(port_identifier)->IsCreated())) { - success = false; - m_error_code = ports.at(port_identifier)->GetErrorCode(); - break; - } - } - } - return success; -} - bool SatTerm_Agent::OpenPorts(std::map>& ports, unsigned long timeout_seconds) { bool success = true; for (auto const& current_port : ports) { - current_port.second->Open(timeout_seconds); - if (!(current_port.second->IsOpened())) { + if (!(current_port.second->Open(timeout_seconds))) { success = false; m_error_code = current_port.second->GetErrorCode(); break; @@ -50,72 +33,72 @@ bool SatTerm_Agent::OpenPorts(std::map>& port } std::string SatTerm_Agent::GetMessage(bool capture_end_char, unsigned long timeout_seconds) { - return GetMessage(m_default_port.in, capture_end_char, timeout_seconds); + return GetMessage(m_default_port_identifier, capture_end_char, timeout_seconds); } -std::string SatTerm_Agent::GetMessage(std::string const& in_port_identifier, bool capture_end_char, unsigned long timeout_seconds) { +std::string SatTerm_Agent::GetMessage(std::string const& port_identifier, bool capture_end_char, unsigned long timeout_seconds) { m_error_code = {0, ""}; std::string received_message = ""; try { - received_message = m_in_ports.at(in_port_identifier)->GetMessage(capture_end_char, timeout_seconds); - m_error_code = m_in_ports.at(in_port_identifier)->GetErrorCode(); + received_message = m_ports.at(port_identifier)->GetMessage(capture_end_char, timeout_seconds); + m_error_code = m_ports.at(port_identifier)->GetErrorCode(); if (m_error_code.err_no != 0) { - SetConnectedFlag(m_in_ports.at(in_port_identifier)->IsOpened()); + SetConnectedFlag(m_ports.at(port_identifier)->IsOpened()); } } catch (const std::out_of_range& oor) { m_error_code = {-1, "GetMessage()_OOR_port_id"}; - std::string error_message = "GetMessage() error - No port matches identifier " + in_port_identifier; + std::string error_message = "GetMessage() error - No port matches identifier " + port_identifier; std::cerr << error_message << std::endl; } return received_message; } std::string SatTerm_Agent::SendMessage(std::string const& message, unsigned long timeout_seconds) { - return SendMessage(message, m_default_port.out, timeout_seconds); + return SendMessage(message, m_default_port_identifier, timeout_seconds); } -std::string SatTerm_Agent::SendMessage(std::string const& message, std::string const& out_port_identifier, unsigned long timeout_seconds) { +std::string SatTerm_Agent::SendMessage(std::string const& message, std::string const& port_identifier, unsigned long timeout_seconds) { m_error_code = {0, ""}; std::string remaining_message = ""; try { - remaining_message = m_out_ports.at(out_port_identifier)->SendMessage(message, timeout_seconds); - m_error_code = m_out_ports.at(out_port_identifier)->GetErrorCode(); + remaining_message = m_ports.at(port_identifier)->SendMessage(message, timeout_seconds); + m_error_code = m_ports.at(port_identifier)->GetErrorCode(); if (m_error_code.err_no != 0) { - SetConnectedFlag(m_out_ports.at(out_port_identifier)->IsOpened()); + SetConnectedFlag(m_ports.at(port_identifier)->IsOpened()); } } catch (const std::out_of_range& oor) { m_error_code = {-1, "SendMessage()_OOR_port_id"}; - std::string error_message = "SendMessage() error - No port matches identifier " + out_port_identifier; + std::string error_message = "SendMessage() error - No port matches identifier " + port_identifier; std::cerr << error_message << std::endl; } return remaining_message; } size_t SatTerm_Agent::SendBytes(const char* bytes, size_t byte_count, unsigned long timeout_seconds) { - return SendBytes(bytes, byte_count, m_default_port.out, timeout_seconds); + return SendBytes(bytes, byte_count, m_default_port_identifier, timeout_seconds); } -size_t SatTerm_Agent::SendBytes(const char* bytes, size_t byte_count, std::string const& out_port_identifier, unsigned long timeout_seconds) { +size_t SatTerm_Agent::SendBytes(const char* bytes, size_t byte_count, std::string const& port_identifier, unsigned long timeout_seconds) { m_error_code = {0, ""}; size_t sent_bytes = 0; try { - sent_bytes = m_out_ports.at(out_port_identifier)->SendBytes(bytes, byte_count, timeout_seconds); - m_error_code = m_out_ports.at(out_port_identifier)->GetErrorCode(); + sent_bytes = m_ports.at(port_identifier)->SendBytes(bytes, byte_count, timeout_seconds); + m_error_code = m_ports.at(port_identifier)->GetErrorCode(); if (m_error_code.err_no != 0) { - SetConnectedFlag(m_out_ports.at(out_port_identifier)->IsOpened()); + SetConnectedFlag(m_ports.at(port_identifier)->IsOpened()); } } catch (const std::out_of_range& oor) { m_error_code = {-1, "SendBytes()_OOR_port_id"}; - std::string error_message = "SendBytes() error - No port matches identifier " + out_port_identifier; + std::string error_message = "SendBytes() error - No port matches identifier " + port_identifier; std::cerr << error_message << std::endl; } return sent_bytes; diff --git a/src/satterm_client.cpp b/src/satterm_client.cpp index d361130..cde9a8b 100644 --- a/src/satterm_client.cpp +++ b/src/satterm_client.cpp @@ -10,10 +10,10 @@ // For a copy, see . // ----------------------------------------------------------------------------------------------------- -#include -#include -#include -#include +#include // std::cout, std::cerr, std::endl. +#include // std::string, std::stoi. +#include // std::map. +#include // std::vector. #include "satellite_terminal.h" @@ -21,43 +21,33 @@ SatTerm_Client::SatTerm_Client(std::string const& identifier, int argc, char* ar m_identifier = identifier; m_display_messages = display_messages; - size_t in_port_count = 0; - size_t out_port_count = 0; - std::vector out_port_identifiers = {}; - std::vector in_port_identifiers = {}; + size_t port_count = 0; + std::vector port_identifiers = {}; size_t argv_start_index = GetArgStartIndex("client_args", argc, argv); - + bool success = false; if (argv_start_index != 0) { + success = true; m_working_path = std::string(argv[argv_start_index]); m_end_char = (char)(std::stoi(std::string(argv[argv_start_index + 1]))); m_stop_message = std::string(argv[argv_start_index + 2]); - out_port_count = std::stoi(std::string(argv[argv_start_index + 3])); - in_port_count = std::stoi(std::string(argv[argv_start_index + 4])); - out_port_identifiers = ParseFifoPaths(argv_start_index + 5, out_port_count, argv); - in_port_identifiers = ParseFifoPaths(argv_start_index + 5 + out_port_count, in_port_count, argv); + port_count = std::stoi(std::string(argv[argv_start_index + 3])); + port_identifiers = ParseFifoPaths(argv_start_index + 4, port_count, argv); - m_default_port.out = out_port_identifiers[0]; - m_default_port.in = in_port_identifiers[0]; + m_default_port_identifier = port_identifiers[0]; if (m_display_messages) { std::string message = "Client working path is " + m_working_path; std::cerr << message << std::endl; } - success = CreatePorts(false, false, m_working_path, out_port_identifiers, m_display_messages, m_end_char, m_out_ports); - if (success) { - success = CreatePorts(false, true, m_working_path, in_port_identifiers, m_display_messages, m_end_char, m_in_ports); - } + CreateClientPorts(m_working_path, port_identifiers, m_display_messages, m_end_char, m_ports); unsigned long timeout_seconds = 5; - if (success) { - success = OpenPorts(m_out_ports, timeout_seconds); - } if (success) { - success = OpenPorts(m_in_ports, timeout_seconds); + success = OpenPorts(m_ports, timeout_seconds); } if (success) { @@ -84,15 +74,12 @@ SatTerm_Client::SatTerm_Client(std::string const& identifier, int argc, char* ar SatTerm_Client::~SatTerm_Client() { // If shutdown is occurring, the server will poll m_default_port.in until it gets EOF, then everything will be closed and unlinked at the // server end. To make sure that unlink() deletes the files, we close any that are open here, saving m_default_port.out at this end for last. - for (const auto& port : m_in_ports) { - port.second->Close(); - } - for (const auto& port : m_out_ports) { - if (port.first != m_default_port.out) { + for (const auto& port : m_ports) { + if (port.first != m_default_port_identifier) { port.second->Close(); } } - m_out_ports.at(m_default_port.out)->Close(); + m_ports.at(m_default_port_identifier)->Close(); // There should not be any more to do. Pointers in m_in_ports and m_out_ports are stored as unique_ptr, so when the maps // are destroyed now the destructors for the Ports should be called automatically. In these the fifos will be unlink()ed if m_is_server is set. } @@ -122,3 +109,10 @@ std::vector SatTerm_Client::ParseFifoPaths(size_t argv_start_index, } return paths_container; } + +void SatTerm_Client::CreateClientPorts(std::string const& working_path, std::vector port_identifiers, + bool display_messages, char end_char, std::map>& ports) { + for (auto const& port_identifier : port_identifiers) { + ports.emplace(port_identifier, std::make_unique(working_path, port_identifier, display_messages, end_char)); + } +} diff --git a/src/satterm_port.cpp b/src/satterm_port.cpp index 00b40ed..4b1770a 100644 --- a/src/satterm_port.cpp +++ b/src/satterm_port.cpp @@ -10,10 +10,10 @@ // For a copy, see . // ----------------------------------------------------------------------------------------------------- -#include -#include -#include -#include +#include // std::cout, std::cerr, std::endl; +#include // std::string, std::to_string. +#include // std::map. +#include // std::vector. #include // time(). #include // perror(). @@ -21,99 +21,32 @@ #include // write(), read(), close(), unlink(). #include // mkfifo(). #include // errno. -#include // SIGPIPE, SIG_IGN. #include "satterm_port.h" -Port::Port(bool is_server, bool is_in_port, std::string const& working_path, std::string const& identifier, bool display_messages, char end_char) { - m_is_server = is_server; - m_is_in_port = is_in_port; - m_working_path = working_path; - m_identifier = identifier; - m_display_messages = display_messages; - m_end_char = end_char; - - signal(SIGPIPE, SIG_IGN); - - if (m_is_server) { - Create(); - } -} - -Port::~Port() { - if (m_opened) { - close(m_fifo_descriptor); - } - if ((m_is_server) && (m_created)) { - Close(); - // Unlink. - std::string fifo_path = m_working_path + m_identifier; - int status = unlink(fifo_path.c_str()); - if ((status < 0) && m_display_messages) { - std::string error_message = "Unable to unlink fifo at path " + fifo_path; - perror(error_message.c_str()); - } - } -} - -void Port::Close(void) { - close(m_fifo_descriptor); - m_opened = false; -} - -void Port::Create(void) { - m_error_code = {0, ""}; - - std::string fifo_path = m_working_path + m_identifier; - - remove(fifo_path.c_str()); // If temporary file already exists, delete it. - - int status = mkfifo(fifo_path.c_str(), S_IFIFO|0666); - - if (status < 0) { - // Info on possible errors for mkfifo() - https://pubs.opengroup.org/onlinepubs/009696799/functions/mkfifo.html - m_error_code = {errno, "mkfifo()"}; - if (m_display_messages) { - std::string error_message = "Server mkfifo() error trying to create fifo at path " + fifo_path; - perror(error_message.c_str()); - } - m_created = false; - } else { - m_created = true; - } -} - -void Port::Open(unsigned long timeout_seconds) { - if (m_is_in_port) { - m_opened = OpenRxFifo(m_working_path + m_identifier, timeout_seconds); - } else { - m_opened = OpenTxFifo(m_working_path + m_identifier, timeout_seconds); - } -} - bool Port::OpenRxFifo(std::string const& fifo_path, unsigned long timeout_seconds) { m_error_code = {0, ""}; - int fifo = open(fifo_path.c_str(), O_RDONLY | O_NONBLOCK); + int fifo_descriptor = open(fifo_path.c_str(), O_RDONLY | O_NONBLOCK); bool success = false; - if (!(fifo < 0)) { - m_fifo_descriptor = fifo; + if (!(fifo_descriptor < 0)) { + m_fifos.in.descriptor = fifo_descriptor; m_current_message = ""; std::string init_message = GetMessage(false, timeout_seconds); if (init_message == "init") { if (m_display_messages) { - std::string message = "In Port " + m_identifier + " opened fifo " + fifo_path + " for reading on descriptor " + std::to_string(fifo); + std::string message = "Port " + m_identifier + " opened fifo " + fifo_path + " for reading on descriptor " + std::to_string(fifo_descriptor); std::cerr << message << std::endl; } success = true; } else { - m_fifo_descriptor = 0; + m_fifos.in.descriptor = 0; if (m_error_code == error_descriptor{-1, "GetMessage()_tx_unconn_timeout"}) { if (m_display_messages) { - std::string error_message = "In Port " + m_identifier + " opened fifo " + fifo_path + " for reading on descriptor " + std::to_string(fifo) + " but timed-out waiting for an init message."; + std::string error_message = "Port " + m_identifier + " opened fifo " + fifo_path + " for reading on descriptor " + std::to_string(fifo_descriptor) + " but timed-out waiting for an init message."; std::cerr << error_message << std::endl; } } @@ -122,7 +55,7 @@ bool Port::OpenRxFifo(std::string const& fifo_path, unsigned long timeout_second } else { m_error_code = {errno, "open()_rx"}; if (m_display_messages) { - std::string error_message = "In Port " + m_identifier + " unable to open fifo " + fifo_path + " for reading."; + std::string error_message = "Port " + m_identifier + " unable to open fifo at " + fifo_path + " for reading."; perror(error_message.c_str()); } success = false; @@ -133,22 +66,22 @@ bool Port::OpenRxFifo(std::string const& fifo_path, unsigned long timeout_second bool Port::OpenTxFifo(std::string const& fifo_path, unsigned long timeout_seconds) { m_error_code = {0, ""}; - int fifo = PollToOpenTxFifo(fifo_path, timeout_seconds); + int fifo_descriptor = PollToOpenTxFifo(fifo_path, timeout_seconds); bool success = false; - if (fifo >= 0) { + if (fifo_descriptor >= 0) { if (m_display_messages) { - std::string message = "Out Port " + m_identifier + " opened fifo " + fifo_path + " for writing on descriptor " + std::to_string(fifo); + std::string message = "Port " + m_identifier + " opened fifo " + fifo_path + " for writing on descriptor " + std::to_string(fifo_descriptor); std::cerr << message << std::endl; } - m_fifo_descriptor = fifo; + m_fifos.out.descriptor = fifo_descriptor; std::string init_message = "init"; init_message = SendMessage(init_message, timeout_seconds); if (GetErrorCode().err_no != 0) { - m_fifo_descriptor = 0; + m_fifos.out.descriptor = 0; success = false; } else { success = true; @@ -163,14 +96,14 @@ int Port::PollToOpenTxFifo(std::string const& fifo_path, unsigned long timeout_s m_error_code = {0, ""}; unsigned long start_time = time(0); - int fifo = -1; + int fifo_descriptor = -1; bool finished = false; while (!finished) { - fifo = open(fifo_path.c_str(), O_WRONLY | O_NONBLOCK); + fifo_descriptor = open(fifo_path.c_str(), O_WRONLY | O_NONBLOCK); - if (fifo >= 0) { + if (fifo_descriptor >= 0) { finished = true; } else { switch (errno) { @@ -183,14 +116,14 @@ int Port::PollToOpenTxFifo(std::string const& fifo_path, unsigned long timeout_s default: m_error_code = {errno, "open()_tx"}; if (m_display_messages) { - std::string error_message = "Out Port " + m_identifier + " unable to open fifo " + fifo_path + " for writing."; + std::string error_message = "Port " + m_identifier + " unable to open() fifo at" + fifo_path + " for writing."; perror(error_message.c_str()); } finished = true; } } } - return fifo; + return fifo_descriptor; } std::string Port::SendMessage(std::string const& message, unsigned long timeout_seconds) { @@ -214,132 +147,114 @@ std::string Port::SendMessage(std::string const& message, unsigned long timeout_ size_t Port::SendBytes(const char* bytes, size_t byte_count, unsigned long timeout_seconds) { m_error_code = {0, ""}; - if (!m_is_in_port) { - unsigned long start_time = time(0); - bool finished = false; - size_t bytes_remaining = byte_count; - size_t offset = 0; + unsigned long start_time = time(0); + bool finished = false; + size_t bytes_remaining = byte_count; + size_t offset = 0; - while (!finished) { - - int status = write(m_fifo_descriptor, bytes + offset, bytes_remaining); - - if (status >= 0) { - if ((size_t)(status) == bytes_remaining) { - finished = true; - } else { - offset += (size_t)(status); - } - bytes_remaining -= (size_t)(status); + while (!finished) { + + int status = write(m_fifos.out.descriptor, bytes + offset, bytes_remaining); + + if (status >= 0) { + if ((size_t)(status) == bytes_remaining) { + finished = true; } else { - switch (errno) { - case EAGAIN: // Erro - thread would block (reader currently reading, etc). Try again unless timeout. - finished = ((time(0) - start_time) > timeout_seconds); - if ((finished) && (timeout_seconds == 0)) { - m_error_code = {errno, "write()_thread_block"}; - } else if ((finished) && (timeout_seconds > 0)) { - m_error_code = {errno, "write()_thread_block_timeout"}; - } - break; - default: // Trap all other write() errors here. - m_error_code = {errno, "write()"}; - if (m_display_messages) { - std::string error_message = "Error on write() for Out_Port " + m_identifier; - perror(error_message.c_str()); - } - m_opened = false; - finished = true; - } + offset += (size_t)(status); + } + bytes_remaining -= (size_t)(status); + } else { + switch (errno) { + case EAGAIN: // Erro - thread would block (reader currently reading, etc). Try again unless timeout. + finished = ((time(0) - start_time) > timeout_seconds); + if ((finished) && (timeout_seconds == 0)) { + m_error_code = {errno, "write()_thread_block"}; + } else if ((finished) && (timeout_seconds > 0)) { + m_error_code = {errno, "write()_thread_block_timeout"}; + } + break; + default: // Trap all other write() errors here. + m_error_code = {errno, "write()"}; + if (m_display_messages) { + std::string error_message = "Port " + m_identifier + " unable to write() to fifo at" + m_fifos.out.identifier; + perror(error_message.c_str()); + } + m_fifos.out.opened = false; + finished = true; } } - return byte_count - bytes_remaining; - } else { - m_error_code = {-1, "SendBytes()_in_port"}; - if (m_display_messages) { - std::string error_message = "In Port " + m_identifier + " unable to SendBytes()."; - std::cerr << error_message << std::endl; - } - return 0; } + return byte_count - bytes_remaining; } std::string Port::GetMessage(bool capture_end_char, unsigned long timeout_seconds) { m_error_code = {0, ""}; - if (m_is_in_port) { - unsigned long start_time = 0; - if (timeout_seconds > 0) { - start_time = time(0); - } + unsigned long start_time = 0; + if (timeout_seconds > 0) { + start_time = time(0); + } + + bool finished = false; + bool end_char_received = false; + char char_in; + std::string message; + + while (!finished) { - bool finished = false; - bool end_char_received = false; - char char_in; - std::string message; + int status = read(m_fifos.in.descriptor, &char_in, 1); - while (!finished) { - - int status = read(m_fifo_descriptor, &char_in, 1); - - if (status > 0) { // read() read-in a character to the char buffer. - if (char_in != m_end_char) { + if (status > 0) { // read() read-in a character to the char buffer. + if (char_in != m_end_char) { + m_current_message.push_back(char_in); + } else { + if (capture_end_char) { m_current_message.push_back(char_in); - } else { - if (capture_end_char) { - m_current_message.push_back(char_in); + } + message = m_current_message; + m_current_message = ""; + end_char_received = true; + finished = true; + } + + } else if (status == 0) { // EOF. read() will return this if no process has the pipe open for writing. + if (!m_fifos.in.opened) { + finished = ((time(0) - start_time) > timeout_seconds); // If the Component has not finished initialising, assume that the partner + if (finished) { // component hasn't opened the fifo for writing yet so continue to poll + m_error_code = {-1, "GetMessage()_tx_unconn_timeout"}; // until timeout. + } + } else { // If the Component is initialised, the partner component no-longer has the + m_error_code = {-1, "read()_EOF"}; // fifo open for writing (has become disconnected). + if (m_display_messages) { + std::string error_message = "EOF error on GetMessage() for Port " + m_identifier + " suggests counterpart terminated."; + std::cerr << error_message << std::endl; } - message = m_current_message; - m_current_message = ""; - end_char_received = true; + m_fifos.in.opened = false; finished = true; } - - } else if (status == 0) { // EOF. read() will return this if no process has the pipe open for writing. - if (!m_opened) { - finished = ((time(0) - start_time) > timeout_seconds); // If the Component has not finished initialising, assume that the partner - if (finished) { // component hasn't opened the fifo for writing yet so continue to poll - m_error_code = {-1, "GetMessage()_tx_unconn_timeout"}; // until timeout. - } - } else { // If the Component is initialised, the partner component no-longer has the - m_error_code = {-1, "read()_EOF"}; // fifo open for writing (has become disconnected). - if (m_display_messages) { - std::string error_message = "EOF error on GetMessage() for In Port " + m_identifier + " suggests counterpart terminated."; - std::cerr << error_message << std::endl; - } - m_opened = false; - finished = true; + + } else if (status < 0) { // read() indicates an error. + switch (errno) { // See under errors here - https://pubs.opengroup.org/onlinepubs/009604599/functions/read.html + case EAGAIN: // Non-blocking read on empty fifo with connected writer will return -1 with error EAGAIN, + finished = ((time(0) - start_time) > timeout_seconds); // so we continue to poll unless timeout. + if (finished && (timeout_seconds > 0)) { // Only set m_error_code to EAGAIN if we have been waiting on a timeout. + m_error_code = {errno, "GetMessage()_tx_conn_timeout"}; } - - } else if (status < 0) { // read() indicates an error. - switch (errno) { // See under errors here - https://pubs.opengroup.org/onlinepubs/009604599/functions/read.html - case EAGAIN: // Non-blocking read on empty fifo with connected writer will return -1 with error EAGAIN, - finished = ((time(0) - start_time) > timeout_seconds); // so we continue to poll unless timeout. - if (finished && (timeout_seconds > 0)) { // Only set m_error_code to EAGAIN if we have been waiting on a timeout. - m_error_code = {errno, "GetMessage()_tx_conn_timeout"}; - } - break; - default: // Trap all other read() errors here. - m_error_code = {errno, "read()"}; - if (m_display_messages) { - std::string error_message = "Error on read() for In Port " + m_identifier; - perror(error_message.c_str()); - } - m_opened = false; - finished = true; - } + break; + default: // Trap all other read() errors here. + m_error_code = {errno, "read()"}; + if (m_display_messages) { + std::string error_message = "Port " + m_identifier + " unable to read() from fifo at " + m_fifos.in.identifier; + perror(error_message.c_str()); + } + m_fifos.in.opened = false; + finished = true; } } - if (end_char_received) { - return message; - } else { - return ""; - } + } + if (end_char_received) { + return message; } else { - m_error_code = {-1, "GetMessage()_out_port"}; - if (m_display_messages) { - std::string error_message = "Out Port " + m_identifier + " unable to GetMessage()."; - std::cerr << error_message << std::endl; - } return ""; } } @@ -348,14 +263,6 @@ error_descriptor Port::GetErrorCode(void) { return m_error_code; } -bool Port::IsCreated(void) { - return m_created; -} - bool Port::IsOpened(void) { - return m_opened; -} - -bool Port::IsInPort(void) { - return m_is_in_port; + return (m_fifos.in.opened && m_fifos.out.opened); } diff --git a/src/satterm_port.h b/src/satterm_port.h index a581d2a..03220f2 100644 --- a/src/satterm_port.h +++ b/src/satterm_port.h @@ -13,65 +13,58 @@ #include // std::string. #include // std::vector. -struct default_port { - std::string out; - std::string in; - default_port& operator=(default_port const& rhs) { - out = rhs.out; - in = rhs.in; - return *this; - } -}; - -struct error_descriptor { - int err_no; - std::string detail; - error_descriptor& operator=(error_descriptor const& rhs) { - err_no = rhs.err_no; - detail = rhs.detail; - return *this; - } - bool operator==(error_descriptor const& rhs) const { - return ((this->err_no == rhs.err_no) && (this->detail == rhs.detail)); - } - bool operator!=(error_descriptor const& rhs) const { - return ((this->err_no != rhs.err_no) || (this->detail != rhs.detail)); - } -}; +#include "satterm_struct.h" class Port { public: - Port(bool is_server, bool is_in_port, std::string const& working_path, std::string const& identifier, bool display_messages, char end_char); - ~Port(); + Port() {} + virtual ~Port() {} - void Create(void); - void Open(unsigned long timeout_seconds); - void Close(void); + virtual bool Open(unsigned long timeout_seconds) = 0; + virtual void Close(void) = 0; std::string GetMessage(bool capture_end_char, unsigned long timeout_seconds); std::string SendMessage(std::string const& message, unsigned long timeout_seconds); size_t SendBytes(const char* bytes, size_t byte_count, unsigned long timeout_seconds); - error_descriptor GetErrorCode(void); - bool IsCreated(void); bool IsOpened(void); - bool IsInPort(void); - - private: + + protected: bool OpenRxFifo(std::string const& fifo_path, unsigned long timeout_seconds); bool OpenTxFifo(std::string const& fifo_path, unsigned long timeout_seconds); int PollToOpenTxFifo(std::string const& fifo_path, unsigned long timeout_seconds); error_descriptor m_error_code = {0, ""}; bool m_display_messages = false; - bool m_is_server = false; - bool m_is_in_port = false; - bool m_created = false; - bool m_opened = false; - std::string m_identifier = ""; std::string m_working_path = ""; char m_end_char = 0; - int m_fifo_descriptor = 0; + fifo_pair m_fifos = {{"", false, false, 0}, {"", false, false, 0}}; std::string m_current_message = ""; + +}; + +class Port_Server : public Port { + public: + Port_Server(std::string const& working_path, std::string const& identifier, bool display_messages, char end_char); + ~Port_Server(); + + bool Open(unsigned long timeout_seconds) override; + void Close(void) override; + + bool IsCreated(void); + + private: + bool CreateFifo(std::string const& fifo_path); + +}; + +class Port_Client : public Port { + public: + Port_Client(std::string const& working_path, std::string const& identifier, bool display_messages, char end_char); + ~Port_Client(); + + bool Open(unsigned long timeout_seconds) override; + void Close(void) override; + }; diff --git a/src/satterm_port_client.cpp b/src/satterm_port_client.cpp new file mode 100644 index 0000000..8a2016b --- /dev/null +++ b/src/satterm_port_client.cpp @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------------------------------------- +// satellite_terminal - Easily spawn and communicate bidirectionally with client processes in separate +// terminal emulator instances. +// ----------------------------------------------------------------------------------------------------- +// seb.nf.sikora@protonmail.com +// +// Copyright © 2021 Dr Seb N.F. Sikora. +// +// This work is licensed under the terms of the MIT license. +// For a copy, see . +// ----------------------------------------------------------------------------------------------------- + +#include // std::cout, std::cerr, std::endl; +#include // std::string, std::to_string. +#include // std::map. +#include // std::vector. +#include // time(). + +#include // SIGPIPE, SIG_IGN. +#include // perror(). +#include // open() and O_RDONLY, O_WRONLY, etc. +#include // write(), read(), close(), unlink(). +#include // mkfifo(). + +#include "satterm_port.h" + +Port_Client::Port_Client(std::string const& working_path, std::string const& identifier, bool display_messages, char end_char) { + m_working_path = working_path; + m_identifier = identifier; + m_fifos.in.identifier = identifier + "_sout"; + m_fifos.out.identifier = identifier + "_sin"; + m_display_messages = display_messages; + m_end_char = end_char; + + signal(SIGPIPE, SIG_IGN); + +} + +Port_Client::~Port_Client() { + Close(); +} + +bool Port_Client::Open(unsigned long timeout_seconds) { + m_fifos.out.opened = OpenTxFifo(m_working_path + m_fifos.out.identifier, timeout_seconds); + if (m_fifos.out.opened) { + m_fifos.in.opened = OpenRxFifo(m_working_path + m_fifos.in.identifier, timeout_seconds); + } + return (m_fifos.out.opened && m_fifos.in.opened); +} + +void Port_Client::Close(void) { + if (m_fifos.in.opened) { + close(m_fifos.in.descriptor); + m_fifos.in.opened = false; + } + if (m_fifos.out.opened) { + close(m_fifos.out.descriptor); + m_fifos.out.opened = false; + } +} + diff --git a/src/satterm_port_server.cpp b/src/satterm_port_server.cpp new file mode 100644 index 0000000..0017bb1 --- /dev/null +++ b/src/satterm_port_server.cpp @@ -0,0 +1,109 @@ +// ----------------------------------------------------------------------------------------------------- +// satellite_terminal - Easily spawn and communicate bidirectionally with client processes in separate +// terminal emulator instances. +// ----------------------------------------------------------------------------------------------------- +// seb.nf.sikora@protonmail.com +// +// Copyright © 2021 Dr Seb N.F. Sikora. +// +// This work is licensed under the terms of the MIT license. +// For a copy, see . +// ----------------------------------------------------------------------------------------------------- + +#include // std::cout, std::cerr, std::endl; +#include // std::string, std::to_string. +#include // std::map. +#include // std::vector. +#include // time(). + +#include // SIGPIPE, SIG_IGN. +#include // perror(). +#include // open() and O_RDONLY, O_WRONLY, etc. +#include // write(), read(), close(), unlink(). +#include // mkfifo(). + +#include "satterm_port.h" + +Port_Server::Port_Server(std::string const& working_path, std::string const& identifier, bool display_messages, char end_char) { + m_working_path = working_path; + m_identifier = identifier; + m_fifos.in.identifier = identifier + "_sin"; + m_fifos.out.identifier = identifier + "_sout"; + m_display_messages = display_messages; + m_end_char = end_char; + + signal(SIGPIPE, SIG_IGN); + + m_fifos.in.created = CreateFifo(m_working_path + m_fifos.in.identifier); + if (m_fifos.in.created) { + m_fifos.out.created = CreateFifo(m_working_path + m_fifos.out.identifier); + } +} + +Port_Server::~Port_Server() { + Close(); + if (m_fifos.in.created) { + // Unlink. + std::string fifo_path = m_working_path + m_fifos.in.identifier; + int status = unlink(fifo_path.c_str()); + if ((status < 0) && m_display_messages) { + std::string error_message = "Unable to unlink() fifo at " + fifo_path; + perror(error_message.c_str()); + } + } + if (m_fifos.out.created) { + // Unlink. + std::string fifo_path = m_working_path + m_fifos.out.identifier; + int status = unlink(fifo_path.c_str()); + if ((status < 0) && m_display_messages) { + std::string error_message = "Unable to unlink() fifo at " + fifo_path; + perror(error_message.c_str()); + } + } + +} + +bool Port_Server::CreateFifo(std::string const& fifo_path) { + m_error_code = {0, ""}; + bool success = false; + + remove(fifo_path.c_str()); // If temporary file already exists, delete it. + + int status = mkfifo(fifo_path.c_str(), S_IFIFO|0666); + + if (status < 0) { + // Info on possible errors for mkfifo() - https://pubs.opengroup.org/onlinepubs/009696799/functions/mkfifo.html + m_error_code = {errno, "mkfifo()"}; + if (m_display_messages) { + std::string error_message = "mkfifo() error trying to create fifo " + fifo_path; + perror(error_message.c_str()); + } + success = false; + } else { + success = true; + } + return success; +} + +bool Port_Server::Open(unsigned long timeout_seconds) { + m_fifos.in.opened = OpenRxFifo(m_working_path + m_fifos.in.identifier, timeout_seconds); + if (m_fifos.in.opened) { + m_fifos.out.opened = OpenTxFifo(m_working_path + m_fifos.out.identifier, timeout_seconds); + } + return (m_fifos.in.opened && m_fifos.out.opened); +} + +void Port_Server::Close(void) { + if (m_fifos.out.opened) { + close(m_fifos.out.descriptor); + m_fifos.out.opened = false; + } + if (m_fifos.in.opened) { + close(m_fifos.in.descriptor); + m_fifos.in.opened = false; + } +} + +bool Port_Server::IsCreated(void) { + return (m_fifos.in.created && m_fifos.out.created); +} diff --git a/src/satterm_server.cpp b/src/satterm_server.cpp index 642164e..6c858a9 100644 --- a/src/satterm_server.cpp +++ b/src/satterm_server.cpp @@ -10,67 +10,53 @@ // For a copy, see . // ----------------------------------------------------------------------------------------------------- -#include +#include // std::cout, std::cerr, std::endl. #include // std::ifstream. -#include -#include -#include -#include -#include +#include // std::string, std::to_string. +#include // std::map. +#include // std::vector. +#include // std::unique_ptr. #include // errno. #include // fork(), execl(), getcwd(). #include "satellite_terminal.h" -SatTerm_Server::SatTerm_Server(std::string const& identifier, std::string const& path_to_client_binary, bool display_messages, - std::string const& path_to_terminal_emulator_paths, std::vector out_port_identifiers, - std::vector in_port_identifiers, char end_char, std::string const& stop_port_identifier, - std::string const& stop_message) { +SatTerm_Server::SatTerm_Server(std::string const& identifier, std::string const& path_to_client_binary, bool display_messages, + std::vector port_identifiers, std::string const& stop_message, + std::string const& path_to_terminal_emulator_paths, char end_char, std::string const& stop_port_identifier, + unsigned long timeout_seconds) { m_identifier = identifier; m_display_messages = display_messages; m_end_char = end_char; m_stop_message = stop_message; - if (out_port_identifiers.size() == 0) { - out_port_identifiers.push_back("server_out"); + if (port_identifiers.size() == 0) { + port_identifiers.push_back("comms"); } - if (in_port_identifiers.size() == 0) { - in_port_identifiers.push_back("server_in"); - } - m_default_port.out = out_port_identifiers[0]; - m_default_port.in = in_port_identifiers[0]; + m_default_port_identifier = port_identifiers[0]; if (stop_port_identifier == "") { - m_stop_port_identifier = m_default_port.out; + m_stop_port_identifier = m_default_port_identifier; } m_working_path = GetWorkingPath(); - + bool success = true; if (m_working_path != "") { - success = CreatePorts(true, true, m_working_path, in_port_identifiers, m_display_messages, m_end_char, m_in_ports); - if (success) { - success = CreatePorts(true, false, m_working_path, out_port_identifiers, m_display_messages, m_end_char, m_out_ports); - } + success = CreateServerPorts(m_working_path, port_identifiers, m_display_messages, m_end_char, m_ports); if (success) { - pid_t client_pid = StartClient(path_to_terminal_emulator_paths, path_to_client_binary, m_working_path, m_end_char, m_stop_message, - in_port_identifiers, out_port_identifiers); + pid_t client_pid = StartClient(path_to_terminal_emulator_paths, path_to_client_binary, m_working_path, m_end_char, m_stop_message, port_identifiers); if (client_pid < 0) { success = false; } } - unsigned long timeout_seconds = 5; - if (success) { - success = OpenPorts(m_in_ports, timeout_seconds); - } - if (success) { - success = OpenPorts(m_out_ports, timeout_seconds); + success = OpenPorts(m_ports, timeout_seconds); } if (success) { @@ -96,7 +82,7 @@ SatTerm_Server::~SatTerm_Server() { if (m_display_messages) { std::cerr << "Waiting for client process to terminate..." << std::endl; } - // Poll for EOF on read on m_default_port.in (tells us that client write end has closed). + // Poll for EOF on read on m_default_port_identifier (tells us that client write end has closed). while(IsConnected()) { GetMessage(false, 0); } @@ -113,7 +99,7 @@ std::string SatTerm_Server::GetWorkingPath(void) { if (retval == NULL) { m_error_code = {errno, "getcwd()"}; if (m_display_messages) { - perror("getcwd() unable to obtain current working path."); + perror("getcwd() unable to obtain current working path"); } return ""; } else { @@ -128,8 +114,22 @@ std::string SatTerm_Server::GetWorkingPath(void) { } } +bool SatTerm_Server::CreateServerPorts(std::string const& working_path, std::vector port_identifiers, + bool display_messages, char end_char, std::map>& ports) { + bool success = true; + for (auto const& port_identifier : port_identifiers) { + ports.emplace(port_identifier, std::make_unique(working_path, port_identifier, display_messages, end_char)); + if (!(static_cast(ports.at(port_identifier).get())->IsCreated())) { + success = false; + m_error_code = ports.at(port_identifier)->GetErrorCode(); + break; + } + } + return success; +} + pid_t SatTerm_Server::StartClient(std::string const& path_to_terminal_emulator_paths, std::string const& path_to_client_binary, std::string const& working_path, - char end_char, std::string const& stop_message, std::vector in_port_identifiers, std::vector out_port_identifiers) { + char end_char, std::string const& stop_message, std::vector port_identifiers) { m_error_code = {0, ""}; std::vector terminal_emulator_paths = LoadTerminalEmulatorPaths(path_to_terminal_emulator_paths); @@ -141,7 +141,7 @@ pid_t SatTerm_Server::StartClient(std::string const& path_to_terminal_emulator_p // Info on possible fork() errors - https://pubs.opengroup.org/onlinepubs/009696799/functions/fork.html m_error_code = {errno, "fork()"}; if (m_display_messages) { - perror("fork() to client process failed."); + perror("fork() to client process failed"); } return process; } @@ -157,13 +157,9 @@ pid_t SatTerm_Server::StartClient(std::string const& path_to_terminal_emulator_p arg_string += " " + working_path; arg_string += " " + std::to_string((int)(end_char)); arg_string += " " + stop_message; - arg_string += " " + std::to_string(in_port_identifiers.size()); - arg_string += " " + std::to_string(out_port_identifiers.size()); + arg_string += " " + std::to_string(port_identifiers.size()); - for (const auto& identifier : in_port_identifiers) { - arg_string += " " + identifier; - } - for (const auto& identifier : out_port_identifiers) { + for (const auto& identifier : port_identifiers) { arg_string += " " + identifier; } diff --git a/src/satterm_struct.h b/src/satterm_struct.h new file mode 100644 index 0000000..5e2178c --- /dev/null +++ b/src/satterm_struct.h @@ -0,0 +1,53 @@ +// ----------------------------------------------------------------------------------------------------- +// satellite_terminal - Easily spawn and communicate bidirectionally with client processes in separate +// terminal emulator instances. +// ----------------------------------------------------------------------------------------------------- +// seb.nf.sikora@protonmail.com +// +// Copyright © 2021 Dr Seb N.F. Sikora. +// +// This work is licensed under the terms of the MIT license. +// For a copy, see . +// ----------------------------------------------------------------------------------------------------- + +struct fifo { + std::string identifier; + bool created; + bool opened; + int descriptor; + + fifo& operator=(fifo const& rhs) { + identifier = rhs.identifier; + created = rhs.created; + opened = rhs.opened; + descriptor = rhs.descriptor; + return *this; + } +}; + +struct fifo_pair { + fifo in; + fifo out; + + fifo_pair& operator=(fifo_pair const& rhs) { + in = rhs.in; + out = rhs.out; + return *this; + } +}; + +struct error_descriptor { + int err_no; + std::string err_detail; + error_descriptor& operator=(error_descriptor const& rhs) { + err_no = rhs.err_no; + err_detail = rhs.err_detail; + return *this; + } + bool operator==(error_descriptor const& rhs) const { + return ((this->err_no == rhs.err_no) && (this->err_detail == rhs.err_detail)); + } + bool operator!=(error_descriptor const& rhs) const { + return ((this->err_no != rhs.err_no) || (this->err_detail != rhs.err_detail)); + } +};