feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -0,0 +1,174 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cwctype>
|
||||
|
||||
// This host-only reader never grants a workspace handle to the sandbox.
|
||||
namespace workspace_read {
|
||||
struct Lifetime {
|
||||
Handle parent{parentProcess()}, done{CreateEventW(nullptr, TRUE, FALSE, nullptr)};
|
||||
std::thread monitor;
|
||||
explicit Lifetime(DWORD timeout = 4000) {
|
||||
require(done.value != nullptr, "Create reader lifecycle event");
|
||||
monitor = std::thread([this, timeout]() {
|
||||
HANDLE events[] = {parent.value, done.value};
|
||||
const DWORD state = WaitForMultipleObjects(2, events, FALSE, timeout);
|
||||
if (state != WAIT_OBJECT_0 + 1) TerminateProcess(GetCurrentProcess(), 125);
|
||||
});
|
||||
}
|
||||
~Lifetime() { SetEvent(done.value); monitor.join(); }
|
||||
};
|
||||
static bool component(const std::wstring& name) {
|
||||
if (name.empty() || name == L"." || name == L".." || name.back() == L'.' || name.back() == L' ') return false;
|
||||
for (wchar_t ch : name) if (ch < 32 || std::wstring(L"<>:\"/\\|?*").find(ch) != std::wstring::npos) return false;
|
||||
auto stem = name.substr(0, name.find(L'.'));
|
||||
std::transform(stem.begin(), stem.end(), stem.begin(), [](wchar_t ch) { return static_cast<wchar_t>(towupper(ch)); });
|
||||
if (stem == L"CON" || stem == L"PRN" || stem == L"AUX" || stem == L"NUL") return false;
|
||||
return !(stem.size() == 4 && (stem.substr(0, 3) == L"COM" || stem.substr(0, 3) == L"LPT") && stem[3] >= L'1' && stem[3] <= L'9');
|
||||
}
|
||||
static std::vector<std::wstring> parts(std::wstring value) {
|
||||
std::replace(value.begin(), value.end(), L'/', L'\\');
|
||||
std::vector<std::wstring> result;
|
||||
size_t start = 0;
|
||||
for (;;) {
|
||||
const auto end = value.find(L'\\', start);
|
||||
const auto name = value.substr(start, end == std::wstring::npos ? end : end - start);
|
||||
if (!component(name)) throw std::runtime_error("Invalid workspace path component");
|
||||
result.push_back(name);
|
||||
if (end == std::wstring::npos) return result;
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
static std::wstring finalPath(HANDLE handle) {
|
||||
std::vector<wchar_t> buffer(32768);
|
||||
const DWORD count = GetFinalPathNameByHandleW(handle, buffer.data(), static_cast<DWORD>(buffer.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_GUID);
|
||||
require(count > 0 && count < buffer.size(), "Resolve workspace handle");
|
||||
std::wstring path(buffer.data(), count);
|
||||
while (path.size() > 1 && path.back() == L'\\') path.pop_back();
|
||||
return path;
|
||||
}
|
||||
static BY_HANDLE_FILE_INFORMATION information(HANDLE handle) {
|
||||
BY_HANDLE_FILE_INFORMATION result = {};
|
||||
require(GetFileInformationByHandle(handle, &result) != FALSE, "Inspect workspace handle");
|
||||
if (result.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DEVICE)) throw std::runtime_error("Workspace reparse point or device denied");
|
||||
if (!(result.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && result.nNumberOfLinks != 1) throw std::runtime_error("Workspace hard link denied");
|
||||
return result;
|
||||
}
|
||||
static std::string identity(HANDLE handle) {
|
||||
FILE_ID_INFO id = {};
|
||||
require(GetFileInformationByHandleEx(handle, FileIdInfo, &id, sizeof(id)) != FALSE, "Identify workspace handle");
|
||||
std::ostringstream text;
|
||||
text << std::hex << std::setfill('0') << std::setw(16) << id.VolumeSerialNumber << ':';
|
||||
for (BYTE byte : id.FileId.Identifier) text << std::setw(2) << static_cast<unsigned int>(byte);
|
||||
return text.str();
|
||||
}
|
||||
struct Target {
|
||||
std::vector<std::unique_ptr<Handle>> held;
|
||||
fs::path path;
|
||||
std::wstring rootFinal;
|
||||
std::string rootIdentity;
|
||||
HANDLE open(bool directory) {
|
||||
// Read sharing only pins names against rename/delete and excludes writable handles,
|
||||
// including handles used to retarget a reparse point. All ancestors stay open.
|
||||
auto handle = std::make_unique<Handle>(CreateFileW(path.c_str(), directory ? FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES : GENERIC_READ,
|
||||
FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
|
||||
require(handle->value != INVALID_HANDLE_VALUE, "Open pinned workspace target");
|
||||
const auto info = information(handle->value);
|
||||
if (directory && !(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) throw std::runtime_error("Workspace ancestor is not a directory");
|
||||
const auto result = handle->value;
|
||||
held.push_back(std::move(handle));
|
||||
return result;
|
||||
}
|
||||
explicit Target(const std::wstring& root) {
|
||||
if (root.size() < 3 || root.size() > 1024 || !iswalpha(root[0]) || root[1] != L':' || root[2] != L'\\') throw std::runtime_error("Workspace requires a local drive path");
|
||||
path = root.substr(0, 3);
|
||||
if (GetDriveTypeW(path.c_str()) != DRIVE_FIXED) throw std::runtime_error("Workspace requires a fixed local volume");
|
||||
HANDLE last = open(true);
|
||||
wchar_t filesystem[32] = {};
|
||||
require(GetVolumeInformationByHandleW(last, nullptr, 0, nullptr, nullptr, nullptr, filesystem, 32) != FALSE, "Inspect workspace volume");
|
||||
if (_wcsicmp(filesystem, L"NTFS") != 0) throw std::runtime_error("Workspace reader currently requires NTFS");
|
||||
if (root.size() > 3) for (const auto& name : parts(root.substr(3))) { path /= name; last = open(true); }
|
||||
rootFinal = finalPath(last);
|
||||
rootIdentity = identity(last);
|
||||
}
|
||||
HANDLE resolve(const std::wstring& relative) {
|
||||
if (relative == L".") return held.back()->value;
|
||||
const auto names = parts(relative);
|
||||
HANDLE last = nullptr;
|
||||
for (size_t index = 0; index < names.size(); ++index) { path /= names[index]; last = open(index + 1 < names.size()); }
|
||||
return last;
|
||||
}
|
||||
std::wstring relative(HANDLE handle) const {
|
||||
const auto actual = finalPath(handle);
|
||||
if (_wcsicmp(actual.c_str(), rootFinal.c_str()) == 0) return L".";
|
||||
if (actual.size() <= rootFinal.size() || _wcsnicmp(actual.c_str(), rootFinal.c_str(), rootFinal.size()) != 0 || actual[rootFinal.size()] != L'\\') throw std::runtime_error("Workspace handle escaped root");
|
||||
return actual.substr(rootFinal.size() + 1);
|
||||
}
|
||||
};
|
||||
static std::string base64(const std::vector<BYTE>& bytes) {
|
||||
static const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string result; result.reserve((bytes.size() + 2) / 3 * 4);
|
||||
for (size_t index = 0; index < bytes.size(); index += 3) {
|
||||
const unsigned int value = (static_cast<unsigned int>(bytes[index]) << 16) |
|
||||
(index + 1 < bytes.size() ? static_cast<unsigned int>(bytes[index + 1]) << 8 : 0) | (index + 2 < bytes.size() ? bytes[index + 2] : 0);
|
||||
result += alphabet[value >> 18]; result += alphabet[(value >> 12) & 63];
|
||||
result += index + 1 < bytes.size() ? alphabet[(value >> 6) & 63] : '=';
|
||||
result += index + 2 < bytes.size() ? alphabet[value & 63] : '=';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static int run(const std::wstring& operation, const std::wstring& root, const std::wstring& relative, const std::string& expected, DWORD limit) {
|
||||
if (inContainer(GetCurrentProcess())) throw std::runtime_error("Workspace reader is host-only");
|
||||
Lifetime lifetime;
|
||||
if (operation != L"identity" && operation != L"read" && operation != L"stat" && operation != L"list") throw std::runtime_error("Invalid workspace reader operation");
|
||||
if (limit == 0 || limit > 8 * 1024 * 1024) throw std::runtime_error("Invalid workspace reader limit");
|
||||
Target target(root);
|
||||
if (operation != L"identity" && target.rootIdentity != expected) throw std::runtime_error("Workspace root identity changed");
|
||||
const HANDLE handle = target.resolve(relative);
|
||||
const auto before = information(handle);
|
||||
const bool directory = (before.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
const auto size = (static_cast<ULONGLONG>(before.nFileSizeHigh) << 32) | before.nFileSizeLow;
|
||||
std::string result = "{\"protocol\":1,\"rootIdentity\":\"" + target.rootIdentity + "\",\"identity\":\"" + identity(handle) +
|
||||
"\",\"path\":\"" + jsonText(target.relative(handle)) + "\",\"kind\":\"" + (directory ? "directory" : "file") + "\",\"size\":" + std::to_string(size);
|
||||
if (operation == L"read") {
|
||||
if (directory) throw std::runtime_error("Workspace target is a directory");
|
||||
if (size > limit) throw std::runtime_error("Workspace file exceeds read limit");
|
||||
std::vector<BYTE> bytes(static_cast<size_t>(size)); DWORD read = 0;
|
||||
if (!bytes.empty()) require(ReadFile(handle, bytes.data(), static_cast<DWORD>(bytes.size()), &read, nullptr) && read == bytes.size(), "Read pinned workspace file");
|
||||
const auto after = information(handle);
|
||||
if (after.nNumberOfLinks != 1 || after.nFileSizeLow != before.nFileSizeLow || after.nFileSizeHigh != before.nFileSizeHigh || CompareFileTime(&before.ftLastWriteTime, &after.ftLastWriteTime) != 0) throw std::runtime_error("Workspace file changed while reading");
|
||||
result += ",\"data\":\"" + base64(bytes) + "\"";
|
||||
} else if (operation == L"list") {
|
||||
if (!directory) throw std::runtime_error("Workspace target is not a directory");
|
||||
result += ",\"entries\":[";
|
||||
std::vector<BYTE> buffer(65536); bool first = true; DWORD count = 0;
|
||||
for (;;) {
|
||||
if (!GetFileInformationByHandleEx(handle, FileIdBothDirectoryInfo, buffer.data(), static_cast<DWORD>(buffer.size()))) {
|
||||
if (GetLastError() == ERROR_NO_MORE_FILES) break;
|
||||
require(false, "Enumerate pinned workspace directory");
|
||||
}
|
||||
size_t offset = 0;
|
||||
for (;;) {
|
||||
const auto entry = reinterpret_cast<FILE_ID_BOTH_DIR_INFO*>(buffer.data() + offset);
|
||||
if (entry->FileNameLength > buffer.size() - offset - offsetof(FILE_ID_BOTH_DIR_INFO, FileName)) throw std::runtime_error("Invalid directory record");
|
||||
const std::wstring name(entry->FileName, entry->FileNameLength / sizeof(wchar_t));
|
||||
if (name != L"." && name != L"..") {
|
||||
if (++count > limit) throw std::runtime_error("Workspace directory exceeds entry limit");
|
||||
if (!first) result += ',';
|
||||
first = false;
|
||||
result += "{\"name\":\"" + jsonText(name) + "\",\"kind\":\"" + ((entry->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ? "link" : (entry->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? "directory" : "file") + "\"}";
|
||||
}
|
||||
if (entry->NextEntryOffset == 0) break;
|
||||
if (entry->NextEntryOffset < sizeof(FILE_ID_BOTH_DIR_INFO) || entry->NextEntryOffset > buffer.size() - offset - sizeof(FILE_ID_BOTH_DIR_INFO)) throw std::runtime_error("Invalid next directory record");
|
||||
offset += entry->NextEntryOffset;
|
||||
}
|
||||
}
|
||||
result += ']';
|
||||
}
|
||||
result += "}\n";
|
||||
DWORD written = 0;
|
||||
require(WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), result.data(), static_cast<DWORD>(result.size()), &written, nullptr) && written == result.size(), "Write workspace reader result");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user