Checkpoint 2: integrate native Editor, MCP, gameplay builds and standalone export

This commit is contained in:
Emil
2026-09-18 03:40:15 +03:00
parent 5c6b24d34d
commit 999686a896
125 changed files with 16086 additions and 1714 deletions
+92 -48
View File
@@ -1,72 +1,116 @@
#include <faset/core/hash.hpp>
#include <faset/core/error.hpp>
#include <array>
#include <bit>
#include <cstdint>
#include <faset/core/error.hpp>
#include <faset/core/hash.hpp>
#include <fstream>
#include <vector>
namespace faset {
namespace {
constexpr std::array<std::uint32_t,64> constants = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
constexpr std::array<std::uint32_t, 64> constants = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
class Digest {
std::array<std::uint32_t,8> state_{0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19};
std::array<std::uint8_t,64> pending_{};
std::uint64_t count_=0;
std::size_t used_=0;
std::array<std::uint32_t, 8> state_{0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
std::array<std::uint8_t, 64> pending_{};
std::uint64_t count_ = 0;
std::size_t used_ = 0;
void block() {
std::array<std::uint32_t,64> w{};
for (int i=0;i<16;++i) w[i]=(std::uint32_t(pending_[i*4])<<24)|(std::uint32_t(pending_[i*4+1])<<16)|(std::uint32_t(pending_[i*4+2])<<8)|pending_[i*4+3];
for (int i=16;i<64;++i) {
const auto x=w[i-15],y=w[i-2];
w[i]=w[i-16]+(std::rotr(x,7)^std::rotr(x,18)^(x>>3))+w[i-7]+(std::rotr(y,17)^std::rotr(y,19)^(y>>10));
std::array<std::uint32_t, 64> w{};
for (int i = 0; i < 16; ++i)
w[i] = (std::uint32_t(pending_[i * 4]) << 24) |
(std::uint32_t(pending_[i * 4 + 1]) << 16) |
(std::uint32_t(pending_[i * 4 + 2]) << 8) | pending_[i * 4 + 3];
for (int i = 16; i < 64; ++i) {
const auto x = w[i - 15], y = w[i - 2];
w[i] = w[i - 16] + (std::rotr(x, 7) ^ std::rotr(x, 18) ^ (x >> 3)) + w[i - 7] +
(std::rotr(y, 17) ^ std::rotr(y, 19) ^ (y >> 10));
}
auto a=state_[0],b=state_[1],c=state_[2],d=state_[3],e=state_[4],f=state_[5],g=state_[6],h=state_[7];
for (int i=0;i<64;++i) {
const auto t1=h+(std::rotr(e,6)^std::rotr(e,11)^std::rotr(e,25))+((e&f)^(~e&g))+constants[i]+w[i];
const auto t2=(std::rotr(a,2)^std::rotr(a,13)^std::rotr(a,22))+((a&b)^(a&c)^(b&c));
h=g;g=f;f=e;e=d+t1;d=c;c=b;b=a;a=t1+t2;
auto a = state_[0], b = state_[1], c = state_[2], d = state_[3], e = state_[4],
f = state_[5], g = state_[6], h = state_[7];
for (int i = 0; i < 64; ++i) {
const auto t1 = h + (std::rotr(e, 6) ^ std::rotr(e, 11) ^ std::rotr(e, 25)) +
((e & f) ^ (~e & g)) + constants[i] + w[i];
const auto t2 = (std::rotr(a, 2) ^ std::rotr(a, 13) ^ std::rotr(a, 22)) +
((a & b) ^ (a & c) ^ (b & c));
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
state_[0]+=a;state_[1]+=b;state_[2]+=c;state_[3]+=d;state_[4]+=e;state_[5]+=f;state_[6]+=g;state_[7]+=h;
state_[0] += a;
state_[1] += b;
state_[2] += c;
state_[3] += d;
state_[4] += e;
state_[5] += f;
state_[6] += g;
state_[7] += h;
}
public:
public:
void update(std::span<const std::byte> bytes) {
count_+=bytes.size();
for (auto byte:bytes) {
pending_[used_++]=std::to_integer<std::uint8_t>(byte);
if (used_==64) { block();used_=0; }
count_ += bytes.size();
for (auto byte : bytes) {
pending_[used_++] = std::to_integer<std::uint8_t>(byte);
if (used_ == 64) {
block();
used_ = 0;
}
}
}
std::string finish() {
const std::uint64_t bits=count_*8;
pending_[used_++]=0x80;
if (used_>56) { while(used_<64) pending_[used_++]=0;block();used_=0; }
while(used_<56) pending_[used_++]=0;
for (int i=7;i>=0;--i) pending_[used_++]=std::uint8_t(bits>>(i*8));
const std::uint64_t bits = count_ * 8;
pending_[used_++] = 0x80;
if (used_ > 56) {
while (used_ < 64)
pending_[used_++] = 0;
block();
used_ = 0;
}
while (used_ < 56)
pending_[used_++] = 0;
for (int i = 7; i >= 0; --i)
pending_[used_++] = std::uint8_t(bits >> (i * 8));
block();
constexpr char hex[]="0123456789abcdef";
std::string result;result.reserve(64);
for (auto word:state_) for (int i=7;i>=0;--i) result+=hex[(word>>(i*4))&15];
constexpr char hex[] = "0123456789abcdef";
std::string result;
result.reserve(64);
for (auto word : state_)
for (int i = 7; i >= 0; --i)
result += hex[(word >> (i * 4)) & 15];
return result;
}
};
}
std::string sha256(std::span<const std::byte> bytes) { Digest digest;digest.update(bytes);return digest.finish(); }
std::string sha256_file(const std::filesystem::path& path) {
std::ifstream stream(path,std::ios::binary);
require(bool(stream),"io.open","Cannot open file for hashing: "+path.string());
Digest digest;std::array<char,65536> buffer{};
while(stream) { stream.read(buffer.data(),buffer.size());digest.update(std::as_bytes(std::span(buffer.data(),static_cast<std::size_t>(stream.gcount())))); }
require(stream.eof(),"io.read","Cannot read file for hashing: "+path.string());
} // namespace
std::string sha256(std::span<const std::byte> bytes) {
Digest digest;
digest.update(bytes);
return digest.finish();
}
std::string sha256_file(const std::filesystem::path& path) {
std::ifstream stream(path, std::ios::binary);
require(bool(stream), "io.open", "Cannot open file for hashing: " + path.string());
Digest digest;
std::array<char, 65536> buffer{};
while (stream) {
stream.read(buffer.data(), buffer.size());
digest.update(
std::as_bytes(std::span(buffer.data(), static_cast<std::size_t>(stream.gcount()))));
}
require(stream.eof(), "io.read", "Cannot read file for hashing: " + path.string());
return digest.finish();
}
} // namespace faset
+97 -43
View File
@@ -1,9 +1,9 @@
#include <faset/core/io.hpp>
#include <faset/core/error.hpp>
#include <array>
#include <faset/core/error.hpp>
#include <faset/core/io.hpp>
#include <fstream>
#include <random>
#include <mutex>
#include <random>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
@@ -16,57 +16,111 @@ namespace faset {
std::string new_id() {
static std::mutex mutex;
static std::random_device random;
std::array<unsigned char,16> bytes{};
{ std::lock_guard lock(mutex); for(auto& byte:bytes) byte=static_cast<unsigned char>(random()); }
bytes[6]=(bytes[6]&0x0f)|0x40;bytes[8]=(bytes[8]&0x3f)|0x80;
constexpr char hex[]="0123456789abcdef";
std::string result;result.reserve(36);
for(std::size_t i=0;i<bytes.size();++i) { if(i==4||i==6||i==8||i==10)result+='-';result+=hex[bytes[i]>>4];result+=hex[bytes[i]&15]; }
std::array<unsigned char, 16> bytes{};
{
std::lock_guard lock(mutex);
for (auto& byte : bytes)
byte = static_cast<unsigned char>(random());
}
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
constexpr char hex[] = "0123456789abcdef";
std::string result;
result.reserve(36);
for (std::size_t i = 0; i < bytes.size(); ++i) {
if (i == 4 || i == 6 || i == 8 || i == 10)
result += '-';
result += hex[bytes[i] >> 4];
result += hex[bytes[i] & 15];
}
return result;
}
std::string read_text(const std::filesystem::path& path) {
std::ifstream stream(path,std::ios::binary);
require(bool(stream),"io.open","Cannot open file: "+path.string());
std::string value((std::istreambuf_iterator<char>(stream)),{});
require(!stream.bad(),"io.read","Cannot read file: "+path.string());return value;
std::ifstream stream(path, std::ios::binary);
require(bool(stream), "io.open", "Cannot open file: " + path.string());
std::string value((std::istreambuf_iterator<char>(stream)), {});
require(!stream.bad(), "io.read", "Cannot read file: " + path.string());
return value;
}
Json read_json(const std::filesystem::path& path) {
try { return Json::parse(read_text(path)); }
catch(const Json::exception& error) { throw Error("format.json","Invalid JSON in "+path.string(),{{"reason",error.what()}}); }
try {
return Json::parse(read_text(path));
} catch (const Json::exception& error) {
throw Error("format.json", "Invalid JSON in " + path.string(), {{"reason", error.what()}});
}
}
void atomic_write(const std::filesystem::path& path,std::string_view bytes) {
const auto parent=path.has_parent_path()?path.parent_path():std::filesystem::path(".");
void atomic_write(const std::filesystem::path& path, std::string_view bytes) {
const auto parent = path.has_parent_path() ? path.parent_path() : std::filesystem::path(".");
std::filesystem::create_directories(parent);
const auto temporary=parent/(path.filename().string()+".tmp-"+new_id());
const auto temporary = parent / (path.filename().string() + ".tmp-" + new_id());
try {
#ifdef _WIN32
HANDLE file=CreateFileW(temporary.c_str(),GENERIC_WRITE,0,nullptr,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,nullptr);
require(file!=INVALID_HANDLE_VALUE,"io.create","Cannot create temporary file");
bool ok=true;std::size_t offset=0;
while(offset<bytes.size()) { DWORD written=0; const auto count=static_cast<DWORD>(std::min<std::size_t>(bytes.size()-offset,1u<<30)); if(!WriteFile(file,bytes.data()+offset,count,&written,nullptr)||written==0){ok=false;break;} offset+=written; }
ok=FlushFileBuffers(file)&&ok;CloseHandle(file);
require(ok,"io.write","Cannot flush temporary file");
require(MoveFileExW(temporary.c_str(),path.c_str(),MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH)!=0,"io.replace","Cannot publish file: "+path.string());
HANDLE file = CreateFileW(temporary.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL, nullptr);
require(file != INVALID_HANDLE_VALUE, "io.create", "Cannot create temporary file");
bool ok = true;
std::size_t offset = 0;
while (offset < bytes.size()) {
DWORD written = 0;
const auto count =
static_cast<DWORD>(std::min<std::size_t>(bytes.size() - offset, 1u << 30));
if (!WriteFile(file, bytes.data() + offset, count, &written, nullptr) || written == 0) {
ok = false;
break;
}
offset += written;
}
ok = FlushFileBuffers(file) && ok;
CloseHandle(file);
require(ok, "io.write", "Cannot flush temporary file");
require(MoveFileExW(temporary.c_str(), path.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0,
"io.replace", "Cannot publish file: " + path.string());
#else
const int fd=::open(temporary.c_str(),O_WRONLY|O_CREAT|O_EXCL,0644);
require(fd>=0,"io.create","Cannot create temporary file");
bool ok=true;std::size_t offset=0;
while(offset<bytes.size()) { const auto count=::write(fd,bytes.data()+offset,bytes.size()-offset);if(count<0&&errno==EINTR)continue;if(count<=0){ok=false;break;}offset+=static_cast<std::size_t>(count); }
ok=(::fsync(fd)==0)&&ok;const auto closed=::close(fd);ok=ok&&(closed==0);
require(ok,"io.write","Cannot flush temporary file");
std::filesystem::rename(temporary,path);
const int directory=::open(parent.c_str(),O_RDONLY|O_DIRECTORY);
if(directory>=0){::fsync(directory);::close(directory);}
const int fd = ::open(temporary.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
require(fd >= 0, "io.create", "Cannot create temporary file");
bool ok = true;
std::size_t offset = 0;
while (offset < bytes.size()) {
const auto count = ::write(fd, bytes.data() + offset, bytes.size() - offset);
if (count < 0 && errno == EINTR)
continue;
if (count <= 0) {
ok = false;
break;
}
offset += static_cast<std::size_t>(count);
}
ok = (::fsync(fd) == 0) && ok;
const auto closed = ::close(fd);
ok = ok && (closed == 0);
require(ok, "io.write", "Cannot flush temporary file");
std::filesystem::rename(temporary, path);
const int directory = ::open(parent.c_str(), O_RDONLY | O_DIRECTORY);
if (directory >= 0) {
::fsync(directory);
::close(directory);
}
#endif
} catch(...) { std::error_code ignored;std::filesystem::remove(temporary,ignored);throw; }
} catch (...) {
std::error_code ignored;
std::filesystem::remove(temporary, ignored);
throw;
}
}
void atomic_write_json(const std::filesystem::path& path,const Json& value) { atomic_write(path,value.dump(2)+"\n"); }
std::filesystem::path project_path(const std::filesystem::path& root,const std::filesystem::path& relative) {
require(!relative.is_absolute(),"path.outside_project","Expected a path relative to the project");
const auto canonical=std::filesystem::weakly_canonical(root);
const auto target=std::filesystem::weakly_canonical(canonical/relative);
auto a=canonical.begin(),b=target.begin();
for(;a!=canonical.end();++a,++b) require(b!=target.end()&&*a==*b,"path.outside_project","Path escapes the project root");
void atomic_write_json(const std::filesystem::path& path, const Json& value) {
atomic_write(path, value.dump(2) + "\n");
}
std::filesystem::path project_path(const std::filesystem::path& root,
const std::filesystem::path& relative) {
require(!relative.is_absolute(), "path.outside_project",
"Expected a path relative to the project");
const auto canonical = std::filesystem::weakly_canonical(root);
const auto target = std::filesystem::weakly_canonical(canonical / relative);
auto a = canonical.begin(), b = target.begin();
for (; a != canonical.end(); ++a, ++b)
require(b != target.end() && *a == *b, "path.outside_project",
"Path escapes the project root");
return target;
}
}
} // namespace faset
+427
View File
@@ -0,0 +1,427 @@
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <faset/core/process.hpp>
#include <stdexcept>
#include <thread>
#include <utility>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#else
#include <cerrno>
#include <csignal>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char** environ;
#endif
namespace faset {
namespace {
#ifdef _WIN32
std::wstring widen(const std::string& value) {
if (value.empty())
return {};
int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(),
static_cast<int>(value.size()), nullptr, 0);
if (!length)
throw std::runtime_error("Invalid UTF-8 process argument");
std::wstring result(length, 0);
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast<int>(value.size()),
result.data(), length);
return result;
}
std::wstring quote(const std::wstring& value) {
std::wstring result = L"\"";
std::size_t slashes{};
for (wchar_t c : value) {
if (c == L'\\') {
++slashes;
continue;
}
if (c == L'\"') {
result.append(slashes * 2 + 1, L'\\');
result += c;
} else {
result.append(slashes, L'\\');
result += c;
}
slashes = 0;
}
result.append(slashes * 2, L'\\');
result += L'\"';
return result;
}
struct CaseInsensitive {
bool operator()(const std::wstring& a, const std::wstring& b) const {
return _wcsicmp(a.c_str(), b.c_str()) < 0;
}
};
#else
std::filesystem::path resolve_program(const std::string& name, const std::string& path,
const std::filesystem::path& cwd) {
if (name.find('/') != std::string::npos) {
auto file = std::filesystem::path(name);
if (file.is_relative())
file = cwd / file;
if (::access(file.c_str(), X_OK) == 0 && !std::filesystem::is_directory(file))
return std::filesystem::absolute(file);
throw std::runtime_error("Executable is missing or not executable: " + name);
}
std::size_t from{};
do {
auto end = path.find(':', from);
auto directory = path.substr(from, end == std::string::npos ? end : end - from);
auto file = (directory.empty() ? cwd : std::filesystem::path(directory)) / name;
if (file.is_relative())
file = cwd / file;
if (::access(file.c_str(), X_OK) == 0 && !std::filesystem::is_directory(file))
return std::filesystem::absolute(file);
if (end == std::string::npos)
break;
from = end + 1;
} while (true);
throw std::runtime_error("Executable not found on PATH: " + name);
}
#endif
} // namespace
std::filesystem::path find_executable(const std::string& name) {
#ifdef _WIN32
auto wide = widen(name);
std::vector<wchar_t> buffer(32768);
DWORD length = SearchPathW(nullptr, wide.c_str(), L".exe", static_cast<DWORD>(buffer.size()),
buffer.data(), nullptr);
if (length == 0 || length >= buffer.size())
throw std::runtime_error("Executable not found on PATH: " + name);
return std::filesystem::path(std::wstring(buffer.data(), length));
#else
const char* path = std::getenv("PATH");
return resolve_program(name, path ? path : "", std::filesystem::current_path());
#endif
}
struct Process::Impl {
#ifdef _WIN32
HANDLE process{}, thread{}, job{}, output{};
#else
pid_t pid{-1};
int output{-1};
#endif
bool running{};
std::optional<int> exit_code;
~Impl() {
try {
cancel();
} catch (...) {
}
#ifdef _WIN32
if (output)
CloseHandle(output);
if (thread)
CloseHandle(thread);
if (process)
CloseHandle(process);
if (job)
CloseHandle(job);
#else
if (output >= 0)
::close(output);
#endif
}
ProcessPoll poll() {
std::string text;
char buffer[8192];
#ifdef _WIN32
if (output) {
DWORD available{};
while (PeekNamedPipe(output, nullptr, 0, nullptr, &available, nullptr) && available) {
DWORD read{};
if (!ReadFile(output, buffer, std::min<DWORD>(available, sizeof(buffer)), &read,
nullptr) ||
!read)
break;
text.append(buffer, read);
}
}
if (running && WaitForSingleObject(process, 0) == WAIT_OBJECT_0) {
DWORD code{};
if (!GetExitCodeProcess(process, &code))
throw std::runtime_error("Cannot read process exit status");
running = false;
exit_code = static_cast<int>(code);
}
#else
if (output >= 0) {
while (true) {
auto count = ::read(output, buffer, sizeof(buffer));
if (count > 0) {
text.append(buffer, static_cast<std::size_t>(count));
continue;
}
if (count < 0 && errno == EINTR)
continue;
if (count == 0) {
::close(output);
output = -1;
} else if (errno != EAGAIN && errno != EWOULDBLOCK)
throw std::runtime_error("Cannot read child output");
break;
}
}
if (running) {
int status{};
pid_t result;
do {
result = ::waitpid(pid, &status, WNOHANG);
} while (result < 0 && errno == EINTR);
if (result == pid) {
running = false;
exit_code = WIFEXITED(status) ? WEXITSTATUS(status)
: WIFSIGNALED(status) ? 128 + WTERMSIG(status)
: 1;
} else if (result < 0)
throw std::runtime_error("Cannot collect child process");
}
#endif
if (!running) {
#ifdef _WIN32
DWORD available{};
while (output && PeekNamedPipe(output, nullptr, 0, nullptr, &available, nullptr) &&
available) {
DWORD read{};
if (!ReadFile(output, buffer, std::min<DWORD>(available, sizeof(buffer)), &read,
nullptr) ||
!read)
break;
text.append(buffer, read);
}
#else
if (output >= 0)
while (true) {
auto count = ::read(output, buffer, sizeof(buffer));
if (count > 0) {
text.append(buffer, static_cast<std::size_t>(count));
continue;
}
if (count < 0 && errno == EINTR)
continue;
if (count == 0) {
::close(output);
output = -1;
}
break;
}
#endif
}
return {running, exit_code, std::move(text)};
}
void cancel() {
if (!running)
return;
#ifdef _WIN32
if (job)
TerminateJobObject(job, 130);
TerminateProcess(process, 130);
WaitForSingleObject(process, INFINITE);
DWORD code{};
GetExitCodeProcess(process, &code);
exit_code = static_cast<int>(code);
running = false;
#else
::kill(-pid, SIGTERM);
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(200);
while (std::chrono::steady_clock::now() < deadline) {
int status{};
auto result = ::waitpid(pid, &status, WNOHANG);
if (result == pid) {
running = false;
exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
::kill(-pid, SIGKILL);
return;
}
if (result < 0 && errno != EINTR) {
running = false;
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
::kill(-pid, SIGKILL);
int status{};
while (::waitpid(pid, &status, 0) < 0 && errno == EINTR) {
}
running = false;
exit_code = 130;
#endif
}
};
Process::Process(const ProcessOptions& options) : impl_(std::make_unique<Impl>()) {
if (options.arguments.empty() || options.arguments[0].empty())
throw std::invalid_argument("Process requires an executable");
for (const auto& argument : options.arguments)
if (argument.find('\0') != std::string::npos)
throw std::invalid_argument("NUL in process argument");
auto cwd = options.working_directory.empty()
? std::filesystem::current_path()
: std::filesystem::absolute(options.working_directory);
if (!std::filesystem::is_directory(cwd))
throw std::runtime_error("Process working directory does not exist");
#ifdef _WIN32
auto program = std::filesystem::path(widen(options.arguments[0]));
if (program.has_parent_path() && program.is_relative())
program = cwd / program;
auto executable = program.has_parent_path() ? program : find_executable(options.arguments[0]);
std::wstring command;
for (const auto& argument : options.arguments) {
if (!command.empty())
command += L' ';
command += quote(widen(argument));
}
std::map<std::wstring, std::wstring, CaseInsensitive> environment;
auto block = GetEnvironmentStringsW();
if (!block)
throw std::runtime_error("Cannot read process environment");
for (auto entry = block; *entry; entry += wcslen(entry) + 1) {
std::wstring text(entry);
auto equals = text.find(L'=', text[0] == L'=' ? 1 : 0);
if (equals != std::wstring::npos)
environment[text.substr(0, equals)] = text.substr(equals + 1);
}
FreeEnvironmentStringsW(block);
for (auto& [key, value] : options.environment) {
if (key.empty() || key.find('=') != std::string::npos ||
key.find('\0') != std::string::npos || value.find('\0') != std::string::npos)
throw std::invalid_argument("Invalid environment entry");
environment[widen(key)] = widen(value);
}
std::vector<wchar_t> env;
for (auto& [key, value] : environment) {
auto item = key + L"=" + value;
env.insert(env.end(), item.begin(), item.end());
env.push_back(0);
}
env.push_back(0);
SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE output_write{}, input{};
if (!CreatePipe(&impl_->output, &output_write, &security, 0))
throw std::runtime_error("Cannot create process pipe");
SetHandleInformation(impl_->output, HANDLE_FLAG_INHERIT, 0);
input = CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &security,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
SIZE_T bytes{};
InitializeProcThreadAttributeList(nullptr, 1, 0, &bytes);
std::vector<std::byte> storage(bytes);
auto* attributes = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(storage.data());
bool initialized = InitializeProcThreadAttributeList(attributes, 1, 0, &bytes) != 0;
HANDLE handles[] = {output_write, input};
bool updated =
initialized && UpdateProcThreadAttribute(attributes, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles, sizeof(handles), nullptr, nullptr) != 0;
STARTUPINFOEXW startup{};
startup.StartupInfo.cb = sizeof(startup);
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
startup.StartupInfo.hStdOutput = startup.StartupInfo.hStdError = output_write;
startup.StartupInfo.hStdInput = input;
startup.lpAttributeList = attributes;
PROCESS_INFORMATION process{};
bool launched =
updated && CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, TRUE,
CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED |
CREATE_NEW_PROCESS_GROUP | EXTENDED_STARTUPINFO_PRESENT,
env.data(), cwd.c_str(), &startup.StartupInfo, &process) != 0;
if (initialized)
DeleteProcThreadAttributeList(attributes);
CloseHandle(output_write);
if (input != INVALID_HANDLE_VALUE)
CloseHandle(input);
if (!launched)
throw std::runtime_error("CreateProcess failed for " + options.arguments[0]);
impl_->process = process.hProcess;
impl_->thread = process.hThread;
impl_->running = true;
impl_->job = CreateJobObjectW(nullptr, nullptr);
if (!impl_->job)
throw std::runtime_error("Cannot create compiler job object");
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(impl_->job, JobObjectExtendedLimitInformation, &limits,
sizeof(limits)) ||
!AssignProcessToJobObject(impl_->job, impl_->process))
throw std::runtime_error("Cannot isolate compiler process group");
if (ResumeThread(impl_->thread) == DWORD(-1))
throw std::runtime_error("Cannot start compiler process");
#else
std::map<std::string, std::string> environment;
for (char** item = environ; *item; ++item) {
std::string text(*item);
auto separator = text.find('=');
if (separator != std::string::npos)
environment[text.substr(0, separator)] = text.substr(separator + 1);
}
for (const auto& [key, value] : options.environment) {
if (key.empty() || key.find('=') != std::string::npos ||
key.find('\0') != std::string::npos || value.find('\0') != std::string::npos)
throw std::invalid_argument("Invalid environment entry");
environment[key] = value;
}
auto executable = resolve_program(options.arguments[0], environment["PATH"], cwd);
std::vector<std::string> env_storage;
std::vector<char*> argv, envp;
for (const auto& argument : options.arguments)
argv.push_back(const_cast<char*>(argument.c_str()));
argv.push_back(nullptr);
for (const auto& [key, value] : environment)
env_storage.push_back(key + "=" + value);
for (auto& entry : env_storage)
envp.push_back(entry.data());
envp.push_back(nullptr);
int pipes[2];
if (::pipe2(pipes, O_CLOEXEC) < 0)
throw std::runtime_error("Cannot create process pipe");
int input = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
if (input < 0) {
::close(pipes[0]);
::close(pipes[1]);
throw std::runtime_error("Cannot open process input");
}
auto pid = ::fork();
if (pid == 0) {
::setpgid(0, 0);
::close(pipes[0]);
if (::dup2(input, STDIN_FILENO) < 0 || ::dup2(pipes[1], STDOUT_FILENO) < 0 ||
::dup2(pipes[1], STDERR_FILENO) < 0 || ::chdir(cwd.c_str()) < 0)
::_exit(126);
::close(input);
::close(pipes[1]);
::execve(executable.c_str(), argv.data(), envp.data());
constexpr char message[] = "Cannot execute child process\n";
::write(STDERR_FILENO, message, sizeof(message) - 1);
::_exit(127);
}
::close(input);
::close(pipes[1]);
if (pid < 0) {
::close(pipes[0]);
throw std::runtime_error("Cannot fork process");
}
::setpgid(pid, pid);
impl_->pid = pid;
impl_->output = pipes[0];
impl_->running = true;
int flags = ::fcntl(pipes[0], F_GETFL, 0);
if (flags < 0 || ::fcntl(pipes[0], F_SETFL, flags | O_NONBLOCK) < 0)
throw std::runtime_error("Cannot configure process output pipe");
#endif
}
Process::~Process() = default;
Process::Process(Process&&) noexcept = default;
Process& Process::operator=(Process&&) noexcept = default;
ProcessPoll Process::poll() {
return impl_->poll();
}
void Process::cancel() {
impl_->cancel();
}
} // namespace faset