#include "fastcgiprocess.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::string_literals; namespace fs = std::filesystem; namespace pt = boost::property_tree; using namespace Reichwein; #define FCGI_LISTENSOCK_FILENO 0 FastCGIProcess::FastCGIProcess(const std::filesystem::path& exe_path, const std::string& host, unsigned short port): m_pid{}, m_command{exe_path.generic_string()}, m_host{host}, m_port{port} { start(); } FastCGIProcess::FastCGIProcess(const std::filesystem::path& exe_path, const fs::path& socket_path): m_pid{}, m_command{exe_path.generic_string()}, m_socket_path{socket_path} { start(); } FastCGIProcess::~FastCGIProcess() { stop(); } void FastCGIProcess::start() { if (m_pid != 0) throw std::runtime_error("Process already running, so it can't be started"); m_pid = fork(); if (m_pid < 0) throw std::runtime_error("Fork unsuccessful."); if (m_pid == 0) { // child process branch try { int fd{}; boost::asio::io_context ioc; boost::asio::ip::tcp::resolver resolver(ioc); boost::asio::ip::tcp::acceptor acceptor(ioc); boost::asio::local::stream_protocol::acceptor file_acceptor(ioc); if (m_socket_path.empty()) { // tcp connection auto const results = resolver.resolve(m_host.c_str(), std::to_string(m_port).c_str()); if (results.begin() == results.end()) std::runtime_error("no resolve result"); boost::asio::ip::tcp::endpoint endpoint{*results.begin()}; acceptor.open(endpoint.protocol()); acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); acceptor.bind(endpoint); acceptor.listen(); fd = acceptor.native_handle(); } else { // unix domain socket std::error_code ec; fs::remove(m_socket_path, ec); // otherwise we get: "bind: Address already in use" boost::asio::local::stream_protocol::endpoint endpoint(m_socket_path); file_acceptor.open(endpoint.protocol()); file_acceptor.set_option(boost::asio::local::stream_protocol::acceptor::reuse_address(true)); file_acceptor.bind(endpoint); file_acceptor.listen(); fd = file_acceptor.native_handle(); } if (fd != FCGI_LISTENSOCK_FILENO) { close(FCGI_LISTENSOCK_FILENO); dup2(fd, FCGI_LISTENSOCK_FILENO); close(fd); } execl(m_command.c_str(), m_command.c_str(), (const char*)nullptr); } catch (const std::exception& ex) { std::cout << "FastCGI process error: " << ex.what() << std::endl; } exit(0); } // wait for server to start up if (m_socket_path.empty()) { // tcp connection Process::wait_for_pid_listening_on(m_pid, m_port); } else { // unix domain socket Process::wait_for_pid_listening_on(m_pid, m_socket_path); } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } void FastCGIProcess::stop() { if (m_pid == 0) throw std::runtime_error("Process not running, so it can't be stopped"); if (kill(m_pid, SIGTERM) != 0) throw std::runtime_error("Unable to kill process"); if (int result = waitpid(m_pid, NULL, 0); result != m_pid) throw std::runtime_error("waitpid returned "s + std::to_string(result)); if (!m_socket_path.empty()) { std::error_code ec; fs::remove(m_socket_path, ec); } m_pid = 0; } bool FastCGIProcess::is_running() { if (m_pid == 0) return false; return Process::is_running(m_pid); }