webserv/webserv/handler/CgiProcess.cpp
2025-11-06 19:10:30 +01:00

153 lines
4.7 KiB
C++

#include <webserv/handler/CgiEnvironment.hpp> // for CgiEnvironment
#include <webserv/handler/CgiHandler.hpp> // for CgiHandler
#include <webserv/handler/CgiProcess.hpp>
#include <webserv/handler/URI.hpp> // for URI
#include <webserv/http/HttpRequest.hpp> // for HttpRequest
#include <webserv/log/Log.hpp> // for Log
#include <webserv/socket/ASocket.hpp> // for ASocket
#include <webserv/socket/CgiSocket.hpp> // for CgiSocket
#include <csignal> // for kill, SIGKILL
#include <cstdlib> // for exit
#include <memory> // for allocator, make_unique, unique_ptr
#include <stdexcept> // for runtime_error
#include <string> // for basic_string, operator+, to_string, char_traits, string
#include <utility> // for move
#include <fcntl.h> // for O_CLOEXEC, O_NONBLOCK
#include <sys/wait.h> // for waitpid, WNOHANG
#include <unistd.h> // 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)
{
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<char *>(cgiPath.c_str());
args[1] = const_cast<char *>(fullPath.c_str());
}
else
{
args[0] = const_cast<char *>(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<CgiSocket>(pipeStdin[1], ASocket::IoState::WRITE, "stdin");
auto cgiStdOut = std::make_unique<CgiSocket>(pipeStdout[0], ASocket::IoState::READ, "stdout");
auto cgiStdErr = std::make_unique<CgiSocket>(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_;
}