Build a minimized Nix with MinGW

At this point many features are stripped out, but this works:

- Can run libnix{util,store,expr} unit tests
- Can run some Nix commands

Co-Authored-By volth <volth@volth.com>
Co-Authored-By Brian McKenna <brian@brianmckenna.org>
This commit is contained in:
John Ericson 2023-09-02 17:35:16 -04:00
parent 2248a3f545
commit 8433027e35
111 changed files with 1162 additions and 140 deletions

View File

@ -7,6 +7,8 @@ clean-files += $(buildprefix)Makefile.config
# List makefiles
include mk/platform.mk
ifeq ($(ENABLE_BUILD), yes)
makefiles = \
mk/precompiled-headers.mk \
@ -20,7 +22,10 @@ makefiles = \
src/nix/local.mk \
src/libutil-c/local.mk \
src/libstore-c/local.mk \
src/libexpr-c/local.mk \
src/libexpr-c/local.mk
ifdef HOST_UNIX
makefiles += \
scripts/local.mk \
misc/bash/local.mk \
misc/fish/local.mk \
@ -29,6 +34,7 @@ makefiles = \
misc/launchd/local.mk \
misc/upstart/local.mk
endif
endif
ifeq ($(ENABLE_UNIT_TESTS), yes)
makefiles += \
@ -42,6 +48,7 @@ makefiles += \
endif
ifeq ($(ENABLE_FUNCTIONAL_TESTS), yes)
ifdef HOST_UNIX
makefiles += \
tests/functional/local.mk \
tests/functional/ca/local.mk \
@ -51,6 +58,7 @@ makefiles += \
tests/functional/test-libstoreconsumer/local.mk \
tests/functional/plugins/local.mk
endif
endif
# Some makefiles require access to built programs and must be included late.
makefiles-late =
@ -79,8 +87,6 @@ else
unexport NIX_HARDENING_ENABLE
endif
include mk/platform.mk
ifdef HOST_WINDOWS
# Windows DLLs are stricter about symbol visibility than Unix shared
# objects --- see https://gcc.gnu.org/wiki/Visibility for details.

View File

@ -46,11 +46,13 @@ AC_DEFUN([ENSURE_NO_GCC_BUG_80431],
]])],
[status_80431=0],
[status_80431=$?],
[
# Assume we're bug-free when cross-compiling
])
[status_80431=''])
AC_LANG_POP(C++)
AS_CASE([$status_80431],
[''],[
AC_MSG_RESULT(cannot check because cross compiling)
AC_MSG_NOTICE(assume we are bug free)
],
[0],[
AC_MSG_RESULT(yes)
],

View File

@ -42,19 +42,22 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#ifndef _WIN32
# include <grp.h>
# include <netdb.h>
# include <pwd.h>
# include <sys/resource.h>
# include <sys/select.h>
# include <sys/socket.h>
# include <sys/utsname.h>
# include <sys/wait.h>
# include <termios.h>
#endif
#include <nlohmann/json.hpp>

View File

@ -181,7 +181,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
v->mkString(arg.s);
},
[&](const AutoArgFile & arg) {
v->mkString(readFile(arg.path));
v->mkString(readFile(arg.path.string()));
},
[&](const AutoArgStdin & arg) {
v->mkString(readFile(STDIN_FILENO));

View File

@ -3,9 +3,9 @@
#include "finally.hh"
#include "terminal.hh"
#include <sys/queue.h>
#if HAVE_LOWDOWN
#include <lowdown.h>
# include <sys/queue.h>
# include <lowdown.h>
#endif
namespace nix {

View File

@ -137,6 +137,7 @@ static constexpr const char * promptForType(ReplPromptType promptType)
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
{
#ifndef _WIN32 // TODO use more signals.hh for this
struct sigaction act, old;
sigset_t savedSignalMask, set;
@ -161,9 +162,12 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
};
setupSignals();
#endif
char * s = readline(promptForType(promptType));
Finally doFree([&]() { free(s); });
#ifndef _WIN32 // TODO use more signals.hh for this
restoreSignals();
#endif
if (g_signal_received) {
g_signal_received = 0;

View File

@ -33,15 +33,17 @@
#include <optional>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <sys/resource.h>
#include <nlohmann/json.hpp>
#include <boost/container/small_vector.hpp>
#ifndef _WIN32 // TODO use portable implementation
# include <sys/resource.h>
#endif
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
@ -2627,9 +2629,11 @@ void EvalState::maybePrintStats()
void EvalState::printStatistics()
{
#ifndef _WIN32 // TODO use portable implementation
struct rusage buf;
getrusage(RUSAGE_SELF, &buf);
float cpuTime = buf.ru_utime.tv_sec + ((float) buf.ru_utime.tv_usec / 1000000);
#endif
uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *);
uint64_t bLists = nrListElems * sizeof(Value *);
@ -2646,7 +2650,9 @@ void EvalState::printStatistics()
if (outPath != "-")
fs.open(outPath, std::fstream::out);
json topObj = json::object();
#ifndef _WIN32 // TODO implement
topObj["cpuTime"] = cpuTime;
#endif
topObj["envs"] = {
{"number", nrEnvs},
{"elements", nrValuesInEnvs},

View File

@ -161,6 +161,8 @@ struct DebugTrace {
bool isError;
};
// Don't want Windows function
#undef SearchPath
class EvalState : public std::enable_shared_from_this<EvalState>
{

View File

@ -28,7 +28,10 @@
#include <algorithm>
#include <cstring>
#include <regex>
#include <dlfcn.h>
#ifndef _WIN32
# include <dlfcn.h>
#endif
#include <cmath>
@ -331,6 +334,8 @@ static RegisterPrimOp primop_import({
}
});
#ifndef _WIN32 // TODO implement via DLL loading on Windows
/* Want reasonable symbol names, so extern C */
/* !!! Should we pass the Pos or the file name too? */
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
@ -403,6 +408,8 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
}
}
#endif
/* Return a string representing the type of the expression. */
static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
@ -4593,6 +4600,7 @@ void EvalState::createBaseEnv()
)",
});
#ifndef _WIN32 // TODO implement on Windows
// Miscellaneous
if (evalSettings.enableNativeCode) {
addPrimOp({
@ -4606,6 +4614,7 @@ void EvalState::createBaseEnv()
.fun = prim_exec,
});
}
#endif
addPrimOp({
.name = "__traceVerbose",

View File

@ -8,6 +8,9 @@
namespace nix {
// Do not want the windows macro (alias to `SearchPathA`)
#undef SearchPath
/**
* A "search path" is a list of ways look for something, used with
* `builtins.findFile` and `< >` lookup expressions.

View File

@ -26,7 +26,7 @@ ref<InputAccessor> makeStorePathAccessor(
// FIXME: should use `store->getFSAccessor()`
auto root = std::filesystem::path { store->toRealPath(storePath) };
auto accessor = makeFSInputAccessor(root);
accessor->setPathDisplay(root);
accessor->setPathDisplay(root.string());
return accessor;
}

View File

@ -151,11 +151,11 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
{
initLibGit2();
if (pathExists(path.native())) {
if (git_repository_open(Setter(repo), path.c_str()))
if (pathExists(path.string())) {
if (git_repository_open(Setter(repo), path.string().c_str()))
throw Error("opening Git repository '%s': %s", path, git_error_last()->message);
} else {
if (git_repository_init(Setter(repo), path.c_str(), bare))
if (git_repository_init(Setter(repo), path.string().c_str(), bare))
throw Error("creating Git repository '%s': %s", path, git_error_last()->message);
}
}
@ -216,7 +216,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
std::vector<Submodule> parseSubmodules(const std::filesystem::path & configFile)
{
GitConfig config;
if (git_config_open_ondisk(Setter(config), configFile.c_str()))
if (git_config_open_ondisk(Setter(config), configFile.string().c_str()))
throw Error("parsing .gitmodules file: %s", git_error_last()->message);
ConfigIterator it;
@ -288,7 +288,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
/* Get submodule info. */
auto modulesFile = path / ".gitmodules";
if (pathExists(modulesFile))
if (pathExists(modulesFile.string()))
info.submodules = parseSubmodules(modulesFile);
return info;
@ -377,10 +377,10 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
auto dir = this->path;
Strings gitArgs;
if (shallow) {
gitArgs = { "-C", dir, "fetch", "--quiet", "--force", "--depth", "1", "--", url, refspec };
gitArgs = { "-C", dir.string(), "fetch", "--quiet", "--force", "--depth", "1", "--", url, refspec };
}
else {
gitArgs = { "-C", dir, "fetch", "--quiet", "--force", "--", url, refspec };
gitArgs = { "-C", dir.string(), "fetch", "--quiet", "--force", "--", url, refspec };
}
runProgram(RunOptions {
@ -426,7 +426,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
.args = {
"-c",
"gpg.ssh.allowedSignersFile=" + allowedSignersFile,
"-C", path,
"-C", path.string(),
"verify-commit",
rev.gitRev()
},

View File

@ -108,7 +108,9 @@ std::string getArg(const std::string & opt,
return *i;
}
#ifndef _WIN32
static void sigHandler(int signo) { }
#endif
void initNix()
@ -121,6 +123,7 @@ void initNix()
initLibStore();
#ifndef _WIN32
unix::startSignalHandlerThread();
/* Reset SIGCHLD to its default. */
@ -135,6 +138,7 @@ void initNix()
/* Install a dummy SIGUSR1 handler for use with pthread_kill(). */
act.sa_handler = sigHandler;
if (sigaction(SIGUSR1, &act, 0)) throw SysError("handling SIGUSR1");
#endif
#if __APPLE__
/* HACK: on darwin, we need cant use sigprocmask with SIGWINCH.
@ -156,21 +160,26 @@ void initNix()
if (sigaction(SIGTRAP, &act, 0)) throw SysError("handling SIGTRAP");
#endif
#ifndef _WIN32
/* Register a SIGSEGV handler to detect stack overflows.
Why not initLibExpr()? initGC() is essentially that, but
detectStackOverflow is not an instance of the init function concept, as
it may have to be invoked more than once per process. */
detectStackOverflow();
#endif
/* There is no privacy in the Nix system ;-) At least not for
now. In particular, store objects should be readable by
everybody. */
umask(0022);
#ifndef _WIN32
/* Initialise the PRNG. */
struct timeval tv;
gettimeofday(&tv, 0);
srandom(tv.tv_usec);
#endif
}
@ -365,6 +374,9 @@ RunPager::RunPager()
Pipe toPager;
toPager.create();
#ifdef _WIN32 // TODO re-enable on Windows, once we can start processes.
throw Error("Commit signature verification not implemented on Windows yet");
#else
pid = startProcess([&]() {
if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin");
@ -383,17 +395,20 @@ RunPager::RunPager()
std_out = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0);
if (dup2(toPager.writeSide.get(), STDOUT_FILENO) == -1)
throw SysError("dupping standard output");
#endif
}
RunPager::~RunPager()
{
try {
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
if (pid != -1) {
std::cout.flush();
dup2(std_out, STDOUT_FILENO);
pid.wait();
}
#endif
} catch (...) {
ignoreException();
}

View File

@ -1,6 +1,7 @@
#pragma once
///@file
#include "file-descriptor.hh"
#include "processes.hh"
#include "args.hh"
#include "args/root.hh"
@ -89,8 +90,10 @@ public:
~RunPager();
private:
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Pid pid;
int std_out;
#endif
Descriptor std_out;
};
extern volatile ::sig_atomic_t blockInt;
@ -112,6 +115,7 @@ struct PrintFreed
};
#ifndef _WIN32
/**
* Install a SIGSEGV handler to detect stack overflows.
*/
@ -141,5 +145,6 @@ extern std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler;
* logger. Exits the process immediately after.
*/
void defaultStackOverflowHandler(siginfo_t * info, void * ctx);
#endif
}

View File

@ -76,7 +76,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
if (unlink(dstFile.c_str()) == -1)
throw SysError("unlinking '%1%'", dstFile);
if (mkdir(dstFile.c_str(), 0755) == -1)
if (mkdir(dstFile.c_str()
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
, 0755
#endif
) == -1)
throw SysError("creating directory '%1%'", dstFile);
createLinks(state, target, dstFile, state.priorities[dstFile]);
createLinks(state, srcFile, dstFile, priority);

View File

@ -1,5 +1,4 @@
#include "daemon.hh"
#include "monitor-fd.hh"
#include "signals.hh"
#include "worker-protocol.hh"
#include "worker-protocol-impl.hh"
@ -16,6 +15,10 @@
#include "args.hh"
#include "git.hh"
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
# include "monitor-fd.hh"
#endif
namespace nix::daemon {
Sink & operator << (Sink & sink, const Logger::Fields & fields)
@ -1018,7 +1021,9 @@ void processConnection(
TrustedFlag trusted,
RecursiveFlag recursive)
{
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
auto monitor = !recursive ? std::make_unique<MonitorFdHup>(from.fd) : nullptr;
#endif
/* Exchange the greeting. */
unsigned int magic = readInt(from);

View File

@ -516,10 +516,12 @@ struct curlFileTransfer : public FileTransfer
Sync<State> state_;
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
/* We can't use a std::condition_variable to wake up the curl
thread, because it only monitors file descriptors. So use a
pipe instead. */
Pipe wakeupPipe;
#endif
std::thread workerThread;
@ -539,8 +541,10 @@ struct curlFileTransfer : public FileTransfer
fileTransferSettings.httpConnections.get());
#endif
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
wakeupPipe.create();
fcntl(wakeupPipe.readSide.get(), F_SETFL, O_NONBLOCK);
#endif
workerThread = std::thread([&]() { workerThreadEntry(); });
}
@ -561,15 +565,19 @@ struct curlFileTransfer : public FileTransfer
auto state(state_.lock());
state->quit = true;
}
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " ", false);
#endif
}
void workerThreadMain()
{
/* Cause this thread to be notified on SIGINT. */
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
auto callback = createInterruptCallback([&]() {
stopWorkerThread();
});
#endif
#if __linux__
unshareFilesystem();
@ -607,9 +615,11 @@ struct curlFileTransfer : public FileTransfer
/* Wait for activity, including wakeup events. */
int numfds = 0;
struct curl_waitfd extraFDs[1];
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
extraFDs[0].fd = wakeupPipe.readSide.get();
extraFDs[0].events = CURL_WAIT_POLLIN;
extraFDs[0].revents = 0;
#endif
long maxSleepTimeMs = items.empty() ? 10000 : 100;
auto sleepTimeMs =
nextWakeup != std::chrono::steady_clock::time_point()
@ -693,7 +703,9 @@ struct curlFileTransfer : public FileTransfer
throw nix::Error("cannot enqueue download request because the download thread is shutting down");
state->incoming.push(item);
}
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " ");
#endif
}
#if ENABLE_S3

View File

@ -9,11 +9,14 @@
#include <map>
#include <mutex>
#include <thread>
#include <dlfcn.h>
#include <sys/utsname.h>
#include <nlohmann/json.hpp>
#ifndef _WIN32
# include <dlfcn.h>
# include <sys/utsname.h>
#endif
#ifdef __GLIBC__
# include <gnu/lib-names.h>
# include <nss.h>
@ -56,7 +59,9 @@ Settings::Settings()
, nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
{
#ifndef _WIN32
buildUsersGroup = isRootUser() ? "nixbld" : "";
#endif
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
@ -239,11 +244,15 @@ StringSet Settings::getDefaultExtraPlatforms()
bool Settings::isWSL1()
{
#if __linux__
struct utsname utsbuf;
uname(&utsbuf);
// WSL1 uses -Microsoft suffix
// WSL2 uses -microsoft-standard suffix
return hasSuffix(utsbuf.release, "-Microsoft");
#else
return false;
#endif
}
Path Settings::getDefaultSSLCertFile()
@ -341,6 +350,7 @@ void initPlugins()
for (const auto & file : pluginFiles) {
/* handle is purposefully leaked as there may be state in the
DSO needed by the action of the plugin. */
#ifndef _WIN32 // TODO implement via DLL loading on Windows
void *handle =
dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
@ -351,6 +361,9 @@ void initPlugins()
void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry");
if (nix_plugin_entry)
nix_plugin_entry();
#else
throw Error("could not dynamically open plugin file '%s'", file);
#endif
}
}

View File

@ -666,6 +666,7 @@ public:
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."};
#ifndef _WIN32
Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups",
R"(
Following the principle of least privilege,
@ -683,6 +684,7 @@ public:
(since `root` usually has permissions to call setgroups)
and `false` otherwise.
)"};
#endif
#if __linux__
Setting<std::string> sandboxShmSize{

View File

@ -4,9 +4,12 @@ libstore_NAME = libnixstore
libstore_DIR := $(d)
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc)
ifdef HOST_UNIX
libstore_SOURCES += $(wildcard $(d)/unix/*.cc)
libstore_SOURCES += $(wildcard $(d)/unix/*.cc $(d)/unix/builtins/*.cc $(d)/unix/build/*.cc)
endif
ifdef HOST_WINDOWS
libstore_SOURCES += $(wildcard $(d)/windows/*.cc)
endif
libstore_LIBS = libutil
@ -55,9 +58,9 @@ libstore_CXXFLAGS += \
ifeq ($(embedded_sandbox_shell),yes)
libstore_CXXFLAGS += -DSANDBOX_SHELL=\"__embedded_sandbox_shell__\"
$(d)/build/local-derivation-goal.cc: $(d)/embedded-sandbox-shell.gen.hh
$(d)/unix/build/local-derivation-goal.cc: $(d)/unix/embedded-sandbox-shell.gen.hh
$(d)/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
$(d)/unix/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
$(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
@mv $@.tmp $@
else
@ -66,11 +69,11 @@ else
endif
endif
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
$(d)/unix/local-store.cc: $(d)/unix/schema.sql.gen.hh $(d)/unix/ca-specific-schema.sql.gen.hh
$(d)/build.cc:
$(d)/unix/build.cc:
clean-files += $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
clean-files += $(d)/unix/schema.sql.gen.hh $(d)/unix/ca-specific-schema.sql.gen.hh
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-store.pc, $(libdir)/pkgconfig, 0644))

View File

@ -1,7 +1,6 @@
#include "derivations.hh"
#include "parsed-derivations.hh"
#include "globals.hh"
#include "local-store.hh"
#include "store-api.hh"
#include "thread-pool.hh"
#include "realisation.hh"

View File

@ -8,6 +8,7 @@
#include "types.hh"
#include "pathlocks.hh"
#include <optional>
#include <time.h>

View File

@ -71,11 +71,15 @@ std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPat
auto narAccessor = makeLazyNarAccessor(listing,
[cacheFile](uint64_t offset, uint64_t length) {
AutoCloseFD fd = open(cacheFile.c_str(), O_RDONLY | O_CLOEXEC);
AutoCloseFD fd = toDescriptor(open(cacheFile.c_str(), O_RDONLY
#ifndef _WIN32
| O_CLOEXEC
#endif
));
if (!fd)
throw SysError("opening NAR cache file '%s'", cacheFile);
if (lseek(fd.get(), offset, SEEK_SET) != (off_t) offset)
if (lseek(fromDescriptorReadOnly(fd.get()), offset, SEEK_SET) != (off_t) offset)
throw SysError("seeking in '%s'", cacheFile);
std::string buf(length, 0);

View File

@ -55,6 +55,9 @@ bool SSHMaster::isMasterRunning() {
std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(
Strings && command, Strings && extraSshArgs)
{
#ifdef _WIN32 // TODO re-enable on Windows, once we can start processes.
throw UnimplementedError("cannot yet SSH on windows because spawning processes is not yet implemented");
#else
Path socketPath = startMaster();
Pipe in, out;
@ -105,8 +108,8 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(
}, options);
in.readSide = -1;
out.writeSide = -1;
in.readSide = INVALID_DESCRIPTOR;
out.writeSide = INVALID_DESCRIPTOR;
// Wait for the SSH connection to be established,
// So that we don't overwrite the password prompt with our progress bar.
@ -126,15 +129,18 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(
conn->in = std::move(in.writeSide);
return conn;
#endif
}
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Path SSHMaster::startMaster()
{
if (!useMaster) return "";
auto state(state_.lock());
if (state->sshMaster != -1) return state->socketPath;
if (state->sshMaster != INVALID_DESCRIPTOR) return state->socketPath;
state->socketPath = (Path) *state->tmpDir + "/ssh.sock";
@ -167,7 +173,7 @@ Path SSHMaster::startMaster()
throw SysError("unable to execute '%s'", args.front());
}, options);
out.writeSide = -1;
out.writeSide = INVALID_DESCRIPTOR;
std::string reply;
try {
@ -182,4 +188,6 @@ Path SSHMaster::startMaster()
return state->socketPath;
}
#endif
}

View File

@ -21,7 +21,9 @@ private:
struct State
{
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Pid sshMaster;
#endif
std::unique_ptr<AutoDelete> tmpDir;
Path socketPath;
};
@ -31,13 +33,19 @@ private:
void addCommonSSHOpts(Strings & args);
bool isMasterRunning();
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Path startMaster();
#endif
public:
SSHMaster(const std::string & host, const std::string & keyFile, const std::string & sshPublicHostKey, bool useMaster, bool compress, int logFD = -1);
struct Connection
{
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Pid sshPid;
#endif
AutoCloseFD out, in;
};
@ -51,8 +59,6 @@ public:
std::unique_ptr<Connection> startCommand(
Strings && command,
Strings && extraSshArgs = {});
Path startMaster();
};
}

View File

@ -13,7 +13,6 @@
#include "archive.hh"
#include "callback.hh"
#include "git.hh"
#include "remote-store.hh"
#include "posix-source-accessor.hh"
// FIXME this should not be here, see TODO below on
// `addMultipleToStore`.
@ -21,6 +20,10 @@
#include "signals.hh"
#include "users.hh"
#ifndef _WIN32
# include "remote-store.hh"
#endif
#include <nlohmann/json.hpp>
#include <regex>
@ -1266,9 +1269,10 @@ Derivation Store::readInvalidDerivation(const StorePath & drvPath)
}
#include "local-store.hh"
#include "uds-remote-store.hh"
#ifndef _WIN32
# include "local-store.hh"
# include "uds-remote-store.hh"
#endif
namespace nix {
@ -1286,6 +1290,9 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri_
return {uri, params};
}
#ifdef _WIN32 // Unused on Windows because the next `#ifndef`
[[maybe_unused]]
#endif
static bool isNonUriPath(const std::string & spec)
{
return
@ -1298,6 +1305,9 @@ static bool isNonUriPath(const std::string & spec)
std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Params & params)
{
// TODO reenable on Windows once we have `LocalStore` and
// `UDSRemoteStore`.
#ifndef _WIN32
if (uri == "" || uri == "auto") {
auto stateDir = getOr(params, "state", settings.nixStateDir);
if (access(stateDir.c_str(), R_OK | W_OK) == 0)
@ -1342,6 +1352,9 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
} else {
return nullptr;
}
#else
return nullptr;
#endif
}
// The `parseURL` function supports both IPv6 URIs as defined in

View File

@ -0,0 +1,37 @@
#include "store-api.hh"
#include "build-result.hh"
namespace nix {
void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMode, std::shared_ptr<Store> evalStore)
{
unsupported("buildPaths");
}
std::vector<KeyedBuildResult> Store::buildPathsWithResults(
const std::vector<DerivedPath> & reqs,
BuildMode buildMode,
std::shared_ptr<Store> evalStore)
{
unsupported("buildPathsWithResults");
}
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode)
{
unsupported("buildDerivation");
}
void Store::ensurePath(const StorePath & path)
{
unsupported("ensurePath");
}
void Store::repairPath(const StorePath & path)
{
unsupported("repairPath");
}
}

View File

@ -9,7 +9,9 @@
#include <fstream>
#include <string>
#include <regex>
#include <glob.h>
#ifndef _WIN32
# include <glob.h>
#endif
namespace nix {
@ -547,6 +549,7 @@ nlohmann::json Args::toJSON()
static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs)
{
completions.setType(Completions::Type::Filenames);
#ifndef _WIN32 // TODO implement globbing completions on Windows
glob_t globbuf;
int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR
@ -564,6 +567,7 @@ static void _completePath(AddCompletions & completions, std::string_view prefix,
}
}
globfree(&globbuf);
#endif
}
void Args::completePath(AddCompletions & completions, size_t, std::string_view prefix)

View File

@ -19,7 +19,9 @@
# include "namespaces.hh"
#endif
#include <sys/mount.h>
#ifndef _WIN32
# include <sys/mount.h>
#endif
namespace nix {
@ -57,6 +59,7 @@ unsigned int getMaxCPU()
//////////////////////////////////////////////////////////////////////
#ifndef _WIN32
rlim_t savedStackSize = 0;
void setStackSize(rlim_t stackSize)
@ -79,16 +82,20 @@ void setStackSize(rlim_t stackSize)
}
}
}
#endif
void restoreProcessContext(bool restoreMounts)
{
#ifndef _WIN32
unix::restoreSignals();
#endif
if (restoreMounts) {
#if __linux__
restoreMountNamespace();
#endif
}
#ifndef _WIN32
if (savedStackSize) {
struct rlimit limit;
if (getrlimit(RLIMIT_STACK, &limit) == 0) {
@ -96,6 +103,7 @@ void restoreProcessContext(bool restoreMounts)
setrlimit(RLIMIT_STACK, &limit);
}
}
#endif
}

View File

@ -2,7 +2,10 @@
///@file
#include <optional>
#include <sys/resource.h>
#ifndef _WIN32
# include <sys/resource.h>
#endif
#include "types.hh"
@ -14,16 +17,18 @@ namespace nix {
*/
unsigned int getMaxCPU();
#ifndef _WIN32 // TODO implement on Windows, if needed.
/**
* Change the stack size.
*/
void setStackSize(rlim_t stackSize);
#endif
/**
* Restore the original inherited Unix process context (such as signal
* masks, stack size).
* See startSignalHandlerThread(), saveSignalMask().
* See unix::startSignalHandlerThread(), unix::saveSignalMask().
*/
void restoreProcessContext(bool restoreMounts = true);

View File

@ -28,6 +28,13 @@ std::optional<std::string> getEnvNonEmpty(const std::string & key);
*/
std::map<std::string, std::string> getEnv();
#ifdef _WIN32
/**
* Implementation of missing POSIX function.
*/
int unsetenv(const char * name);
#endif
/**
* Like POSIX `setenv`, but always overrides.
*

View File

@ -247,6 +247,23 @@ public:
}
};
#ifdef _WIN32
class WinError;
#endif
/**
* Convenience alias for when we use a `errno`-based error handling
* function on Unix, and `GetLastError()`-based error handling on on
* Windows.
*/
using NativeSysError =
#ifdef _WIN32
WinError
#else
SysError
#endif
;
/**
* Throw an exception for the purpose of checking that exception
* handling works; see 'initLibUtil()'.

View File

@ -5,6 +5,11 @@
#include <fcntl.h>
#include <unistd.h>
#ifdef _WIN32
# include <winnt.h>
# include <fileapi.h>
# include "windows-error.hh"
#endif
namespace nix {
@ -20,7 +25,13 @@ std::string drainFD(Descriptor fd, bool block, const size_t reserveSize)
// the parser needs two extra bytes to append terminating characters, other users will
// not care very much about the extra memory.
StringSink sink(reserveSize + 2);
#ifdef _WIN32
// non-blocking is not supported this way on Windows
assert(block);
drainFD(fd, sink);
#else
drainFD(fd, sink, block);
#endif
return std::move(sink.s);
}
@ -68,9 +79,15 @@ Descriptor AutoCloseFD::get() const
void AutoCloseFD::close()
{
if (fd != INVALID_DESCRIPTOR) {
if(::close(fd) == -1)
if(
#ifdef _WIN32
::CloseHandle(fd)
#else
::close(fd)
#endif
== -1)
/* This should never happen. */
throw SysError("closing file descriptor %1%", fd);
throw NativeSysError("closing file descriptor %1%", fd);
fd = INVALID_DESCRIPTOR;
}
}
@ -80,14 +97,16 @@ void AutoCloseFD::fsync()
if (fd != INVALID_DESCRIPTOR) {
int result;
result =
#if __APPLE__
#ifdef _WIN32
::FlushFileBuffers(fd)
#elif __APPLE__
::fcntl(fd, F_FULLFSYNC)
#else
::fsync(fd)
#endif
;
if (result == -1)
throw SysError("fsync file descriptor %1%", fd);
throw NativeSysError("fsync file descriptor %1%", fd);
}
}

View File

@ -4,6 +4,11 @@
#include "types.hh"
#include "error.hh"
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
namespace nix {
struct Sink;
@ -12,9 +17,21 @@ struct Source;
/**
* Operating System capability
*/
typedef int Descriptor;
typedef
#if _WIN32
HANDLE
#else
int
#endif
Descriptor;
const Descriptor INVALID_DESCRIPTOR = -1;
const Descriptor INVALID_DESCRIPTOR =
#if _WIN32
INVALID_HANDLE_VALUE
#else
-1
#endif
;
/**
* Convert a native `Descriptor` to a POSIX file descriptor
@ -23,17 +40,26 @@ const Descriptor INVALID_DESCRIPTOR = -1;
*/
static inline Descriptor toDescriptor(int fd)
{
#ifdef _WIN32
return (HANDLE) _get_osfhandle(fd);
#else
return fd;
#endif
}
/**
* Convert a POSIX file descriptor to a native `Descriptor`
* Convert a POSIX file descriptor to a native `Descriptor` in read-only
* mode.
*
* This is a no-op except on Windows.
*/
static inline int fromDescriptor(Descriptor fd, int flags)
static inline int fromDescriptorReadOnly(Descriptor fd)
{
#ifdef _WIN32
return _open_osfhandle((intptr_t) fd, _O_RDONLY);
#else
return fd;
#endif
}
/**
@ -64,11 +90,24 @@ void writeLine(Descriptor fd, std::string s);
*/
std::string drainFD(Descriptor fd, bool block = true, const size_t reserveSize=0);
void drainFD(Descriptor fd, Sink & sink, bool block = true);
/**
* The Windows version is always blocking.
*/
void drainFD(
Descriptor fd
, Sink & sink
#ifndef _WIN32
, bool block = true
#endif
);
[[gnu::always_inline]]
inline Descriptor getStandardOut() {
#ifndef _WIN32
return STDOUT_FILENO;
#else
return GetStdHandle(STD_OUTPUT_HANDLE);
#endif
}
/**
@ -100,6 +139,8 @@ public:
void close();
};
#ifndef _WIN32 // Not needed on Windows, where we don't fork
/**
* Close all file descriptors except those listed in the given set.
* Good practice in child processes.
@ -111,6 +152,15 @@ void closeMostFDs(const std::set<Descriptor> & exceptions);
*/
void closeOnExec(Descriptor fd);
#endif
#ifdef _WIN32
# if _WIN32_WINNT >= 0x0600
Path handleToPath(Descriptor handle);
std::wstring handleToFileName(Descriptor handle);
# endif
#endif
MakeError(EndOfFile, Error);
}

52
src/libutil/file-path.hh Normal file
View File

@ -0,0 +1,52 @@
#pragma once
///@file
#include <optional>
#include <filesystem>
#include "types.hh"
namespace nix {
/**
* Paths are just `std::filesystem::path`s.
*
* @todo drop `NG` suffix and replace the ones in `types.hh`.
*/
typedef std::filesystem::path PathNG;
typedef std::list<Path> PathsNG;
typedef std::set<Path> PathSetNG;
/**
* Stop gap until `std::filesystem::path_view` from P1030R6 exists in a
* future C++ standard.
*
* @todo drop `NG` suffix and replace the one in `types.hh`.
*/
struct PathViewNG : std::basic_string_view<PathNG::value_type>
{
using string_view = std::basic_string_view<PathNG::value_type>;
using string_view::string_view;
PathViewNG(const PathNG & path)
: std::basic_string_view<PathNG::value_type>(path.native())
{ }
PathViewNG(const PathNG::string_type & path)
: std::basic_string_view<PathNG::value_type>(path)
{ }
const string_view & native() const { return *this; }
string_view & native() { return *this; }
};
std::string os_string_to_string(PathViewNG::string_view path);
PathNG::string_type string_to_os_string(std::string_view s);
std::optional<PathNG> maybePathNG(PathView path);
PathNG pathNG(PathView path);
}

View File

@ -1,5 +1,6 @@
#include "environment-variables.hh"
#include "file-system.hh"
#include "file-path.hh"
#include "file-path-impl.hh"
#include "signals.hh"
#include "finally.hh"
@ -18,6 +19,10 @@
#include <sys/time.h>
#include <unistd.h>
#ifdef _WIN32
# include <io.h>
#endif
namespace fs = std::filesystem;
namespace nix {
@ -128,10 +133,10 @@ std::string_view baseNameOf(std::string_view path)
return "";
auto last = path.size() - 1;
while (last > 0 && path[last] == '/')
while (last > 0 && NativePathTrait::isPathSep(path[last]))
last -= 1;
auto pos = path.rfind('/', last);
auto pos = NativePathTrait::rfindPathSep(path, last);
if (pos == path.npos)
pos = 0;
else
@ -164,11 +169,16 @@ struct stat stat(const Path & path)
return st;
}
#ifdef _WIN32
# define STAT stat
#else
# define STAT lstat
#endif
struct stat lstat(const Path & path)
{
struct stat st;
if (lstat(path.c_str(), &st))
if (STAT(path.c_str(), &st))
throw SysError("getting status of '%1%'", path);
return st;
}
@ -177,7 +187,7 @@ struct stat lstat(const Path & path)
std::optional<struct stat> maybeLstat(const Path & path)
{
std::optional<struct stat> st{std::in_place};
if (lstat(path.c_str(), &*st))
if (STAT(path.c_str(), &*st))
{
if (errno == ENOENT || errno == ENOTDIR)
st.reset();
@ -207,6 +217,7 @@ bool pathAccessible(const Path & path)
Path readLink(const Path & path)
{
#ifndef _WIN32
checkInterrupt();
std::vector<char> buf;
for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) {
@ -220,13 +231,16 @@ Path readLink(const Path & path)
else if (rlSize < bufSize)
return std::string(buf.data(), rlSize);
}
#else
// TODO modern Windows does in fact support symlinks
throw UnimplementedError("reading symbolic link '%1%'", path);
#endif
}
bool isLink(const Path & path)
{
struct stat st = lstat(path);
return S_ISLNK(st.st_mode);
return getFileType(path) == DT_LNK;
}
@ -274,7 +288,12 @@ unsigned char getFileType(const Path & path)
std::string readFile(const Path & path)
{
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_RDONLY
// TODO
#ifndef _WIN32
| O_CLOEXEC
#endif
));
if (!fd)
throw SysError("opening file '%1%'", path);
return readFile(fd.get());
@ -283,7 +302,12 @@ std::string readFile(const Path & path)
void readFile(const Path & path, Sink & sink)
{
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_RDONLY
// TODO
#ifndef _WIN32
| O_CLOEXEC
#endif
));
if (!fd)
throw SysError("opening file '%s'", path);
drainFD(fd.get(), sink);
@ -292,7 +316,12 @@ void readFile(const Path & path, Sink & sink)
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
{
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
// TODO
#ifndef _WIN32
| O_CLOEXEC
#endif
, mode));
if (!fd)
throw SysError("opening file '%1%'", path);
try {
@ -312,7 +341,12 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
{
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
// TODO
#ifndef _WIN32
| O_CLOEXEC
#endif
, mode));
if (!fd)
throw SysError("opening file '%1%'", path);
@ -339,21 +373,23 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
void syncParent(const Path & path)
{
AutoCloseFD fd = open(dirOf(path).c_str(), O_RDONLY, 0);
AutoCloseFD fd = toDescriptor(open(dirOf(path).c_str(), O_RDONLY, 0));
if (!fd)
throw SysError("opening file '%1%'", path);
fd.fsync();
}
static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
static void _deletePath(Descriptor parentfd, const Path & path, uint64_t & bytesFreed)
{
#ifndef _WIN32
checkInterrupt();
std::string name(baseNameOf(path));
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
if (fstatat(parentfd, name.c_str(), &st,
AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT) return;
throw SysError("getting status of '%1%'", path);
}
@ -405,6 +441,10 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
if (errno == ENOENT) return;
throw SysError("cannot unlink '%1%'", path);
}
#else
// TODO implement
throw UnimplementedError("_deletePath");
#endif
}
static void _deletePath(const Path & path, uint64_t & bytesFreed)
@ -413,7 +453,7 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed)
if (dir == "")
dir = "/";
AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)};
AutoCloseFD dirfd = toDescriptor(open(dir.c_str(), O_RDONLY));
if (!dirfd) {
if (errno == ENOENT) return;
throw SysError("opening directory '%1%'", path);
@ -436,11 +476,15 @@ Paths createDirs(const Path & path)
if (path == "/") return created;
struct stat st;
if (lstat(path.c_str(), &st) == -1) {
if (STAT(path.c_str(), &st) == -1) {
created = createDirs(dirOf(path));
if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
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 = lstat(path);
st = STAT(path);
created.push_back(path);
}
@ -526,7 +570,11 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
while (1) {
checkInterrupt();
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
if (mkdir(tmpDir.c_str(), mode) == 0) {
if (mkdir(tmpDir.c_str()
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
, mode
#endif
) == 0) {
#if __FreeBSD__
/* Explicitly set the group of the directory. This is to
work around around problems caused by BSD's group
@ -552,17 +600,24 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
Path tmpl(defaultTempDir() + "/" + prefix + ".XXXXXX");
// Strictly speaking, this is UB, but who cares...
// FIXME: use O_TMPFILE.
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
AutoCloseFD fd = toDescriptor(mkstemp((char *) tmpl.c_str()));
if (!fd)
throw SysError("creating temporary file '%s'", tmpl);
#ifndef _WIN32
closeOnExec(fd.get());
#endif
return {std::move(fd), tmpl};
}
void createSymlink(const Path & target, const Path & link)
{
#ifndef _WIN32
if (symlink(target.c_str(), link.c_str()))
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
#else
// TODO modern Windows does in fact support symlinks
throw UnimplementedError("createSymlink");
#endif
}
void replaceSymlink(const Path & target, const Path & link)
@ -583,7 +638,8 @@ void replaceSymlink(const Path & target, const Path & link)
}
}
void setWriteTime(const fs::path & p, const struct stat & st)
#ifndef _WIN32
static void setWriteTime(const fs::path & p, const struct stat & st)
{
struct timeval times[2];
times[0] = {
@ -597,11 +653,14 @@ void setWriteTime(const fs::path & p, const struct stat & st)
if (lutimes(p.c_str(), times) != 0)
throw SysError("changing modification time of '%s'", p);
}
#endif
void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
{
#ifndef _WIN32
// TODO: Rewrite the `is_*` to use `symlink_status()`
auto statOfFrom = lstat(from.path().c_str());
#endif
auto fromStatus = from.symlink_status();
// Mark the directory as writable so that we can delete its children
@ -621,7 +680,9 @@ void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
throw Error("file '%s' has an unsupported type", from.path());
}
#ifndef _WIN32
setWriteTime(to, statOfFrom);
#endif
if (andDelete) {
if (!fs::is_symlink(fromStatus))
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
@ -648,14 +709,18 @@ void moveFile(const Path & oldName, const Path & newName)
auto newPath = fs::path(newName);
// For the move to be as atomic as possible, copy to a temporary
// directory
fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
fs::path temp = createTempDir(
os_string_to_string(PathViewNG { newPath.parent_path() }),
"rename-tmp");
Finally removeTemp = [&]() { fs::remove(temp); };
auto tempCopyTarget = temp / "copy-target";
if (e.code().value() == EXDEV) {
fs::remove(newPath);
warn("Cant rename %s as %s, copying instead", oldName, newName);
copy(fs::directory_entry(oldPath), tempCopyTarget, true);
renameFile(tempCopyTarget, newPath);
renameFile(
os_string_to_string(PathViewNG { tempCopyTarget }),
os_string_to_string(PathViewNG { newPath }));
}
}
}

View File

@ -14,6 +14,9 @@
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#ifdef _WIN32
# include <windef.h>
#endif
#include <signal.h>
#include <boost/lexical_cast.hpp>
@ -31,6 +34,17 @@
#define DT_DIR 3
#endif
/**
* Polyfill for MinGW
*
* Windows does in fact support symlinks, but the C runtime interfaces predate this.
*
* @todo get rid of this, and stop using `stat` when we want `lstat` too.
*/
#ifndef S_ISLNK
# define S_ISLNK(m) false
#endif
namespace nix {
struct Sink;

View File

@ -1,8 +1,15 @@
#include <fcntl.h>
#include "error.hh"
#include "config.hh"
#include "fs-sink.hh"
#if _WIN32
# include <fileapi.h>
# include "file-path.hh"
# include "windows-error.hh"
#endif
namespace nix {
void copyRecursive(
@ -65,8 +72,14 @@ static GlobalConfig::Register r1(&restoreSinkSettings);
void RestoreSink::createDirectory(const Path & path)
{
Path p = dstPath + path;
if (mkdir(p.c_str(), 0777) == -1)
throw SysError("creating directory '%1%'", p);
if (
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
mkdir(p.c_str(), 0777) == -1
#else
!CreateDirectoryW(pathNG(p).c_str(), NULL)
#endif
)
throw NativeSysError("creating directory '%1%'", p);
};
struct RestoreRegularFile : CreateRegularFileSink {
@ -81,18 +94,28 @@ void RestoreSink::createRegularFile(const Path & path, std::function<void(Create
{
Path p = dstPath + path;
RestoreRegularFile crf;
crf.fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
if (!crf.fd) throw SysError("creating file '%1%'", p);
crf.fd =
#ifdef _WIN32
CreateFileW(pathNG(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
#else
open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)
#endif
;
if (!crf.fd) throw NativeSysError("creating file '%1%'", p);
func(crf);
}
void RestoreRegularFile::isExecutable()
{
// Windows doesn't have a notion of executable file permissions we
// care about here, right?
#ifndef _WIN32
struct stat st;
if (fstat(fd.get(), &st) == -1)
throw SysError("fstat");
if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1)
throw SysError("fchmod");
#endif
}
void RestoreRegularFile::preallocateContents(uint64_t len)

View File

@ -11,6 +11,9 @@ endif
ifdef HOST_LINUX
libutil_SOURCES += $(wildcard $(d)/linux/*.cc)
endif
ifdef HOST_WINDOWS
libutil_SOURCES += $(wildcard $(d)/windows/*.cc)
endif
# Not just for this library itself, but also for downstream libraries using this library
@ -21,6 +24,9 @@ endif
ifdef HOST_LINUX
INCLUDE_libutil += -I $(d)/linux
endif
ifdef HOST_WINDOWS
INCLUDE_libutil += -I $(d)/windows
endif
libutil_CXXFLAGS += $(INCLUDE_libutil)
libutil_LDFLAGS += $(THREAD_LDFLAGS) $(LIBCURL_LIBS) $(SODIUM_LIBS) $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context

View File

@ -116,7 +116,13 @@ Verbosity verbosity = lvlInfo;
void writeToStderr(std::string_view s)
{
try {
writeFull(STDERR_FILENO, s, false);
writeFull(
#ifdef _WIN32
GetStdHandle(STD_ERROR_HANDLE),
#else
STDERR_FILENO,
#endif
s, false);
} catch (SystemError & e) {
/* Ignore failing writes to stderr. We need to ignore write
errors to ensure that cleanup code that logs to stderr runs
@ -132,9 +138,18 @@ Logger * makeSimpleLogger(bool printBuildLogs)
std::atomic<uint64_t> nextId{0};
static uint64_t getPid()
{
#ifndef _WIN32
return getpid();
#else
return GetCurrentProcessId();
#endif
}
Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
const std::string & s, const Logger::Fields & fields, ActivityId parent)
: logger(logger), id(nextId++ + (((uint64_t) getpid()) << 32))
: logger(logger), id(nextId++ + (((uint64_t) getPid()) << 32))
{
logger.startActivity(id, lvl, type, s, fields, parent);
}

View File

@ -10,7 +10,7 @@ PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && root)
: root(std::move(root))
{
assert(root.empty() || root.is_absolute());
displayPrefix = root;
displayPrefix = root.string();
}
PosixSourceAccessor::PosixSourceAccessor()
@ -19,10 +19,10 @@ PosixSourceAccessor::PosixSourceAccessor()
std::pair<PosixSourceAccessor, CanonPath> PosixSourceAccessor::createAtRoot(const std::filesystem::path & path)
{
std::filesystem::path path2 = absPath(path.native());
std::filesystem::path path2 = absPath(path.string());
return {
PosixSourceAccessor { path2.root_path() },
CanonPath { static_cast<std::string>(path2.relative_path()) },
CanonPath { path2.relative_path().string() },
};
}
@ -47,12 +47,16 @@ void PosixSourceAccessor::readFile(
auto ap = makeAbsPath(path);
AutoCloseFD fd = open(ap.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
AutoCloseFD fd = toDescriptor(open(ap.string().c_str(), O_RDONLY
#ifndef _WIN32
| O_NOFOLLOW | O_CLOEXEC
#endif
));
if (!fd)
throw SysError("opening file '%1%'", ap.native());
throw SysError("opening file '%1%'", ap.string());
struct stat st;
if (fstat(fd.get(), &st) == -1)
if (fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)
throw SysError("statting file");
sizeCallback(st.st_size);
@ -62,7 +66,7 @@ void PosixSourceAccessor::readFile(
std::array<unsigned char, 64 * 1024> buf;
while (left) {
checkInterrupt();
ssize_t rd = read(fd.get(), buf.data(), (size_t) std::min(left, (off_t) buf.size()));
ssize_t rd = read(fromDescriptorReadOnly(fd.get()), buf.data(), (size_t) std::min(left, (off_t) buf.size()));
if (rd == -1) {
if (errno != EINTR)
throw SysError("reading from file '%s'", showPath(path));
@ -80,7 +84,7 @@ void PosixSourceAccessor::readFile(
bool PosixSourceAccessor::pathExists(const CanonPath & path)
{
if (auto parent = path.parent()) assertNoSymlinks(*parent);
return nix::pathExists(makeAbsPath(path));
return nix::pathExists(makeAbsPath(path).string());
}
std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & path)
@ -89,7 +93,7 @@ std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & pa
// Note: we convert std::filesystem::path to Path because the
// former is not hashable on libc++.
Path absPath = makeAbsPath(path);
Path absPath = makeAbsPath(path).string();
{
auto cache(_cache.lock());
@ -127,11 +131,13 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
{
assertNoSymlinks(path);
DirEntries res;
for (auto & entry : nix::readDirectory(makeAbsPath(path))) {
for (auto & entry : nix::readDirectory(makeAbsPath(path).string())) {
std::optional<Type> type;
switch (entry.type) {
case DT_REG: type = Type::tRegular; break;
#ifndef _WIN32
case DT_LNK: type = Type::tSymlink; break;
#endif
case DT_DIR: type = Type::tDirectory; break;
}
res.emplace(entry.name, type);
@ -142,7 +148,7 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
std::string PosixSourceAccessor::readLink(const CanonPath & path)
{
if (auto parent = path.parent()) assertNoSymlinks(*parent);
return nix::readLink(makeAbsPath(path));
return nix::readLink(makeAbsPath(path).string());
}
std::optional<std::filesystem::path> PosixSourceAccessor::getPhysicalPath(const CanonPath & path)

View File

@ -25,6 +25,7 @@ namespace nix {
struct Sink;
struct Source;
#ifndef _WIN32
class Pid
{
pid_t pid = -1;
@ -43,13 +44,16 @@ public:
void setKillSignal(int signal);
pid_t release();
};
#endif
#ifndef _WIN32
/**
* Kill all processes running under the specified uid by sending them
* a SIGKILL.
*/
void killUser(uid_t uid);
#endif
/**
@ -68,8 +72,9 @@ struct ProcessOptions
int cloneFlags = 0;
};
#ifndef _WIN32
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions());
#endif
/**
* Run a program and return its stdout in a string (i.e., like the
@ -84,8 +89,10 @@ struct RunOptions
Path program;
bool searchPath = true;
Strings args;
#ifndef _WIN32
std::optional<uid_t> uid;
std::optional<uid_t> gid;
#endif
std::optional<Path> chdir;
std::optional<std::map<std::string, std::string>> environment;
std::optional<std::string> input;
@ -111,6 +118,7 @@ public:
{ }
};
#ifndef _WIN32
/**
* Convert the exit status of a child as returned by wait() into an
@ -120,4 +128,6 @@ std::string statusToString(int status);
bool statusOk(int status);
#endif
}

View File

@ -7,6 +7,11 @@
#include <boost/coroutine2/coroutine.hpp>
#ifdef _WIN32
# include <fileapi.h>
# include "windows-error.hh"
#endif
namespace nix {
@ -126,6 +131,14 @@ bool BufferedSource::hasData()
size_t FdSource::readUnbuffered(char * data, size_t len)
{
#ifdef _WIN32
DWORD n;
checkInterrupt();
if (!::ReadFile(fd, data, len, &n, NULL)) {
_good = false;
throw WinError("ReadFile when FdSource::readUnbuffered");
}
#else
ssize_t n;
do {
checkInterrupt();
@ -133,6 +146,7 @@ size_t FdSource::readUnbuffered(char * data, size_t len)
} while (n == -1 && errno == EINTR);
if (n == -1) { _good = false; throw SysError("reading from file"); }
if (n == 0) { _good = false; throw EndOfFile(std::string(*endOfFileError)); }
#endif
read += n;
return n;
}

View File

@ -2,7 +2,12 @@
#include "environment-variables.hh"
#include "sync.hh"
#include <sys/ioctl.h>
#if _WIN32
# include <io.h>
# define isatty _isatty
#else
# include <sys/ioctl.h>
#endif
#include <unistd.h>
namespace nix {
@ -92,6 +97,7 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
#ifndef _WIN32
void updateWindowSize()
{
struct winsize ws;
@ -101,6 +107,7 @@ void updateWindowSize()
windowSize_->second = ws.ws_col;
}
}
#endif
std::pair<unsigned short, unsigned short> getWindowSize()

View File

@ -21,12 +21,16 @@ std::string filterANSIEscapes(std::string_view s,
bool filterAll = false,
unsigned int width = std::numeric_limits<unsigned int>::max());
#ifndef _WIN32
/**
* Recalculate the window size, updating a global variable. Used in the
* `SIGWINCH` signal handler.
*/
void updateWindowSize();
#endif
/**
* @return the number of rows and columns of the terminal.
*

View File

@ -81,8 +81,10 @@ void ThreadPool::doWork(bool mainThread)
{
ReceiveInterrupts receiveInterrupts;
#ifndef _WIN32 // Does Windows need anything similar for async exit handling?
if (!mainThread)
unix::interruptCheck = [&]() { return (bool) quit; };
#endif
bool didWork = false;
std::exception_ptr exc;

View File

@ -0,0 +1,31 @@
#include <algorithm>
#include <codecvt>
#include <iostream>
#include <locale>
#include "file-path.hh"
#include "util.hh"
namespace nix {
std::string os_string_to_string(PathViewNG::string_view path)
{
return std::string { path };
}
PathNG::string_type string_to_os_string(std::string_view s)
{
return std::string { s };
}
std::optional<PathNG> maybePathNG(PathView path)
{
return { path };
}
PathNG pathNG(PathView path)
{
return path;
}
}

View File

@ -3,16 +3,20 @@
#include "types.hh"
#include <sys/types.h>
#ifndef _WIN32
# include <sys/types.h>
#endif
namespace nix {
std::string getUserName();
#ifndef _WIN32
/**
* @return the given user's home directory from /etc/passwd.
*/
Path getHomeOf(uid_t userId);
#endif
/**
* @return $HOME or the user's home directory from /etc/passwd.
@ -58,6 +62,8 @@ std::string expandTilde(std::string_view path);
/**
* Is the current user UID 0 on Unix?
*
* Currently always false on Windows, but that may change.
*/
bool isRootUser();

View File

@ -0,0 +1,17 @@
#include "environment-variables.hh"
#include "processenv.h"
namespace nix {
int unsetenv(const char *name)
{
return -SetEnvironmentVariableA(name, nullptr);
}
int setEnv(const char * name, const char * value)
{
return -SetEnvironmentVariableA(name, value);
}
}

View File

@ -0,0 +1,148 @@
#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 {
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 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 handleToPath(HANDLE handle) {
return os_string_to_string(handleToFileName(handle));
}
#endif
}

View File

@ -0,0 +1,52 @@
#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(PathNG::string_type { path });
}
PathNG::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 });
}
std::optional<PathNG> maybePathNG(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])) {
PathNG::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])) {
PathNG::string_type sw = string_to_os_string(path);
std::replace(sw.begin(), sw.end(), '/', '\\');
return sw;
}
return std::optional<PathNG::string_type>();
}
PathNG pathNG(PathView path)
{
std::optional<PathNG::string_type> sw = maybePathNG(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,48 @@
#include "current-process.hh"
#include "environment-variables.hh"
#include "signals.hh"
#include "processes.hh"
#include "finally.hh"
#include "serialise.hh"
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <future>
#include <iostream>
#include <sstream>
#include <thread>
#include <sys/types.h>
#include <unistd.h>
#ifdef __APPLE__
# include <sys/syscall.h>
#endif
#ifdef __linux__
# include <sys/prctl.h>
# include <sys/mman.h>
#endif
namespace nix {
std::string runProgram(Path program, bool searchPath, const Strings & args,
const std::optional<std::string> & input, bool isInteractive)
{
throw UnimplementedError("Cannot shell out to git on Windows yet");
}
// 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");
}
void runProgram2(const RunOptions & options)
{
throw UnimplementedError("Cannot shell out to git on Windows yet");
}
}

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,50 @@
#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 {
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,31 @@
#include "windows-error.hh"
#include <error.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace nix {
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 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);
};
}

View File

@ -1342,8 +1342,16 @@ static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs
RunPager pager;
for (auto & i : gens) {
#ifdef _WIN32 // TODO portable wrapper in libutil
tm * tp = localtime(&i.creationTime);
if (!tp)
throw Error("cannot convert time");
auto & t = *tp;
#else
tm t;
if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
if (!localtime_r(&i.creationTime, &t))
throw Error("cannot convert time");
#endif
logger->cout("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||",
i.number,
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,

View File

@ -168,7 +168,7 @@ static int main_nix_instantiate(int argc, char * * argv)
for (auto & i : files) {
auto p = state->findFile(i);
if (auto fn = p.getPhysicalPath())
std::cout << fn->native() << std::endl;
std::cout << fn->string() << std::endl;
else
throw Error("'%s' has no physical path", p);
}

Some files were not shown because too many files have changed in this diff Show More