mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-07 02:32:32 +00:00
203 lines
5.7 KiB
C++
203 lines
5.7 KiB
C++
#include "config.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/un.h>
|
|
#include <torrent/torrent.h>
|
|
#include <torrent/exceptions.h>
|
|
#include <torrent/net/fd.h>
|
|
#include <torrent/net/socket_address.h>
|
|
#include <torrent/system/poll.h>
|
|
#include <torrent/runtime/socket_manager.h>
|
|
|
|
#include "control.h"
|
|
#include "globals.h"
|
|
#include "rpc/scgi_task.h"
|
|
|
|
// TODO: Figure out why moving this to the top causes a build error.
|
|
#include "rpc/scgi.h"
|
|
|
|
namespace rpc {
|
|
|
|
SCgi::SCgi() {
|
|
std::generate(m_tasks.begin(), m_tasks.end(), []() { return std::make_unique<SCgiTask>(); });
|
|
|
|
m_current = m_tasks.begin();
|
|
}
|
|
|
|
SCgi::~SCgi() {
|
|
assert(!is_open() && "SCgi::~SCgi() called while open");
|
|
}
|
|
|
|
void
|
|
SCgi::open_port(sockaddr* sa, unsigned int length, bool dont_route) {
|
|
int fd = torrent::fd_open_family(torrent::fd_flag_stream | torrent::fd_flag_nonblock | torrent::fd_flag_reuse_address,
|
|
sa->sa_family);
|
|
|
|
if (fd == -1)
|
|
throw torrent::resource_error("Could not open socket for listening: " + std::string(std::strerror(errno)));
|
|
|
|
if (dont_route && !torrent::fd_set_dont_route(fd, true)) {
|
|
torrent::fd_close(fd);
|
|
throw torrent::resource_error("Could not set socket option IP_DONTROUTE: " + std::string(std::strerror(errno)));
|
|
}
|
|
|
|
set_file_descriptor(fd);
|
|
|
|
open(reinterpret_cast<sockaddr*>(sa), length);
|
|
|
|
torrent::runtime::socket_manager()->register_event_or_throw(this, torrent::runtime::category_rpc, []() {});
|
|
}
|
|
|
|
void
|
|
SCgi::open_named(const std::string& filename) {
|
|
if (filename.empty() || filename.size() >= sizeof(sockaddr_un::sun_path))
|
|
throw torrent::resource_error("Invalid filename length.");
|
|
|
|
auto buffer = std::make_unique<char[]>(sizeof(sockaddr_un) + filename.size() + 1);
|
|
|
|
sockaddr_un* sa = reinterpret_cast<sockaddr_un*>(buffer.get());
|
|
sa->sun_family = AF_LOCAL;
|
|
|
|
std::memcpy(sa->sun_path, filename.c_str(), filename.size() + 1);
|
|
|
|
int fd = torrent::fd_open_local(torrent::fd_flag_stream | torrent::fd_flag_nonblock | torrent::fd_flag_reuse_address);
|
|
|
|
if (fd == -1)
|
|
throw torrent::resource_error("Could not open socket for listening: " + std::string(std::strerror(errno)));
|
|
|
|
set_file_descriptor(fd);
|
|
|
|
open(reinterpret_cast<sockaddr*>(sa), offsetof(struct sockaddr_un, sun_path) + filename.size() + 1);
|
|
|
|
torrent::runtime::socket_manager()->register_event_or_throw(this, torrent::runtime::category_rpc, []() {});
|
|
|
|
m_path = filename;
|
|
}
|
|
|
|
void
|
|
SCgi::open_fd(int fd) {
|
|
if (!torrent::fd_set_nonblock(fd))
|
|
throw torrent::resource_error("Could not set non-blocking on systemd fd: " +
|
|
std::string(std::strerror(errno)));
|
|
set_file_descriptor(fd);
|
|
|
|
// fd is already bound and listening; no bind()/listen() needed.
|
|
|
|
torrent::runtime::socket_manager()->register_event_or_throw(this, torrent::runtime::category_rpc, []() {});
|
|
}
|
|
|
|
void
|
|
SCgi::open(sockaddr* sa, unsigned int length) {
|
|
try {
|
|
if (::bind(file_descriptor(), sa, length) == -1)
|
|
throw torrent::resource_error("Could not bind socket for listening: " + std::string(std::strerror(errno)));
|
|
|
|
if (!torrent::fd_listen(file_descriptor(), max_tasks))
|
|
throw torrent::resource_error("Could not prepare socket for listening: " + std::string(std::strerror(errno)));
|
|
|
|
} catch (torrent::resource_error& e) {
|
|
torrent::fd_close(file_descriptor());
|
|
set_file_descriptor(-1);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void
|
|
SCgi::activate() {
|
|
assert(torrent::this_thread::thread() == scgi_thread::thread());
|
|
|
|
torrent::this_thread::poll()->open(this);
|
|
torrent::this_thread::poll()->insert_read(this);
|
|
}
|
|
|
|
// TODO: This should close the fd to avoid reuse.
|
|
void
|
|
SCgi::stop() {
|
|
assert(torrent::this_thread::thread() == scgi_thread::thread());
|
|
|
|
if (!is_open())
|
|
return;
|
|
|
|
for (auto& itr : m_tasks) {
|
|
if (itr->is_open())
|
|
itr->close();
|
|
}
|
|
|
|
torrent::runtime::socket_manager()->unregister_event_or_throw(this, [this]() {
|
|
torrent::this_thread::poll()->remove_and_close(this);
|
|
|
|
torrent::fd_close(file_descriptor());
|
|
set_file_descriptor(-1);
|
|
});
|
|
|
|
if (!m_path.empty())
|
|
::unlink(m_path.c_str());
|
|
}
|
|
|
|
void
|
|
SCgi::event_read() {
|
|
if (m_current < m_tasks.begin() || m_current >= m_tasks.end())
|
|
throw torrent::internal_error("SCgi::event_read() m_current is out of bounds");
|
|
|
|
while (true) {
|
|
// TODO: Optimize this by keeping track of count.
|
|
auto prev = m_current;
|
|
|
|
m_current = std::find_if(m_current + 1, m_tasks.end(), [](const auto& task) { return !task->is_open(); });
|
|
|
|
if (m_current == m_tasks.end())
|
|
m_current = std::find_if(m_tasks.begin(), prev, [](const auto& task) { return !task->is_open(); });
|
|
|
|
int fd = torrent::fd_accept(file_descriptor());
|
|
|
|
if (fd == -1) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
|
return;
|
|
|
|
// Force a new event_read() call just to be sure we don't enter an infinite loop.
|
|
if (errno == ECONNABORTED)
|
|
return;
|
|
|
|
throw torrent::resource_error("Listener port accept() failed: " + std::string(std::strerror(errno)));
|
|
}
|
|
|
|
if (m_current == prev) {
|
|
torrent::fd_close(fd);
|
|
continue;
|
|
}
|
|
|
|
auto open_func = [this, fd, task = m_current->get()]() {
|
|
task->open(this, fd);
|
|
};
|
|
|
|
auto cleanup_func = [fd, task = m_current->get()](bool opened) {
|
|
if (!opened) {
|
|
torrent::fd_close(fd);
|
|
return;
|
|
}
|
|
|
|
task->cancel_open();
|
|
};
|
|
|
|
bool result = torrent::runtime::socket_manager()->open_event_or_cleanup(m_current->get(), torrent::runtime::category_rpc, open_func, cleanup_func);
|
|
|
|
if (!result)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
SCgi::event_write() {
|
|
throw torrent::internal_error("Listener does not support write().");
|
|
}
|
|
|
|
void
|
|
SCgi::event_error() {
|
|
throw torrent::internal_error("SCGI listener port received an error event.");
|
|
}
|
|
|
|
}
|