From d14582a1d92e036780166a0b5ec0494d7353cc75 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Fri, 13 Jan 2023 16:20:42 +0100 Subject: Implemented and tested managed FCGI application start, separated out process check functions to libreichwein --- plugins/fcgi/Makefile | 1 + plugins/fcgi/fastcgiprocess.cpp | 151 ++++++++++++++++++++++++++++++++++++++++ plugins/fcgi/fastcgiprocess.h | 30 ++++++++ plugins/fcgi/socket.cpp | 59 ++++++++++++++-- plugins/fcgi/socket.h | 20 ++++++ 5 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 plugins/fcgi/fastcgiprocess.cpp create mode 100644 plugins/fcgi/fastcgiprocess.h (limited to 'plugins/fcgi') diff --git a/plugins/fcgi/Makefile b/plugins/fcgi/Makefile index cd99cbe..e878c5d 100644 --- a/plugins/fcgi/Makefile +++ b/plugins/fcgi/Makefile @@ -20,6 +20,7 @@ LDLIBS=\ -ldl PROGSRC=\ + fastcgiprocess.cpp \ fcgi.cpp \ fcgiid.cpp \ socket.cpp diff --git a/plugins/fcgi/fastcgiprocess.cpp b/plugins/fcgi/fastcgiprocess.cpp new file mode 100644 index 0000000..dd51583 --- /dev/null +++ b/plugins/fcgi/fastcgiprocess.cpp @@ -0,0 +1,151 @@ +#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); +} + diff --git a/plugins/fcgi/fastcgiprocess.h b/plugins/fcgi/fastcgiprocess.h new file mode 100644 index 0000000..07b6539 --- /dev/null +++ b/plugins/fcgi/fastcgiprocess.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +class FastCGIProcess +{ +public: + FastCGIProcess(const std::filesystem::path& exe_path, const std::string& host, unsigned short port); + FastCGIProcess(const std::filesystem::path& exe_path, const std::filesystem::path& socket_path); + ~FastCGIProcess(); + bool is_running(); + +private: + void start(); + void stop(); + + pid_t m_pid{}; + std::string m_command; + std::string m_host; + unsigned short m_port{}; + std::filesystem::path m_socket_path; +}; diff --git a/plugins/fcgi/socket.cpp b/plugins/fcgi/socket.cpp index 2b34bc3..c899de5 100644 --- a/plugins/fcgi/socket.cpp +++ b/plugins/fcgi/socket.cpp @@ -1,5 +1,7 @@ #include "socket.h" +#include + #include #include @@ -22,16 +24,16 @@ SocketFactory::SocketFactory() std::shared_ptr SocketFactory::create(const std::string& app_addr) { + std::error_code ec; size_t pos { app_addr.find_last_of(':') }; if (pos != app_addr.npos) { // tcp socket: host:port return std::make_shared(app_addr.substr(0, pos), app_addr.substr(pos + 1), m_io_context); - } else if (fs::is_socket(fs::path{app_addr})) { // Unix domain socket + } else if (fs::is_socket(fs::path{app_addr}, ec)) { // Unix domain socket return std::make_shared(app_addr, m_io_context); - } else if (fs::is_regular_file(fs::path{app_addr})) { // Executable to start - // TODO - std::cerr << "FCGI Error: Executable FCGI not yet implemented." << std::endl; + } else if (fs::is_regular_file(fs::path{app_addr}, ec)) { // Executable to start + return std::make_shared(app_addr, m_io_context); } else { std::cerr << "FCGI Error: Invalid app_addr type." << std::endl; } @@ -232,3 +234,52 @@ size_t FileSocket::read(std::vector& data) } } +std::string generate_unix_domain_socket(const std::string& directory) +{ + for (int i = 0; i < 10000; i++) { + std::string path{fmt::format("{}/fcgi-socket{}", directory, i)}; + if (fs::exists(path)) + continue; + return path; + } + throw std::runtime_error("dynamic unix domain socket couldn't be generated."); +} + +FileSocketApp::FileSocketApp(const std::string& app_addr, boost::asio::io_context& io_context): + m_socket_file{generate_unix_domain_socket("/var/lib/webserver")}, + m_fcgi_process{app_addr, m_socket_file}, + m_file_socket{m_socket_file, io_context} +{ +} + +FileSocketApp::~FileSocketApp() +{ + std::error_code ec; + fs::remove(m_socket_file, ec); +} + +void FileSocketApp::open() +{ + m_file_socket.open(); +} + +void FileSocketApp::close() +{ + m_file_socket.close(); +} + +bool FileSocketApp::is_open() +{ + return m_file_socket.is_open(); +} + +size_t FileSocketApp::write(const std::vector& data) +{ + return m_file_socket.write(data); +} + +size_t FileSocketApp::read(std::vector& data) +{ + return m_file_socket.read(data); +} + diff --git a/plugins/fcgi/socket.h b/plugins/fcgi/socket.h index 272b844..ceee2a4 100644 --- a/plugins/fcgi/socket.h +++ b/plugins/fcgi/socket.h @@ -1,5 +1,6 @@ #pragma once +#include "fastcgiprocess.h" #include "fcgiid.h" #include @@ -83,3 +84,22 @@ public: size_t read(std::vector& data) override; }; +// File Socket, with Application started by us +class FileSocketApp: public Socket +{ +public: + FileSocketApp(const std::string& app_addr, boost::asio::io_context& io_context); + ~FileSocketApp() override; + + void open() override; + void close() override; + bool is_open() override; + size_t write(const std::vector& data) override; + size_t read(std::vector& data) override; + +private: + std::string m_socket_file; + FastCGIProcess m_fcgi_process; // Application server + FileSocket m_file_socket; // Connection from client side +}; + -- cgit v1.2.3