1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-05 20:41:47 +02:00

Merge branch 'master' into fix-sandbox-escape

This commit is contained in:
John Ericson 2024-06-26 18:11:39 -04:00
commit 8a420162ab
274 changed files with 3295 additions and 900 deletions

View file

@ -6,7 +6,7 @@
#include <strings.h> // for strcasecmp
#include "archive.hh"
#include "config.hh"
#include "config-global.hh"
#include "posix-source-accessor.hh"
#include "source-path.hh"
#include "file-system.hh"

View file

@ -0,0 +1,67 @@
#include "config-global.hh"
namespace nix {
bool GlobalConfig::set(const std::string & name, const std::string & value)
{
for (auto & config : *configRegistrations)
if (config->set(name, value))
return true;
unknownSettings.emplace(name, value);
return false;
}
void GlobalConfig::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (auto & config : *configRegistrations)
config->getSettings(res, overriddenOnly);
}
void GlobalConfig::resetOverridden()
{
for (auto & config : *configRegistrations)
config->resetOverridden();
}
nlohmann::json GlobalConfig::toJSON()
{
auto res = nlohmann::json::object();
for (const auto & config : *configRegistrations)
res.update(config->toJSON());
return res;
}
std::string GlobalConfig::toKeyValue()
{
std::string res;
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
for (const auto & s : settings)
res += fmt("%s = %s\n", s.first, s.second.value);
return res;
}
void GlobalConfig::convertToArgs(Args & args, const std::string & category)
{
for (auto & config : *configRegistrations)
config->convertToArgs(args, category);
}
GlobalConfig globalConfig;
GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations;
GlobalConfig::Register::Register(Config * config)
{
if (!configRegistrations)
configRegistrations = new ConfigRegistrations;
configRegistrations->emplace_back(config);
}
ExperimentalFeatureSettings experimentalFeatureSettings;
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
}

View file

@ -0,0 +1,33 @@
#pragma once
///@file
#include "config.hh"
namespace nix {
struct GlobalConfig : public AbstractConfig
{
typedef std::vector<Config *> ConfigRegistrations;
static ConfigRegistrations * configRegistrations;
bool set(const std::string & name, const std::string & value) override;
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
void resetOverridden() override;
nlohmann::json toJSON() override;
std::string toKeyValue() override;
void convertToArgs(Args & args, const std::string & category) override;
struct Register
{
Register(Config * config);
};
};
extern GlobalConfig globalConfig;
}

View file

@ -443,67 +443,6 @@ void OptionalPathSetting::operator =(const std::optional<Path> & v)
this->assign(v);
}
bool GlobalConfig::set(const std::string & name, const std::string & value)
{
for (auto & config : *configRegistrations)
if (config->set(name, value)) return true;
unknownSettings.emplace(name, value);
return false;
}
void GlobalConfig::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (auto & config : *configRegistrations)
config->getSettings(res, overriddenOnly);
}
void GlobalConfig::resetOverridden()
{
for (auto & config : *configRegistrations)
config->resetOverridden();
}
nlohmann::json GlobalConfig::toJSON()
{
auto res = nlohmann::json::object();
for (const auto & config : *configRegistrations)
res.update(config->toJSON());
return res;
}
std::string GlobalConfig::toKeyValue()
{
std::string res;
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
for (const auto & s : settings)
res += fmt("%s = %s\n", s.first, s.second.value);
return res;
}
void GlobalConfig::convertToArgs(Args & args, const std::string & category)
{
for (auto & config : *configRegistrations)
config->convertToArgs(args, category);
}
GlobalConfig globalConfig;
GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations;
GlobalConfig::Register::Register(Config * config)
{
if (!configRegistrations)
configRegistrations = new ConfigRegistrations;
configRegistrations->emplace_back(config);
}
ExperimentalFeatureSettings experimentalFeatureSettings;
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const
{
auto & f = experimentalFeatures.get();

View file

@ -375,31 +375,6 @@ public:
void operator =(const std::optional<Path> & v);
};
struct GlobalConfig : public AbstractConfig
{
typedef std::vector<Config*> ConfigRegistrations;
static ConfigRegistrations * configRegistrations;
bool set(const std::string & name, const std::string & value) override;
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
void resetOverridden() override;
nlohmann::json toJSON() override;
std::string toKeyValue() override;
void convertToArgs(Args & args, const std::string & category) override;
struct Register
{
Register(Config * config);
};
};
extern GlobalConfig globalConfig;
struct ExperimentalFeatureSettings : Config {

View file

@ -155,6 +155,7 @@ public:
: err(e)
{ }
/** The error message without "error: " prefixed to it. */
std::string message() {
return err.msg.str();
}

View file

@ -10,7 +10,7 @@ static std::optional<FileSerialisationMethod> parseFileSerialisationMethodOpt(st
if (input == "flat") {
return FileSerialisationMethod::Flat;
} else if (input == "nar") {
return FileSerialisationMethod::Recursive;
return FileSerialisationMethod::NixArchive;
} else {
return std::nullopt;
}
@ -45,7 +45,7 @@ std::string_view renderFileSerialisationMethod(FileSerialisationMethod method)
switch (method) {
case FileSerialisationMethod::Flat:
return "flat";
case FileSerialisationMethod::Recursive:
case FileSerialisationMethod::NixArchive:
return "nar";
default:
assert(false);
@ -57,7 +57,7 @@ std::string_view renderFileIngestionMethod(FileIngestionMethod method)
{
switch (method) {
case FileIngestionMethod::Flat:
case FileIngestionMethod::Recursive:
case FileIngestionMethod::NixArchive:
return renderFileSerialisationMethod(
static_cast<FileSerialisationMethod>(method));
case FileIngestionMethod::Git:
@ -78,7 +78,7 @@ void dumpPath(
case FileSerialisationMethod::Flat:
path.readFile(sink);
break;
case FileSerialisationMethod::Recursive:
case FileSerialisationMethod::NixArchive:
path.dumpPath(sink, filter);
break;
}
@ -94,7 +94,7 @@ void restorePath(
case FileSerialisationMethod::Flat:
writeFile(path, source);
break;
case FileSerialisationMethod::Recursive:
case FileSerialisationMethod::NixArchive:
restorePath(path, source);
break;
}
@ -119,7 +119,7 @@ std::pair<Hash, std::optional<uint64_t>> hashPath(
{
switch (method) {
case FileIngestionMethod::Flat:
case FileIngestionMethod::Recursive: {
case FileIngestionMethod::NixArchive: {
auto res = hashPath(path, (FileSerialisationMethod) method, ht, filter);
return {res.first, {res.second}};
}

View file

@ -35,14 +35,14 @@ enum struct FileSerialisationMethod : uint8_t {
* See `file-system-object/content-address.md#serial-nix-archive` in
* the manual.
*/
Recursive,
NixArchive,
};
/**
* Parse a `FileSerialisationMethod` by name. Choice of:
*
* - `flat`: `FileSerialisationMethod::Flat`
* - `nar`: `FileSerialisationMethod::Recursive`
* - `nar`: `FileSerialisationMethod::NixArchive`
*
* Opposite of `renderFileSerialisationMethod`.
*/
@ -107,16 +107,18 @@ enum struct FileIngestionMethod : uint8_t {
Flat,
/**
* Hash `FileSerialisationMethod::Recursive` serialisation.
* Hash `FileSerialisationMethod::NixArchive` serialisation.
*
* See `file-system-object/content-address.md#serial-flat` in the
* manual.
*/
Recursive,
NixArchive,
/**
* Git hashing.
*
* Part of `ExperimentalFeature::GitHashing`.
*
* See `file-system-object/content-address.md#serial-git` in the
* manual.
*/
@ -127,7 +129,7 @@ enum struct FileIngestionMethod : uint8_t {
* Parse a `FileIngestionMethod` by name. Choice of:
*
* - `flat`: `FileIngestionMethod::Flat`
* - `nar`: `FileIngestionMethod::Recursive`
* - `nar`: `FileIngestionMethod::NixArchive`
* - `git`: `FileIngestionMethod::Git`
*
* Opposite of `renderFileIngestionMethod`.

View file

@ -418,30 +418,13 @@ void createDir(const Path & path, mode_t mode)
throw SysError("creating directory '%1%'", path);
}
Paths createDirs(const Path & path)
void createDirs(const Path & path)
{
Paths created;
if (path == "/") return created;
struct stat st;
if (STAT(path.c_str(), &st) == -1) {
created = createDirs(dirOf(path));
if (mkdir(path.c_str()
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
, 0777
#endif
) == -1 && errno != EEXIST)
throw SysError("creating directory '%1%'", path);
st = STAT(path);
created.push_back(path);
try {
fs::create_directories(path);
} catch (fs::filesystem_error & e) {
throw SysError("creating directory '%1%'", path);
}
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
throw SysError("statting symlink '%1%'", path);
if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
return created;
}
@ -579,29 +562,69 @@ void replaceSymlink(const Path & target, const Path & link)
}
}
#ifndef _WIN32
static void setWriteTime(const fs::path & p, const struct stat & st)
void setWriteTime(
const std::filesystem::path & path,
time_t accessedTime,
time_t modificationTime,
std::optional<bool> optIsSymlink)
{
struct timeval times[2];
times[0] = {
.tv_sec = st.st_atime,
.tv_usec = 0,
#ifndef _WIN32
struct timeval times[2] = {
{
.tv_sec = accessedTime,
.tv_usec = 0,
},
{
.tv_sec = modificationTime,
.tv_usec = 0,
},
};
times[1] = {
.tv_sec = st.st_mtime,
.tv_usec = 0,
};
if (lutimes(p.c_str(), times) != 0)
throw SysError("changing modification time of '%s'", p);
}
#endif
auto nonSymlink = [&]{
bool isSymlink = optIsSymlink
? *optIsSymlink
: fs::is_symlink(path);
if (!isSymlink) {
#ifdef _WIN32
// FIXME use `fs::last_write_time`.
//
// Would be nice to use std::filesystem unconditionally, but
// doesn't support access time just modification time.
//
// System clock vs File clock issues also make that annoying.
warn("Changing file times is not yet implemented on Windows, path is '%s'", path);
#else
if (utimes(path.c_str(), times) == -1) {
throw SysError("changing modification time of '%s' (not a symlink)", path);
}
#endif
} else {
throw Error("Cannot modification time of symlink '%s'", path);
}
};
#if HAVE_LUTIMES
if (lutimes(path.c_str(), times) == -1) {
if (errno == ENOSYS)
nonSymlink();
else
throw SysError("changing modification time of '%s'", path);
}
#else
nonSymlink();
#endif
}
void setWriteTime(const fs::path & path, const struct stat & st)
{
setWriteTime(path, st.st_atime, st.st_mtime, S_ISLNK(st.st_mode));
}
void copyFile(const fs::path & from, const fs::path & to, bool andDelete)
{
#ifndef _WIN32
// TODO: Rewrite the `is_*` to use `symlink_status()`
auto statOfFrom = lstat(from.c_str());
#endif
auto fromStatus = fs::symlink_status(from);
// Mark the directory as writable so that we can delete its children
@ -621,9 +644,7 @@ void copyFile(const fs::path & from, const fs::path & to, bool andDelete)
throw Error("file '%s' has an unsupported type", from);
}
#ifndef _WIN32
setWriteTime(to, statOfFrom);
#endif
setWriteTime(to, lstat(from.string().c_str()));
if (andDelete) {
if (!fs::is_symlink(fromStatus))
fs::permissions(from, fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);

View file

@ -148,11 +148,10 @@ void deletePath(const std::filesystem::path & path);
void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed);
/**
* Create a directory and all its parents, if necessary. Returns the
* list of created directories, in order of creation.
* Create a directory and all its parents, if necessary.
*/
Paths createDirs(const Path & path);
inline Paths createDirs(PathView path)
void createDirs(const Path & path);
inline void createDirs(PathView path)
{
return createDirs(Path(path));
}
@ -162,6 +161,30 @@ inline Paths createDirs(PathView path)
*/
void createDir(const Path & path, mode_t mode = 0755);
/**
* Set the access and modification times of the given path, not
* following symlinks.
*
* @param accessTime Specified in seconds.
*
* @param modificationTime Specified in seconds.
*
* @param isSymlink Whether the file in question is a symlink. Used for
* fallback code where we don't have `lutimes` or similar. if
* `std::optional` is passed, the information will be recomputed if it
* is needed. Race conditions are possible so be careful!
*/
void setWriteTime(
const std::filesystem::path & path,
time_t accessedTime,
time_t modificationTime,
std::optional<bool> isSymlink = std::nullopt);
/**
* Convenience wrapper that takes all arguments from the `struct stat`.
*/
void setWriteTime(const std::filesystem::path & path, const struct stat & st);
/**
* Create a symlink.
*/

View file

@ -111,6 +111,8 @@ std::ostream & operator<<(std::ostream & out, const Magenta<T> & y)
/**
* Values wrapped in this class are printed without coloring.
*
* Specifically, the color is reset to normal before printing the value.
*
* By default, arguments to `HintFmt` are printed in magenta (see `Magenta`).
*/
template <class T>

View file

@ -1,7 +1,7 @@
#include <fcntl.h>
#include "error.hh"
#include "config.hh"
#include "config-global.hh"
#include "fs-sink.hh"
#if _WIN32

View file

@ -3,7 +3,7 @@
#include "environment-variables.hh"
#include "terminal.hh"
#include "util.hh"
#include "config.hh"
#include "config-global.hh"
#include "source-path.hh"
#include "position.hh"

View file

@ -161,6 +161,7 @@ sources = files(
'compression.cc',
'compute-levels.cc',
'config.cc',
'config-global.cc',
'current-process.cc',
'english.cc',
'environment-variables.cc',
@ -211,6 +212,7 @@ headers = [config_h] + files(
'comparator.hh',
'compression.hh',
'compute-levels.hh',
'config-global.hh',
'config-impl.hh',
'config.hh',
'current-process.hh',

View file

@ -1,7 +1,6 @@
{ lib
, stdenv
, releaseTools
, fileset
, meson
, ninja
@ -18,7 +17,6 @@
# Configuration Options
, versionSuffix ? ""
, officialRelease ? false
# Check test coverage of Nix. Probably want to use with at least
# one of `doCheck` or `doInstallCheck` enabled.
@ -26,6 +24,8 @@
}:
let
inherit (lib) fileset;
version = lib.fileContents ./.version + versionSuffix;
mkDerivation =

View file

@ -3,6 +3,7 @@
#include "types.hh"
#include "error.hh"
#include "file-descriptor.hh"
#include "logging.hh"
#include "ansicolor.hh"
@ -23,26 +24,36 @@ namespace nix {
struct Sink;
struct Source;
#ifndef _WIN32
class Pid
{
#ifndef _WIN32
pid_t pid = -1;
bool separatePG = false;
int killSignal = SIGKILL;
#else
AutoCloseFD pid = INVALID_DESCRIPTOR;
#endif
public:
Pid();
#ifndef _WIN32
Pid(pid_t pid);
~Pid();
void operator =(pid_t pid);
operator pid_t();
#else
Pid(AutoCloseFD pid);
void operator =(AutoCloseFD pid);
#endif
~Pid();
int kill();
int wait();
// TODO: Implement for Windows
#ifndef _WIN32
void setSeparatePG(bool separatePG);
void setKillSignal(int signal);
pid_t release();
};
#endif
};
#ifndef _WIN32

View file

@ -1,9 +1,15 @@
#include "current-process.hh"
#include "environment-variables.hh"
#include "error.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>
@ -16,25 +22,347 @@
#include <sys/types.h>
#include <unistd.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix {
std::string runProgram(Path program, bool lookupPath, const Strings & args,
const std::optional<std::string> & input, bool isInteractive)
using namespace nix::windows;
Pid::Pid() {}
Pid::Pid(AutoCloseFD pid)
: pid(std::move(pid))
{
throw UnimplementedError("Cannot shell out to git on Windows yet");
}
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)
{
throw UnimplementedError("Cannot shell out to git on Windows yet");
}
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)
{
throw UnimplementedError("Cannot shell out to git on Windows yet");
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)
@ -45,10 +373,8 @@ std::string statusToString(int status)
return "succeeded";
}
bool statusOk(int status)
{
return status == 0;
}
}