Files
2026-09-11 10:00:16 +08:00

150 lines
4.6 KiB
C++

#ifndef _WIN32
#include "platform.hpp"
#include <chrono>
#include <csignal>
#include <fcntl.h>
#include <set>
#include <sstream>
#include <stdexcept>
#include <sys/types.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
namespace connector {
namespace {
constexpr unsigned short kConnectorPort = 27843;
std::filesystem::path lsof_path() {
for (const char* candidate : {"/usr/sbin/lsof", "/usr/bin/lsof"}) {
if (std::filesystem::exists(candidate)) return candidate;
}
return {};
}
std::set<pid_t> connector_port_pids() {
const std::filesystem::path executable = lsof_path();
if (executable.empty()) return {};
const ProcessResult result = run_process(executable.string(),
{"-nP", "-tiTCP:" + std::to_string(kConnectorPort), "-sTCP:LISTEN"}, {}, 3);
if (result.timed_out || (result.exit_code != 0 && result.exit_code != 1)) return {};
std::set<pid_t> pids;
std::istringstream lines(result.output);
std::string line;
while (std::getline(lines, line)) {
try {
const long value = std::stol(line);
if (value > 0) pids.insert(static_cast<pid_t>(value));
} catch (...) {
// lsof 可能输出诊断信息;只接受纯数字 PID。
}
}
return pids;
}
} // namespace
ProcessResult run_process(const std::string& executable,
const std::vector<std::string>& arguments,
const std::filesystem::path& working_directory,
int timeout_seconds) {
int output_pipe[2];
if (pipe(output_pipe) != 0) throw std::runtime_error("无法创建 Spine 输出管道");
const pid_t child = fork();
if (child < 0) {
close(output_pipe[0]);
close(output_pipe[1]);
throw std::runtime_error("无法启动 Spine 进程");
}
if (child == 0) {
close(output_pipe[0]);
dup2(output_pipe[1], STDOUT_FILENO);
dup2(output_pipe[1], STDERR_FILENO);
close(output_pipe[1]);
if (!working_directory.empty()) chdir(working_directory.c_str());
std::vector<char*> argv;
argv.push_back(const_cast<char*>(executable.c_str()));
for (const std::string& argument : arguments) argv.push_back(const_cast<char*>(argument.c_str()));
argv.push_back(nullptr);
execv(executable.c_str(), argv.data());
_exit(127);
}
close(output_pipe[1]);
fcntl(output_pipe[0], F_SETFL, fcntl(output_pipe[0], F_GETFL) | O_NONBLOCK);
ProcessResult result;
int status = 0;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_seconds);
while (true) {
char buffer[8192];
const ssize_t count = read(output_pipe[0], buffer, sizeof(buffer));
if (count > 0 && result.output.size() < 4 * 1024 * 1024) result.output.append(buffer, static_cast<std::size_t>(count));
const pid_t state = waitpid(child, &status, WNOHANG);
if (state == child) break;
if (std::chrono::steady_clock::now() >= deadline) {
result.timed_out = true;
kill(child, SIGKILL);
waitpid(child, &status, 0);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
while (true) {
char buffer[8192];
const ssize_t count = read(output_pipe[0], buffer, sizeof(buffer));
if (count <= 0) break;
if (result.output.size() < 4 * 1024 * 1024) result.output.append(buffer, static_cast<std::size_t>(count));
}
close(output_pipe[0]);
if (WIFEXITED(status)) result.exit_code = WEXITSTATUS(status);
else if (WIFSIGNALED(status)) result.exit_code = 128 + WTERMSIG(status);
return result;
}
bool connector_port_in_use() {
return !connector_port_pids().empty();
}
bool stop_connector_port(std::string& error) {
const std::set<pid_t> pids = connector_port_pids();
if (pids.empty()) {
error.clear();
return true;
}
for (const pid_t pid : pids) {
if (pid == getpid()) {
error = "无法从外部停止当前连接器进程";
return false;
}
if (kill(pid, SIGTERM) != 0) {
error = "无法停止占用端口 27843 的进程,请检查系统权限";
return false;
}
}
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
while (std::chrono::steady_clock::now() < deadline) {
if (!connector_port_in_use()) {
error.clear();
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
for (const pid_t pid : pids) {
if (kill(pid, 0) == 0) kill(pid, SIGKILL);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (connector_port_in_use()) {
error = "已请求停止进程,但端口 27843 仍未释放";
return false;
}
error.clear();
return true;
}
} // namespace connector
#endif