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:
parent
4db9487823
commit
84e2963f8e
737 changed files with 504 additions and 505 deletions
45
subprojects/libutil/windows/environment-variables.cc
Normal file
45
subprojects/libutil/windows/environment-variables.cc
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
150
subprojects/libutil/windows/file-descriptor.cc
Normal file
150
subprojects/libutil/windows/file-descriptor.cc
Normal 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
|
||||
|
||||
}
|
||||
40
subprojects/libutil/windows/file-path.cc
Normal file
40
subprojects/libutil/windows/file-path.cc
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
17
subprojects/libutil/windows/file-system.cc
Normal file
17
subprojects/libutil/windows/file-system.cc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
20
subprojects/libutil/windows/meson.build
Normal file
20
subprojects/libutil/windows/meson.build
Normal 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',
|
||||
)
|
||||
70
subprojects/libutil/windows/muxable-pipe.cc
Normal file
70
subprojects/libutil/windows/muxable-pipe.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
subprojects/libutil/windows/os-string.cc
Normal file
24
subprojects/libutil/windows/os-string.cc
Normal 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});
|
||||
}
|
||||
|
||||
}
|
||||
388
subprojects/libutil/windows/processes.cc
Normal file
388
subprojects/libutil/windows/processes.cc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
41
subprojects/libutil/windows/signals-impl.hh
Normal file
41
subprojects/libutil/windows/signals-impl.hh
Normal 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() {}
|
||||
};
|
||||
|
||||
}
|
||||
52
subprojects/libutil/windows/users.cc
Normal file
52
subprojects/libutil/windows/users.cc
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
49
subprojects/libutil/windows/windows-async-pipe.cc
Normal file
49
subprojects/libutil/windows/windows-async-pipe.cc
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
27
subprojects/libutil/windows/windows-async-pipe.hh
Normal file
27
subprojects/libutil/windows/windows-async-pipe.hh
Normal 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();
|
||||
};
|
||||
|
||||
}
|
||||
31
subprojects/libutil/windows/windows-error.cc
Normal file
31
subprojects/libutil/windows/windows-error.cc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
51
subprojects/libutil/windows/windows-error.hh
Normal file
51
subprojects/libutil/windows/windows-error.hh
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue