1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-22 02:09:36 +01:00

Tagging release 2.26.2

-----BEGIN PGP SIGNATURE-----
 
 iQFHBAABCAAxFiEEtUHVUwEnDgvPFcpdgXC0cm1xmN4FAmetA5oTHGVkb2xzdHJh
 QGdtYWlsLmNvbQAKCRCBcLRybXGY3g2pB/9JAFyjmaXuccbMTO/6x9qwsWuuXNLk
 OQWzfbdUekvsihZZSFZg1r7KqqXHCi64f0nxLPsJ/0oeDWZktJ5KnbV630nuUlDj
 ulLCpKdvhWFa8dVx9LiziGwQw4KLx8PjOfwThtQ4DqCWxWEmu6lKkijag9cE+ai4
 3mw9YtUjBRxlXyhYLzWz3whLbv37c/m+R8iGS8xm8W260pmei6D0beOIPdfXYBQF
 PzPlPORyI08A06uqyA3z7bTxzmSMnzvu0QInCPCKSHzFUnTZPHUYuYStFl28NrZS
 fXKK59L0G7QEfdTRAmqQkdHdtPj2RlYFiMN0kQiNLflvKfGGWdi/kvdx
 =rRix
 -----END PGP SIGNATURE-----

Merge tag '2.26.2' into sync-2.26.2

Tagging release 2.26.2
This commit is contained in:
Eelco Dolstra 2025-02-18 19:56:22 +01:00
commit 4055239936
1395 changed files with 24694 additions and 16040 deletions

View file

@ -82,7 +82,7 @@ void SourceAccessor::dumpPath(
name.erase(pos);
}
if (!unhacked.emplace(name, i.first).second)
throw Error("file name collision in between '%s' and '%s'",
throw Error("file name collision between '%s' and '%s'",
(path / unhacked[name]),
(path / i.first));
} else
@ -128,9 +128,10 @@ void dumpString(std::string_view s, Sink & sink)
}
static SerialisationError badArchive(const std::string & s)
template<typename... Args>
static SerialisationError badArchive(std::string_view s, const Args & ... args)
{
return SerialisationError("bad archive: " + s);
return SerialisationError("bad archive: " + s, args...);
}
@ -167,120 +168,97 @@ struct CaseInsensitiveCompare
static void parse(FileSystemObjectSink & sink, Source & source, const CanonPath & path)
{
std::string s;
s = readString(source);
if (s != "(") throw badArchive("expected open tag");
std::map<Path, int, CaseInsensitiveCompare> names;
auto getString = [&]() {
checkInterrupt();
return readString(source);
};
// For first iteration
s = getString();
auto expectTag = [&](std::string_view expected) {
auto tag = getString();
if (tag != expected)
throw badArchive("expected tag '%s', got '%s'", expected, tag);
};
while (1) {
expectTag("(");
if (s == ")") {
break;
}
expectTag("type");
else if (s == "type") {
std::string t = getString();
auto type = getString();
if (t == "regular") {
sink.createRegularFile(path, [&](auto & crf) {
while (1) {
s = getString();
if (type == "regular") {
sink.createRegularFile(path, [&](auto & crf) {
auto tag = getString();
if (s == "contents") {
parseContents(crf, source);
}
else if (s == "executable") {
auto s2 = getString();
if (s2 != "") throw badArchive("executable marker has non-empty value");
crf.isExecutable();
}
else break;
}
});
if (tag == "executable") {
auto s2 = getString();
if (s2 != "") throw badArchive("executable marker has non-empty value");
crf.isExecutable();
tag = getString();
}
else if (t == "directory") {
sink.createDirectory(path);
if (tag == "contents")
parseContents(crf, source);
std::string prevName;
while (1) {
s = getString();
if (s == "entry") {
std::string name;
s = getString();
if (s != "(") throw badArchive("expected open tag");
while (1) {
s = getString();
if (s == ")") {
break;
} else if (s == "name") {
name = getString();
if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos || name.find((char) 0) != std::string::npos)
throw Error("NAR contains invalid file name '%1%'", name);
if (name <= prevName)
throw Error("NAR directory is not sorted");
prevName = name;
if (archiveSettings.useCaseHack) {
auto i = names.find(name);
if (i != names.end()) {
debug("case collision between '%1%' and '%2%'", i->first, name);
name += caseHackSuffix;
name += std::to_string(++i->second);
auto j = names.find(name);
if (j != names.end())
throw Error("NAR contains file name '%s' that collides with case-hacked file name '%s'", prevName, j->first);
} else
names[name] = 0;
}
} else if (s == "node") {
if (name.empty()) throw badArchive("entry name missing");
parse(sink, source, path / name);
} else
throw badArchive("unknown field " + s);
}
}
else break;
}
}
else if (t == "symlink") {
s = getString();
if (s != "target")
throw badArchive("expected 'target' got " + s);
std::string target = getString();
sink.createSymlink(path, target);
// for the next iteration
s = getString();
}
else throw badArchive("unknown file type " + t);
}
else
throw badArchive("unknown field " + s);
expectTag(")");
});
}
else if (type == "directory") {
sink.createDirectory(path);
std::map<Path, int, CaseInsensitiveCompare> names;
std::string prevName;
while (1) {
auto tag = getString();
if (tag == ")") break;
if (tag != "entry")
throw badArchive("expected tag 'entry' or ')', got '%s'", tag);
expectTag("(");
expectTag("name");
auto name = getString();
if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos || name.find((char) 0) != std::string::npos)
throw badArchive("NAR contains invalid file name '%1%'", name);
if (name <= prevName)
throw badArchive("NAR directory is not sorted");
prevName = name;
if (archiveSettings.useCaseHack) {
auto i = names.find(name);
if (i != names.end()) {
debug("case collision between '%1%' and '%2%'", i->first, name);
name += caseHackSuffix;
name += std::to_string(++i->second);
auto j = names.find(name);
if (j != names.end())
throw badArchive("NAR contains file name '%s' that collides with case-hacked file name '%s'", prevName, j->first);
} else
names[name] = 0;
}
expectTag("node");
parse(sink, source, path / name);
expectTag(")");
}
}
else if (type == "symlink") {
expectTag("target");
auto target = getString();
sink.createSymlink(path, target);
expectTag(")");
}
else throw badArchive("unknown file type '%s'", type);
}
@ -299,9 +277,9 @@ void parseDump(FileSystemObjectSink & sink, Source & source)
}
void restorePath(const std::filesystem::path & path, Source & source)
void restorePath(const std::filesystem::path & path, Source & source, bool startFsync)
{
RestoreSink sink;
RestoreSink sink{startFsync};
sink.dstPath = path;
parseDump(sink, source);
}

View file

@ -75,7 +75,7 @@ void dumpString(std::string_view s, Sink & sink);
void parseDump(FileSystemObjectSink & sink, Source & source);
void restorePath(const std::filesystem::path & path, Source & source);
void restorePath(const std::filesystem::path & path, Source & source, bool startFsync = false);
/**
* Read a NAR from 'source' and write it to 'sink'.

View file

@ -91,9 +91,6 @@ struct Parser {
/**
* @brief Parse the next character(s)
*
* @param r
* @return std::shared_ptr<Parser>
*/
virtual void operator()(std::shared_ptr<Parser> & state, Strings & r) = 0;
@ -293,7 +290,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
// We match one space after `nix` so that we preserve indentation.
// No space is necessary for an empty line. An empty line has basically no effect.
if (std::regex_match(line, match, std::regex("^#!\\s*nix(:? |$)(.*)$")))
shebangContent += match[2].str() + "\n";
shebangContent += std::string_view{match[2].first, match[2].second} + "\n";
}
for (const auto & word : parseShebangContent(shebangContent)) {
cmdline.push_back(word);
@ -351,7 +348,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
/* Now that all the other args are processed, run the deferred completions.
*/
for (auto d : deferredCompletions)
for (const auto & d : deferredCompletions)
d.completer(*completions, d.n, d.prefix);
}

View file

@ -2,6 +2,7 @@
///@file
#include <functional>
#include <filesystem>
#include <map>
#include <memory>
#include <optional>
@ -113,6 +114,16 @@ protected:
, arity(1)
{ }
Handler(std::filesystem::path * dest)
: fun([dest](std::vector<std::string> ss) { *dest = ss[0]; })
, arity(1)
{ }
Handler(std::optional<std::filesystem::path> * dest)
: fun([dest](std::vector<std::string> ss) { *dest = ss[0]; })
, arity(1)
{ }
template<class T>
Handler(T * dest, const T & val)
: fun([dest, val](std::vector<std::string> ss) { *dest = val; })
@ -283,6 +294,18 @@ public:
});
}
/**
* Expect a path argument.
*/
void expectArg(const std::string & label, std::filesystem::path * dest, bool optional = false)
{
expectArgs({
.label = label,
.optional = optional,
.handler = {dest}
});
}
/**
* Expect 0 or more arguments.
*/
@ -348,7 +371,7 @@ using Commands = std::map<std::string, std::function<ref<Command>()>>;
/**
* An argument parser that supports multiple subcommands,
* i.e. <command> <subcommand>.
* i.e. `<command> <subcommand>`.
*/
class MultiCommand : virtual public Args
{

View file

@ -1 +0,0 @@
../../build-utils-meson

View file

@ -1,6 +1,7 @@
#pragma once
///@file
#include <cassert>
#include <future>
#include <functional>
@ -21,7 +22,9 @@ public:
Callback(std::function<void(std::future<T>)> fun) : fun(fun) { }
Callback(Callback && callback) : fun(std::move(callback.fun))
// NOTE: std::function is noexcept move-constructible since C++20.
Callback(Callback && callback) noexcept(std::is_nothrow_move_constructible_v<decltype(fun)>)
: fun(std::move(callback.fun))
{
auto prev = callback.done.test_and_set();
if (prev) done.test_and_set();

View file

@ -0,0 +1,184 @@
#pragma once
/**
* @file
*
* Checked arithmetic with classes that make it hard to accidentally make something an unchecked operation.
*/
#include <compare>
#include <concepts> // IWYU pragma: keep
#include <exception>
#include <ostream>
#include <limits>
#include <optional>
#include <type_traits>
namespace nix::checked {
class DivideByZero : std::exception
{};
/**
* Numeric value enforcing checked arithmetic. Performing mathematical operations on such values will return a Result
* type which needs to be checked.
*/
template<std::integral T>
struct Checked
{
using Inner = T;
// TODO: this must be a "trivial default constructor", which means it
// cannot set the value to NOT DO UB on uninit.
T value;
Checked() = default;
explicit Checked(T const value)
: value{value}
{
}
Checked(Checked<T> const & other) = default;
Checked(Checked<T> && other) = default;
Checked<T> & operator=(Checked<T> const & other) = default;
std::strong_ordering operator<=>(Checked<T> const & other) const = default;
std::strong_ordering operator<=>(T const & other) const
{
return value <=> other;
}
explicit operator T() const
{
return value;
}
enum class OverflowKind {
NoOverflow,
Overflow,
DivByZero,
};
class Result
{
T value;
OverflowKind overflowed_;
public:
Result(T value, bool overflowed)
: value{value}
, overflowed_{overflowed ? OverflowKind::Overflow : OverflowKind::NoOverflow}
{
}
Result(T value, OverflowKind overflowed)
: value{value}
, overflowed_{overflowed}
{
}
bool operator==(Result other) const
{
return value == other.value && overflowed_ == other.overflowed_;
}
std::optional<T> valueChecked() const
{
if (overflowed_ != OverflowKind::NoOverflow) {
return std::nullopt;
} else {
return value;
}
}
/**
* Returns the result as if the arithmetic were performed as wrapping arithmetic.
*
* \throws DivideByZero if the operation was a divide by zero.
*/
T valueWrapping() const
{
if (overflowed_ == OverflowKind::DivByZero) {
throw DivideByZero{};
}
return value;
}
bool overflowed() const
{
return overflowed_ == OverflowKind::Overflow;
}
bool divideByZero() const
{
return overflowed_ == OverflowKind::DivByZero;
}
};
Result operator+(Checked<T> const other) const
{
return (*this) + other.value;
}
Result operator+(T const other) const
{
T result;
bool overflowed = __builtin_add_overflow(value, other, &result);
return Result{result, overflowed};
}
Result operator-(Checked<T> const other) const
{
return (*this) - other.value;
}
Result operator-(T const other) const
{
T result;
bool overflowed = __builtin_sub_overflow(value, other, &result);
return Result{result, overflowed};
}
Result operator*(Checked<T> const other) const
{
return (*this) * other.value;
}
Result operator*(T const other) const
{
T result;
bool overflowed = __builtin_mul_overflow(value, other, &result);
return Result{result, overflowed};
}
Result operator/(Checked<T> const other) const
{
return (*this) / other.value;
}
/**
* Performs a checked division.
*
* If the right hand side is zero, the result is marked as a DivByZero and
* valueWrapping will throw.
*/
Result operator/(T const other) const
{
constexpr T const minV = std::numeric_limits<T>::min();
// It's only possible to overflow with signed division since doing so
// requires crossing the two's complement limits by MIN / -1 (since
// two's complement has one more in range in the negative direction
// than in the positive one).
if (std::is_signed<T>() && (value == minV && other == -1)) {
return Result{minV, true};
} else if (other == 0) {
return Result{0, OverflowKind::DivByZero};
} else {
T result = value / other;
return Result{result, false};
}
}
};
template<std::integral T>
std::ostream & operator<<(std::ostream & ios, Checked<T> v)
{
ios << v.value;
return ios;
}
}

View file

@ -1,5 +1,7 @@
#include "config-global.hh"
#include <nlohmann/json.hpp>
namespace nix {
bool GlobalConfig::set(const std::string & name, const std::string & value)

View file

@ -13,6 +13,7 @@
*/
#include "config.hh"
#include "args.hh"
namespace nix {

View file

@ -115,6 +115,8 @@ public:
* Re-applies all previously attempted changes to unknown settings
*/
void reapplyUnknownSettings();
virtual ~AbstractConfig() = default;
};
/**
@ -260,6 +262,7 @@ public:
operator const T &() const { return value; }
operator T &() { return value; }
const T & get() const { return value; }
T & get() { return value; }
template<typename U>
bool operator ==(const U & v2) const { return value == v2; }
template<typename U>

View file

@ -32,11 +32,7 @@ unsigned int getMaxCPU()
auto cgroupFS = getCgroupFS();
if (!cgroupFS) return 0;
auto cgroups = getCgroups("/proc/self/cgroup");
auto cgroup = cgroups[""];
if (cgroup == "") return 0;
auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max";
auto cpuFile = *cgroupFS + "/" + getCurrentCgroup() + "/cpu.max";
auto cpuMax = readFile(cpuFile);
auto cpuMaxParts = tokenizeString<std::vector<std::string>>(cpuMax, " \n");
@ -49,7 +45,7 @@ unsigned int getMaxCPU()
auto period = cpuMaxParts[1];
if (quota != "max")
return std::ceil(std::stoi(quota) / std::stof(period));
} catch (Error &) { ignoreException(lvlDebug); }
} catch (Error &) { ignoreExceptionInDestructor(lvlDebug); }
#endif
return 0;

View file

@ -1,20 +1,23 @@
#include "util.hh"
#include "environment-variables.hh"
extern char * * environ __attribute__((weak));
extern char ** environ __attribute__((weak));
namespace nix {
std::optional<std::string> getEnv(const std::string & key)
{
char * value = getenv(key.c_str());
if (!value) return {};
if (!value)
return {};
return std::string(value);
}
std::optional<std::string> getEnvNonEmpty(const std::string & key) {
std::optional<std::string> getEnvNonEmpty(const std::string & key)
{
auto value = getEnv(key);
if (value == "") return {};
if (value == "")
return {};
return value;
}

View file

@ -9,14 +9,22 @@
#include <optional>
#include "types.hh"
#include "file-path.hh"
namespace nix {
static constexpr auto environmentVariablesCategory = "Options that change environment variables";
/**
* @return an environment variable.
*/
std::optional<std::string> getEnv(const std::string & key);
/**
* Like `getEnv`, but using `OsString` to avoid coercions.
*/
std::optional<OsString> getEnvOs(const OsString & key);
/**
* @return a non empty environment variable. Returns nullopt if the env
* variable is set to ""
@ -43,6 +51,11 @@ int unsetenv(const char * name);
*/
int setEnv(const char * name, const char * value);
/**
* Like `setEnv`, but using `OsString` to avoid coercions.
*/
int setEnvOs(const OsString & name, const OsString & value);
/**
* Clear the environment.
*/

15
src/libutil/exec.hh Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include "os-string.hh"
namespace nix {
/**
* `execvpe` is a GNU extension, so we need to implement it for other POSIX
* platforms.
*
* We use our own implementation unconditionally for consistency.
*/
int execvpe(const OsChar * file0, const OsChar * const argv[], const OsChar * const envp[]);
}

View file

@ -0,0 +1,98 @@
#include "environment-variables.hh"
#include "executable-path.hh"
#include "strings-inline.hh"
#include "util.hh"
#include "file-path-impl.hh"
namespace nix {
namespace fs {
using namespace std::filesystem;
}
constexpr static const OsStringView path_var_separator{
&ExecutablePath::separator,
1,
};
ExecutablePath ExecutablePath::load()
{
// "If PATH is unset or is set to null, the path search is
// implementation-defined."
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
return ExecutablePath::parse(getEnvOs(OS_STR("PATH")).value_or(OS_STR("")));
}
ExecutablePath ExecutablePath::parse(const OsString & path)
{
auto strings = path.empty() ? (std::list<OsString>{})
: basicSplitString<std::list<OsString>, OsChar>(path, path_var_separator);
std::vector<fs::path> ret;
ret.reserve(strings.size());
std::transform(
std::make_move_iterator(strings.begin()),
std::make_move_iterator(strings.end()),
std::back_inserter(ret),
[](OsString && str) {
return fs::path{
str.empty()
// "A zero-length prefix is a legacy feature that
// indicates the current working directory. It
// appears as two adjacent <colon> characters
// ("::"), as an initial <colon> preceding the rest
// of the list, or as a trailing <colon> following
// the rest of the list."
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
? OS_STR(".")
: std::move(str),
};
});
return {ret};
}
OsString ExecutablePath::render() const
{
std::vector<PathViewNG> path2;
path2.reserve(directories.size());
for (auto & p : directories)
path2.push_back(p.native());
return basicConcatStringsSep(path_var_separator, path2);
}
std::optional<fs::path>
ExecutablePath::findName(const OsString & exe, std::function<bool(const fs::path &)> isExecutable) const
{
// "If the pathname being sought contains a <slash>, the search
// through the path prefixes shall not be performed."
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
assert(OsPathTrait<fs::path::value_type>::rfindPathSep(exe) == exe.npos);
for (auto & dir : directories) {
auto candidate = dir / exe;
if (isExecutable(candidate))
return candidate.lexically_normal();
}
return std::nullopt;
}
fs::path ExecutablePath::findPath(const fs::path & exe, std::function<bool(const fs::path &)> isExecutable) const
{
// "If the pathname being sought contains a <slash>, the search
// through the path prefixes shall not be performed."
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
if (exe.filename() == exe) {
auto resOpt = findName(exe, isExecutable);
if (resOpt)
return *resOpt;
else
throw ExecutableLookupError("Could not find executable '%s'", exe.string());
} else {
return exe;
}
}
} // namespace nix

View file

@ -0,0 +1,81 @@
#pragma once
///@file
#include "file-system.hh"
namespace nix {
MakeError(ExecutableLookupError, Error);
/**
* @todo rename, it is not just good for execuatable paths, but also
* other lists of paths.
*/
struct ExecutablePath
{
std::vector<std::filesystem::path> directories;
constexpr static const OsChar separator =
#ifdef WIN32
L';'
#else
':'
#endif
;
/**
* Parse `path` into a list of paths.
*
* On Unix we split on `:`, on Windows we split on `;`.
*
* For Unix, this is according to the POSIX spec for `PATH`.
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
*/
static ExecutablePath parse(const OsString & path);
/**
* Load the `PATH` environment variable and `parse` it.
*/
static ExecutablePath load();
/**
* Opposite of `parse`
*/
OsString render() const;
/**
* Search for an executable.
*
* For Unix, this is according to the POSIX spec for `PATH`.
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
*
* @param exe This must just be a name, and not contain any `/` (or
* `\` on Windows). in case it does, per the spec no lookup should
* be perfomed, and the path (it is not just a file name) as is.
* This is the caller's respsonsibility.
*
* This is a pure function, except for the default `isExecutable`
* argument, which uses the ambient file system to check if a file is
* executable (and exists).
*
* @return path to a resolved executable
*/
std::optional<std::filesystem::path> findName(
const OsString & exe,
std::function<bool(const std::filesystem::path &)> isExecutableFile = isExecutableFileAmbient) const;
/**
* Like the `findName` but also allows a file path as input.
*
* This implements the full POSIX spec: if the path is just a name,
* it searches like the above. Otherwise, it returns the path as is.
* If (in the name case) the search fails, an exception is thrown.
*/
std::filesystem::path findPath(
const std::filesystem::path & exe,
std::function<bool(const std::filesystem::path &)> isExecutable = isExecutableFileAmbient) const;
bool operator==(const ExecutablePath &) const = default;
};
} // namespace nix

View file

@ -2,9 +2,10 @@
///@file
#include "error.hh"
#include "json-utils.hh"
#include "types.hh"
#include <nlohmann/json_fwd.hpp>
namespace nix {
/**
@ -98,10 +99,4 @@ public:
void to_json(nlohmann::json &, const ExperimentalFeature &);
void from_json(const nlohmann::json &, ExperimentalFeature &);
/**
* It is always rendered as a string
*/
template<>
struct json_avoids_null<ExperimentalFeature> : std::true_type {};
}

View file

@ -88,14 +88,15 @@ void dumpPath(
void restorePath(
const Path & path,
Source & source,
FileSerialisationMethod method)
FileSerialisationMethod method,
bool startFsync)
{
switch (method) {
case FileSerialisationMethod::Flat:
writeFile(path, source);
writeFile(path, source, 0666, startFsync);
break;
case FileSerialisationMethod::NixArchive:
restorePath(path, source);
restorePath(path, source, startFsync);
break;
}
}

View file

@ -65,12 +65,13 @@ void dumpPath(
/**
* Restore a serialisation of the given file system object.
*
* @TODO use an arbitrary `FileSystemObjectSink`.
* \todo use an arbitrary `FileSystemObjectSink`.
*/
void restorePath(
const Path & path,
Source & source,
FileSerialisationMethod method);
FileSerialisationMethod method,
bool startFsync = false);
/**

View file

@ -2,6 +2,7 @@
#include "signals.hh"
#include "finally.hh"
#include "serialise.hh"
#include "util.hh"
#include <fcntl.h>
#include <unistd.h>
@ -44,8 +45,9 @@ AutoCloseFD::AutoCloseFD() : fd{INVALID_DESCRIPTOR} {}
AutoCloseFD::AutoCloseFD(Descriptor fd) : fd{fd} {}
AutoCloseFD::AutoCloseFD(AutoCloseFD && that) : fd{that.fd}
// NOTE: This can be noexcept since we are just copying a value and resetting
// the file descriptor in the rhs.
AutoCloseFD::AutoCloseFD(AutoCloseFD && that) noexcept : fd{that.fd}
{
that.fd = INVALID_DESCRIPTOR;
}
@ -65,7 +67,7 @@ AutoCloseFD::~AutoCloseFD()
try {
close();
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}
@ -92,7 +94,7 @@ void AutoCloseFD::close()
}
}
void AutoCloseFD::fsync()
void AutoCloseFD::fsync() const
{
if (fd != INVALID_DESCRIPTOR) {
int result;
@ -111,6 +113,18 @@ void AutoCloseFD::fsync()
}
void AutoCloseFD::startFsync() const
{
#if __linux__
if (fd != -1) {
/* Ignore failure, since fsync must be run later anyway. This is just a performance optimization. */
::sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE);
}
#endif
}
AutoCloseFD::operator bool() const
{
return fd != INVALID_DESCRIPTOR;

View file

@ -77,8 +77,13 @@ void writeFull(Descriptor fd, std::string_view s, bool allowInterrupts = true);
/**
* Read a line from a file descriptor.
*
* @param fd The file descriptor to read from
* @param eofOk If true, return an unterminated line if EOF is reached. (e.g. the empty string)
*
* @return A line of text ending in `\n`, or a string without `\n` if `eofOk` is true and EOF is reached.
*/
std::string readLine(Descriptor fd);
std::string readLine(Descriptor fd, bool eofOk = false);
/**
* Write a line to a file descriptor.
@ -101,8 +106,25 @@ void drainFD(
#endif
);
/**
* Get [Standard Input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin))
*/
[[gnu::always_inline]]
inline Descriptor getStandardOut() {
inline Descriptor getStandardInput()
{
#ifndef _WIN32
return STDIN_FILENO;
#else
return GetStdHandle(STD_INPUT_HANDLE);
#endif
}
/**
* Get [Standard Output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout))
*/
[[gnu::always_inline]]
inline Descriptor getStandardOutput()
{
#ifndef _WIN32
return STDOUT_FILENO;
#else
@ -110,6 +132,19 @@ inline Descriptor getStandardOut() {
#endif
}
/**
* Get [Standard Error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr))
*/
[[gnu::always_inline]]
inline Descriptor getStandardError()
{
#ifndef _WIN32
return STDERR_FILENO;
#else
return GetStdHandle(STD_ERROR_HANDLE);
#endif
}
/**
* Automatic cleanup of resources.
*/
@ -120,7 +155,7 @@ public:
AutoCloseFD();
AutoCloseFD(Descriptor fd);
AutoCloseFD(const AutoCloseFD & fd) = delete;
AutoCloseFD(AutoCloseFD&& fd);
AutoCloseFD(AutoCloseFD&& fd) noexcept;
~AutoCloseFD();
AutoCloseFD& operator =(const AutoCloseFD & fd) = delete;
AutoCloseFD& operator =(AutoCloseFD&& fd);
@ -128,7 +163,18 @@ public:
explicit operator bool() const;
Descriptor release();
void close();
void fsync();
/**
* Perform a blocking fsync operation.
*/
void fsync() const;
/**
* Asynchronously flush to disk without blocking, if available on
* the platform. This is just a performance optimization, and
* fsync must be run later even if this is called.
*/
void startFsync() const;
};
class Pipe
@ -143,10 +189,10 @@ public:
namespace unix {
/**
* Close all file descriptors except those listed in the given set.
* Close all file descriptors except stdio fds (ie 0, 1, 2).
* Good practice in child processes.
*/
void closeMostFDs(const std::set<Descriptor> & exceptions);
void closeExtraFDs();
/**
* Set the close-on-exec flag for the given file descriptor.

View file

@ -91,13 +91,10 @@ struct WindowsPathTrait
};
/**
* @todo Revisit choice of `char` or `wchar_t` for `WindowsPathTrait`
* argument.
*/
using NativePathTrait =
template<typename CharT>
using OsPathTrait =
#ifdef _WIN32
WindowsPathTrait<char>
WindowsPathTrait<CharT>
#else
UnixPathTrait
#endif

View file

@ -1,10 +1,10 @@
#pragma once
///@file
#include <optional>
#include <filesystem>
#include "types.hh"
#include "os-string.hh"
namespace nix {
@ -22,39 +22,26 @@ typedef std::set<std::filesystem::path> PathSetNG;
*
* @todo drop `NG` suffix and replace the one in `types.hh`.
*/
struct PathViewNG : std::basic_string_view<std::filesystem::path::value_type>
struct PathViewNG : OsStringView
{
using string_view = std::basic_string_view<std::filesystem::path::value_type>;
using string_view = OsStringView;
using string_view::string_view;
PathViewNG(const std::filesystem::path & path)
: std::basic_string_view<std::filesystem::path::value_type>(path.native())
: OsStringView{path.native()}
{ }
PathViewNG(const std::filesystem::path::string_type & path)
: std::basic_string_view<std::filesystem::path::value_type>(path)
PathViewNG(const OsString & path)
: OsStringView{path}
{ }
const string_view & native() const { return *this; }
string_view & native() { return *this; }
};
std::string os_string_to_string(PathViewNG::string_view path);
std::filesystem::path::string_type string_to_os_string(std::string_view s);
std::optional<std::filesystem::path> maybePath(PathView path);
std::filesystem::path pathNG(PathView path);
/**
* Create string literals with the native character width of paths
*/
#ifndef _WIN32
# define PATHNG_LITERAL(s) s
#else
# define PATHNG_LITERAL(s) L ## s
#endif
}

View file

@ -5,12 +5,14 @@
#include "signals.hh"
#include "finally.hh"
#include "serialise.hh"
#include "util.hh"
#include <atomic>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <sstream>
#include <filesystem>
@ -25,16 +27,11 @@
#include "strings-inline.hh"
namespace fs = std::filesystem;
namespace nix {
/**
* Treat the string as possibly an absolute path, by inspecting the
* start of it. Return whether it was probably intended to be
* absolute.
*/
static bool isAbsolute(PathView path)
namespace fs { using namespace std::filesystem; }
bool isAbsolute(PathView path)
{
return fs::path { path }.is_absolute();
}
@ -72,6 +69,10 @@ Path absPath(PathView path, std::optional<PathView> dir, bool resolveSymlinks)
return canonPath(path, resolveSymlinks);
}
std::filesystem::path absPath(const std::filesystem::path & path, bool resolveSymlinks)
{
return absPath(path.string(), std::nullopt, resolveSymlinks);
}
Path canonPath(PathView path, bool resolveSymlinks)
{
@ -92,7 +93,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
arbitrary (but high) limit to prevent infinite loops. */
unsigned int followCount = 0, maxFollow = 1024;
auto ret = canonPathInner<NativePathTrait>(
auto ret = canonPathInner<OsPathTrait<char>>(
path,
[&followCount, &temp, maxFollow, resolveSymlinks]
(std::string & result, std::string_view & remaining) {
@ -122,7 +123,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
Path dirOf(const PathView path)
{
Path::size_type pos = NativePathTrait::rfindPathSep(path);
Path::size_type pos = OsPathTrait<char>::rfindPathSep(path);
if (pos == path.npos)
return ".";
return fs::path{path}.parent_path().string();
@ -135,10 +136,10 @@ std::string_view baseNameOf(std::string_view path)
return "";
auto last = path.size() - 1;
while (last > 0 && NativePathTrait::isPathSep(path[last]))
while (last > 0 && OsPathTrait<char>::isPathSep(path[last]))
last -= 1;
auto pos = NativePathTrait::rfindPathSep(path, last);
auto pos = OsPathTrait<char>::rfindPathSep(path, last);
if (pos == path.npos)
pos = 0;
else
@ -205,10 +206,10 @@ bool pathExists(const Path & path)
return maybeLstat(path).has_value();
}
bool pathAccessible(const Path & path)
bool pathAccessible(const std::filesystem::path & path)
{
try {
return pathExists(path);
return pathExists(path.string());
} catch (SysError & e) {
// swallow EPERM
if (e.errNo == EPERM) return false;
@ -237,6 +238,11 @@ std::string readFile(const Path & path)
return readFile(fd.get());
}
std::string readFile(const std::filesystem::path & path)
{
return readFile(os_string_to_string(PathViewNG { path }));
}
void readFile(const Path & path, Sink & sink)
{
@ -318,6 +324,51 @@ void syncParent(const Path & path)
}
void recursiveSync(const Path & path)
{
/* If it's a file or symlink, just fsync and return. */
auto st = lstat(path);
if (S_ISREG(st.st_mode)) {
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_RDONLY, 0));
if (!fd)
throw SysError("opening file '%1%'", path);
fd.fsync();
return;
} else if (S_ISLNK(st.st_mode))
return;
/* Otherwise, perform a depth-first traversal of the directory and
fsync all the files. */
std::deque<fs::path> dirsToEnumerate;
dirsToEnumerate.push_back(path);
std::vector<fs::path> dirsToFsync;
while (!dirsToEnumerate.empty()) {
auto currentDir = dirsToEnumerate.back();
dirsToEnumerate.pop_back();
for (auto & entry : std::filesystem::directory_iterator(currentDir)) {
auto st = entry.symlink_status();
if (fs::is_directory(st)) {
dirsToEnumerate.emplace_back(entry.path());
} else if (fs::is_regular_file(st)) {
AutoCloseFD fd = toDescriptor(open(entry.path().string().c_str(), O_RDONLY, 0));
if (!fd)
throw SysError("opening file '%1%'", entry.path());
fd.fsync();
}
}
dirsToFsync.emplace_back(std::move(currentDir));
}
/* Fsync all the directories. */
for (auto dir = dirsToFsync.rbegin(); dir != dirsToFsync.rend(); ++dir) {
AutoCloseFD fd = toDescriptor(open(dir->string().c_str(), O_RDONLY, 0));
if (!fd)
throw SysError("opening directory '%1%'", *dir);
fd.fsync();
}
}
static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & bytesFreed)
{
#ifndef _WIN32
@ -329,7 +380,7 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
if (fstatat(parentfd, name.c_str(), &st,
AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT) return;
throw SysError("getting status of '%1%'", path);
throw SysError("getting status of %1%", path);
}
if (!S_ISDIR(st.st_mode)) {
@ -361,15 +412,15 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
throw SysError("chmod '%1%'", path);
throw SysError("chmod %1%", path);
}
int fd = openat(parentfd, path.c_str(), O_RDONLY);
if (fd == -1)
throw SysError("opening directory '%1%'", path);
throw SysError("opening directory %1%", path);
AutoCloseDir dir(fdopendir(fd));
if (!dir)
throw SysError("opening directory '%1%'", path);
throw SysError("opening directory %1%", path);
struct dirent * dirent;
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
@ -378,13 +429,13 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
if (childName == "." || childName == "..") continue;
_deletePath(dirfd(dir.get()), path + "/" + childName, bytesFreed);
}
if (errno) throw SysError("reading directory '%1%'", path);
if (errno) throw SysError("reading directory %1%", path);
}
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
if (errno == ENOENT) return;
throw SysError("cannot unlink '%1%'", path);
throw SysError("cannot unlink %1%", path);
}
#else
// TODO implement
@ -446,7 +497,7 @@ void deletePath(const fs::path & path, uint64_t & bytesFreed)
AutoDelete::AutoDelete() : del{false} {}
AutoDelete::AutoDelete(const fs::path & p, bool recursive) : _path(p)
AutoDelete::AutoDelete(const std::filesystem::path & p, bool recursive) : _path(p)
{
del = true;
this->recursive = recursive;
@ -463,7 +514,7 @@ AutoDelete::~AutoDelete()
}
}
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}
@ -547,29 +598,40 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
void createSymlink(const Path & target, const Path & link)
{
fs::create_symlink(target, link);
try {
fs::create_symlink(target, link);
} catch (fs::filesystem_error & e) {
throw SysError("creating symlink '%1%' -> '%2%'", link, target);
}
}
void replaceSymlink(const Path & target, const Path & link)
void replaceSymlink(const fs::path & target, const fs::path & link)
{
for (unsigned int n = 0; true; n++) {
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
auto tmp = link.parent_path() / fs::path{fmt(".%d_%s", n, link.filename().string())};
tmp = tmp.lexically_normal();
try {
createSymlink(target, tmp);
fs::create_symlink(target, tmp);
} catch (fs::filesystem_error & e) {
if (e.code() == std::errc::file_exists) continue;
throw;
throw SysError("creating symlink '%1%' -> '%2%'", tmp, target);
}
try {
fs::rename(tmp, link);
} catch (fs::filesystem_error & e) {
if (e.code() == std::errc::file_exists) continue;
throw SysError("renaming '%1%' to '%2%'", tmp, link);
}
std::filesystem::rename(tmp, link);
break;
}
}
void setWriteTime(
const std::filesystem::path & path,
const fs::path & path,
time_t accessedTime,
time_t modificationTime,
std::optional<bool> optIsSymlink)
@ -581,7 +643,7 @@ void setWriteTime(
// doesn't support access time just modification time.
//
// System clock vs File clock issues also make that annoying.
warn("Changing file times is not yet implemented on Windows, path is '%s'", path);
warn("Changing file times is not yet implemented on Windows, path is %s", path);
#elif HAVE_UTIMENSAT && HAVE_DECL_AT_SYMLINK_NOFOLLOW
struct timespec times[2] = {
{
@ -594,7 +656,7 @@ void setWriteTime(
},
};
if (utimensat(AT_FDCWD, path.c_str(), times, AT_SYMLINK_NOFOLLOW) == -1)
throw SysError("changing modification time of '%s' (using `utimensat`)", path);
throw SysError("changing modification time of %s (using `utimensat`)", path);
#else
struct timeval times[2] = {
{
@ -608,7 +670,7 @@ void setWriteTime(
};
#if HAVE_LUTIMES
if (lutimes(path.c_str(), times) == -1)
throw SysError("changing modification time of '%s'", path);
throw SysError("changing modification time of %s", path);
#else
bool isSymlink = optIsSymlink
? *optIsSymlink
@ -616,9 +678,9 @@ void setWriteTime(
if (!isSymlink) {
if (utimes(path.c_str(), times) == -1)
throw SysError("changing modification time of '%s' (not a symlink)", path);
throw SysError("changing modification time of %s (not a symlink)", path);
} else {
throw Error("Cannot modification time of symlink '%s'", path);
throw Error("Cannot modification time of symlink %s", path);
}
#endif
#endif
@ -647,7 +709,7 @@ void copyFile(const fs::path & from, const fs::path & to, bool andDelete)
copyFile(entry, to / entry.path().filename(), andDelete);
}
} else {
throw Error("file '%s' has an unsupported type", from);
throw Error("file %s has an unsupported type", from);
}
setWriteTime(to, lstat(from.string().c_str()));
@ -674,7 +736,7 @@ void moveFile(const Path & oldName, const Path & newName)
auto tempCopyTarget = temp / "copy-target";
if (e.code().value() == EXDEV) {
fs::remove(newPath);
warn("Cant rename %s as %s, copying instead", oldName, newName);
warn("cant rename %s as %s, copying instead", oldName, newName);
copyFile(oldPath, tempCopyTarget, true);
std::filesystem::rename(
os_string_to_string(PathViewNG { tempCopyTarget }),
@ -685,4 +747,33 @@ void moveFile(const Path & oldName, const Path & newName)
//////////////////////////////////////////////////////////////////////
bool isExecutableFileAmbient(const fs::path & exe) {
// Check file type, because directory being executable means
// something completely different.
// `is_regular_file` follows symlinks before checking.
return std::filesystem::is_regular_file(exe)
&& access(exe.string().c_str(),
#ifdef WIN32
0 // TODO do better
#else
X_OK
#endif
) == 0;
}
std::filesystem::path makeParentCanonical(const std::filesystem::path & rawPath)
{
std::filesystem::path path(absPath(rawPath));;
try {
auto parent = path.parent_path();
if (parent == path) {
// `path` is a root directory => trivially canonical
return parent;
}
return std::filesystem::canonical(parent) / path.filename();
} catch (fs::filesystem_error & e) {
throw SysError("canonicalising parent path of '%1%'", path);
}
}
} // namespace nix

View file

@ -42,20 +42,42 @@ namespace nix {
struct Sink;
struct Source;
/**
* Return whether the path denotes an absolute path.
*/
bool isAbsolute(PathView path);
/**
* @return An absolutized path, resolving paths relative to the
* specified directory, or the current directory otherwise. The path
* is also canonicalised.
*
* In the process of being deprecated for `std::filesystem::absolute`.
*/
Path absPath(PathView path,
std::optional<PathView> dir = {},
bool resolveSymlinks = false);
inline Path absPath(const Path & path,
std::optional<PathView> dir = {},
bool resolveSymlinks = false)
{
return absPath(PathView{path}, dir, resolveSymlinks);
}
std::filesystem::path absPath(const std::filesystem::path & path,
bool resolveSymlinks = false);
/**
* Canonicalise a path by removing all `.` or `..` components and
* double or trailing slashes. Optionally resolves all symlink
* components such that each component of the resulting path is *not*
* a symbolic link.
*
* In the process of being deprecated for
* `std::filesystem::path::lexically_normal` (for the `resolveSymlinks =
* false` case), and `std::filesystem::weakly_canonical` (for the
* `resolveSymlinks = true` case).
*/
Path canonPath(PathView path, bool resolveSymlinks = false);
@ -64,12 +86,18 @@ Path canonPath(PathView path, bool resolveSymlinks = false);
* everything before the final `/`. If the path is the root or an
* immediate child thereof (e.g., `/foo`), this means `/`
* is returned.
*
* In the process of being deprecated for
* `std::filesystem::path::parent_path`.
*/
Path dirOf(const PathView path);
/**
* @return the base name of the given canonical path, i.e., everything
* following the final `/` (trailing slashes are removed).
*
* In the process of being deprecated for
* `std::filesystem::path::filename`.
*/
std::string_view baseNameOf(std::string_view path);
@ -98,20 +126,59 @@ std::optional<struct stat> maybeLstat(const Path & path);
/**
* @return true iff the given path exists.
*
* In the process of being deprecated for `fs::symlink_exists`.
*/
bool pathExists(const Path & path);
namespace fs {
/**
* ```
* symlink_exists(p) = std::filesystem::exists(std::filesystem::symlink_status(p))
* ```
* Missing convenience analogous to
* ```
* std::filesystem::exists(p) = std::filesystem::exists(std::filesystem::status(p))
* ```
*/
inline bool symlink_exists(const std::filesystem::path & path) {
return std::filesystem::exists(std::filesystem::symlink_status(path));
}
} // namespace fs
/**
* Canonicalize a path except for the last component.
*
* This is useful for getting the canonical location of a symlink.
*
* Consider the case where `foo/l` is a symlink. `canonical("foo/l")` will
* resolve the symlink `l` to its target.
* `makeParentCanonical("foo/l")` will not resolve the symlink `l` to its target,
* but does ensure that the returned parent part of the path, `foo` is resolved
* to `canonical("foo")`, and can therefore be retrieved without traversing any
* symlinks.
*
* If a relative path is passed, it will be made absolute, so that the parent
* can always be canonicalized.
*/
std::filesystem::path makeParentCanonical(const std::filesystem::path & path);
/**
* A version of pathExists that returns false on a permission error.
* Useful for inferring default paths across directories that might not
* be readable.
* @return true iff the given path can be accessed and exists
*/
bool pathAccessible(const Path & path);
bool pathAccessible(const std::filesystem::path & path);
/**
* Read the contents (target) of a symbolic link. The result is not
* in any way canonicalised.
*
* In the process of being deprecated for
* `std::filesystem::read_symlink`.
*/
Path readLink(const Path & path);
@ -124,20 +191,34 @@ Descriptor openDirectory(const std::filesystem::path & path);
* Read the contents of a file into a string.
*/
std::string readFile(const Path & path);
std::string readFile(const std::filesystem::path & path);
void readFile(const Path & path, Sink & sink);
/**
* Write a string to a file.
*/
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, bool sync = false)
{
return writeFile(path.string(), s, mode, sync);
}
void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, bool sync = false)
{
return writeFile(path.string(), source, mode, sync);
}
/**
* Flush a file's parent directory to disk
* Flush a path's parent directory to disk.
*/
void syncParent(const Path & path);
/**
* Flush a file or entire directory tree to disk.
*/
void recursiveSync(const Path & path);
/**
* Delete a path; i.e., in the case of a directory, it is deleted
* recursively. It's not an error if the path does not exist. The
@ -149,6 +230,9 @@ void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed);
/**
* Create a directory and all its parents, if necessary.
*
* In the process of being deprecated for
* `std::filesystem::create_directories`.
*/
void createDirs(const Path & path);
inline void createDirs(PathView path)
@ -165,7 +249,7 @@ void createDir(const Path & path, mode_t mode = 0755);
* Set the access and modification times of the given path, not
* following symlinks.
*
* @param accessTime Specified in seconds.
* @param accessedTime Specified in seconds.
*
* @param modificationTime Specified in seconds.
*
@ -187,13 +271,19 @@ void setWriteTime(const std::filesystem::path & path, const struct stat & st);
/**
* Create a symlink.
*
*/
void createSymlink(const Path & target, const Path & link);
/**
* Atomically create or replace a symlink.
*/
void replaceSymlink(const Path & target, const Path & link);
void replaceSymlink(const std::filesystem::path & target, const std::filesystem::path & link);
inline void replaceSymlink(const Path & target, const Path & link)
{
return replaceSymlink(std::filesystem::path{target}, std::filesystem::path{link});
}
/**
* Similar to 'renameFile', but fallback to a copy+remove if `src` and `dst`
@ -263,6 +353,12 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
*/
Path defaultTempDir();
/**
* Interpret `exe` as a location in the ambient file system and return
* whether it resolves to a file that is executable.
*/
bool isExecutableFileAmbient(const std::filesystem::path & exe);
/**
* Used in various places.
*/

View file

@ -20,7 +20,11 @@ public:
// Copying Finallys is definitely not a good idea and will cause them to be
// called twice.
Finally(Finally &other) = delete;
Finally(Finally &&other) : fun(std::move(other.fun)) {
// NOTE: Move constructor can be nothrow if the callable type is itself nothrow
// move-constructible.
Finally(Finally && other) noexcept(std::is_nothrow_move_constructible_v<Fn>)
: fun(std::move(other.fun))
{
other.movedFrom = true;
}
~Finally() noexcept(false)

View file

@ -49,11 +49,13 @@ void copyRecursive(
break;
}
case SourceAccessor::tMisc:
throw Error("file '%1%' has an unsupported type", from);
case SourceAccessor::tChar:
case SourceAccessor::tBlock:
case SourceAccessor::tSocket:
case SourceAccessor::tFifo:
case SourceAccessor::tUnknown:
default:
unreachable();
throw Error("file '%1%' has an unsupported type of %2%", from, stat.typeString());
}
}
@ -85,6 +87,17 @@ void RestoreSink::createDirectory(const CanonPath & path)
struct RestoreRegularFile : CreateRegularFileSink {
AutoCloseFD fd;
bool startFsync = false;
~RestoreRegularFile()
{
/* Initiate an fsync operation without waiting for the
result. The real fsync should be run before registering a
store path, but this is a performance optimization to allow
the disk write to start early. */
if (fd && startFsync)
fd.startFsync();
}
void operator () (std::string_view data) override;
void isExecutable() override;
@ -96,9 +109,10 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
auto p = append(dstPath, path);
RestoreRegularFile crf;
crf.startFsync = startFsync;
crf.fd =
#ifdef _WIN32
CreateFileW(p.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
CreateFileW(p.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)
#else
open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)
#endif

View file

@ -78,6 +78,11 @@ struct NullFileSystemObjectSink : FileSystemObjectSink
struct RestoreSink : FileSystemObjectSink
{
std::filesystem::path dstPath;
bool startFsync = false;
explicit RestoreSink(bool startFsync)
: startFsync{startFsync}
{ }
void createDirectory(const CanonPath & path) override;

View file

@ -200,7 +200,11 @@ std::optional<Mode> convertMode(SourceAccessor::Type type)
case SourceAccessor::tSymlink: return Mode::Symlink;
case SourceAccessor::tRegular: return Mode::Regular;
case SourceAccessor::tDirectory: return Mode::Directory;
case SourceAccessor::tMisc: return std::nullopt;
case SourceAccessor::tChar:
case SourceAccessor::tBlock:
case SourceAccessor::tSocket:
case SourceAccessor::tFifo: return std::nullopt;
case SourceAccessor::tUnknown:
default: unreachable();
}
}
@ -314,9 +318,13 @@ Mode dump(
return Mode::Symlink;
}
case SourceAccessor::tMisc:
case SourceAccessor::tChar:
case SourceAccessor::tBlock:
case SourceAccessor::tSocket:
case SourceAccessor::tFifo:
case SourceAccessor::tUnknown:
default:
throw Error("file '%1%' has an unsupported type", path);
throw Error("file '%1%' has an unsupported type of %2%", path, st.typeString());
}
}

View file

@ -104,7 +104,7 @@ void parseTree(
/**
* Helper putting the previous three `parse*` functions together.
*
* @rootModeIfBlob How to interpret a root blob, for which there is no
* @param rootModeIfBlob How to interpret a root blob, for which there is no
* disambiguating dir entry to answer that questino. If the root it not
* a blob, this is ignored.
*/

View file

@ -134,7 +134,8 @@ std::string Hash::to_string(HashFormat hashFormat, bool includeAlgo) const
Hash Hash::dummy(HashAlgorithm::SHA256);
Hash Hash::parseSRI(std::string_view original) {
Hash Hash::parseSRI(std::string_view original)
{
auto rest = original;
// Parse the has type before the separater, if there was one.

View file

@ -3,12 +3,13 @@
#include <nlohmann/json.hpp>
#include <list>
#include <nlohmann/json_fwd.hpp>
#include "types.hh"
namespace nix {
enum struct ExperimentalFeature;
const nlohmann::json * get(const nlohmann::json & map, const std::string & key);
nlohmann::json * get(nlohmann::json & map, const std::string & key);
@ -71,6 +72,12 @@ struct json_avoids_null<std::list<T>> : std::true_type {};
template<typename K, typename V>
struct json_avoids_null<std::map<K, V>> : std::true_type {};
/**
* `ExperimentalFeature` is always rendered as a string.
*/
template<>
struct json_avoids_null<ExperimentalFeature> : std::true_type {};
}
namespace nlohmann {
@ -84,12 +91,14 @@ namespace nlohmann {
* round trip. We do that with a static assert.
*/
template<typename T>
struct adl_serializer<std::optional<T>> {
struct adl_serializer<std::optional<T>>
{
/**
* @brief Convert a JSON type to an `optional<T>` treating
* `null` as `std::nullopt`.
*/
static void from_json(const json & json, std::optional<T> & t) {
static void from_json(const json & json, std::optional<T> & t)
{
static_assert(
nix::json_avoids_null<T>::value,
"null is already in use for underlying type's JSON");
@ -102,7 +111,8 @@ struct adl_serializer<std::optional<T>> {
* @brief Convert an optional type to a JSON type treating `std::nullopt`
* as `null`.
*/
static void to_json(json & json, const std::optional<T> & t) {
static void to_json(json & json, const std::optional<T> & t)
{
static_assert(
nix::json_avoids_null<T>::value,
"null is already in use for underlying type's JSON");

View file

@ -144,4 +144,23 @@ CgroupStats destroyCgroup(const Path & cgroup)
return destroyCgroup(cgroup, true);
}
std::string getCurrentCgroup()
{
auto cgroupFS = getCgroupFS();
if (!cgroupFS)
throw Error("cannot determine the cgroups file system");
auto ourCgroups = getCgroups("/proc/self/cgroup");
auto ourCgroup = ourCgroups[""];
if (ourCgroup == "")
throw Error("cannot determine cgroup name from /proc/self/cgroup");
return ourCgroup;
}
std::string getRootCgroup()
{
static std::string rootCgroup = getCurrentCgroup();
return rootCgroup;
}
}

View file

@ -25,4 +25,13 @@ struct CgroupStats
*/
CgroupStats destroyCgroup(const Path & cgroup);
std::string getCurrentCgroup();
/**
* Get the cgroup that should be used as the parent when creating new
* sub-cgroups. The first time this is called, the current cgroup will be
* returned, and then all subsequent calls will return the original cgroup.
*/
std::string getRootCgroup();
}

View file

@ -118,7 +118,7 @@ void saveMountNamespace()
void restoreMountNamespace()
{
try {
auto savedCwd = absPath(".");
auto savedCwd = std::filesystem::current_path();
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
throw SysError("restoring parent mount namespace");

View file

@ -1,44 +0,0 @@
libraries += libutil
libutil_NAME = libnixutil
libutil_DIR := $(d)
libutil_SOURCES := $(wildcard $(d)/*.cc $(d)/signature/*.cc)
ifdef HOST_UNIX
libutil_SOURCES += $(wildcard $(d)/unix/*.cc)
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
INCLUDE_libutil := -I $(d)
ifdef HOST_UNIX
INCLUDE_libutil += -I $(d)/unix
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
$(foreach i, $(wildcard $(d)/args/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/args, 0644)))
$(foreach i, $(wildcard $(d)/signature/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/signature, 0644)))
ifeq ($(HAVE_LIBCPUID), 1)
libutil_LDFLAGS += -lcpuid
endif
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-util.pc, $(libdir)/pkgconfig, 0644))

View file

@ -38,7 +38,7 @@ void Logger::warn(const std::string & msg)
void Logger::writeToStdout(std::string_view s)
{
Descriptor standard_out = getStandardOut();
Descriptor standard_out = getStandardOutput();
writeFull(standard_out, s);
writeFull(standard_out, "\n");
}
@ -85,10 +85,10 @@ public:
void logEI(const ErrorInfo & ei) override
{
std::stringstream oss;
std::ostringstream oss;
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
log(ei.level, oss.str());
log(ei.level, toView(oss));
}
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
@ -118,11 +118,7 @@ void writeToStderr(std::string_view s)
{
try {
writeFull(
#ifdef _WIN32
GetStdHandle(STD_ERROR_HANDLE),
#else
STDERR_FILENO,
#endif
getStandardError(),
s, false);
} catch (SystemError & e) {
/* Ignore failing writes to stderr. We need to ignore write
@ -284,61 +280,72 @@ static Logger::Fields getFields(nlohmann::json & json)
return fields;
}
std::optional<nlohmann::json> parseJSONMessage(const std::string & msg)
std::optional<nlohmann::json> parseJSONMessage(const std::string & msg, std::string_view source)
{
if (!hasPrefix(msg, "@nix ")) return std::nullopt;
try {
return nlohmann::json::parse(std::string(msg, 5));
} catch (std::exception & e) {
printError("bad JSON log message from builder: %s", e.what());
printError("bad JSON log message from %s: %s",
Uncolored(source),
e.what());
}
return std::nullopt;
}
bool handleJSONLogMessage(nlohmann::json & json,
const Activity & act, std::map<ActivityId, Activity> & activities,
bool trusted)
std::string_view source, bool trusted)
{
std::string action = json["action"];
try {
std::string action = json["action"];
if (action == "start") {
auto type = (ActivityType) json["type"];
if (trusted || type == actFileTransfer)
activities.emplace(std::piecewise_construct,
std::forward_as_tuple(json["id"]),
std::forward_as_tuple(*logger, (Verbosity) json["level"], type,
json["text"], getFields(json["fields"]), act.id));
if (action == "start") {
auto type = (ActivityType) json["type"];
if (trusted || type == actFileTransfer)
activities.emplace(std::piecewise_construct,
std::forward_as_tuple(json["id"]),
std::forward_as_tuple(*logger, (Verbosity) json["level"], type,
json["text"], getFields(json["fields"]), act.id));
}
else if (action == "stop")
activities.erase((ActivityId) json["id"]);
else if (action == "result") {
auto i = activities.find((ActivityId) json["id"]);
if (i != activities.end())
i->second.result((ResultType) json["type"], getFields(json["fields"]));
}
else if (action == "setPhase") {
std::string phase = json["phase"];
act.result(resSetPhase, phase);
}
else if (action == "msg") {
std::string msg = json["msg"];
logger->log((Verbosity) json["level"], msg);
}
return true;
} catch (const nlohmann::json::exception &e) {
warn(
"Unable to handle a JSON message from %s: %s",
Uncolored(source),
e.what()
);
return false;
}
else if (action == "stop")
activities.erase((ActivityId) json["id"]);
else if (action == "result") {
auto i = activities.find((ActivityId) json["id"]);
if (i != activities.end())
i->second.result((ResultType) json["type"], getFields(json["fields"]));
}
else if (action == "setPhase") {
std::string phase = json["phase"];
act.result(resSetPhase, phase);
}
else if (action == "msg") {
std::string msg = json["msg"];
logger->log((Verbosity) json["level"], msg);
}
return true;
}
bool handleJSONLogMessage(const std::string & msg,
const Activity & act, std::map<ActivityId, Activity> & activities, bool trusted)
const Activity & act, std::map<ActivityId, Activity> & activities, std::string_view source, bool trusted)
{
auto json = parseJSONMessage(msg);
auto json = parseJSONMessage(msg, source);
if (!json) return false;
return handleJSONLogMessage(*json, act, activities, trusted);
return handleJSONLogMessage(*json, act, activities, source, trusted);
}
Activity::~Activity()
@ -346,7 +353,7 @@ Activity::~Activity()
try {
logger.stopActivity(id);
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}

View file

@ -185,14 +185,25 @@ Logger * makeSimpleLogger(bool printBuildLogs = true);
Logger * makeJSONLogger(Logger & prevLogger);
std::optional<nlohmann::json> parseJSONMessage(const std::string & msg);
/**
* @param source A noun phrase describing the source of the message, e.g. "the builder".
*/
std::optional<nlohmann::json> parseJSONMessage(const std::string & msg, std::string_view source);
/**
* @param source A noun phrase describing the source of the message, e.g. "the builder".
*/
bool handleJSONLogMessage(nlohmann::json & json,
const Activity & act, std::map<ActivityId, Activity> & activities,
std::string_view source,
bool trusted);
/**
* @param source A noun phrase describing the source of the message, e.g. "the builder".
*/
bool handleJSONLogMessage(const std::string & msg,
const Activity & act, std::map<ActivityId, Activity> & activities,
std::string_view source,
bool trusted);
/**

View file

@ -4,8 +4,6 @@ project('nix-util', 'cpp',
'cpp_std=c++2a',
# TODO(Qyriad): increase the warning level
'warning_level=1',
'debug=true',
'optimization=2',
'errorlogs=true', # Please print logs for tests that fail
],
meson_version : '>= 1.1',
@ -14,7 +12,7 @@ project('nix-util', 'cpp',
cxx = meson.get_compiler('cpp')
subdir('build-utils-meson/deps-lists')
subdir('nix-meson-build-support/deps-lists')
configdata = configuration_data()
@ -22,12 +20,13 @@ deps_private_maybe_subproject = [
]
deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
subdir('nix-meson-build-support/subprojects')
# Check for each of these functions, and create a define like `#define
# HAVE_LUTIMES 1`. The `#define` is unconditional, 0 for not found and 1
# for found. One therefore uses it with `#if` not `#ifdef`.
check_funcs = [
'close_range',
# Optionally used for changing the mtime of symlinks.
'lutimes',
# Optionally used for creating pipes on Unix
@ -52,7 +51,7 @@ endforeach
configdata.set('HAVE_DECL_AT_SYMLINK_NOFOLLOW', cxx.has_header_symbol('fcntl.h', 'AT_SYMLINK_NOFOLLOW').to_int())
subdir('build-utils-meson/threads')
subdir('nix-meson-build-support/libatomic')
if host_machine.system() == 'windows'
socket = cxx.find_library('ws2_32')
@ -107,6 +106,8 @@ deps_private += cpuid
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
cxx = meson.get_compiler('cpp')
config_h = configure_file(
configuration : configdata,
output : 'config-util.hh',
@ -119,7 +120,7 @@ add_project_arguments(
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
subdir('nix-meson-build-support/common')
sources = files(
'archive.cc',
@ -133,6 +134,7 @@ sources = files(
'english.cc',
'environment-variables.cc',
'error.cc',
'executable-path.cc',
'exit.cc',
'experimental-features.cc',
'file-content-address.cc',
@ -166,6 +168,10 @@ sources = files(
)
include_dirs = [include_directories('.')]
if not cxx.has_header('widechar_width.h', required : false)
# use vendored widechar_width.h
include_dirs += include_directories('./widecharwidth')
endif
headers = [config_h] + files(
'abstract-setting-to-json.hh',
@ -175,6 +181,7 @@ headers = [config_h] + files(
'args/root.hh',
'callback.hh',
'canon-path.hh',
'checked-arithmetic.hh',
'chunked-vector.hh',
'closure.hh',
'comparator.hh',
@ -187,6 +194,8 @@ headers = [config_h] + files(
'english.hh',
'environment-variables.hh',
'error.hh',
'exec.hh',
'executable-path.hh',
'exit.hh',
'experimental-features.hh',
'file-content-address.hh',
@ -206,6 +215,7 @@ headers = [config_h] + files(
'lru-cache.hh',
'memory-source-accessor.hh',
'muxable-pipe.hh',
'os-string.hh',
'pool.hh',
'position.hh',
'posix-source-accessor.hh',
@ -250,7 +260,8 @@ else
subdir('unix')
endif
subdir('build-utils-meson/export-all-symbols')
subdir('nix-meson-build-support/export-all-symbols')
subdir('nix-meson-build-support/windows-version')
this_library = library(
'nixutil',
@ -271,4 +282,4 @@ if host_machine.system() == 'windows'
libraries_private += ['-lws2_32']
endif
subdir('build-utils-meson/export')
subdir('nix-meson-build-support/export')

View file

@ -0,0 +1 @@
../../nix-meson-build-support

View file

@ -1,9 +0,0 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@
Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixutil
Cflags: -I${includedir}/nix -std=c++2a

52
src/libutil/os-string.hh Normal file
View file

@ -0,0 +1,52 @@
#pragma once
///@file
#include <optional>
#include <string>
#include <string_view>
namespace nix {
/**
* Named because it is similar to the Rust type, except it is in the
* native encoding not WTF-8.
*
* Same as `std::filesystem::path::value_type`, but manually defined to
* avoid including a much more complex header.
*/
using OsChar =
#if defined(_WIN32) && !defined(__CYGWIN__)
wchar_t
#else
char
#endif
;
/**
* Named because it is similar to the Rust type, except it is in the
* native encoding not WTF-8.
*
* Same as `std::filesystem::path::string_type`, but manually defined
* for the same reason as `OsChar`.
*/
using OsString = std::basic_string<OsChar>;
/**
* `std::string_view` counterpart for `OsString`.
*/
using OsStringView = std::basic_string_view<OsChar>;
std::string os_string_to_string(OsStringView path);
OsString string_to_os_string(std::string_view s);
/**
* Create string literals with the native character width of paths
*/
#ifndef _WIN32
# define OS_STR(s) s
#else
# define OS_STR(s) L##s
#endif
}

View file

@ -1,39 +1,36 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
{
lib,
stdenv,
mkMesonLibrary,
, meson
, ninja
, pkg-config
boost,
brotli,
libarchive,
libcpuid,
libsodium,
nlohmann_json,
openssl,
, boost
, brotli
, libarchive
, libcpuid
, libsodium
, nlohmann_json
, openssl
# Configuration Options
# Configuration Options
, version
version,
}:
let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
mkMesonLibrary (finalAttrs: {
pname = "nix-util";
inherit version;
workDir = ./.;
fileset = fileset.unions [
../../build-utils-meson
./build-utils-meson
../../nix-meson-build-support
./nix-meson-build-support
../../.version
./.version
./widecharwidth
./meson.build
./meson.options
./linux/meson.build
@ -43,20 +40,11 @@ mkMesonDerivation (finalAttrs: {
(fileset.fileFilter (file: file.hasExt "hh") ./.)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
];
buildInputs = [
brotli
libsodium
openssl
] ++ lib.optional stdenv.hostPlatform.isx86_64 libcpuid
;
] ++ lib.optional stdenv.hostPlatform.isx86_64 libcpuid;
propagatedBuildInputs = [
boost
@ -84,14 +72,8 @@ mkMesonDerivation (finalAttrs: {
# https://github.com/NixOS/nixpkgs/issues/86131.
BOOST_INCLUDEDIR = "${lib.getDev boost}/include";
BOOST_LIBRARYDIR = "${lib.getLib boost}/lib";
} // lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
LDFLAGS = "-fuse-ld=gold";
};
separateDebugInfo = !stdenv.hostPlatform.isStatic;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};

View file

@ -109,7 +109,15 @@ public:
Handle(Pool & pool, std::shared_ptr<R> r) : pool(pool), r(r) { }
public:
Handle(Handle && h) : pool(h.pool), r(h.r) { h.r.reset(); }
// NOTE: Copying std::shared_ptr and calling a .reset() on it is always noexcept.
Handle(Handle && h) noexcept
: pool(h.pool)
, r(h.r)
{
static_assert(noexcept(h.r.reset()));
static_assert(noexcept(std::shared_ptr(h.r)));
h.r.reset();
}
Handle(const Handle & l) = delete;

View file

@ -9,7 +9,7 @@ Pos::Pos(const Pos * other)
}
line = other->line;
column = other->column;
origin = std::move(other->origin);
origin = other->origin;
}
Pos::operator std::shared_ptr<Pos>() const

View file

@ -7,8 +7,8 @@
namespace nix {
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && root)
: root(std::move(root))
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && argRoot)
: root(std::move(argRoot))
{
assert(root.empty() || root.is_absolute());
displayPrefix = root.string();
@ -20,7 +20,7 @@ PosixSourceAccessor::PosixSourceAccessor()
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path)
{
std::filesystem::path path2 = absPath(path.string());
std::filesystem::path path2 = absPath(path);
return {
make_ref<PosixSourceAccessor>(path2.root_path()),
CanonPath { path2.relative_path().string() },
@ -122,7 +122,13 @@ std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonP
S_ISREG(st->st_mode) ? tRegular :
S_ISDIR(st->st_mode) ? tDirectory :
S_ISLNK(st->st_mode) ? tSymlink :
tMisc,
S_ISCHR(st->st_mode) ? tChar :
S_ISBLK(st->st_mode) ? tBlock :
#ifdef S_ISSOCK
S_ISSOCK(st->st_mode) ? tSocket :
#endif
S_ISFIFO(st->st_mode) ? tFifo :
tUnknown,
.fileSize = S_ISREG(st->st_mode) ? std::optional<uint64_t>(st->st_size) : std::nullopt,
.isExecutable = S_ISREG(st->st_mode) && st->st_mode & S_IXUSR,
};
@ -156,7 +162,11 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
case std::filesystem::file_type::regular: return Type::tRegular; break;
case std::filesystem::file_type::symlink: return Type::tSymlink; break;
case std::filesystem::file_type::directory: return Type::tDirectory; break;
default: return tMisc;
case std::filesystem::file_type::character: return Type::tChar; break;
case std::filesystem::file_type::block: return Type::tBlock; break;
case std::filesystem::file_type::fifo: return Type::tFifo; break;
case std::filesystem::file_type::socket: return Type::tSocket; break;
default: return tUnknown;
}
#pragma GCC diagnostic pop
}();

View file

@ -43,13 +43,25 @@ struct PosixSourceAccessor : virtual SourceAccessor
std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override;
/**
* Create a `PosixSourceAccessor` and `CanonPath` corresponding to
* Create a `PosixSourceAccessor` and `SourcePath` corresponding to
* some native path.
*
* The `PosixSourceAccessor` is rooted as far up the tree as
* possible, (e.g. on Windows it could scoped to a drive like
* `C:\`). This allows more `..` parent accessing to work.
*
* @note When `path` is trusted user input, canonicalize it using
* `std::filesystem::canonical`, `makeParentCanonical`, `std::filesystem::weakly_canonical`, etc,
* as appropriate for the use case. At least weak canonicalization is
* required for the `SourcePath` to do anything useful at the location it
* points to.
*
* @note A canonicalizing behavior is not built in `createAtRoot` so that
* callers do not accidentally introduce symlink-related security vulnerabilities.
* Furthermore, `createAtRoot` does not know whether the file pointed to by
* `path` should be resolved if it is itself a symlink. In other words,
* `createAtRoot` can not decide between aforementioned `canonical`, `makeParentCanonical`, etc. for its callers.
*
* See
* [`std::filesystem::path::root_path`](https://en.cppreference.com/w/cpp/filesystem/path/root_path)
* and

View file

@ -18,11 +18,6 @@ private:
std::shared_ptr<T> p;
public:
ref(const ref<T> & r)
: p(r.p)
{ }
explicit ref(const std::shared_ptr<T> & p)
: p(p)
{
@ -75,8 +70,6 @@ public:
return ref<T2>((std::shared_ptr<T2>) p);
}
ref<T> & operator=(ref<T> const & rhs) = default;
bool operator == (const ref<T> & other) const
{
return p == other.p;

View file

@ -2,6 +2,8 @@
///@file
#include <string_view>
#include <string>
#include <sstream>
namespace nix::regex {
@ -10,22 +12,23 @@ namespace nix::regex {
static inline std::string either(std::string_view a, std::string_view b)
{
return std::string { a } + "|" + b;
std::stringstream ss;
ss << a << "|" << b;
return ss.str();
}
static inline std::string group(std::string_view a)
{
return std::string { "(" } + a + ")";
}
static inline std::string many(std::string_view a)
{
return std::string { "(?:" } + a + ")*";
std::stringstream ss;
ss << "(" << a << ")";
return ss.str();
}
static inline std::string list(std::string_view a)
{
return std::string { a } + many(group("," + a));
std::stringstream ss;
ss << a << "(," << a << ")*";
return ss.str();
}
}

View file

@ -1,5 +1,6 @@
#include "serialise.hh"
#include "signals.hh"
#include "util.hh"
#include <cstring>
#include <cerrno>
@ -9,7 +10,10 @@
#ifdef _WIN32
# include <fileapi.h>
# include <winsock2.h>
# include "windows-error.hh"
#else
# include <poll.h>
#endif
@ -49,7 +53,7 @@ void BufferedSink::flush()
FdSink::~FdSink()
{
try { flush(); } catch (...) { ignoreException(); }
try { flush(); } catch (...) { ignoreExceptionInDestructor(); }
}
@ -86,7 +90,6 @@ void Source::operator () (std::string_view data)
void Source::drainInto(Sink & sink)
{
std::string s;
std::array<char, 8192> buf;
while (true) {
size_t n;
@ -158,6 +161,30 @@ bool FdSource::good()
}
bool FdSource::hasData()
{
if (BufferedSource::hasData()) return true;
while (true) {
fd_set fds;
FD_ZERO(&fds);
int fd_ = fromDescriptorReadOnly(fd);
FD_SET(fd_, &fds);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
auto n = select(fd_ + 1, &fds, nullptr, nullptr, &timeout);
if (n < 0) {
if (errno == EINTR) continue;
throw SysError("polling file descriptor");
}
return FD_ISSET(fd, &fds);
}
}
size_t StringSource::read(char * data, size_t len)
{
if (pos == s.size()) throw EndOfFile("end of string reached");
@ -399,7 +426,7 @@ Error readError(Source & source)
auto type = readString(source);
assert(type == "Error");
auto level = (Verbosity) readInt(source);
auto name = readString(source); // removed
[[maybe_unused]] auto name = readString(source); // removed
auto msg = readString(source);
ErrorInfo info {
.level = level,

View file

@ -2,6 +2,7 @@
///@file
#include <memory>
#include <type_traits>
#include "types.hh"
#include "util.hh"
@ -104,6 +105,9 @@ struct BufferedSource : Source
size_t read(char * data, size_t len) override;
/**
* Return true if the buffer is not empty.
*/
bool hasData();
protected:
@ -162,6 +166,13 @@ struct FdSource : BufferedSource
FdSource & operator=(FdSource && s) = default;
bool good() override;
/**
* Return true if the buffer is not empty after a non-blocking
* read.
*/
bool hasData();
protected:
size_t readUnbuffered(char * data, size_t len) override;
private:
@ -192,7 +203,14 @@ struct StringSource : Source
{
std::string_view s;
size_t pos;
// NOTE: Prevent unintentional dangling views when an implicit conversion
// from std::string -> std::string_view occurs when the string is passed
// by rvalue.
StringSource(std::string &&) = delete;
StringSource(std::string_view s) : s(s), pos(0) { }
StringSource(const std::string& str): StringSource(std::string_view(str)) {}
size_t read(char * data, size_t len) override;
};
@ -493,7 +511,7 @@ struct FramedSource : Source
}
}
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}
@ -522,15 +540,16 @@ struct FramedSource : Source
/**
* Write as chunks in the format expected by FramedSource.
*
* The exception_ptr reference can be used to terminate the stream when you
* detect that an error has occurred on the remote end.
* The `checkError` function can be used to terminate the stream when you
* detect that an error has occurred. It does so by throwing an exception.
*/
struct FramedSink : nix::BufferedSink
{
BufferedSink & to;
std::exception_ptr & ex;
std::function<void()> checkError;
FramedSink(BufferedSink & to, std::exception_ptr & ex) : to(to), ex(ex)
FramedSink(BufferedSink & to, std::function<void()> && checkError)
: to(to), checkError(checkError)
{ }
~FramedSink()
@ -539,19 +558,15 @@ struct FramedSink : nix::BufferedSink
to << 0;
to.flush();
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}
void writeUnbuffered(std::string_view data) override
{
/* Don't send more data if the remote has
encountered an error. */
if (ex) {
auto ex2 = ex;
ex = nullptr;
std::rethrow_exception(ex2);
}
/* Don't send more data if an error has occured. */
checkError();
to << data.size();
to(data);
};

View file

@ -5,6 +5,26 @@ namespace nix {
static std::atomic<size_t> nextNumber{0};
bool SourceAccessor::Stat::isNotNARSerialisable()
{
return this->type != tRegular && this->type != tSymlink && this->type != tDirectory;
}
std::string SourceAccessor::Stat::typeString() {
switch (this->type) {
case tRegular: return "regular";
case tSymlink: return "symlink";
case tDirectory: return "directory";
case tChar: return "character device";
case tBlock: return "block device";
case tSocket: return "socket";
case tFifo: return "fifo";
case tUnknown:
default: return "unknown";
}
return "unknown";
}
SourceAccessor::SourceAccessor()
: number(++nextNumber)
, displayPrefix{"«unknown»"}
@ -84,9 +104,10 @@ CanonPath SourceAccessor::resolveSymlinks(
todo.pop_front();
if (c == "" || c == ".")
;
else if (c == "..")
res.pop();
else {
else if (c == "..") {
if (!res.isRoot())
res.pop();
} else {
res.push(c);
if (mode == SymlinkResolution::Full || !todo.empty()) {
if (auto st = maybeLstat(res); st && st->type == SourceAccessor::tSymlink) {
@ -94,7 +115,7 @@ CanonPath SourceAccessor::resolveSymlinks(
throw Error("infinite symlink recursion in path '%s'", showPath(path));
auto target = readLink(res);
res.pop();
if (hasPrefix(target, "/"))
if (isAbsolute(target))
res = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}

View file

@ -88,12 +88,13 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
Unlike `DT_UNKNOWN`, this must not be used for deferring the lookup of types.
*/
tMisc
tChar, tBlock, tSocket, tFifo,
tUnknown
};
struct Stat
{
Type type = tMisc;
Type type = tUnknown;
/**
* For regular files only: the size of the file. Not all
@ -112,6 +113,9 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
* file in the NAR. Only returned by NAR accessors.
*/
std::optional<uint64_t> narOffset;
bool isNotNARSerialisable();
std::string typeString();
};
Stat lstat(const CanonPath & path);

View file

@ -1,14 +1,17 @@
#pragma once
//!@file Hashing utilities for use with unordered_map, etc. (ie low level implementation logic, not domain logic like
//! Nix hashing)
/**
* @file
*
* Hashing utilities for use with `std::unordered_map`, etc. (i.e. low
* level implementation logic, not domain logic like Nix hashing).
*/
#include <functional>
namespace nix {
/**
* hash_combine() from Boost. Hash several hashable values together
* `hash_combine()` from Boost. Hash several hashable values together
* into a single hash.
*/
inline void hash_combine(std::size_t & seed) {}

View file

@ -4,8 +4,51 @@
namespace nix {
template<class C, class CharT>
C basicTokenizeString(std::basic_string_view<CharT> s, std::basic_string_view<CharT> separators)
{
C result;
auto pos = s.find_first_not_of(separators, 0);
while (pos != s.npos) {
auto end = s.find_first_of(separators, pos + 1);
if (end == s.npos)
end = s.size();
result.insert(result.end(), std::basic_string<CharT>(s, pos, end - pos));
pos = s.find_first_not_of(separators, end);
}
return result;
}
template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss)
C tokenizeString(std::string_view s, std::string_view separators)
{
return basicTokenizeString<C, char>(s, separators);
}
template<class C, class CharT>
C basicSplitString(std::basic_string_view<CharT> s, std::basic_string_view<CharT> separators)
{
C result;
size_t pos = 0;
while (pos <= s.size()) {
auto end = s.find_first_of(separators, pos);
if (end == s.npos)
end = s.size();
result.insert(result.end(), std::basic_string<CharT>(s, pos, end - pos));
pos = end + 1;
}
return result;
}
template<class C>
C splitString(std::string_view s, std::string_view separators)
{
return basicSplitString<C, char>(s, separators);
}
template<class CharT, class C>
std::basic_string<CharT> basicConcatStringsSep(const std::basic_string_view<CharT> sep, const C & ss)
{
size_t size = 0;
bool tail = false;
@ -13,10 +56,10 @@ std::string concatStringsSep(const std::string_view sep, const C & ss)
for (const auto & s : ss) {
if (tail)
size += sep.size();
size += std::string_view(s).size();
size += std::basic_string_view<CharT>{s}.size();
tail = true;
}
std::string s;
std::basic_string<CharT> s;
s.reserve(size);
tail = false;
for (auto & i : ss) {
@ -28,4 +71,36 @@ std::string concatStringsSep(const std::string_view sep, const C & ss)
return s;
}
template<class C>
std::string concatStringsSep(const std::string_view sep, const C & ss)
{
return basicConcatStringsSep<char, C>(sep, ss);
}
template<class C>
std::string dropEmptyInitThenConcatStringsSep(const std::string_view sep, const C & ss)
{
size_t size = 0;
// TODO? remove to make sure we don't rely on the empty item ignoring behavior,
// or just get rid of this function by understanding the remaining calls.
// for (auto & i : ss) {
// // Make sure we don't rely on the empty item ignoring behavior
// assert(!i.empty());
// break;
// }
// need a cast to string_view since this is also called with Symbols
for (const auto & s : ss)
size += sep.size() + std::string_view(s).size();
std::string s;
s.reserve(size);
for (auto & i : ss) {
if (s.size() != 0)
s += sep;
s += i;
}
return s;
}
} // namespace nix

View file

@ -1,12 +1,41 @@
#include <filesystem>
#include <string>
#include <sstream>
#include "strings-inline.hh"
#include "util.hh"
#include "os-string.hh"
#include "error.hh"
namespace nix {
template std::string concatStringsSep(std::string_view, const Strings &);
template std::string concatStringsSep(std::string_view, const StringSet &);
struct view_stringbuf : public std::stringbuf
{
inline std::string_view toView()
{
auto begin = pbase();
return {begin, begin + pubseekoff(0, std::ios_base::cur, std::ios_base::out)};
}
};
std::string_view toView(const std::ostringstream & os)
{
auto buf = static_cast<view_stringbuf *>(os.rdbuf());
return buf->toView();
}
template std::list<std::string> tokenizeString(std::string_view s, std::string_view separators);
template std::set<std::string> tokenizeString(std::string_view s, std::string_view separators);
template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);
template std::list<std::string> splitString(std::string_view s, std::string_view separators);
template std::set<std::string> splitString(std::string_view s, std::string_view separators);
template std::vector<std::string> splitString(std::string_view s, std::string_view separators);
template std::list<OsString>
basicSplitString(std::basic_string_view<OsChar> s, std::basic_string_view<OsChar> separators);
template std::string concatStringsSep(std::string_view, const std::list<std::string> &);
template std::string concatStringsSep(std::string_view, const std::set<std::string> &);
template std::string concatStringsSep(std::string_view, const std::vector<std::string> &);
typedef std::string_view strings_2[2];
@ -16,4 +45,111 @@ template std::string concatStringsSep(std::string_view, const strings_3 &);
typedef std::string_view strings_4[4];
template std::string concatStringsSep(std::string_view, const strings_4 &);
template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::list<std::string> &);
template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::set<std::string> &);
template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::vector<std::string> &);
/**
* Shell split string: split a string into shell arguments, respecting quotes and backslashes.
*
* Used for NIX_SSHOPTS handling, which previously used `tokenizeString` and was broken by
* Arguments that need to be passed to ssh with spaces in them.
*
* Read https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html for the
* POSIX shell specification, which is technically what we are implementing here.
*/
std::list<std::string> shellSplitString(std::string_view s)
{
std::list<std::string> result;
std::string current;
bool startedCurrent = false;
bool escaping = false;
auto pushCurrent = [&]() {
if (startedCurrent) {
result.push_back(current);
current.clear();
startedCurrent = false;
}
};
auto pushChar = [&](char c) {
current.push_back(c);
startedCurrent = true;
};
auto pop = [&]() {
auto c = s[0];
s.remove_prefix(1);
return c;
};
auto inDoubleQuotes = [&]() {
startedCurrent = true;
// in double quotes, escaping with backslash is only effective for $, `, ", and backslash
while (!s.empty()) {
auto c = pop();
if (escaping) {
switch (c) {
case '$':
case '`':
case '"':
case '\\':
pushChar(c);
break;
default:
pushChar('\\');
pushChar(c);
break;
}
escaping = false;
} else if (c == '\\') {
escaping = true;
} else if (c == '"') {
return;
} else {
pushChar(c);
}
}
if (s.empty()) {
throw Error("unterminated double quote");
}
};
auto inSingleQuotes = [&]() {
startedCurrent = true;
while (!s.empty()) {
auto c = pop();
if (c == '\'') {
return;
}
pushChar(c);
}
if (s.empty()) {
throw Error("unterminated single quote");
}
};
while (!s.empty()) {
auto c = pop();
if (escaping) {
pushChar(c);
escaping = false;
} else if (c == '\\') {
escaping = true;
} else if (c == ' ' || c == '\t') {
pushCurrent();
} else if (c == '"') {
inDoubleQuotes();
} else if (c == '\'') {
inSingleQuotes();
} else {
pushChar(c);
}
}
pushCurrent();
return result;
}
} // namespace nix

View file

@ -8,6 +8,43 @@
namespace nix {
/*
* workaround for unavailable view() method (C++20) of std::ostringstream under MacOS with clang-16
*/
std::string_view toView(const std::ostringstream & os);
/**
* String tokenizer.
*
* See also `basicSplitString()`, which preserves empty strings between separators, as well as at the start and end.
*/
template<class C, class CharT = char>
C basicTokenizeString(std::basic_string_view<CharT> s, std::basic_string_view<CharT> separators);
/**
* Like `basicTokenizeString` but specialized to the default `char`
*/
template<class C>
C tokenizeString(std::string_view s, std::string_view separators = " \t\n\r");
extern template std::list<std::string> tokenizeString(std::string_view s, std::string_view separators);
extern template std::set<std::string> tokenizeString(std::string_view s, std::string_view separators);
extern template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);
/**
* Split a string, preserving empty strings between separators, as well as at the start and end.
*
* Returns a non-empty collection of strings.
*/
template<class C, class CharT = char>
C basicSplitString(std::basic_string_view<CharT> s, std::basic_string_view<CharT> separators);
template<typename C>
C splitString(std::string_view s, std::string_view separators);
extern template std::list<std::string> splitString(std::string_view s, std::string_view separators);
extern template std::set<std::string> splitString(std::string_view s, std::string_view separators);
extern template std::vector<std::string> splitString(std::string_view s, std::string_view separators);
/**
* Concatenate the given strings with a separator between the elements.
*/
@ -18,4 +55,27 @@ extern template std::string concatStringsSep(std::string_view, const std::list<s
extern template std::string concatStringsSep(std::string_view, const std::set<std::string> &);
extern template std::string concatStringsSep(std::string_view, const std::vector<std::string> &);
/**
* Ignore any empty strings at the start of the list, and then concatenate the
* given strings with a separator between the elements.
*
* @deprecated This function exists for historical reasons. You probably just
* want to use `concatStringsSep`.
*/
template<class C>
[[deprecated(
"Consider removing the empty string dropping behavior. If acceptable, use concatStringsSep instead.")]] std::string
dropEmptyInitThenConcatStringsSep(const std::string_view sep, const C & ss);
extern template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::list<std::string> &);
extern template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::set<std::string> &);
extern template std::string dropEmptyInitThenConcatStringsSep(std::string_view, const std::vector<std::string> &);
/**
* Shell split string: split a string into shell arguments, respecting quotes and backslashes.
*
* Used for NIX_SSHOPTS handling, which previously used `tokenizeString` and was broken by
* Arguments that need to be passed to ssh with spaces in them.
*/
std::list<std::string> shellSplitString(std::string_view s);
}

View file

@ -106,7 +106,7 @@ TarArchive::TarArchive(Source & source, bool raw, std::optional<std::string> com
"Failed to open archive (%s)");
}
TarArchive::TarArchive(const fs::path & path)
TarArchive::TarArchive(const std::filesystem::path & path)
: archive{archive_read_new()}
, buffer(defaultBufferSize)
{

View file

@ -11,6 +11,53 @@
# include <sys/ioctl.h>
#endif
#include <unistd.h>
#include <widechar_width.h>
namespace {
inline std::pair<int, size_t> charWidthUTF8Helper(std::string_view s)
{
size_t bytes = 1;
uint32_t ch = s[0];
uint32_t max = 1U << 7;
if ((ch & 0x80U) == 0U) {
} else if ((ch & 0xe0U) == 0xc0U) {
ch &= 0x1fU;
bytes = 2;
max = 1U << 11;
} else if ((ch & 0xf0U) == 0xe0U) {
ch &= 0x0fU;
bytes = 3;
max = 1U << 16;
} else if ((ch & 0xf8U) == 0xf0U) {
ch &= 0x07U;
bytes = 4;
max = 0x110000U;
} else {
return {bytes, bytes}; // invalid UTF-8 start byte
}
for (size_t i = 1; i < bytes; i++) {
if (i < s.size() && (s[i] & 0xc0) == 0x80) {
ch = (ch << 6) | (s[i] & 0x3f);
} else {
return {i, i}; // invalid UTF-8 encoding; assume one character per byte
}
}
int width = bytes; // in case of overlong encoding
if (ch < max) {
width = widechar_wcwidth(ch);
if (width == widechar_ambiguous) {
width = 1; // just a guess...
} else if (width == widechar_widened_in_9) {
width = 2;
} else if (width < 0) {
width = 0;
}
}
return {width, bytes};
}
}
namespace nix {
@ -26,11 +73,11 @@ bool isTTY()
std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int width)
{
std::string t, e;
std::string t;
size_t w = 0;
auto i = s.begin();
while (w < (size_t) width && i != s.end()) {
while (i != s.end()) {
if (*i == '\e') {
std::string e;
@ -45,6 +92,13 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w
while (i != s.end() && *i >= 0x20 && *i <= 0x2f) e += *i++;
// eat final byte
if (i != s.end() && *i >= 0x40 && *i <= 0x7e) e += last = *i++;
} else if (i != s.end() && *i == ']') {
// OSC
e += *i++;
// eat ESC
while (i != s.end() && *i != '\e') e += *i++;
// eat backslash
if (i != s.end() && *i == '\\') e += last = *i++;
} else {
if (i != s.end() && *i >= 0x40 && *i <= 0x5f) e += *i++;
}
@ -54,10 +108,12 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w
}
else if (*i == '\t') {
i++; t += ' '; w++;
while (w < (size_t) width && w % 8) {
t += ' '; w++;
}
do {
if (++w > (size_t) width)
return t;
t += ' ';
} while (w % 8);
i++;
}
else if (*i == '\r' || *i == '\a')
@ -65,35 +121,18 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w
i++;
else {
w++;
// Copy one UTF-8 character.
if ((*i & 0xe0) == 0xc0) {
t += *i++;
if (i != s.end() && ((*i & 0xc0) == 0x80)) t += *i++;
} else if ((*i & 0xf0) == 0xe0) {
t += *i++;
if (i != s.end() && ((*i & 0xc0) == 0x80)) {
t += *i++;
if (i != s.end() && ((*i & 0xc0) == 0x80)) t += *i++;
}
} else if ((*i & 0xf8) == 0xf0) {
t += *i++;
if (i != s.end() && ((*i & 0xc0) == 0x80)) {
t += *i++;
if (i != s.end() && ((*i & 0xc0) == 0x80)) {
t += *i++;
if (i != s.end() && ((*i & 0xc0) == 0x80)) t += *i++;
}
}
} else
t += *i++;
auto [chWidth, bytes] = charWidthUTF8Helper({i, s.end()});
w += chWidth;
if (w > (size_t) width) {
break;
}
t += {i, i + bytes};
i += bytes;
}
}
return t;
}
//////////////////////////////////////////////////////////////////////
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};

View file

@ -110,11 +110,15 @@ void ThreadPool::doWork(bool mainThread)
propagate it. */
try {
std::rethrow_exception(exc);
} catch (const Interrupted &) {
// The interrupted state may be picked up by multiple
// workers, which is expected, so we should ignore
// it silently and let the first one bubble up,
// rethrown via the original state->exception.
} catch (const ThreadPoolShutDown &) {
// Similarly expected.
} catch (std::exception & e) {
if (!dynamic_cast<Interrupted*>(&e) &&
!dynamic_cast<ThreadPoolShutDown*>(&e))
ignoreException();
} catch (...) {
ignoreExceptionExceptInterrupt();
}
}
}

View file

@ -83,7 +83,6 @@ private:
*/
template<typename T>
void processGraph(
ThreadPool & pool,
const std::set<T> & nodes,
std::function<std::set<T>(const T &)> getEdges,
std::function<void(const T &)> processNode)
@ -97,6 +96,10 @@ void processGraph(
std::function<void(const T &)> worker;
/* Create pool last to ensure threads are stopped before other destructors
* run */
ThreadPool pool;
worker = [&](const T & node) {
{
@ -147,8 +150,16 @@ void processGraph(
}
};
for (auto & node : nodes)
pool.enqueue(std::bind(worker, std::ref(node)));
for (auto & node : nodes) {
try {
pool.enqueue(std::bind(worker, std::ref(node)));
} catch (ThreadPoolShutDown &) {
/* Stop if the thread pool is shutting down. It means a
previous work item threw an exception, so process()
below will rethrow it. */
break;
}
}
pool.process();

View file

@ -43,9 +43,11 @@ template<typename T>
struct Explicit {
T t;
bool operator ==(const Explicit<T> & other) const
bool operator ==(const Explicit<T> & other) const = default;
bool operator <(const Explicit<T> & other) const
{
return t == other.t;
return t < other.t;
}
};

View file

@ -9,4 +9,14 @@ int setEnv(const char * name, const char * value)
return ::setenv(name, value, 1);
}
std::optional<std::string> getEnvOs(const std::string & key)
{
return getEnv(key);
}
int setEnvOs(const OsString & name, const OsString & value)
{
return setEnv(name.c_str(), value.c_str());
}
}

View file

@ -47,7 +47,7 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts)
}
std::string readLine(int fd)
std::string readLine(int fd, bool eofOk)
{
std::string s;
while (1) {
@ -58,8 +58,12 @@ std::string readLine(int fd)
if (rd == -1) {
if (errno != EINTR)
throw SysError("reading a line");
} else if (rd == 0)
throw EndOfFile("unexpected EOF reading a line");
} else if (rd == 0) {
if (eofOk)
return s;
else
throw EndOfFile("unexpected EOF reading a line");
}
else {
if (ch == '\n') return s;
s += ch;
@ -120,14 +124,38 @@ void Pipe::create()
//////////////////////////////////////////////////////////////////////
void unix::closeMostFDs(const std::set<int> & exceptions)
#if __linux__ || __FreeBSD__
static int unix_close_range(unsigned int first, unsigned int last, int flags)
{
#if !HAVE_CLOSE_RANGE
return syscall(SYS_close_range, first, last, (unsigned int)flags);
#else
return close_range(first, last, flags);
#endif
}
#endif
void unix::closeExtraFDs()
{
constexpr int MAX_KEPT_FD = 2;
static_assert(std::max({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) == MAX_KEPT_FD);
#if __linux__ || __FreeBSD__
// first try to close_range everything we don't care about. if this
// returns an error with these parameters we're running on a kernel
// that does not implement close_range (i.e. pre 5.9) and fall back
// to the old method. we should remove that though, in some future.
if (unix_close_range(MAX_KEPT_FD + 1, ~0U, 0) == 0) {
return;
}
#endif
#if __linux__
try {
for (auto & s : std::filesystem::directory_iterator{"/proc/self/fd"}) {
checkInterrupt();
auto fd = std::stoi(s.path().filename());
if (!exceptions.count(fd)) {
if (fd > MAX_KEPT_FD) {
debug("closing leaked FD %d", fd);
close(fd);
}
@ -142,9 +170,8 @@ void unix::closeMostFDs(const std::set<int> & exceptions)
#if HAVE_SYSCONF
maxFD = sysconf(_SC_OPEN_MAX);
#endif
for (int fd = 0; fd < maxFD; ++fd)
if (!exceptions.count(fd))
close(fd); /* ignore result */
for (int fd = MAX_KEPT_FD + 1; fd < maxFD; ++fd)
close(fd); /* ignore result */
}

View file

@ -8,16 +8,6 @@
namespace nix {
std::string os_string_to_string(PathViewNG::string_view path)
{
return std::string { path };
}
std::filesystem::path::string_type string_to_os_string(std::string_view s)
{
return std::string { s };
}
std::optional<std::filesystem::path> maybePath(PathView path)
{
return { path };

View file

@ -4,6 +4,7 @@ sources += files(
'file-path.cc',
'file-system.cc',
'muxable-pipe.cc',
'os-string.cc',
'processes.cc',
'signals.cc',
'users.cc',

View file

@ -0,0 +1,21 @@
#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};
}
std::filesystem::path::string_type string_to_os_string(std::string_view s)
{
return std::string{s};
}
}

View file

@ -1,5 +1,6 @@
#include "current-process.hh"
#include "environment-variables.hh"
#include "executable-path.hh"
#include "signals.hh"
#include "processes.hh"
#include "finally.hh"
@ -419,4 +420,12 @@ bool statusOk(int status)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
int execvpe(const char * file0, const char * const argv[], const char * const envp[])
{
auto file = ExecutablePath::load().findPath(file0);
// `const_cast` is safe. See the note in
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/exec.html
return execve(file.c_str(), const_cast<char *const *>(argv), const_cast<char *const *>(envp));
}
}

View file

@ -14,6 +14,7 @@
#include "error.hh"
#include "logging.hh"
#include "ansicolor.hh"
#include "signals.hh"
#include <sys/types.h>
#include <sys/stat.h>
@ -84,6 +85,12 @@ static inline bool getInterrupted()
return unix::_isInterrupted;
}
/**
* Throw `Interrupted` exception if the process has been interrupted.
*
* Call this in long-running loops and between slow operations to terminate
* them as needed.
*/
void inline checkInterrupt()
{
using namespace unix;

View file

@ -91,7 +91,7 @@ void unix::triggerInterrupt()
try {
callback();
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}
}

View file

@ -9,6 +9,8 @@
namespace nix {
namespace fs { using namespace std::filesystem; }
std::string getUserName()
{
auto pw = getpwuid(geteuid());

View file

@ -22,7 +22,6 @@ ParsedURL parseURL(const std::string & url)
std::smatch match;
if (std::regex_match(url, match, uriRegex)) {
auto & base = match[1];
std::string scheme = match[2];
auto authority = match[3].matched
? std::optional<std::string>(match[3]) : std::nullopt;
@ -40,8 +39,6 @@ ParsedURL parseURL(const std::string & url)
path = "/";
return ParsedURL{
.url = url,
.base = base,
.scheme = scheme,
.authority = authority,
.path = percentDecode(path),
@ -77,12 +74,16 @@ std::map<std::string, std::string> decodeQuery(const std::string & query)
{
std::map<std::string, std::string> result;
for (auto s : tokenizeString<Strings>(query, "&")) {
for (const auto & s : tokenizeString<Strings>(query, "&")) {
auto e = s.find('=');
if (e != std::string::npos)
result.emplace(
s.substr(0, e),
percentDecode(std::string_view(s).substr(e + 1)));
if (e == std::string::npos) {
warn("dubious URI query '%s' is missing equal sign '%s', ignoring", s, "=");
continue;
}
result.emplace(
s.substr(0, e),
percentDecode(std::string_view(s).substr(e + 1)));
}
return result;
@ -132,6 +133,12 @@ std::string ParsedURL::to_string() const
+ (fragment.empty() ? "" : "#" + percentEncode(fragment));
}
std::ostream & operator << (std::ostream & os, const ParsedURL & url)
{
os << url.to_string();
return os;
}
bool ParsedURL::operator ==(const ParsedURL & other) const noexcept
{
return

View file

@ -7,9 +7,6 @@ namespace nix {
struct ParsedURL
{
std::string url;
/// URL without query/fragment
std::string base;
std::string scheme;
std::optional<std::string> authority;
std::string path;
@ -26,6 +23,8 @@ struct ParsedURL
ParsedURL canonicalise();
};
std::ostream & operator << (std::ostream & os, const ParsedURL & url);
MakeError(BadURL, Error);
std::string percentDecode(std::string_view in);

View file

@ -7,15 +7,33 @@ namespace nix {
Path getCacheDir()
{
auto cacheDir = getEnv("XDG_CACHE_HOME");
return cacheDir ? *cacheDir : getHome() + "/.cache";
auto dir = getEnv("NIX_CACHE_HOME");
if (dir) {
return *dir;
} else {
auto xdgDir = getEnv("XDG_CACHE_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
} else {
return getHome() + "/.cache/nix";
}
}
}
Path getConfigDir()
{
auto configDir = getEnv("XDG_CONFIG_HOME");
return configDir ? *configDir : getHome() + "/.config";
auto dir = getEnv("NIX_CONFIG_HOME");
if (dir) {
return *dir;
} else {
auto xdgDir = getEnv("XDG_CONFIG_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
} else {
return getHome() + "/.config/nix";
}
}
}
std::vector<Path> getConfigDirs()
@ -23,6 +41,9 @@ std::vector<Path> getConfigDirs()
Path configHome = getConfigDir();
auto configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
std::vector<Path> result = tokenizeString<std::vector<std::string>>(configDirs, ":");
for (auto& p : result) {
p += "/nix";
}
result.insert(result.begin(), configHome);
return result;
}
@ -30,19 +51,37 @@ std::vector<Path> getConfigDirs()
Path getDataDir()
{
auto dataDir = getEnv("XDG_DATA_HOME");
return dataDir ? *dataDir : getHome() + "/.local/share";
auto dir = getEnv("NIX_DATA_HOME");
if (dir) {
return *dir;
} else {
auto xdgDir = getEnv("XDG_DATA_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
} else {
return getHome() + "/.local/share/nix";
}
}
}
Path getStateDir()
{
auto stateDir = getEnv("XDG_STATE_HOME");
return stateDir ? *stateDir : getHome() + "/.local/state";
auto dir = getEnv("NIX_STATE_HOME");
if (dir) {
return *dir;
} else {
auto xdgDir = getEnv("XDG_STATE_HOME");
if (xdgDir) {
return *xdgDir + "/nix";
} else {
return getHome() + "/.local/state/nix";
}
}
}
Path createNixStateDir()
{
Path dir = getStateDir() + "/nix";
Path dir = getStateDir();
createDirs(dir);
return dir;
}

View file

@ -24,12 +24,12 @@ Path getHomeOf(uid_t userId);
Path getHome();
/**
* @return $XDG_CACHE_HOME or $HOME/.cache.
* @return $NIX_CACHE_HOME or $XDG_CACHE_HOME/nix or $HOME/.cache/nix.
*/
Path getCacheDir();
/**
* @return $XDG_CONFIG_HOME or $HOME/.config.
* @return $NIX_CONFIG_HOME or $XDG_CONFIG_HOME/nix or $HOME/.config/nix.
*/
Path getConfigDir();
@ -39,12 +39,12 @@ Path getConfigDir();
std::vector<Path> getConfigDirs();
/**
* @return $XDG_DATA_HOME or $HOME/.local/share.
* @return $NIX_DATA_HOME or $XDG_DATA_HOME/nix or $HOME/.local/share/nix.
*/
Path getDataDir();
/**
* @return $XDG_STATE_HOME or $HOME/.local/state.
* @return $NIX_STATE_HOME or $XDG_STATE_HOME/nix or $HOME/.local/state/nix.
*/
Path getStateDir();

View file

@ -1,5 +1,7 @@
#include "util.hh"
#include "fmt.hh"
#include "file-path.hh"
#include "signals.hh"
#include <array>
#include <cctype>
@ -53,24 +55,6 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss)
//////////////////////////////////////////////////////////////////////
template<class C> C tokenizeString(std::string_view s, std::string_view separators)
{
C result;
auto pos = s.find_first_not_of(separators, 0);
while (pos != s.npos) {
auto end = s.find_first_of(separators, pos + 1);
if (end == s.npos) end = s.size();
result.insert(result.end(), std::string(s, pos, end - pos));
pos = s.find_first_not_of(separators, end);
}
return result;
}
template Strings tokenizeString(std::string_view s, std::string_view separators);
template StringSet tokenizeString(std::string_view s, std::string_view separators);
template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);
std::string chomp(std::string_view s)
{
size_t i = s.find_last_not_of(" \n\r\t");
@ -199,7 +183,7 @@ std::string shellEscape(const std::string_view s)
}
void ignoreException(Verbosity lvl)
void ignoreExceptionInDestructor(Verbosity lvl)
{
/* Make sure no exceptions leave this function.
printError() also throws when remote is closed. */
@ -212,6 +196,17 @@ void ignoreException(Verbosity lvl)
} catch (...) { }
}
void ignoreExceptionExceptInterrupt(Verbosity lvl)
{
try {
throw;
} catch (const Interrupted & e) {
throw;
} catch (std::exception & e) {
printMsg(lvl, "error (ignored): %1%", e.what());
}
}
constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

View file

@ -28,49 +28,11 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss);
MakeError(FormatError, Error);
/**
* String tokenizer.
*/
template<class C> C tokenizeString(std::string_view s, std::string_view separators = " \t\n\r");
/**
* Ignore any empty strings at the start of the list, and then concatenate the
* given strings with a separator between the elements.
*
* @deprecated This function exists for historical reasons. You probably just
* want to use `concatStringsSep`.
*/
template<class C>
[[deprecated("Consider removing the empty string dropping behavior. If acceptable, use concatStringsSep instead.")]]
std::string dropEmptyInitThenConcatStringsSep(const std::string_view sep, const C & ss)
{
size_t size = 0;
// TODO? remove to make sure we don't rely on the empty item ignoring behavior,
// or just get rid of this function by understanding the remaining calls.
// for (auto & i : ss) {
// // Make sure we don't rely on the empty item ignoring behavior
// assert(!i.empty());
// break;
// }
// need a cast to string_view since this is also called with Symbols
for (const auto & s : ss) size += sep.size() + std::string_view(s).size();
std::string s;
s.reserve(size);
for (auto & i : ss) {
if (s.size() != 0) s += sep;
s += i;
}
return s;
}
template<class ... Parts>
auto concatStrings(Parts && ... parts)
template<class... Parts>
auto concatStrings(Parts &&... parts)
-> std::enable_if_t<(... && std::is_convertible_v<Parts, std::string_view>), std::string>
{
std::string_view views[sizeof...(parts)] = { parts... };
std::string_view views[sizeof...(parts)] = {parts...};
return concatStringsSep({}, views);
}
@ -194,9 +156,26 @@ std::string toLower(std::string s);
std::string shellEscape(const std::string_view s);
/* Exception handling in destructors: print an error message, then
ignore the exception. */
void ignoreException(Verbosity lvl = lvlError);
/**
* Exception handling in destructors: print an error message, then
* ignore the exception.
*
* If you're not in a destructor, you usually want to use `ignoreExceptionExceptInterrupt()`.
*
* This function might also be used in callbacks whose caller may not handle exceptions,
* but ideally we propagate the exception using an exception_ptr in such cases.
* See e.g. `PackBuilderContext`
*/
void ignoreExceptionInDestructor(Verbosity lvl = lvlError);
/**
* Not destructor-safe.
* Print an error message, then ignore the exception.
* If the exception is an `Interrupted` exception, rethrow it.
*
* This may be used in a few places where Interrupt can't happen, but that's ok.
*/
void ignoreExceptionExceptInterrupt(Verbosity lvl = lvlError);
@ -295,6 +274,17 @@ std::optional<typename T::value_type> pop(T & c)
}
/**
* Append items to a container. TODO: remove this once we can use
* C++23's `append_range()`.
*/
template<class C, typename T>
void append(C & c, std::initializer_list<T> l)
{
c.insert(c.end(), l.begin(), l.end());
}
template<typename T>
class Callback;
@ -359,7 +349,9 @@ std::string showBytes(uint64_t bytes);
*/
inline std::string operator + (const std::string & s1, std::string_view s2)
{
auto s = s1;
std::string s;
s.reserve(s1.size() + s2.size());
s.append(s1);
s.append(s2);
return s;
}
@ -372,10 +364,11 @@ inline std::string operator + (std::string && s, std::string_view s2)
inline std::string operator + (std::string_view s1, const char * s2)
{
auto s2Size = strlen(s2);
std::string s;
s.reserve(s1.size() + strlen(s2));
s.reserve(s1.size() + s2Size);
s.append(s1);
s.append(s2);
s.append(s2, s2Size);
return s;
}

View file

@ -0,0 +1,4 @@
widecharwidth - wcwidth implementation
Written in 2018 by ridiculous_fish
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

File diff suppressed because it is too large Load diff

View file

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

View file

@ -5,6 +5,7 @@
#include "windows-error.hh"
#include "file-path.hh"
#ifdef _WIN32
#include <fileapi.h>
#include <error.h>
#include <namedpipeapi.h>
@ -61,7 +62,7 @@ void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts)
}
std::string readLine(HANDLE handle)
std::string readLine(HANDLE handle, bool eofOk)
{
std::string s;
while (1) {
@ -71,8 +72,12 @@ std::string readLine(HANDLE handle)
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 (rd == 0) {
if (eofOk)
return s;
else
throw EndOfFile("unexpected EOF reading a line");
}
else {
if (ch == '\n') return s;
s += ch;
@ -148,3 +153,4 @@ Path windows::handleToPath(HANDLE handle) {
#endif
}
#endif

View file

@ -9,18 +9,6 @@
namespace nix {
std::string os_string_to_string(PathViewNG::string_view path)
{
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(std::filesystem::path::string_type { path });
}
std::filesystem::path::string_type string_to_os_string(std::string_view s)
{
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(std::string { s });
}
std::optional<std::filesystem::path> maybePath(PathView path)
{
if (path.length() >= 3 && (('A' <= path[0] && path[0] <= 'Z') || ('a' <= path[0] && path[0] <= 'z')) && path[1] == ':' && WindowsPathTrait<char>::isPathSep(path[2])) {

View file

@ -1,5 +1,6 @@
#include "file-system.hh"
#ifdef _WIN32
namespace nix {
Descriptor openDirectory(const std::filesystem::path & path)
@ -15,3 +16,4 @@ Descriptor openDirectory(const std::filesystem::path & path)
}
}
#endif

View file

@ -4,6 +4,7 @@ sources += files(
'file-path.cc',
'file-system.cc',
'muxable-pipe.cc',
'os-string.cc',
'processes.cc',
'users.cc',
'windows-async-pipe.cc',

View file

@ -1,9 +1,10 @@
#include <ioapiset.h>
#include "windows-error.hh"
#ifdef _WIN32
# include <ioapiset.h>
# include "windows-error.hh"
#include "logging.hh"
#include "util.hh"
#include "muxable-pipe.hh"
# include "logging.hh"
# include "util.hh"
# include "muxable-pipe.hh"
namespace nix {
@ -68,3 +69,4 @@ void MuxablePipePollState::iterate(
}
}
#endif

View file

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

View file

@ -1,6 +1,7 @@
#include "current-process.hh"
#include "environment-variables.hh"
#include "error.hh"
#include "executable-path.hh"
#include "file-descriptor.hh"
#include "file-path.hh"
#include "signals.hh"
@ -22,6 +23,8 @@
#include <sys/types.h>
#include <unistd.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -377,4 +380,13 @@ bool statusOk(int status)
{
return status == 0;
}
int execvpe(const wchar_t * file0, const wchar_t * const argv[], const wchar_t * const envp[])
{
auto file = ExecutablePath::load().findPath(file0);
return _wexecve(file.c_str(), argv, envp);
}
}
#endif

View file

@ -4,6 +4,7 @@
#include "file-system.hh"
#include "windows-error.hh"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -50,3 +51,4 @@ bool isRootUser() {
}
}
#endif

View file

@ -1,6 +1,8 @@
#include "windows-async-pipe.hh"
#include "windows-error.hh"
#ifdef _WIN32
namespace nix::windows {
void AsyncPipe::createAsyncPipe(HANDLE iocp)
@ -47,3 +49,5 @@ void AsyncPipe::close()
}
}
#endif

View file

@ -2,6 +2,7 @@
///@file
#include "file-descriptor.hh"
#ifdef _WIN32
namespace nix::windows {
@ -25,3 +26,4 @@ public:
};
}
#endif

View file

@ -1,5 +1,6 @@
#include "windows-error.hh"
#ifdef _WIN32
#include <error.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -29,3 +30,4 @@ std::string WinError::renderError(DWORD lastError)
}
}
#endif

View file

@ -1,6 +1,7 @@
#pragma once
///@file
#ifdef _WIN32
#include <errhandlingapi.h>
#include "error.hh"
@ -49,3 +50,4 @@ private:
};
}
#endif