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 # List makefiles
include mk/platform.mk
ifeq ($(ENABLE_BUILD), yes) ifeq ($(ENABLE_BUILD), yes)
makefiles = \ makefiles = \
mk/precompiled-headers.mk \ mk/precompiled-headers.mk \
@ -20,7 +22,10 @@ makefiles = \
src/nix/local.mk \ src/nix/local.mk \
src/libutil-c/local.mk \ src/libutil-c/local.mk \
src/libstore-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 \ scripts/local.mk \
misc/bash/local.mk \ misc/bash/local.mk \
misc/fish/local.mk \ misc/fish/local.mk \
@ -29,6 +34,7 @@ makefiles = \
misc/launchd/local.mk \ misc/launchd/local.mk \
misc/upstart/local.mk misc/upstart/local.mk
endif endif
endif
ifeq ($(ENABLE_UNIT_TESTS), yes) ifeq ($(ENABLE_UNIT_TESTS), yes)
makefiles += \ makefiles += \
@ -42,6 +48,7 @@ makefiles += \
endif endif
ifeq ($(ENABLE_FUNCTIONAL_TESTS), yes) ifeq ($(ENABLE_FUNCTIONAL_TESTS), yes)
ifdef HOST_UNIX
makefiles += \ makefiles += \
tests/functional/local.mk \ tests/functional/local.mk \
tests/functional/ca/local.mk \ tests/functional/ca/local.mk \
@ -51,6 +58,7 @@ makefiles += \
tests/functional/test-libstoreconsumer/local.mk \ tests/functional/test-libstoreconsumer/local.mk \
tests/functional/plugins/local.mk tests/functional/plugins/local.mk
endif endif
endif
# Some makefiles require access to built programs and must be included late. # Some makefiles require access to built programs and must be included late.
makefiles-late = makefiles-late =
@ -79,8 +87,6 @@ else
unexport NIX_HARDENING_ENABLE unexport NIX_HARDENING_ENABLE
endif endif
include mk/platform.mk
ifdef HOST_WINDOWS ifdef HOST_WINDOWS
# Windows DLLs are stricter about symbol visibility than Unix shared # Windows DLLs are stricter about symbol visibility than Unix shared
# objects --- see https://gcc.gnu.org/wiki/Visibility for details. # 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=0],
[status_80431=$?], [status_80431=$?],
[ [status_80431=''])
# Assume we're bug-free when cross-compiling
])
AC_LANG_POP(C++) AC_LANG_POP(C++)
AS_CASE([$status_80431], AS_CASE([$status_80431],
[''],[
AC_MSG_RESULT(cannot check because cross compiling)
AC_MSG_NOTICE(assume we are bug free)
],
[0],[ [0],[
AC_MSG_RESULT(yes) AC_MSG_RESULT(yes)
], ],

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,9 @@
namespace nix { 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 * A "search path" is a list of ways look for something, used with
* `builtins.findFile` and `< >` lookup expressions. * `builtins.findFile` and `< >` lookup expressions.

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
///@file ///@file
#include "file-descriptor.hh"
#include "processes.hh" #include "processes.hh"
#include "args.hh" #include "args.hh"
#include "args/root.hh" #include "args/root.hh"
@ -89,8 +90,10 @@ public:
~RunPager(); ~RunPager();
private: private:
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
Pid pid; Pid pid;
int std_out; #endif
Descriptor std_out;
}; };
extern volatile ::sig_atomic_t blockInt; extern volatile ::sig_atomic_t blockInt;
@ -112,6 +115,7 @@ struct PrintFreed
}; };
#ifndef _WIN32
/** /**
* Install a SIGSEGV handler to detect stack overflows. * 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. * logger. Exits the process immediately after.
*/ */
void defaultStackOverflowHandler(siginfo_t * info, void * ctx); 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); throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
if (unlink(dstFile.c_str()) == -1) if (unlink(dstFile.c_str()) == -1)
throw SysError("unlinking '%1%'", dstFile); 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); throw SysError("creating directory '%1%'", dstFile);
createLinks(state, target, dstFile, state.priorities[dstFile]); createLinks(state, target, dstFile, state.priorities[dstFile]);
createLinks(state, srcFile, dstFile, priority); createLinks(state, srcFile, dstFile, priority);

View file

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

View file

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

View file

@ -9,11 +9,14 @@
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <dlfcn.h>
#include <sys/utsname.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#ifndef _WIN32
# include <dlfcn.h>
# include <sys/utsname.h>
#endif
#ifdef __GLIBC__ #ifdef __GLIBC__
# include <gnu/lib-names.h> # include <gnu/lib-names.h>
# include <nss.h> # include <nss.h>
@ -56,7 +59,9 @@ Settings::Settings()
, nixManDir(canonPath(NIX_MAN_DIR)) , nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH))) , nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
{ {
#ifndef _WIN32
buildUsersGroup = isRootUser() ? "nixbld" : ""; buildUsersGroup = isRootUser() ? "nixbld" : "";
#endif
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1"; allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or("")); 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() bool Settings::isWSL1()
{ {
#if __linux__
struct utsname utsbuf; struct utsname utsbuf;
uname(&utsbuf); uname(&utsbuf);
// WSL1 uses -Microsoft suffix // WSL1 uses -Microsoft suffix
// WSL2 uses -microsoft-standard suffix // WSL2 uses -microsoft-standard suffix
return hasSuffix(utsbuf.release, "-Microsoft"); return hasSuffix(utsbuf.release, "-Microsoft");
#else
return false;
#endif
} }
Path Settings::getDefaultSSLCertFile() Path Settings::getDefaultSSLCertFile()
@ -341,6 +350,7 @@ void initPlugins()
for (const auto & file : pluginFiles) { for (const auto & file : pluginFiles) {
/* handle is purposefully leaked as there may be state in the /* handle is purposefully leaked as there may be state in the
DSO needed by the action of the plugin. */ DSO needed by the action of the plugin. */
#ifndef _WIN32 // TODO implement via DLL loading on Windows
void *handle = void *handle =
dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL); dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
@ -351,6 +361,9 @@ void initPlugins()
void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry"); void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry");
if (nix_plugin_entry) if (nix_plugin_entry)
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", Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."}; "Whether to disable sandboxing when the kernel doesn't allow it."};
#ifndef _WIN32
Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups", Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups",
R"( R"(
Following the principle of least privilege, Following the principle of least privilege,
@ -683,6 +684,7 @@ public:
(since `root` usually has permissions to call setgroups) (since `root` usually has permissions to call setgroups)
and `false` otherwise. and `false` otherwise.
)"}; )"};
#endif
#if __linux__ #if __linux__
Setting<std::string> sandboxShmSize{ Setting<std::string> sandboxShmSize{

View file

@ -4,9 +4,12 @@ libstore_NAME = libnixstore
libstore_DIR := $(d) 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 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 endif
libstore_LIBS = libutil libstore_LIBS = libutil
@ -55,9 +58,9 @@ libstore_CXXFLAGS += \
ifeq ($(embedded_sandbox_shell),yes) ifeq ($(embedded_sandbox_shell),yes)
libstore_CXXFLAGS += -DSANDBOX_SHELL=\"__embedded_sandbox_shell__\" 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 $(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
@mv $@.tmp $@ @mv $@.tmp $@
else else
@ -66,11 +69,11 @@ else
endif endif
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)) $(eval $(call install-file-in, $(buildprefix)$(d)/nix-store.pc, $(libdir)/pkgconfig, 0644))

View file

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

View file

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

View file

@ -71,11 +71,15 @@ std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPat
auto narAccessor = makeLazyNarAccessor(listing, auto narAccessor = makeLazyNarAccessor(listing,
[cacheFile](uint64_t offset, uint64_t length) { [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) if (!fd)
throw SysError("opening NAR cache file '%s'", cacheFile); 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); throw SysError("seeking in '%s'", cacheFile);
std::string buf(length, 0); std::string buf(length, 0);

View file

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

View file

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

View file

@ -13,7 +13,6 @@
#include "archive.hh" #include "archive.hh"
#include "callback.hh" #include "callback.hh"
#include "git.hh" #include "git.hh"
#include "remote-store.hh"
#include "posix-source-accessor.hh" #include "posix-source-accessor.hh"
// FIXME this should not be here, see TODO below on // FIXME this should not be here, see TODO below on
// `addMultipleToStore`. // `addMultipleToStore`.
@ -21,6 +20,10 @@
#include "signals.hh" #include "signals.hh"
#include "users.hh" #include "users.hh"
#ifndef _WIN32
# include "remote-store.hh"
#endif
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <regex> #include <regex>
@ -1266,9 +1269,10 @@ Derivation Store::readInvalidDerivation(const StorePath & drvPath)
} }
#ifndef _WIN32
# include "local-store.hh" # include "local-store.hh"
# include "uds-remote-store.hh" # include "uds-remote-store.hh"
#endif
namespace nix { namespace nix {
@ -1286,6 +1290,9 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri_
return {uri, params}; return {uri, params};
} }
#ifdef _WIN32 // Unused on Windows because the next `#ifndef`
[[maybe_unused]]
#endif
static bool isNonUriPath(const std::string & spec) static bool isNonUriPath(const std::string & spec)
{ {
return 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) 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") { if (uri == "" || uri == "auto") {
auto stateDir = getOr(params, "state", settings.nixStateDir); auto stateDir = getOr(params, "state", settings.nixStateDir);
if (access(stateDir.c_str(), R_OK | W_OK) == 0) 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 { } else {
return nullptr; return nullptr;
} }
#else
return nullptr;
#endif
} }
// The `parseURL` function supports both IPv6 URIs as defined in // 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 <fstream>
#include <string> #include <string>
#include <regex> #include <regex>
#ifndef _WIN32
# include <glob.h> # include <glob.h>
#endif
namespace nix { namespace nix {
@ -547,6 +549,7 @@ nlohmann::json Args::toJSON()
static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs) static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs)
{ {
completions.setType(Completions::Type::Filenames); completions.setType(Completions::Type::Filenames);
#ifndef _WIN32 // TODO implement globbing completions on Windows
glob_t globbuf; glob_t globbuf;
int flags = GLOB_NOESCAPE; int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR #ifdef GLOB_ONLYDIR
@ -564,6 +567,7 @@ static void _completePath(AddCompletions & completions, std::string_view prefix,
} }
} }
globfree(&globbuf); globfree(&globbuf);
#endif
} }
void Args::completePath(AddCompletions & completions, size_t, std::string_view prefix) void Args::completePath(AddCompletions & completions, size_t, std::string_view prefix)

View file

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

View file

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

View file

@ -5,6 +5,11 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#ifdef _WIN32
# include <winnt.h>
# include <fileapi.h>
# include "windows-error.hh"
#endif
namespace nix { 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 // the parser needs two extra bytes to append terminating characters, other users will
// not care very much about the extra memory. // not care very much about the extra memory.
StringSink sink(reserveSize + 2); 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); drainFD(fd, sink, block);
#endif
return std::move(sink.s); return std::move(sink.s);
} }
@ -68,9 +79,15 @@ Descriptor AutoCloseFD::get() const
void AutoCloseFD::close() void AutoCloseFD::close()
{ {
if (fd != INVALID_DESCRIPTOR) { if (fd != INVALID_DESCRIPTOR) {
if(::close(fd) == -1) if(
#ifdef _WIN32
::CloseHandle(fd)
#else
::close(fd)
#endif
== -1)
/* This should never happen. */ /* This should never happen. */
throw SysError("closing file descriptor %1%", fd); throw NativeSysError("closing file descriptor %1%", fd);
fd = INVALID_DESCRIPTOR; fd = INVALID_DESCRIPTOR;
} }
} }
@ -80,14 +97,16 @@ void AutoCloseFD::fsync()
if (fd != INVALID_DESCRIPTOR) { if (fd != INVALID_DESCRIPTOR) {
int result; int result;
result = result =
#if __APPLE__ #ifdef _WIN32
::FlushFileBuffers(fd)
#elif __APPLE__
::fcntl(fd, F_FULLFSYNC) ::fcntl(fd, F_FULLFSYNC)
#else #else
::fsync(fd) ::fsync(fd)
#endif #endif
; ;
if (result == -1) 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 "types.hh"
#include "error.hh" #include "error.hh"
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
namespace nix { namespace nix {
struct Sink; struct Sink;
@ -12,9 +17,21 @@ struct Source;
/** /**
* Operating System capability * 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 * Convert a native `Descriptor` to a POSIX file descriptor
@ -23,17 +40,26 @@ const Descriptor INVALID_DESCRIPTOR = -1;
*/ */
static inline Descriptor toDescriptor(int fd) static inline Descriptor toDescriptor(int fd)
{ {
#ifdef _WIN32
return (HANDLE) _get_osfhandle(fd);
#else
return fd; 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. * 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; 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); 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]] [[gnu::always_inline]]
inline Descriptor getStandardOut() { inline Descriptor getStandardOut() {
#ifndef _WIN32
return STDOUT_FILENO; return STDOUT_FILENO;
#else
return GetStdHandle(STD_OUTPUT_HANDLE);
#endif
} }
/** /**
@ -100,6 +139,8 @@ public:
void close(); void close();
}; };
#ifndef _WIN32 // Not needed on Windows, where we don't fork
/** /**
* Close all file descriptors except those listed in the given set. * Close all file descriptors except those listed in the given set.
* Good practice in child processes. * Good practice in child processes.
@ -111,6 +152,15 @@ void closeMostFDs(const std::set<Descriptor> & exceptions);
*/ */
void closeOnExec(Descriptor fd); 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); 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 "environment-variables.hh"
#include "file-system.hh" #include "file-system.hh"
#include "file-path.hh"
#include "file-path-impl.hh" #include "file-path-impl.hh"
#include "signals.hh" #include "signals.hh"
#include "finally.hh" #include "finally.hh"
@ -18,6 +19,10 @@
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#ifdef _WIN32
# include <io.h>
#endif
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace nix { namespace nix {
@ -128,10 +133,10 @@ std::string_view baseNameOf(std::string_view path)
return ""; return "";
auto last = path.size() - 1; auto last = path.size() - 1;
while (last > 0 && path[last] == '/') while (last > 0 && NativePathTrait::isPathSep(path[last]))
last -= 1; last -= 1;
auto pos = path.rfind('/', last); auto pos = NativePathTrait::rfindPathSep(path, last);
if (pos == path.npos) if (pos == path.npos)
pos = 0; pos = 0;
else else
@ -164,11 +169,16 @@ struct stat stat(const Path & path)
return st; return st;
} }
#ifdef _WIN32
# define STAT stat
#else
# define STAT lstat
#endif
struct stat lstat(const Path & path) struct stat lstat(const Path & path)
{ {
struct stat st; struct stat st;
if (lstat(path.c_str(), &st)) if (STAT(path.c_str(), &st))
throw SysError("getting status of '%1%'", path); throw SysError("getting status of '%1%'", path);
return st; return st;
} }
@ -177,7 +187,7 @@ struct stat lstat(const Path & path)
std::optional<struct stat> maybeLstat(const Path & path) std::optional<struct stat> maybeLstat(const Path & path)
{ {
std::optional<struct stat> st{std::in_place}; 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) if (errno == ENOENT || errno == ENOTDIR)
st.reset(); st.reset();
@ -207,6 +217,7 @@ bool pathAccessible(const Path & path)
Path readLink(const Path & path) Path readLink(const Path & path)
{ {
#ifndef _WIN32
checkInterrupt(); checkInterrupt();
std::vector<char> buf; std::vector<char> buf;
for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) { for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) {
@ -220,13 +231,16 @@ Path readLink(const Path & path)
else if (rlSize < bufSize) else if (rlSize < bufSize)
return std::string(buf.data(), rlSize); 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) bool isLink(const Path & path)
{ {
struct stat st = lstat(path); return getFileType(path) == DT_LNK;
return S_ISLNK(st.st_mode);
} }
@ -274,7 +288,12 @@ unsigned char getFileType(const Path & path)
std::string readFile(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) if (!fd)
throw SysError("opening file '%1%'", path); throw SysError("opening file '%1%'", path);
return readFile(fd.get()); return readFile(fd.get());
@ -283,7 +302,12 @@ std::string readFile(const Path & path)
void readFile(const Path & path, Sink & sink) 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) if (!fd)
throw SysError("opening file '%s'", path); throw SysError("opening file '%s'", path);
drainFD(fd.get(), sink); 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) 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) if (!fd)
throw SysError("opening file '%1%'", path); throw SysError("opening file '%1%'", path);
try { 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) 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) if (!fd)
throw SysError("opening file '%1%'", path); 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) 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) if (!fd)
throw SysError("opening file '%1%'", path); throw SysError("opening file '%1%'", path);
fd.fsync(); 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(); checkInterrupt();
std::string name(baseNameOf(path)); std::string name(baseNameOf(path));
struct stat st; 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; if (errno == ENOENT) return;
throw SysError("getting status of '%1%'", path); 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; if (errno == ENOENT) return;
throw SysError("cannot unlink '%1%'", path); throw SysError("cannot unlink '%1%'", path);
} }
#else
// TODO implement
throw UnimplementedError("_deletePath");
#endif
} }
static void _deletePath(const Path & path, uint64_t & bytesFreed) static void _deletePath(const Path & path, uint64_t & bytesFreed)
@ -413,7 +453,7 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed)
if (dir == "") if (dir == "")
dir = "/"; dir = "/";
AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)}; AutoCloseFD dirfd = toDescriptor(open(dir.c_str(), O_RDONLY));
if (!dirfd) { if (!dirfd) {
if (errno == ENOENT) return; if (errno == ENOENT) return;
throw SysError("opening directory '%1%'", path); throw SysError("opening directory '%1%'", path);
@ -436,11 +476,15 @@ Paths createDirs(const Path & path)
if (path == "/") return created; if (path == "/") return created;
struct stat st; struct stat st;
if (lstat(path.c_str(), &st) == -1) { if (STAT(path.c_str(), &st) == -1) {
created = createDirs(dirOf(path)); 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); throw SysError("creating directory '%1%'", path);
st = lstat(path); st = STAT(path);
created.push_back(path); created.push_back(path);
} }
@ -526,7 +570,11 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
while (1) { while (1) {
checkInterrupt(); checkInterrupt();
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter); 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__ #if __FreeBSD__
/* Explicitly set the group of the directory. This is to /* Explicitly set the group of the directory. This is to
work around around problems caused by BSD's group 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"); Path tmpl(defaultTempDir() + "/" + prefix + ".XXXXXX");
// Strictly speaking, this is UB, but who cares... // Strictly speaking, this is UB, but who cares...
// FIXME: use O_TMPFILE. // FIXME: use O_TMPFILE.
AutoCloseFD fd(mkstemp((char *) tmpl.c_str())); AutoCloseFD fd = toDescriptor(mkstemp((char *) tmpl.c_str()));
if (!fd) if (!fd)
throw SysError("creating temporary file '%s'", tmpl); throw SysError("creating temporary file '%s'", tmpl);
#ifndef _WIN32
closeOnExec(fd.get()); closeOnExec(fd.get());
#endif
return {std::move(fd), tmpl}; return {std::move(fd), tmpl};
} }
void createSymlink(const Path & target, const Path & link) void createSymlink(const Path & target, const Path & link)
{ {
#ifndef _WIN32
if (symlink(target.c_str(), link.c_str())) if (symlink(target.c_str(), link.c_str()))
throw SysError("creating symlink from '%1%' to '%2%'", link, target); 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) 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]; struct timeval times[2];
times[0] = { times[0] = {
@ -597,11 +653,14 @@ void setWriteTime(const fs::path & p, const struct stat & st)
if (lutimes(p.c_str(), times) != 0) if (lutimes(p.c_str(), times) != 0)
throw SysError("changing modification time of '%s'", p); throw SysError("changing modification time of '%s'", p);
} }
#endif
void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete) void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
{ {
#ifndef _WIN32
// TODO: Rewrite the `is_*` to use `symlink_status()` // TODO: Rewrite the `is_*` to use `symlink_status()`
auto statOfFrom = lstat(from.path().c_str()); auto statOfFrom = lstat(from.path().c_str());
#endif
auto fromStatus = from.symlink_status(); auto fromStatus = from.symlink_status();
// Mark the directory as writable so that we can delete its children // 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()); throw Error("file '%s' has an unsupported type", from.path());
} }
#ifndef _WIN32
setWriteTime(to, statOfFrom); setWriteTime(to, statOfFrom);
#endif
if (andDelete) { if (andDelete) {
if (!fs::is_symlink(fromStatus)) if (!fs::is_symlink(fromStatus))
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow); 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); auto newPath = fs::path(newName);
// For the move to be as atomic as possible, copy to a temporary // For the move to be as atomic as possible, copy to a temporary
// directory // 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); }; Finally removeTemp = [&]() { fs::remove(temp); };
auto tempCopyTarget = temp / "copy-target"; auto tempCopyTarget = temp / "copy-target";
if (e.code().value() == EXDEV) { if (e.code().value() == EXDEV) {
fs::remove(newPath); fs::remove(newPath);
warn("Cant rename %s as %s, copying instead", oldName, newName); warn("Cant rename %s as %s, copying instead", oldName, newName);
copy(fs::directory_entry(oldPath), tempCopyTarget, true); 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 <sys/stat.h>
#include <dirent.h> #include <dirent.h>
#include <unistd.h> #include <unistd.h>
#ifdef _WIN32
# include <windef.h>
#endif
#include <signal.h> #include <signal.h>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
@ -31,6 +34,17 @@
#define DT_DIR 3 #define DT_DIR 3
#endif #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 { namespace nix {
struct Sink; struct Sink;

View file

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

View file

@ -11,6 +11,9 @@ endif
ifdef HOST_LINUX ifdef HOST_LINUX
libutil_SOURCES += $(wildcard $(d)/linux/*.cc) libutil_SOURCES += $(wildcard $(d)/linux/*.cc)
endif 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 # Not just for this library itself, but also for downstream libraries using this library
@ -21,6 +24,9 @@ endif
ifdef HOST_LINUX ifdef HOST_LINUX
INCLUDE_libutil += -I $(d)/linux INCLUDE_libutil += -I $(d)/linux
endif endif
ifdef HOST_WINDOWS
INCLUDE_libutil += -I $(d)/windows
endif
libutil_CXXFLAGS += $(INCLUDE_libutil) libutil_CXXFLAGS += $(INCLUDE_libutil)
libutil_LDFLAGS += $(THREAD_LDFLAGS) $(LIBCURL_LIBS) $(SODIUM_LIBS) $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context 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) void writeToStderr(std::string_view s)
{ {
try { try {
writeFull(STDERR_FILENO, s, false); writeFull(
#ifdef _WIN32
GetStdHandle(STD_ERROR_HANDLE),
#else
STDERR_FILENO,
#endif
s, false);
} catch (SystemError & e) { } catch (SystemError & e) {
/* Ignore failing writes to stderr. We need to ignore write /* Ignore failing writes to stderr. We need to ignore write
errors to ensure that cleanup code that logs to stderr runs 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}; 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, Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
const std::string & s, const Logger::Fields & fields, ActivityId parent) 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); logger.startActivity(id, lvl, type, s, fields, parent);
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -81,8 +81,10 @@ void ThreadPool::doWork(bool mainThread)
{ {
ReceiveInterrupts receiveInterrupts; ReceiveInterrupts receiveInterrupts;
#ifndef _WIN32 // Does Windows need anything similar for async exit handling?
if (!mainThread) if (!mainThread)
unix::interruptCheck = [&]() { return (bool) quit; }; unix::interruptCheck = [&]() { return (bool) quit; };
#endif
bool didWork = false; bool didWork = false;
std::exception_ptr exc; 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 "types.hh"
#ifndef _WIN32
# include <sys/types.h> # include <sys/types.h>
#endif
namespace nix { namespace nix {
std::string getUserName(); std::string getUserName();
#ifndef _WIN32
/** /**
* @return the given user's home directory from /etc/passwd. * @return the given user's home directory from /etc/passwd.
*/ */
Path getHomeOf(uid_t userId); Path getHomeOf(uid_t userId);
#endif
/** /**
* @return $HOME or the user's home directory from /etc/passwd. * @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? * Is the current user UID 0 on Unix?
*
* Currently always false on Windows, but that may change.
*/ */
bool isRootUser(); 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; RunPager pager;
for (auto & i : gens) { 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; 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| %||", logger->cout("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||",
i.number, i.number,
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, 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) { for (auto & i : files) {
auto p = state->findFile(i); auto p = state->findFile(i);
if (auto fn = p.getPhysicalPath()) if (auto fn = p.getPhysicalPath())
std::cout << fn->native() << std::endl; std::cout << fn->string() << std::endl;
else else
throw Error("'%s' has no physical path", p); throw Error("'%s' has no physical path", p);
} }

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