导出数据转spine
This commit is contained in:
1 parent
d258d4f435
commit
3a7c0b4092
65 files changed
+6000
No files matched your search
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dts {
|
||||
|
||||
struct ProcessRequest {
|
||||
std::filesystem::path program;
|
||||
std::vector<std::string> arguments;
|
||||
std::filesystem::path workingDirectory;
|
||||
std::chrono::milliseconds timeout{10000};
|
||||
};
|
||||
|
||||
struct ProcessResult {
|
||||
bool started = false;
|
||||
bool timedOut = false;
|
||||
int exitCode = -1;
|
||||
std::string output;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
class IProcessRunner {
|
||||
public:
|
||||
virtual ~IProcessRunner() = default;
|
||||
[[nodiscard]] virtual ProcessResult run(const ProcessRequest& request) const = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<IProcessRunner> createDefaultProcessRunner();
|
||||
|
||||
} // namespace dts
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "platform/ProcessRunner.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <memory>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace dts {
|
||||
namespace {
|
||||
|
||||
void appendAvailableOutput(int descriptor, std::string& output) {
|
||||
char buffer[4096];
|
||||
while (true) {
|
||||
const auto count = ::read(descriptor, buffer, sizeof(buffer));
|
||||
if (count > 0) {
|
||||
output.append(buffer, static_cast<std::size_t>(count));
|
||||
continue;
|
||||
}
|
||||
if (count < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class PosixProcessRunner final : public IProcessRunner {
|
||||
public:
|
||||
ProcessResult run(const ProcessRequest& request) const override {
|
||||
ProcessResult result;
|
||||
int outputPipe[2];
|
||||
if (::pipe(outputPipe) != 0) {
|
||||
result.error = std::strerror(errno);
|
||||
return result;
|
||||
}
|
||||
|
||||
const pid_t child = ::fork();
|
||||
if (child < 0) {
|
||||
result.error = std::strerror(errno);
|
||||
::close(outputPipe[0]);
|
||||
::close(outputPipe[1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (child == 0) {
|
||||
::setpgid(0, 0);
|
||||
::close(outputPipe[0]);
|
||||
::dup2(outputPipe[1], STDOUT_FILENO);
|
||||
::dup2(outputPipe[1], STDERR_FILENO);
|
||||
::close(outputPipe[1]);
|
||||
|
||||
if (!request.workingDirectory.empty()) {
|
||||
::chdir(request.workingDirectory.c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> values;
|
||||
values.reserve(request.arguments.size() + 1);
|
||||
values.push_back(request.program.string());
|
||||
values.insert(values.end(), request.arguments.begin(), request.arguments.end());
|
||||
|
||||
std::vector<char*> arguments;
|
||||
arguments.reserve(values.size() + 1);
|
||||
for (auto& value : values) {
|
||||
arguments.push_back(value.data());
|
||||
}
|
||||
arguments.push_back(nullptr);
|
||||
::execv(request.program.c_str(), arguments.data());
|
||||
::_exit(127);
|
||||
}
|
||||
|
||||
result.started = true;
|
||||
::setpgid(child, child);
|
||||
::close(outputPipe[1]);
|
||||
const auto flags = ::fcntl(outputPipe[0], F_GETFL, 0);
|
||||
::fcntl(outputPipe[0], F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
const auto deadline = std::chrono::steady_clock::now() + request.timeout;
|
||||
int status = 0;
|
||||
bool finished = false;
|
||||
while (!finished) {
|
||||
appendAvailableOutput(outputPipe[0], result.output);
|
||||
const auto waitResult = ::waitpid(child, &status, WNOHANG);
|
||||
if (waitResult == child) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
if (waitResult < 0) {
|
||||
result.error = std::strerror(errno);
|
||||
break;
|
||||
}
|
||||
if (std::chrono::steady_clock::now() >= deadline) {
|
||||
result.timedOut = true;
|
||||
::kill(-child, SIGTERM);
|
||||
for (int attempt = 0; attempt < 25; ++attempt) {
|
||||
if (::waitpid(child, &status, WNOHANG) == child) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
if (!finished) {
|
||||
::kill(-child, SIGKILL);
|
||||
::waitpid(child, &status, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
pollfd descriptor{outputPipe[0], POLLIN, 0};
|
||||
::poll(&descriptor, 1, 25);
|
||||
}
|
||||
|
||||
appendAvailableOutput(outputPipe[0], result.output);
|
||||
::close(outputPipe[0]);
|
||||
if (WIFEXITED(status)) {
|
||||
result.exitCode = WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
result.exitCode = 128 + WTERMSIG(status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IProcessRunner> createDefaultProcessRunner() {
|
||||
return std::make_unique<PosixProcessRunner>();
|
||||
}
|
||||
|
||||
} // namespace dts
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "platform/ProcessRunner.hpp"
|
||||
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
namespace dts {
|
||||
namespace {
|
||||
|
||||
std::wstring widen(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return {};
|
||||
}
|
||||
const int size = MultiByteToWideChar(CP_UTF8, 0, value.data(),
|
||||
static_cast<int>(value.size()), nullptr, 0);
|
||||
std::wstring output(static_cast<std::size_t>(size), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()),
|
||||
output.data(), size);
|
||||
return output;
|
||||
}
|
||||
|
||||
std::wstring quote(const std::wstring& value) {
|
||||
if (value.find_first_of(L" \t\"") == std::wstring::npos) {
|
||||
return value;
|
||||
}
|
||||
std::wstring output = L"\"";
|
||||
std::size_t slashes = 0;
|
||||
for (const auto character : value) {
|
||||
if (character == L'\\') {
|
||||
++slashes;
|
||||
} else if (character == L'\"') {
|
||||
output.append(slashes * 2 + 1, L'\\');
|
||||
output += character;
|
||||
slashes = 0;
|
||||
} else {
|
||||
output.append(slashes, L'\\');
|
||||
output += character;
|
||||
slashes = 0;
|
||||
}
|
||||
}
|
||||
output.append(slashes * 2, L'\\');
|
||||
return output + L"\"";
|
||||
}
|
||||
|
||||
std::wstring commandLine(const ProcessRequest& request) {
|
||||
std::wstring value = quote(request.program.wstring());
|
||||
for (const auto& argument : request.arguments) {
|
||||
value += L" " + quote(widen(argument));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
class WindowsProcessRunner final : public IProcessRunner {
|
||||
public:
|
||||
ProcessResult run(const ProcessRequest& request) const override {
|
||||
ProcessResult result;
|
||||
SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
|
||||
HANDLE readPipe = nullptr;
|
||||
HANDLE writePipe = nullptr;
|
||||
if (!CreatePipe(&readPipe, &writePipe, &attributes, 0)) {
|
||||
result.error = "CreatePipe failed.";
|
||||
return result;
|
||||
}
|
||||
SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
startup.dwFlags = STARTF_USESTDHANDLES;
|
||||
startup.hStdOutput = writePipe;
|
||||
startup.hStdError = writePipe;
|
||||
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
PROCESS_INFORMATION process{};
|
||||
auto command = commandLine(request);
|
||||
const auto directory = request.workingDirectory.wstring();
|
||||
|
||||
const BOOL created = CreateProcessW(request.program.wstring().c_str(), command.data(),
|
||||
nullptr, nullptr, TRUE, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, nullptr,
|
||||
directory.empty() ? nullptr : directory.c_str(), &startup, &process);
|
||||
CloseHandle(writePipe);
|
||||
if (!created) {
|
||||
CloseHandle(readPipe);
|
||||
result.error = "CreateProcessW failed: " + std::to_string(GetLastError());
|
||||
return result;
|
||||
}
|
||||
|
||||
result.started = true;
|
||||
HANDLE job = CreateJobObjectW(nullptr, nullptr);
|
||||
if (job) {
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
|
||||
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limits, sizeof(limits));
|
||||
AssignProcessToJobObject(job, process.hProcess);
|
||||
}
|
||||
const DWORD waitTime = static_cast<DWORD>(request.timeout.count());
|
||||
if (WaitForSingleObject(process.hProcess, waitTime) == WAIT_TIMEOUT) {
|
||||
result.timedOut = true;
|
||||
if (job) {
|
||||
TerminateJobObject(job, 1);
|
||||
} else {
|
||||
TerminateProcess(process.hProcess, 1);
|
||||
}
|
||||
WaitForSingleObject(process.hProcess, 1000);
|
||||
}
|
||||
|
||||
char buffer[4096];
|
||||
DWORD count = 0;
|
||||
while (ReadFile(readPipe, buffer, sizeof(buffer), &count, nullptr) && count > 0) {
|
||||
result.output.append(buffer, count);
|
||||
}
|
||||
DWORD exitCode = 1;
|
||||
GetExitCodeProcess(process.hProcess, &exitCode);
|
||||
result.exitCode = static_cast<int>(exitCode);
|
||||
|
||||
CloseHandle(readPipe);
|
||||
if (job) {
|
||||
CloseHandle(job);
|
||||
}
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IProcessRunner> createDefaultProcessRunner() {
|
||||
return std::make_unique<WindowsProcessRunner>();
|
||||
}
|
||||
|
||||
} // namespace dts
|
||||
Reference in new issue
Block a user