#include // for CgiEnvironment #include // for CgiHandler #include #include // for URI #include // for HttpRequest #include // for Log #include // for ASocket #include // for CgiSocket #include // for kill, SIGKILL #include // for exit #include // for allocator, make_unique, unique_ptr #include // for runtime_error #include // for basic_string, operator+, to_string, char_traits, string #include // for move #include // for O_CLOEXEC, O_NONBLOCK #include // for waitpid, WNOHANG #include // for close, dup2, pipe2, execve, fork, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO CgiProcess::CgiProcess(const HttpRequest &request, CgiHandler &handler) : request_(request), handler_(handler), pid_(-1), status_(-1) { if (!request_.getUri().isCgi()) { throw std::runtime_error("URI is not a CGI"); } spawn(); } void CgiProcess::spawn() { const URI &uri = request_.getUri(); auto cgiPath = uri.getCgiPath(); // pipes // TODO pipe can handle flags O_CLOEXEC | O_NONBLOCK as open we should use this everywhere, then we dont need to set // non blocking and the fd will be closed when exec is run // NOLINTBEGIN int pipeStdin[2]; int pipeStdout[2]; int pipeStderr[2]; if (pipe2(pipeStdin, O_CLOEXEC | O_NONBLOCK) == -1 || pipe2(pipeStdout, O_CLOEXEC | O_NONBLOCK) == -1 || pipe2(pipeStderr, O_CLOEXEC | O_NONBLOCK) == -1) { throw std::runtime_error("Failed to create pipes"); } // NOLINTEND CgiEnvironment cgiEnv(uri, request_); pid_ = fork(); if (pid_ < 0) { close(pipeStdin[0]); close(pipeStdin[1]); close(pipeStdout[0]); close(pipeStdout[1]); close(pipeStderr[0]); close(pipeStderr[1]); throw std::runtime_error("Failed to fork"); } if (pid_ == 0) { int flags = fcntl(pipeStdin[0], F_GETFL, 0); fcntl(pipeStdin[0], F_SETFL, flags & ~O_NONBLOCK); flags = fcntl(pipeStdout[1], F_GETFL, 0); fcntl(pipeStdout[1], F_SETFL, flags & ~O_NONBLOCK); flags = fcntl(pipeStderr[1], F_GETFL, 0); fcntl(pipeStderr[1], F_SETFL, flags & ~O_NONBLOCK); dup2(pipeStdin[0], STDIN_FILENO); dup2(pipeStdout[1], STDOUT_FILENO); dup2(pipeStderr[1], STDERR_FILENO); // close(pipeStdin[0]); // close(pipeStdin[1]); // close(pipeStdout[0]); // close(pipeStdout[1]); // close(pipeStderr[0]); // close(pipeStderr[1]); // Log::debug("Executing CGI: " + cgiPath); // std::cerr << "Executing CGI: " << cgiPath << std::endl; Log::clearChannels(); // Prepare arguments std::string fullPath = uri.getFullPath(); char *args[3] = {nullptr, nullptr, nullptr}; if (!cgiPath.empty()) { args[0] = const_cast(cgiPath.c_str()); args[1] = const_cast(fullPath.c_str()); } else { args[0] = const_cast(fullPath.c_str()); } // Log::debug("With args:", {args[0], args[1]}); // TODO: Close all FDs execve(args[0], args, cgiEnv.toEnvp()); exit(1); } else { // Parent process auto cgiStdIn = std::make_unique(pipeStdin[1], ASocket::IoState::WRITE, "stdin"); auto cgiStdOut = std::make_unique(pipeStdout[0], ASocket::IoState::READ, "stdout"); auto cgiStdErr = std::make_unique(pipeStderr[0], ASocket::IoState::READ, "stderr"); close(pipeStdin[0]); close(pipeStdout[1]); close(pipeStderr[1]); Log::debug("CGI process forked with PID: " + std::to_string(pid_)); handler_.setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut), std::move(cgiStdErr)); handler_.setPid(pid_); } } void CgiProcess::kill() const noexcept { if (pid_ > 0) { ::kill(pid_, SIGKILL); Log::debug("Killed CGI process with PID: " + std::to_string(pid_)); } } void CgiProcess::wait() noexcept { if (pid_ > 0) { int status; int waitResult = ::waitpid(pid_, &status, WNOHANG); if (waitResult == -1) { Log::error("Error while waiting for CGI process with PID: " + std::to_string(pid_)); return; } if (waitResult == 0) { // Still running return; } Log::debug("CGI process with PID " + std::to_string(pid_) + " has terminated with status " + std::to_string(WEXITSTATUS(status))); status_ = WEXITSTATUS(status); pid_ = -1; } } int CgiProcess::getExitCode() const noexcept { return status_; }