1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-21 16:31:07 +01:00

Move /src to /subprojects

This will facilitate breaking up Nix into multiple packages for each
component with Meson.
This commit is contained in:
John Ericson 2024-06-03 13:59:53 -04:00
parent 4db9487823
commit 84e2963f8e
737 changed files with 504 additions and 505 deletions

View file

@ -0,0 +1,45 @@
#include "environment-variables.hh"
#include "processenv.h"
namespace nix {
std::optional<OsString> getEnvOs(const OsString & key)
{
// Determine the required buffer size for the environment variable value
DWORD bufferSize = GetEnvironmentVariableW(key.c_str(), nullptr, 0);
if (bufferSize == 0) {
return std::nullopt;
}
// Allocate a buffer to hold the environment variable value
std::wstring value{L'\0', bufferSize};
// Retrieve the environment variable value
DWORD resultSize = GetEnvironmentVariableW(key.c_str(), &value[0], bufferSize);
if (resultSize == 0) {
return std::nullopt;
}
// Resize the string to remove the extra null characters
value.resize(resultSize);
return value;
}
int unsetenv(const char * name)
{
return -SetEnvironmentVariableA(name, nullptr);
}
int setEnv(const char * name, const char * value)
{
return -SetEnvironmentVariableA(name, value);
}
int setEnvOs(const OsString & name, const OsString & value)
{
return -SetEnvironmentVariableW(name.c_str(), value.c_str());
}
}

View file

@ -0,0 +1,150 @@
#include "file-system.hh"
#include "signals.hh"
#include "finally.hh"
#include "serialise.hh"
#include "windows-error.hh"
#include "file-path.hh"
#include <fileapi.h>
#include <error.h>
#include <namedpipeapi.h>
#include <namedpipeapi.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix {
using namespace nix::windows;
std::string readFile(HANDLE handle)
{
LARGE_INTEGER li;
if (!GetFileSizeEx(handle, &li))
throw WinError("%s:%d statting file", __FILE__, __LINE__);
return drainFD(handle, true, li.QuadPart);
}
void readFull(HANDLE handle, char * buf, size_t count)
{
while (count) {
checkInterrupt();
DWORD res;
if (!ReadFile(handle, (char *) buf, count, &res, NULL))
throw WinError("%s:%d reading from file", __FILE__, __LINE__);
if (res == 0) throw EndOfFile("unexpected end-of-file");
count -= res;
buf += res;
}
}
void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts)
{
while (!s.empty()) {
if (allowInterrupts) checkInterrupt();
DWORD res;
#if _WIN32_WINNT >= 0x0600
auto path = handleToPath(handle); // debug; do it before becuase handleToPath changes lasterror
if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) {
throw WinError("writing to file %1%:%2%", handle, path);
}
#else
if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) {
throw WinError("writing to file %1%", handle);
}
#endif
if (res > 0)
s.remove_prefix(res);
}
}
std::string readLine(HANDLE handle)
{
std::string s;
while (1) {
checkInterrupt();
char ch;
// FIXME: inefficient
DWORD rd;
if (!ReadFile(handle, &ch, 1, &rd, NULL)) {
throw WinError("reading a line");
} else if (rd == 0)
throw EndOfFile("unexpected EOF reading a line");
else {
if (ch == '\n') return s;
s += ch;
}
}
}
void drainFD(HANDLE handle, Sink & sink/*, bool block*/)
{
std::vector<unsigned char> buf(64 * 1024);
while (1) {
checkInterrupt();
DWORD rd;
if (!ReadFile(handle, buf.data(), buf.size(), &rd, NULL)) {
WinError winError("%s:%d reading from handle %p", __FILE__, __LINE__, handle);
if (winError.lastError == ERROR_BROKEN_PIPE)
break;
throw winError;
}
else if (rd == 0) break;
sink({(char *) buf.data(), (size_t) rd});
}
}
//////////////////////////////////////////////////////////////////////
void Pipe::create()
{
SECURITY_ATTRIBUTES saAttr = {0};
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.lpSecurityDescriptor = NULL;
saAttr.bInheritHandle = TRUE;
HANDLE hReadPipe, hWritePipe;
if (!CreatePipe(&hReadPipe, &hWritePipe, &saAttr, 0))
throw WinError("CreatePipe");
readSide = hReadPipe;
writeSide = hWritePipe;
}
//////////////////////////////////////////////////////////////////////
#if _WIN32_WINNT >= 0x0600
std::wstring windows::handleToFileName(HANDLE handle) {
std::vector<wchar_t> buf(0x100);
DWORD dw = GetFinalPathNameByHandleW(handle, buf.data(), buf.size(), FILE_NAME_OPENED);
if (dw == 0) {
if (handle == GetStdHandle(STD_INPUT_HANDLE )) return L"<stdin>";
if (handle == GetStdHandle(STD_OUTPUT_HANDLE)) return L"<stdout>";
if (handle == GetStdHandle(STD_ERROR_HANDLE )) return L"<stderr>";
return (boost::wformat(L"<unnnamed handle %X>") % handle).str();
}
if (dw > buf.size()) {
buf.resize(dw);
if (GetFinalPathNameByHandleW(handle, buf.data(), buf.size(), FILE_NAME_OPENED) != dw-1)
throw WinError("GetFinalPathNameByHandleW");
dw -= 1;
}
return std::wstring(buf.data(), dw);
}
Path windows::handleToPath(HANDLE handle) {
return os_string_to_string(handleToFileName(handle));
}
#endif
}

View file

@ -0,0 +1,40 @@
#include <algorithm>
#include <codecvt>
#include <iostream>
#include <locale>
#include "file-path.hh"
#include "file-path-impl.hh"
#include "util.hh"
namespace nix {
std::optional<std::filesystem::path> maybePath(PathView path)
{
if (path.length() >= 3 && (('A' <= path[0] && path[0] <= 'Z') || ('a' <= path[0] && path[0] <= 'z')) && path[1] == ':' && WindowsPathTrait<char>::isPathSep(path[2])) {
std::filesystem::path::string_type sw = string_to_os_string(
std::string { "\\\\?\\" } + path);
std::replace(sw.begin(), sw.end(), '/', '\\');
return sw;
}
if (path.length() >= 7 && path[0] == '\\' && path[1] == '\\' && (path[2] == '.' || path[2] == '?') && path[3] == '\\' &&
('A' <= path[4] && path[4] <= 'Z') && path[5] == ':' && WindowsPathTrait<char>::isPathSep(path[6])) {
std::filesystem::path::string_type sw = string_to_os_string(path);
std::replace(sw.begin(), sw.end(), '/', '\\');
return sw;
}
return std::optional<std::filesystem::path::string_type>();
}
std::filesystem::path pathNG(PathView path)
{
std::optional<std::filesystem::path::string_type> sw = maybePath(path);
if (!sw) {
// FIXME why are we not using the regular error handling?
std::cerr << "invalid path for WinAPI call ["<<path<<"]"<<std::endl;
_exit(111);
}
return *sw;
}
}

View file

@ -0,0 +1,17 @@
#include "file-system.hh"
namespace nix {
Descriptor openDirectory(const std::filesystem::path & path)
{
return CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
}
}

View file

@ -0,0 +1,20 @@
sources += files(
'environment-variables.cc',
'file-descriptor.cc',
'file-path.cc',
'file-system.cc',
'muxable-pipe.cc',
'os-string.cc',
'processes.cc',
'users.cc',
'windows-async-pipe.cc',
'windows-error.cc',
)
include_dirs += include_directories('.')
headers += files(
'signals-impl.hh',
'windows-async-pipe.hh',
'windows-error.hh',
)

View file

@ -0,0 +1,70 @@
#include <ioapiset.h>
#include "windows-error.hh"
#include "logging.hh"
#include "util.hh"
#include "muxable-pipe.hh"
namespace nix {
void MuxablePipePollState::poll(HANDLE ioport, std::optional<unsigned int> timeout)
{
/* We are on at least Windows Vista / Server 2008 and can get many
(countof(oentries)) statuses in one API call. */
if (!GetQueuedCompletionStatusEx(
ioport, oentries, sizeof(oentries) / sizeof(*oentries), &removed, timeout ? *timeout : INFINITE, false)) {
windows::WinError winError("GetQueuedCompletionStatusEx");
if (winError.lastError != WAIT_TIMEOUT)
throw winError;
assert(removed == 0);
} else {
assert(0 < removed && removed <= sizeof(oentries) / sizeof(*oentries));
}
}
void MuxablePipePollState::iterate(
std::set<MuxablePipePollState::CommChannel> & channels,
std::function<void(Descriptor fd, std::string_view data)> handleRead,
std::function<void(Descriptor fd)> handleEOF)
{
auto p = channels.begin();
while (p != channels.end()) {
decltype(p) nextp = p;
++nextp;
for (ULONG i = 0; i < removed; i++) {
if (oentries[i].lpCompletionKey == ((ULONG_PTR) ((*p)->readSide.get()) ^ 0x5555)) {
printMsg(lvlVomit, "read %s bytes", oentries[i].dwNumberOfBytesTransferred);
if (oentries[i].dwNumberOfBytesTransferred > 0) {
std::string data{
(char *) (*p)->buffer.data(),
oentries[i].dwNumberOfBytesTransferred,
};
handleRead((*p)->readSide.get(), data);
}
if (gotEOF) {
handleEOF((*p)->readSide.get());
nextp = channels.erase(p); // no need to maintain `channels`?
} else {
BOOL rc = ReadFile(
(*p)->readSide.get(), (*p)->buffer.data(), (*p)->buffer.size(), &(*p)->got, &(*p)->overlapped);
if (rc) {
// here is possible (but not obligatory) to call
// `handleRead` and repeat ReadFile immediately
} else {
windows::WinError winError("ReadFile(%s, ..)", (*p)->readSide.get());
if (winError.lastError == ERROR_BROKEN_PIPE) {
handleEOF((*p)->readSide.get());
nextp = channels.erase(p); // no need to maintain `channels` ?
} else if (winError.lastError != ERROR_IO_PENDING)
throw winError;
}
}
break;
}
}
p = nextp;
}
}
}

View file

@ -0,0 +1,24 @@
#include <algorithm>
#include <codecvt>
#include <iostream>
#include <locale>
#include "file-path.hh"
#include "file-path-impl.hh"
#include "util.hh"
namespace nix {
std::string os_string_to_string(PathViewNG::string_view path)
{
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(std::filesystem::path::string_type{path});
}
std::filesystem::path::string_type string_to_os_string(std::string_view s)
{
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(std::string{s});
}
}

View file

@ -0,0 +1,388 @@
#include "current-process.hh"
#include "environment-variables.hh"
#include "error.hh"
#include "executable-path.hh"
#include "file-descriptor.hh"
#include "file-path.hh"
#include "signals.hh"
#include "processes.hh"
#include "finally.hh"
#include "serialise.hh"
#include "file-system.hh"
#include "util.hh"
#include "windows-error.hh"
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <future>
#include <iostream>
#include <sstream>
#include <thread>
#include <sys/types.h>
#include <unistd.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix {
using namespace nix::windows;
Pid::Pid() {}
Pid::Pid(AutoCloseFD pid)
: pid(std::move(pid))
{
}
Pid::~Pid()
{
if (pid.get() != INVALID_DESCRIPTOR)
kill();
}
void Pid::operator=(AutoCloseFD pid)
{
if (this->pid.get() != INVALID_DESCRIPTOR && this->pid.get() != pid.get())
kill();
this->pid = std::move(pid);
}
// TODO: Implement (not needed for process spawning yet)
int Pid::kill()
{
assert(pid.get() != INVALID_DESCRIPTOR);
debug("killing process %1%", pid.get());
throw UnimplementedError("Pid::kill unimplemented");
}
int Pid::wait()
{
// https://github.com/nix-windows/nix/blob/windows-meson/src/libutil/util.cc#L1938
assert(pid.get() != INVALID_DESCRIPTOR);
DWORD status = WaitForSingleObject(pid.get(), INFINITE);
if (status != WAIT_OBJECT_0) {
debug("WaitForSingleObject returned %1%", status);
}
DWORD exitCode = 0;
if (GetExitCodeProcess(pid.get(), &exitCode) == FALSE) {
debug("GetExitCodeProcess failed on pid %1%", pid.get());
}
pid.close();
return exitCode;
}
// TODO: Merge this with Unix's runProgram since it's identical logic.
std::string runProgram(
Path program, bool lookupPath, const Strings & args, const std::optional<std::string> & input, bool isInteractive)
{
auto res = runProgram(RunOptions{
.program = program, .lookupPath = lookupPath, .args = args, .input = input, .isInteractive = isInteractive});
if (!statusOk(res.first))
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
return res.second;
}
std::optional<Path> getProgramInterpreter(const Path & program)
{
// These extensions are automatically handled by Windows and don't require an interpreter.
static constexpr const char * exts[] = {".exe", ".cmd", ".bat"};
for (const auto ext : exts) {
if (hasSuffix(program, ext)) {
return {};
}
}
// TODO: Open file and read the shebang
throw UnimplementedError("getProgramInterpreter unimplemented");
}
// TODO: Not sure if this is needed in the unix version but it might be useful as a member func
void setFDInheritable(AutoCloseFD & fd, bool inherit)
{
if (fd.get() != INVALID_DESCRIPTOR) {
if (!SetHandleInformation(fd.get(), HANDLE_FLAG_INHERIT, inherit ? HANDLE_FLAG_INHERIT : 0)) {
throw WinError("Couldn't disable inheriting of handle");
}
}
}
AutoCloseFD nullFD()
{
// Create null handle to discard reads / writes
// https://stackoverflow.com/a/25609668
// https://github.com/nix-windows/nix/blob/windows-meson/src/libutil/util.cc#L2228
AutoCloseFD nul = CreateFileW(
L"NUL",
GENERIC_READ | GENERIC_WRITE,
// We don't care who reads / writes / deletes this file since it's NUL anyways
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (!nul.get()) {
throw WinError("Couldn't open NUL device");
}
// Let this handle be inheritable by child processes
setFDInheritable(nul, true);
return nul;
}
// Adapted from
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
std::string windowsEscape(const std::string & str, bool cmd)
{
// TODO: This doesn't handle cmd.exe escaping.
if (cmd) {
throw UnimplementedError("cmd.exe escaping is not implemented");
}
if (str.find_first_of(" \t\n\v\"") == str.npos && !str.empty()) {
// No need to escape this one, the nonempty contents don't have a special character
return str;
}
std::string buffer;
// Add the opening quote
buffer += '"';
for (auto iter = str.begin();; ++iter) {
size_t backslashes = 0;
while (iter != str.end() && *iter == '\\') {
++iter;
++backslashes;
}
// We only escape backslashes if:
// - They come immediately before the closing quote
// - They come immediately before a quote in the middle of the string
// Both of these cases break the escaping if not handled. Otherwise backslashes are fine as-is
if (iter == str.end()) {
// Need to escape each backslash
buffer.append(backslashes * 2, '\\');
// Exit since we've reached the end of the string
break;
} else if (*iter == '"') {
// Need to escape each backslash and the intermediate quote character
buffer.append(backslashes * 2, '\\');
buffer += "\\\"";
} else {
// Don't escape the backslashes since they won't break the delimiter
buffer.append(backslashes, '\\');
buffer += *iter;
}
}
// Add the closing quote
return buffer + '"';
}
Pid spawnProcess(const Path & realProgram, const RunOptions & options, Pipe & out, Pipe & in)
{
// Setup pipes.
if (options.standardOut) {
// Don't inherit the read end of the output pipe
setFDInheritable(out.readSide, false);
} else {
out.writeSide = nullFD();
}
if (options.standardIn) {
// Don't inherit the write end of the input pipe
setFDInheritable(in.writeSide, false);
} else {
in.readSide = nullFD();
}
STARTUPINFOW startInfo = {0};
startInfo.cb = sizeof(startInfo);
startInfo.dwFlags = STARTF_USESTDHANDLES;
startInfo.hStdInput = in.readSide.get();
startInfo.hStdOutput = out.writeSide.get();
startInfo.hStdError = out.writeSide.get();
std::string envline;
// Retain the current processes' environment variables.
for (const auto & envVar : getEnv()) {
envline += (envVar.first + '=' + envVar.second + '\0');
}
// Also add new ones specified in options.
if (options.environment) {
for (const auto & envVar : *options.environment) {
envline += (envVar.first + '=' + envVar.second + '\0');
}
}
std::string cmdline = windowsEscape(realProgram, false);
for (const auto & arg : options.args) {
// TODO: This isn't the right way to escape windows command
// See https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
cmdline += ' ' + windowsEscape(arg, false);
}
PROCESS_INFORMATION procInfo = {0};
if (CreateProcessW(
// EXE path is provided in the cmdline
NULL,
string_to_os_string(cmdline).data(),
NULL,
NULL,
TRUE,
CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED,
string_to_os_string(envline).data(),
options.chdir.has_value() ? string_to_os_string(*options.chdir).data() : NULL,
&startInfo,
&procInfo)
== 0) {
throw WinError("CreateProcessW failed (%1%)", cmdline);
}
// Convert these to use RAII
AutoCloseFD process = procInfo.hProcess;
AutoCloseFD thread = procInfo.hThread;
// Add current process and child to job object so child terminates when parent terminates
// TODO: This spawns one job per child process. We can probably keep this as a global, and
// add children a single job so we don't use so many jobs at once.
Descriptor job = CreateJobObjectW(NULL, NULL);
if (job == NULL) {
TerminateProcess(procInfo.hProcess, 0);
throw WinError("Couldn't create job object for child process");
}
if (AssignProcessToJobObject(job, procInfo.hProcess) == FALSE) {
TerminateProcess(procInfo.hProcess, 0);
throw WinError("Couldn't assign child process to job object");
}
if (ResumeThread(procInfo.hThread) == (DWORD) -1) {
TerminateProcess(procInfo.hProcess, 0);
throw WinError("Couldn't resume child process thread");
}
return process;
}
// TODO: Merge this with Unix's runProgram since it's identical logic.
// Output = error code + "standard out" output stream
std::pair<int, std::string> runProgram(RunOptions && options)
{
StringSink sink;
options.standardOut = &sink;
int status = 0;
try {
runProgram2(options);
} catch (ExecError & e) {
status = e.status;
}
return {status, std::move(sink.s)};
}
void runProgram2(const RunOptions & options)
{
checkInterrupt();
assert(!(options.standardIn && options.input));
std::unique_ptr<Source> source_;
Source * source = options.standardIn;
if (options.input) {
source_ = std::make_unique<StringSource>(*options.input);
source = source_.get();
}
/* Create a pipe. */
Pipe out, in;
// TODO: I copied this from unix but this is handled again in spawnProcess, so might be weird to split it up like
// this
if (options.standardOut)
out.create();
if (source)
in.create();
Path realProgram = options.program;
// TODO: Implement shebang / program interpreter lookup on Windows
auto interpreter = getProgramInterpreter(realProgram);
std::optional<Finally<std::function<void()>>> resumeLoggerDefer;
if (options.isInteractive) {
logger->pause();
resumeLoggerDefer.emplace([]() { logger->resume(); });
}
Pid pid = spawnProcess(interpreter.has_value() ? *interpreter : realProgram, options, out, in);
// TODO: This is identical to unix, deduplicate?
out.writeSide.close();
std::thread writerThread;
std::promise<void> promise;
Finally doJoin([&] {
if (writerThread.joinable())
writerThread.join();
});
if (source) {
in.readSide.close();
writerThread = std::thread([&] {
try {
std::vector<char> buf(8 * 1024);
while (true) {
size_t n;
try {
n = source->read(buf.data(), buf.size());
} catch (EndOfFile &) {
break;
}
writeFull(in.writeSide.get(), {buf.data(), n});
}
promise.set_value();
} catch (...) {
promise.set_exception(std::current_exception());
}
in.writeSide.close();
});
}
if (options.standardOut)
drainFD(out.readSide.get(), *options.standardOut);
/* Wait for the child to finish. */
int status = pid.wait();
/* Wait for the writer thread to finish. */
if (source)
promise.get_future().get();
if (status)
throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status));
}
std::string statusToString(int status)
{
if (status != 0)
return fmt("with exit code %d", status);
else
return "succeeded";
}
bool statusOk(int status)
{
return status == 0;
}
int execvpe(const wchar_t * file0, const wchar_t * const argv[], const wchar_t * const envp[])
{
auto file = ExecutablePath::load().findPath(file0);
return _wexecve(file.c_str(), argv, envp);
}
}

View file

@ -0,0 +1,41 @@
#pragma once
///@file
#include "types.hh"
namespace nix {
/* User interruption. */
static inline void setInterrupted(bool isInterrupted)
{
/* Do nothing for now */
}
static inline bool getInterrupted()
{
return false;
}
inline void setInterruptThrown()
{
/* Do nothing for now */
}
void inline checkInterrupt()
{
/* Do nothing for now */
}
/**
* Does nothing, unlike Unix counterpart, but allows avoiding C++
*/
struct ReceiveInterrupts
{
/**
* Explicit destructor avoids dead code warnings.
*/
~ReceiveInterrupts() {}
};
}

View file

@ -0,0 +1,52 @@
#include "util.hh"
#include "users.hh"
#include "environment-variables.hh"
#include "file-system.hh"
#include "windows-error.hh"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix {
using namespace nix::windows;
std::string getUserName()
{
// Get the required buffer size
DWORD size = 0;
if (!GetUserNameA(nullptr, &size)) {
auto lastError = GetLastError();
if (lastError != ERROR_INSUFFICIENT_BUFFER)
throw WinError(lastError, "cannot figure out size of user name");
}
std::string name;
// Allocate a buffer of sufficient size
//
// - 1 because no need for null byte
name.resize(size - 1);
// Retrieve the username
if (!GetUserNameA(&name[0], &size))
throw WinError("cannot figure out user name");
return name;
}
Path getHome()
{
static Path homeDir = []()
{
Path homeDir = getEnv("USERPROFILE").value_or("C:\\Users\\Default");
assert(!homeDir.empty());
return canonPath(homeDir);
}();
return homeDir;
}
bool isRootUser() {
return false;
}
}

View file

@ -0,0 +1,49 @@
#include "windows-async-pipe.hh"
#include "windows-error.hh"
namespace nix::windows {
void AsyncPipe::createAsyncPipe(HANDLE iocp)
{
// std::cerr << (format("-----AsyncPipe::createAsyncPipe(%x)") % iocp) << std::endl;
buffer.resize(0x1000);
memset(&overlapped, 0, sizeof(overlapped));
std::string pipeName = fmt("\\\\.\\pipe\\nix-%d-%p", GetCurrentProcessId(), (void *) this);
readSide = CreateNamedPipeA(
pipeName.c_str(),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES,
0,
0,
INFINITE,
NULL);
if (!readSide)
throw WinError("CreateNamedPipeA(%s)", pipeName);
HANDLE hIocp = CreateIoCompletionPort(readSide.get(), iocp, (ULONG_PTR) (readSide.get()) ^ 0x5555, 0);
if (hIocp != iocp)
throw WinError("CreateIoCompletionPort(%x[%s], %x, ...) returned %x", readSide.get(), pipeName, iocp, hIocp);
if (!ConnectNamedPipe(readSide.get(), &overlapped) && GetLastError() != ERROR_IO_PENDING)
throw WinError("ConnectNamedPipe(%s)", pipeName);
SECURITY_ATTRIBUTES psa2 = {0};
psa2.nLength = sizeof(SECURITY_ATTRIBUTES);
psa2.bInheritHandle = TRUE;
writeSide = CreateFileA(pipeName.c_str(), GENERIC_WRITE, 0, &psa2, OPEN_EXISTING, 0, NULL);
if (!readSide)
throw WinError("CreateFileA(%s)", pipeName);
}
void AsyncPipe::close()
{
readSide.close();
writeSide.close();
}
}

View file

@ -0,0 +1,27 @@
#pragma once
///@file
#include "file-descriptor.hh"
namespace nix::windows {
/***
* An "async pipe" is a pipe that supports I/O Completion Ports so
* multiple pipes can be listened too.
*
* Unfortunately, only named pipes support that on windows, so we use
* those with randomized temp file names.
*/
class AsyncPipe
{
public:
AutoCloseFD writeSide, readSide;
OVERLAPPED overlapped;
DWORD got;
std::vector<unsigned char> buffer;
void createAsyncPipe(HANDLE iocp);
void close();
};
}

View file

@ -0,0 +1,31 @@
#include "windows-error.hh"
#include <error.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix::windows {
std::string WinError::renderError(DWORD lastError)
{
LPSTR errorText = NULL;
FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM // use system message tables to retrieve error text
|FORMAT_MESSAGE_ALLOCATE_BUFFER // allocate buffer on local heap for error text
|FORMAT_MESSAGE_IGNORE_INSERTS, // Important! will fail otherwise, since we're not (and CANNOT) pass insertion parameters
NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM
lastError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errorText, // output
0, // minimum size for output buffer
NULL); // arguments - see note
if (NULL != errorText ) {
std::string s2 { errorText };
LocalFree(errorText);
return s2;
}
return fmt("CODE=%d", lastError);
}
}

View file

@ -0,0 +1,51 @@
#pragma once
///@file
#include <errhandlingapi.h>
#include "error.hh"
namespace nix::windows {
/**
* Windows Error type.
*
* Unless you need to catch a specific error number, don't catch this in
* portable code. Catch `SystemError` instead.
*/
class WinError : public SystemError
{
public:
DWORD lastError;
/**
* Construct using the explicitly-provided error number.
* `FormatMessageA` will be used to try to add additional
* information to the message.
*/
template<typename... Args>
WinError(DWORD lastError, const Args & ... args)
: SystemError(""), lastError(lastError)
{
auto hf = HintFmt(args...);
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), renderError(lastError));
}
/**
* Construct using `GetLastError()` and the ambient "last error".
*
* Be sure to not perform another last-error-modifying operation
* before calling this constructor!
*/
template<typename... Args>
WinError(const Args & ... args)
: WinError(GetLastError(), args ...)
{
}
private:
std::string renderError(DWORD lastError);
};
}