1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-06 05:01:48 +02:00

Merge branch 'master' into overlayfs-store

This commit is contained in:
John Ericson 2024-02-29 09:54:21 -05:00
commit cb4f85f11c
253 changed files with 5623 additions and 2830 deletions

View file

@ -77,20 +77,20 @@ void SourceAccessor::dumpPath(
std::string name(i.first);
size_t pos = i.first.find(caseHackSuffix);
if (pos != std::string::npos) {
debug("removing case hack suffix from '%s'", path + i.first);
debug("removing case hack suffix from '%s'", path / i.first);
name.erase(pos);
}
if (!unhacked.emplace(name, i.first).second)
throw Error("file name collision in between '%s' and '%s'",
(path + unhacked[name]),
(path + i.first));
(path / unhacked[name]),
(path / i.first));
} else
unhacked.emplace(i.first, i.first);
for (auto & i : unhacked)
if (filter((path + i.first).abs())) {
if (filter((path / i.first).abs())) {
sink << "entry" << "(" << "name" << i.first << "node";
dump(path + i.second);
dump(path / i.second);
sink << ")";
}
}
@ -110,8 +110,8 @@ void SourceAccessor::dumpPath(
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
{
PosixSourceAccessor accessor;
accessor.dumpPath(CanonPath::fromCwd(path), sink, filter);
auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(path);
accessor.dumpPath(canonPath, sink, filter);
return accessor.mtime;
}

View file

@ -544,73 +544,6 @@ nlohmann::json Args::toJSON()
return res;
}
static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & format : hashFormats) {
if (hasPrefix(format, prefix)) {
completions.add(format);
}
}
}
Args::Flag Args::Flag::mkHashFormatFlagWithDefault(std::string &&longName, HashFormat * hf) {
assert(*hf == nix::HashFormat::SRI);
return Flag{
.longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.",
.labels = {"hash-format"},
.handler = {[hf](std::string s) {
*hf = parseHashFormat(s);
}},
.completer = hashFormatCompleter,
};
}
Args::Flag Args::Flag::mkHashFormatOptFlag(std::string && longName, std::optional<HashFormat> * ohf) {
return Flag{
.longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`).",
.labels = {"hash-format"},
.handler = {[ohf](std::string s) {
*ohf = std::optional<HashFormat>{parseHashFormat(s)};
}},
.completer = hashFormatCompleter,
};
}
static void hashAlgoCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & algo : hashAlgorithms)
if (hasPrefix(algo, prefix))
completions.add(algo);
}
Args::Flag Args::Flag::mkHashAlgoFlag(std::string && longName, HashAlgorithm * ha)
{
return Flag{
.longName = std::move(longName),
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`).",
.labels = {"hash-algo"},
.handler = {[ha](std::string s) {
*ha = parseHashAlgo(s);
}},
.completer = hashAlgoCompleter,
};
}
Args::Flag Args::Flag::mkHashAlgoOptFlag(std::string && longName, std::optional<HashAlgorithm> * oha)
{
return Flag{
.longName = std::move(longName),
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.",
.labels = {"hash-algo"},
.handler = {[oha](std::string s) {
*oha = std::optional<HashAlgorithm>{parseHashAlgo(s)};
}},
.completer = hashAlgoCompleter,
};
}
static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs)
{
completions.setType(Completions::Type::Filenames);

View file

@ -155,6 +155,8 @@ protected:
*/
using CompleterClosure = std::function<CompleterFun>;
public:
/**
* Description of flags / options
*
@ -175,19 +177,10 @@ protected:
CompleterClosure completer;
std::optional<ExperimentalFeature> experimentalFeature;
static Flag mkHashAlgoFlag(std::string && longName, HashAlgorithm * ha);
static Flag mkHashAlgoFlag(HashAlgorithm * ha) {
return mkHashAlgoFlag("hash-algo", ha);
}
static Flag mkHashAlgoOptFlag(std::string && longName, std::optional<HashAlgorithm> * oha);
static Flag mkHashAlgoOptFlag(std::optional<HashAlgorithm> * oha) {
return mkHashAlgoOptFlag("hash-algo", oha);
}
static Flag mkHashFormatFlagWithDefault(std::string && longName, HashFormat * hf);
static Flag mkHashFormatOptFlag(std::string && longName, std::optional<HashFormat> * ohf);
};
protected:
/**
* Index of all registered "long" flag descriptions (flags like
* `--long`).
@ -206,6 +199,8 @@ protected:
*/
virtual bool processFlag(Strings::iterator & pos, Strings::iterator end);
public:
/**
* Description of positional arguments
*
@ -220,6 +215,8 @@ protected:
CompleterClosure completer;
};
protected:
/**
* Queue of expected positional argument forms.
*

View file

@ -1,16 +1,25 @@
#include "canon-path.hh"
#include "file-system.hh"
#include "util.hh"
#include "file-path-impl.hh"
namespace nix {
CanonPath CanonPath::root = CanonPath("/");
static std::string absPathPure(std::string_view path)
{
return canonPathInner<UnixPathTrait>(path, [](auto &, auto &){});
}
CanonPath::CanonPath(std::string_view raw)
: path(absPath(raw, "/"))
: path(absPathPure(concatStrings("/", raw)))
{ }
CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
: path(absPath(raw, root.abs()))
: path(absPathPure(
raw.size() > 0 && raw[0] == '/'
? raw
: concatStrings(root.abs(), "/", raw)))
{ }
CanonPath::CanonPath(const std::vector<std::string> & elems)
@ -20,11 +29,6 @@ CanonPath::CanonPath(const std::vector<std::string> & elems)
push(s);
}
CanonPath CanonPath::fromCwd(std::string_view path)
{
return CanonPath(unchecked_t(), absPath(path));
}
std::optional<CanonPath> CanonPath::parent() const
{
if (isRoot()) return std::nullopt;
@ -63,7 +67,7 @@ void CanonPath::extend(const CanonPath & x)
path += x.abs();
}
CanonPath CanonPath::operator + (const CanonPath & x) const
CanonPath CanonPath::operator / (const CanonPath & x) const
{
auto res = *this;
res.extend(x);
@ -78,7 +82,7 @@ void CanonPath::push(std::string_view c)
path += c;
}
CanonPath CanonPath::operator + (std::string_view c) const
CanonPath CanonPath::operator / (std::string_view c) const
{
auto res = *this;
res.push(c);

View file

@ -21,9 +21,21 @@ namespace nix {
*
* - There are no components equal to '.' or '..'.
*
* Note that the path does not need to correspond to an actually
* existing path, and there is no guarantee that symlinks are
* resolved.
* `CanonPath` are "virtual" Nix paths for abstract file system objects;
* they are always Unix-style paths, regardless of what OS Nix is
* running on. The `/` root doesn't denote the ambient host file system
* root, but some virtual FS root.
*
* @note It might be useful to compare `openat(some_fd, "foo/bar")` on
* Unix. `"foo/bar"` is a relative path because an absolute path would
* "override" the `some_fd` directory file descriptor and escape to the
* "system root". Conversely, Nix's abstract file operations *never* escape the
* designated virtual file system (i.e. `SourceAccessor` or
* `ParseSink`), so `CanonPath` does not need an absolute/relative
* distinction.
*
* @note The path does not need to correspond to an actually existing
* path, and the path may or may not have unresolved symlinks.
*/
class CanonPath
{
@ -52,8 +64,6 @@ public:
*/
CanonPath(const std::vector<std::string> & elems);
static CanonPath fromCwd(std::string_view path = ".");
static CanonPath root;
/**
@ -190,14 +200,14 @@ public:
/**
* Concatenate two paths.
*/
CanonPath operator + (const CanonPath & x) const;
CanonPath operator / (const CanonPath & x) const;
/**
* Add a path component to this one. It must not contain any slashes.
*/
void push(std::string_view c);
CanonPath operator + (std::string_view c) const;
CanonPath operator / (std::string_view c) const;
/**
* Check whether access to this path is allowed, which is the case

View file

@ -4,7 +4,7 @@
*
* Template implementations (as opposed to mere declarations).
*
* This file is an exmample of the "impl.hh" pattern. See the
* This file is an example of the "impl.hh" pattern. See the
* contributing guide.
*
* One only needs to include this when one is declaring a

View file

@ -84,7 +84,9 @@ void AbstractConfig::reapplyUnknownSettings()
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (const auto & opt : _settings)
if (!opt.second.isAlias && (!overriddenOnly || opt.second.setting->overridden))
if (!opt.second.isAlias
&& (!overriddenOnly || opt.second.setting->overridden)
&& experimentalFeatureSettings.isEnabled(opt.second.setting->experimentalFeature))
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
}

View file

@ -63,7 +63,7 @@ void setStackSize(rlim_t stackSize)
if (setrlimit(RLIMIT_STACK, &limit) != 0) {
logger->log(
lvlError,
hintfmt(
HintFmt(
"Failed to increase stack size from %1% to %2% (maximum allowed stack size: %3%): %4%",
savedStackSize,
stackSize,

View file

@ -11,9 +11,9 @@
namespace nix {
void BaseError::addTrace(std::shared_ptr<Pos> && e, hintformat hint, bool frame)
void BaseError::addTrace(std::shared_ptr<Pos> && e, HintFmt hint)
{
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint });
}
void throwExceptionSelfCheck(){
@ -37,7 +37,7 @@ const std::string & BaseError::calcWhat() const
std::optional<std::string> ErrorInfo::programName = std::nullopt;
std::ostream & operator <<(std::ostream & os, const hintformat & hf)
std::ostream & operator <<(std::ostream & os, const HintFmt & hf)
{
return os << hf.str();
}
@ -61,8 +61,7 @@ inline bool operator<(const Trace& lhs, const Trace& rhs)
// This formats a freshly formatted hint string and then throws it away, which
// shouldn't be much of a problem because it only runs when pos is equal, and this function is
// used for trace printing, which is infrequent.
return std::forward_as_tuple(lhs.hint.str(), lhs.frame)
< std::forward_as_tuple(rhs.hint.str(), rhs.frame);
return lhs.hint.str() < rhs.hint.str();
}
inline bool operator> (const Trace& lhs, const Trace& rhs) { return rhs < lhs; }
inline bool operator<=(const Trace& lhs, const Trace& rhs) { return !(lhs > rhs); }
@ -335,7 +334,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
* try {
* e->eval(*this, env, v);
* if (v.type() != nAttrs)
* throwTypeError("expected a set but found %1%", v);
* error<TypeError>("expected a set but found %1%", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
@ -349,7 +348,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
* e->eval(*this, env, v);
* try {
* if (v.type() != nAttrs)
* throwTypeError("expected a set but found %1%", v);
* error<TypeError>("expected a set but found %1%", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
@ -373,7 +372,6 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
// prepended to each element of the trace
auto ellipsisIndent = " ";
bool frameOnly = false;
if (!einfo.traces.empty()) {
// Stack traces seen since we last printed a chunk of `duplicate frames
// omitted`.
@ -384,7 +382,6 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
for (const auto & trace : einfo.traces) {
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
if (!showTrace && count > 3) {
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
@ -400,7 +397,6 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
printSkippedTracesMaybe(oss, ellipsisIndent, count, skippedTraces, tracesSeen);
count++;
frameOnly = trace.frame;
printTrace(oss, ellipsisIndent, count, trace);
}
@ -411,7 +407,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
oss << einfo.msg << "\n";
printPosMaybe(oss, "", einfo.errPos);
printPosMaybe(oss, "", einfo.pos);
auto suggestions = einfo.suggestions.trim();
if (!suggestions.suggestions.empty()) {

View file

@ -31,15 +31,6 @@
#include <sys/stat.h>
#include <fcntl.h>
/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
* its (virtual) destructor and what() in c++11 mode, in violation of spec
*/
#ifdef __GNUC__
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
#define EXCEPTION_NEEDS_THROW_SPEC
#endif
#endif
namespace nix {
@ -72,8 +63,7 @@ void printCodeLines(std::ostream & out,
struct Trace {
std::shared_ptr<Pos> pos;
hintformat hint;
bool frame;
HintFmt hint;
};
inline bool operator<(const Trace& lhs, const Trace& rhs);
@ -83,10 +73,15 @@ inline bool operator>=(const Trace& lhs, const Trace& rhs);
struct ErrorInfo {
Verbosity level;
hintformat msg;
std::shared_ptr<Pos> errPos;
HintFmt msg;
std::shared_ptr<Pos> pos;
std::list<Trace> traces;
/**
* Exit status.
*/
unsigned int status = 1;
Suggestions suggestions;
static std::optional<std::string> programName;
@ -103,31 +98,34 @@ class BaseError : public std::exception
protected:
mutable ErrorInfo err;
/**
* Cached formatted contents of `err.msg`.
*/
mutable std::optional<std::string> what_;
/**
* Format `err.msg` and set `what_` to the resulting value.
*/
const std::string & calcWhat() const;
public:
unsigned int status = 1; // exit status
BaseError(const BaseError &) = default;
template<typename... Args>
BaseError(unsigned int status, const Args & ... args)
: err { .level = lvlError, .msg = hintfmt(args...) }
, status(status)
: err { .level = lvlError, .msg = HintFmt(args...), .status = status }
{ }
template<typename... Args>
explicit BaseError(const std::string & fs, const Args & ... args)
: err { .level = lvlError, .msg = hintfmt(fs, args...) }
: err { .level = lvlError, .msg = HintFmt(fs, args...) }
{ }
template<typename... Args>
BaseError(const Suggestions & sug, const Args & ... args)
: err { .level = lvlError, .msg = hintfmt(args...), .suggestions = sug }
: err { .level = lvlError, .msg = HintFmt(args...), .suggestions = sug }
{ }
BaseError(hintformat hint)
BaseError(HintFmt hint)
: err { .level = lvlError, .msg = hint }
{ }
@ -139,16 +137,19 @@ public:
: err(e)
{ }
#ifdef EXCEPTION_NEEDS_THROW_SPEC
~BaseError() throw () { };
const char * what() const throw () { return calcWhat().c_str(); }
#else
const char * what() const noexcept override { return calcWhat().c_str(); }
#endif
const std::string & msg() const { return calcWhat(); }
const ErrorInfo & info() const { calcWhat(); return err; }
void withExitStatus(unsigned int status)
{
err.status = status;
}
void atPos(std::shared_ptr<Pos> pos) {
err.pos = pos;
}
void pushTrace(Trace trace)
{
err.traces.push_front(trace);
@ -157,10 +158,10 @@ public:
template<typename... Args>
void addTrace(std::shared_ptr<Pos> && e, std::string_view fs, const Args & ... args)
{
addTrace(std::move(e), hintfmt(std::string(fs), args...));
addTrace(std::move(e), HintFmt(std::string(fs), args...));
}
void addTrace(std::shared_ptr<Pos> && e, hintformat hint, bool frame = false);
void addTrace(std::shared_ptr<Pos> && e, HintFmt hint);
bool hasTrace() const { return !err.traces.empty(); }
@ -212,8 +213,8 @@ public:
SysError(int errNo, const Args & ... args)
: SystemError(""), errNo(errNo)
{
auto hf = hintfmt(args...);
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
auto hf = HintFmt(args...);
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), strerror(errNo));
}
/**

7
src/libutil/exit.cc Normal file
View file

@ -0,0 +1,7 @@
#include "exit.hh"
namespace nix {
Exit::~Exit() {}
}

19
src/libutil/exit.hh Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <exception>
namespace nix {
/**
* Exit the program with a given exit code.
*/
class Exit : public std::exception
{
public:
int status;
Exit() : status(0) { }
explicit Exit(int status) : status(status) { }
virtual ~Exit();
};
}

View file

@ -1,19 +1,83 @@
#include "file-content-address.hh"
#include "archive.hh"
#include "git.hh"
namespace nix {
static std::optional<FileSerialisationMethod> parseFileSerialisationMethodOpt(std::string_view input)
{
if (input == "flat") {
return FileSerialisationMethod::Flat;
} else if (input == "nar") {
return FileSerialisationMethod::Recursive;
} else {
return std::nullopt;
}
}
FileSerialisationMethod parseFileSerialisationMethod(std::string_view input)
{
auto ret = parseFileSerialisationMethodOpt(input);
if (ret)
return *ret;
else
throw UsageError("Unknown file serialiation method '%s', expect `flat` or `nar`");
}
FileIngestionMethod parseFileIngestionMethod(std::string_view input)
{
if (input == "git") {
return FileIngestionMethod::Git;
} else {
auto ret = parseFileSerialisationMethodOpt(input);
if (ret)
return static_cast<FileIngestionMethod>(*ret);
else
throw UsageError("Unknown file ingestion method '%s', expect `flat`, `nar`, or `git`");
}
}
std::string_view renderFileSerialisationMethod(FileSerialisationMethod method)
{
switch (method) {
case FileSerialisationMethod::Flat:
return "flat";
case FileSerialisationMethod::Recursive:
return "nar";
default:
assert(false);
}
}
std::string_view renderFileIngestionMethod(FileIngestionMethod method)
{
switch (method) {
case FileIngestionMethod::Flat:
case FileIngestionMethod::Recursive:
return renderFileSerialisationMethod(
static_cast<FileSerialisationMethod>(method));
case FileIngestionMethod::Git:
return "git";
default:
abort();
}
}
void dumpPath(
SourceAccessor & accessor, const CanonPath & path,
Sink & sink,
FileIngestionMethod method,
FileSerialisationMethod method,
PathFilter & filter)
{
switch (method) {
case FileIngestionMethod::Flat:
case FileSerialisationMethod::Flat:
accessor.readFile(path, sink);
break;
case FileIngestionMethod::Recursive:
case FileSerialisationMethod::Recursive:
accessor.dumpPath(path, sink, filter);
break;
}
@ -23,13 +87,13 @@ void dumpPath(
void restorePath(
const Path & path,
Source & source,
FileIngestionMethod method)
FileSerialisationMethod method)
{
switch (method) {
case FileIngestionMethod::Flat:
case FileSerialisationMethod::Flat:
writeFile(path, source);
break;
case FileIngestionMethod::Recursive:
case FileSerialisationMethod::Recursive:
restorePath(path, source);
break;
}
@ -38,12 +102,28 @@ void restorePath(
HashResult hashPath(
SourceAccessor & accessor, const CanonPath & path,
FileIngestionMethod method, HashAlgorithm ht,
FileSerialisationMethod method, HashAlgorithm ha,
PathFilter & filter)
{
HashSink sink { ht };
HashSink sink { ha };
dumpPath(accessor, path, sink, method, filter);
return sink.finish();
}
Hash hashPath(
SourceAccessor & accessor, const CanonPath & path,
FileIngestionMethod method, HashAlgorithm ht,
PathFilter & filter)
{
switch (method) {
case FileIngestionMethod::Flat:
case FileIngestionMethod::Recursive:
return hashPath(accessor, path, (FileSerialisationMethod) method, ht, filter).first;
case FileIngestionMethod::Git:
return git::dumpHash(ht, accessor, path, filter).hash;
}
}
}

View file

@ -8,49 +8,138 @@
namespace nix {
/**
* An enumeration of the main ways we can serialize file system
* An enumeration of the ways we can serialize file system
* objects.
*/
enum struct FileIngestionMethod : uint8_t {
enum struct FileSerialisationMethod : uint8_t {
/**
* Flat-file hashing. Directly ingest the contents of a single file
* Flat-file. The contents of a single file exactly.
*/
Flat = 0,
Flat,
/**
* Recursive (or NAR) hashing. Serializes the file-system object in
* Nix Archive format and ingest that.
* Nix Archive. Serializes the file-system object in
* Nix Archive format.
*/
Recursive = 1,
Recursive,
};
/**
* Parse a `FileSerialisationMethod` by name. Choice of:
*
* - `flat`: `FileSerialisationMethod::Flat`
* - `nar`: `FileSerialisationMethod::Recursive`
*
* Opposite of `renderFileSerialisationMethod`.
*/
FileSerialisationMethod parseFileSerialisationMethod(std::string_view input);
/**
* Render a `FileSerialisationMethod` by name.
*
* Opposite of `parseFileSerialisationMethod`.
*/
std::string_view renderFileSerialisationMethod(FileSerialisationMethod method);
/**
* Dump a serialization of the given file system object.
*/
void dumpPath(
SourceAccessor & accessor, const CanonPath & path,
Sink & sink,
FileIngestionMethod method,
FileSerialisationMethod method,
PathFilter & filter = defaultPathFilter);
/**
* Restore a serialization of the given file system object.
* Restore a serialisation of the given file system object.
*
* @TODO use an arbitrary `FileSystemObjectSink`.
*/
void restorePath(
const Path & path,
Source & source,
FileIngestionMethod method);
FileSerialisationMethod method);
/**
* Compute the hash of the given file system object according to the
* given method.
*
* The hash is defined as (essentially) hashString(ht, dumpPath(path)).
* the hash is defined as (in pseudocode):
*
* ```
* hashString(ha, dumpPath(...))
* ```
*/
HashResult hashPath(
SourceAccessor & accessor, const CanonPath & path,
FileIngestionMethod method, HashAlgorithm ht,
FileSerialisationMethod method, HashAlgorithm ha,
PathFilter & filter = defaultPathFilter);
/**
* An enumeration of the ways we can ingest file system
* objects, producing a hash or digest.
*/
enum struct FileIngestionMethod : uint8_t {
/**
* Hash `FileSerialisationMethod::Flat` serialisation.
*/
Flat,
/**
* Hash `FileSerialisationMethod::Git` serialisation.
*/
Recursive,
/**
* Git hashing. In particular files are hashed as git "blobs", and
* directories are hashed as git "trees".
*
* Unlike `Flat` and `Recursive`, this is not a hash of a single
* serialisation but a [Merkle
* DAG](https://en.wikipedia.org/wiki/Merkle_tree) of multiple
* rounds of serialisation and hashing.
*
* @note Git's data model is slightly different, in that a plain
* file doesn't have an executable bit, directory entries do
* instead. We decide treat a bare file as non-executable by fiat,
* as we do with `FileIngestionMethod::Flat` which also lacks this
* information. Thus, Git can encode some but all of Nix's "File
* System Objects", and this sort of hashing is likewise partial.
*/
Git,
};
/**
* Parse a `FileIngestionMethod` by name. Choice of:
*
* - `flat`: `FileIngestionMethod::Flat`
* - `nar`: `FileIngestionMethod::Recursive`
* - `git`: `FileIngestionMethod::Git`
*
* Opposite of `renderFileIngestionMethod`.
*/
FileIngestionMethod parseFileIngestionMethod(std::string_view input);
/**
* Render a `FileIngestionMethod` by name.
*
* Opposite of `parseFileIngestionMethod`.
*/
std::string_view renderFileIngestionMethod(FileIngestionMethod method);
/**
* Compute the hash of the given file system object according to the
* given method.
*
* Unlike the other `hashPath`, this works on an arbitrary
* `FileIngestionMethod` instead of `FileSerialisationMethod`, but
* doesn't return the size as this is this is not a both simple and
* useful defined for a merkle format.
*/
Hash hashPath(
SourceAccessor & accessor, const CanonPath & path,
FileIngestionMethod method, HashAlgorithm ha,
PathFilter & filter = defaultPathFilter);
}

View file

@ -0,0 +1,176 @@
#pragma once
/**
* @file
*
* Pure (no IO) infrastructure just for defining other path types;
* should not be used directly outside of utilities.
*/
#include <string>
#include <string_view>
namespace nix {
/**
* Unix-style path primives.
*
* Nix'result own "logical" paths are always Unix-style. So this is always
* used for that, and additionally used for native paths on Unix.
*/
struct UnixPathTrait
{
using CharT = char;
using String = std::string;
using StringView = std::string_view;
constexpr static char preferredSep = '/';
static inline bool isPathSep(char c)
{
return c == '/';
}
static inline size_t findPathSep(StringView path, size_t from = 0)
{
return path.find('/', from);
}
static inline size_t rfindPathSep(StringView path, size_t from = StringView::npos)
{
return path.rfind('/', from);
}
};
/**
* Windows-style path primitives.
*
* The character type is a parameter because while windows paths rightly
* work over UTF-16 (*) using `wchar_t`, at the current time we are
* often manipulating them converted to UTF-8 (*) using `char`.
*
* (Actually neither are guaranteed to be valid unicode; both are
* arbitrary non-0 8- or 16-bit bytes. But for charcters with specifical
* meaning like '/', '\\', ':', etc., we refer to an encoding scheme,
* and also for sake of UIs that display paths a text.)
*/
template<class CharT0>
struct WindowsPathTrait
{
using CharT = CharT0;
using String = std::basic_string<CharT>;
using StringView = std::basic_string_view<CharT>;
constexpr static CharT preferredSep = '\\';
static inline bool isPathSep(CharT c)
{
return c == '/' || c == preferredSep;
}
static size_t findPathSep(StringView path, size_t from = 0)
{
size_t p1 = path.find('/', from);
size_t p2 = path.find(preferredSep, from);
return p1 == String::npos ? p2 :
p2 == String::npos ? p1 :
std::min(p1, p2);
}
static size_t rfindPathSep(StringView path, size_t from = String::npos)
{
size_t p1 = path.rfind('/', from);
size_t p2 = path.rfind(preferredSep, from);
return p1 == String::npos ? p2 :
p2 == String::npos ? p1 :
std::max(p1, p2);
}
};
/**
* @todo Revisit choice of `char` or `wchar_t` for `WindowsPathTrait`
* argument.
*/
using NativePathTrait =
#ifdef _WIN32
WindowsPathTrait<char>
#else
UnixPathTrait
#endif
;
/**
* Core pure path canonicalization algorithm.
*
* @param hookComponent
* A callback which is passed two arguments,
* references to
*
* 1. the result so far
*
* 2. the remaining path to resolve
*
* This is a chance to modify those two paths in arbitrary way, e.g. if
* "result" points to a symlink.
*/
template<class PathDict>
typename PathDict::String canonPathInner(
typename PathDict::StringView remaining,
auto && hookComponent)
{
assert(remaining != "");
typename PathDict::String result;
result.reserve(256);
while (true) {
/* Skip slashes. */
while (!remaining.empty() && PathDict::isPathSep(remaining[0]))
remaining.remove_prefix(1);
if (remaining.empty()) break;
auto nextComp = ({
auto nextPathSep = PathDict::findPathSep(remaining);
nextPathSep == remaining.npos ? remaining : remaining.substr(0, nextPathSep);
});
/* Ignore `.'. */
if (nextComp == ".")
remaining.remove_prefix(1);
/* If `..', delete the last component. */
else if (nextComp == "..")
{
if (!result.empty()) result.erase(PathDict::rfindPathSep(result));
remaining.remove_prefix(2);
}
/* Normal component; copy it. */
else {
result += PathDict::preferredSep;
if (const auto slash = PathDict::findPathSep(remaining); slash == result.npos) {
result += remaining;
remaining = {};
} else {
result += remaining.substr(0, slash);
remaining = remaining.substr(slash);
}
hookComponent(result, remaining);
}
}
if (result.empty())
result = typename PathDict::String { PathDict::preferredSep };
return result;
}
}

View file

@ -1,5 +1,6 @@
#include "environment-variables.hh"
#include "file-system.hh"
#include "file-path-impl.hh"
#include "signals.hh"
#include "finally.hh"
#include "serialise.hh"
@ -21,11 +22,22 @@ 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)
{
return fs::path { path }.is_absolute();
}
Path absPath(PathView path, std::optional<PathView> dir, bool resolveSymlinks)
{
std::string scratch;
if (path[0] != '/') {
if (!isAbsolute(path)) {
// In this case we need to call `canonPath` on a newly-created
// string. We set `scratch` to that string first, and then set
// `path` to `scratch`. This ensures the newly-created string
@ -58,69 +70,46 @@ Path canonPath(PathView path, bool resolveSymlinks)
{
assert(path != "");
std::string s;
s.reserve(256);
if (path[0] != '/')
if (!isAbsolute(path))
throw Error("not an absolute path: '%1%'", path);
// For Windows
auto rootName = fs::path { path }.root_name();
/* This just exists because we cannot set the target of `remaining`
(the callback parameter) directly to a newly-constructed string,
since it is `std::string_view`. */
std::string temp;
/* Count the number of times we follow a symlink and stop at some
arbitrary (but high) limit to prevent infinite loops. */
unsigned int followCount = 0, maxFollow = 1024;
while (1) {
/* Skip slashes. */
while (!path.empty() && path[0] == '/') path.remove_prefix(1);
if (path.empty()) break;
/* Ignore `.'. */
if (path == "." || path.substr(0, 2) == "./")
path.remove_prefix(1);
/* If `..', delete the last component. */
else if (path == ".." || path.substr(0, 3) == "../")
{
if (!s.empty()) s.erase(s.rfind('/'));
path.remove_prefix(2);
}
/* Normal component; copy it. */
else {
s += '/';
if (const auto slash = path.find('/'); slash == path.npos) {
s += path;
path = {};
} else {
s += path.substr(0, slash);
path = path.substr(slash);
}
/* If s points to a symlink, resolve it and continue from there */
if (resolveSymlinks && isLink(s)) {
auto ret = canonPathInner<NativePathTrait>(
path,
[&followCount, &temp, maxFollow, resolveSymlinks]
(std::string & result, std::string_view & remaining) {
if (resolveSymlinks && isLink(result)) {
if (++followCount >= maxFollow)
throw Error("infinite symlink recursion in path '%1%'", path);
temp = concatStrings(readLink(s), path);
path = temp;
if (!temp.empty() && temp[0] == '/') {
s.clear(); /* restart for symlinks pointing to absolute path */
throw Error("infinite symlink recursion in path '%0%'", remaining);
remaining = (temp = concatStrings(readLink(result), remaining));
if (isAbsolute(remaining)) {
/* restart for symlinks pointing to absolute path */
result.clear();
} else {
s = dirOf(s);
if (s == "/") { // we dont want trailing slashes here, which dirOf only produces if s = /
s.clear();
result = dirOf(result);
if (result == "/") {
/* we dont want trailing slashes here, which `dirOf`
only produces if `result = /` */
result.clear();
}
}
}
}
}
});
if (s.empty()) {
s = "/";
}
return s;
if (!rootName.empty())
ret = rootName.string() + std::move(ret);
return ret;
}

View file

@ -8,37 +8,64 @@
namespace nix {
namespace {
/**
* Inherit some names from other namespaces for convenience.
*/
using boost::format;
/**
* A variadic template that does nothing. Useful to call a function
* for all variadic arguments but ignoring the result.
*/
struct nop { template<typename... T> nop(T...) {} };
/**
* A helper for formatting strings. fmt(format, a_0, ..., a_n) is
* equivalent to boost::format(format) % a_0 % ... %
* ... a_n. However, fmt(s) is equivalent to s (so no %-expansion
* takes place).
* A helper for writing `boost::format` expressions.
*
* These are equivalent:
*
* ```
* formatHelper(formatter, a_0, ..., a_n)
* formatter % a_0 % ... % a_n
* ```
*
* With a single argument, `formatHelper(s)` is a no-op.
*/
template<class F>
inline void formatHelper(F & f)
{
}
{ }
template<class F, typename T, typename... Args>
inline void formatHelper(F & f, const T & x, const Args & ... args)
{
// Interpolate one argument and then recurse.
formatHelper(f % x, args...);
}
/**
* Set the correct exceptions for `fmt`.
*/
void setExceptions(boost::format & fmt)
{
fmt.exceptions(
boost::io::all_error_bits ^
boost::io::too_many_args_bit ^
boost::io::too_few_args_bit);
}
}
/**
* A helper for writing a `boost::format` expression to a string.
*
* These are (roughly) equivalent:
*
* ```
* fmt(formatString, a_0, ..., a_n)
* (boost::format(formatString) % a_0 % ... % a_n).str()
* ```
*
* However, when called with a single argument, the string is returned
* unchanged.
*
* If you write code like this:
*
* ```
* std::cout << boost::format(stringFromUserInput) << std::endl;
* ```
*
* And `stringFromUserInput` contains formatting placeholders like `%s`, then
* the code will crash at runtime. `fmt` helps you avoid this pitfall.
*/
inline std::string fmt(const std::string & s)
{
return s;
@ -58,68 +85,96 @@ template<typename... Args>
inline std::string fmt(const std::string & fs, const Args & ... args)
{
boost::format f(fs);
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
setExceptions(f);
formatHelper(f, args...);
return f.str();
}
// -----------------------------------------------------------------------------
// format function for hints in errors. same as fmt, except templated values
// are always in yellow.
/**
* Values wrapped in this struct are printed in magenta.
*
* By default, arguments to `HintFmt` are printed in magenta. To avoid this,
* either wrap the argument in `Uncolored` or add a specialization of
* `HintFmt::operator%`.
*/
template <class T>
struct yellowtxt
struct Magenta
{
yellowtxt(const T &s) : value(s) {}
Magenta(const T &s) : value(s) {}
const T & value;
};
template <class T>
std::ostream & operator<<(std::ostream & out, const yellowtxt<T> & y)
std::ostream & operator<<(std::ostream & out, const Magenta<T> & y)
{
return out << ANSI_WARNING << y.value << ANSI_NORMAL;
}
/**
* Values wrapped in this class are printed without coloring.
*
* By default, arguments to `HintFmt` are printed in magenta (see `Magenta`).
*/
template <class T>
struct normaltxt
struct Uncolored
{
normaltxt(const T & s) : value(s) {}
Uncolored(const T & s) : value(s) {}
const T & value;
};
template <class T>
std::ostream & operator<<(std::ostream & out, const normaltxt<T> & y)
std::ostream & operator<<(std::ostream & out, const Uncolored<T> & y)
{
return out << ANSI_NORMAL << y.value;
}
class hintformat
/**
* A wrapper around `boost::format` which colors interpolated arguments in
* magenta by default.
*/
class HintFmt
{
public:
hintformat(const std::string & format) : fmt(format)
{
fmt.exceptions(boost::io::all_error_bits ^
boost::io::too_many_args_bit ^
boost::io::too_few_args_bit);
}
private:
boost::format fmt;
hintformat(const hintformat & hf)
public:
/**
* Format the given string literally, without interpolating format
* placeholders.
*/
HintFmt(const std::string & literal)
: HintFmt("%s", Uncolored(literal))
{ }
/**
* Interpolate the given arguments into the format string.
*/
template<typename... Args>
HintFmt(const std::string & format, const Args & ... args)
: HintFmt(boost::format(format), args...)
{ }
HintFmt(const HintFmt & hf)
: fmt(hf.fmt)
{ }
hintformat(format && fmt)
template<typename... Args>
HintFmt(boost::format && fmt, const Args & ... args)
: fmt(std::move(fmt))
{ }
{
setExceptions(fmt);
formatHelper(*this, args...);
}
template<class T>
hintformat & operator%(const T & value)
HintFmt & operator%(const T & value)
{
fmt % yellowtxt(value);
fmt % Magenta(value);
return *this;
}
template<class T>
hintformat & operator%(const normaltxt<T> & value)
HintFmt & operator%(const Uncolored<T> & value)
{
fmt % value.value;
return *this;
@ -129,25 +184,8 @@ public:
{
return fmt.str();
}
private:
format fmt;
};
std::ostream & operator<<(std::ostream & os, const hintformat & hf);
template<typename... Args>
inline hintformat hintfmt(const std::string & fs, const Args & ... args)
{
hintformat f(fs);
formatHelper(f, args...);
return f;
}
inline hintformat hintfmt(const std::string & plain_string)
{
// we won't be receiving any args in this case, so just print the original string
return hintfmt("%s", normaltxt(plain_string));
}
std::ostream & operator<<(std::ostream & os, const HintFmt & hf);
}

View file

@ -15,6 +15,7 @@ void copyRecursive(
case SourceAccessor::tSymlink:
{
sink.createSymlink(to, accessor.readLink(from));
break;
}
case SourceAccessor::tRegular:
@ -34,10 +35,11 @@ void copyRecursive(
sink.createDirectory(to);
for (auto & [name, _] : accessor.readDirectory(from)) {
copyRecursive(
accessor, from + name,
accessor, from / name,
sink, to + "/" + name);
break;
}
break;
}
case SourceAccessor::tMisc:

View file

@ -26,6 +26,8 @@ struct CreateRegularFileSink : Sink
struct FileSystemObjectSink
{
virtual ~FileSystemObjectSink() = default;
virtual void createDirectory(const Path & path) = 0;
/**

View file

@ -259,7 +259,7 @@ Mode dump(
{
Tree entries;
for (auto & [name, _] : accessor.readDirectory(path)) {
auto child = path + name;
auto child = path / name;
if (!filter(child.abs())) continue;
auto entry = hook(child);

View file

@ -274,7 +274,7 @@ Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashAlgorithm> ha
{
if (hashStr.empty()) {
if (!ha)
throw BadHash("empty hash requires explicit hash type");
throw BadHash("empty hash requires explicit hash algorithm");
Hash h(*ha);
warn("found empty hash, assuming '%s'", h.to_string(HashFormat::SRI, true));
return h;

View file

@ -58,7 +58,7 @@ struct Hash
* Parse the hash from a string representation in the format
* "[<type>:]<base16|base32|base64>" or "<type>-<base64>" (a
* Subresource Integrity hash expression). If the 'type' argument
* is not present, then the hash type must be specified in the
* is not present, then the hash algorithm must be specified in the
* string.
*/
static Hash parseAny(std::string_view s, std::optional<HashAlgorithm> optAlgo);
@ -200,7 +200,7 @@ std::optional<HashFormat> parseHashFormatOpt(std::string_view hashFormatName);
std::string_view printHashFormat(HashFormat hashFormat);
/**
* Parse a string representing a hash type.
* Parse a string representing a hash algorithm.
*/
HashAlgorithm parseHashAlgo(std::string_view s);

View file

@ -199,7 +199,7 @@ struct JSONLogger : Logger {
json["level"] = ei.level;
json["msg"] = oss.str();
json["raw_msg"] = ei.msg.str();
to_json(json, ei.errPos);
to_json(json, ei.pos);
if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
nlohmann::json traces = nlohmann::json::array();

View file

@ -120,6 +120,17 @@ public:
{ }
};
/**
* A variadic template that does nothing.
*
* Useful to call a function with each argument in a parameter pack.
*/
struct nop
{
template<typename... T> nop(T...)
{ }
};
ActivityId getCurActivity();
void setCurActivity(const ActivityId activityId);

View file

@ -6,6 +6,38 @@
namespace nix {
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && root)
: root(std::move(root))
{
assert(root.empty() || root.is_absolute());
displayPrefix = root;
}
PosixSourceAccessor::PosixSourceAccessor()
: PosixSourceAccessor(std::filesystem::path {})
{ }
std::pair<PosixSourceAccessor, CanonPath> PosixSourceAccessor::createAtRoot(const std::filesystem::path & path)
{
std::filesystem::path path2 = absPath(path.native());
return {
PosixSourceAccessor { path2.root_path() },
CanonPath { static_cast<std::string>(path2.relative_path()) },
};
}
std::filesystem::path PosixSourceAccessor::makeAbsPath(const CanonPath & path)
{
return root.empty()
? (std::filesystem::path { path.abs() })
: path.isRoot()
? /* Don't append a slash for the root of the accessor, since
it can be a non-directory (e.g. in the case of `fetchTree
{ type = "file" }`). */
root
: root / path.rel();
}
void PosixSourceAccessor::readFile(
const CanonPath & path,
Sink & sink,
@ -13,9 +45,11 @@ void PosixSourceAccessor::readFile(
{
assertNoSymlinks(path);
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
auto ap = makeAbsPath(path);
AutoCloseFD fd = open(ap.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
if (!fd)
throw SysError("opening file '%1%'", path);
throw SysError("opening file '%1%'", ap.native());
struct stat st;
if (fstat(fd.get(), &st) == -1)
@ -46,7 +80,7 @@ void PosixSourceAccessor::readFile(
bool PosixSourceAccessor::pathExists(const CanonPath & path)
{
if (auto parent = path.parent()) assertNoSymlinks(*parent);
return nix::pathExists(path.abs());
return nix::pathExists(makeAbsPath(path));
}
std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & path)
@ -60,7 +94,7 @@ std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & pa
}
std::optional<struct stat> st{std::in_place};
if (::lstat(path.c_str(), &*st)) {
if (::lstat(makeAbsPath(path).c_str(), &*st)) {
if (errno == ENOENT || errno == ENOTDIR)
st.reset();
else
@ -95,7 +129,7 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
{
assertNoSymlinks(path);
DirEntries res;
for (auto & entry : nix::readDirectory(path.abs())) {
for (auto & entry : nix::readDirectory(makeAbsPath(path))) {
std::optional<Type> type;
switch (entry.type) {
case DT_REG: type = Type::tRegular; break;
@ -110,12 +144,12 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
std::string PosixSourceAccessor::readLink(const CanonPath & path)
{
if (auto parent = path.parent()) assertNoSymlinks(*parent);
return nix::readLink(path.abs());
return nix::readLink(makeAbsPath(path));
}
std::optional<CanonPath> PosixSourceAccessor::getPhysicalPath(const CanonPath & path)
std::optional<std::filesystem::path> PosixSourceAccessor::getPhysicalPath(const CanonPath & path)
{
return path;
return makeAbsPath(path);
}
void PosixSourceAccessor::assertNoSymlinks(CanonPath path)

View file

@ -9,6 +9,16 @@ namespace nix {
*/
struct PosixSourceAccessor : virtual SourceAccessor
{
/**
* Optional root path to prefix all operations into the native file
* system. This allows prepending funny things like `C:\` that
* `CanonPath` intentionally doesn't support.
*/
const std::filesystem::path root;
PosixSourceAccessor();
PosixSourceAccessor(std::filesystem::path && root);
/**
* The most recent mtime seen by lstat(). This is a hack to
* support dumpPathAndGetMtime(). Should remove this eventually.
@ -28,7 +38,22 @@ struct PosixSourceAccessor : virtual SourceAccessor
std::string readLink(const CanonPath & path) override;
std::optional<CanonPath> getPhysicalPath(const CanonPath & path) override;
std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override;
/**
* Create a `PosixSourceAccessor` and `CanonPath` 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.
*
* See
* [`std::filesystem::path::root_path`](https://en.cppreference.com/w/cpp/filesystem/path/root_path)
* and
* [`std::filesystem::path::relative_path`](https://en.cppreference.com/w/cpp/filesystem/path/relative_path).
*/
static std::pair<PosixSourceAccessor, CanonPath> createAtRoot(const std::filesystem::path & path);
private:
@ -38,6 +63,8 @@ private:
void assertNoSymlinks(CanonPath path);
std::optional<struct stat> cachedLstat(const CanonPath & path);
std::filesystem::path makeAbsPath(const CanonPath & path);
};
}

View file

@ -448,7 +448,7 @@ Error readError(Source & source)
auto msg = readString(source);
ErrorInfo info {
.level = level,
.msg = hintfmt(msg),
.msg = HintFmt(msg),
};
auto havePos = readNum<size_t>(source);
assert(havePos == 0);
@ -457,7 +457,7 @@ Error readError(Source & source)
havePos = readNum<size_t>(source);
assert(havePos == 0);
info.traces.push_back(Trace {
.hint = hintfmt(readString(source))
.hint = HintFmt(readString(source))
});
}
return Error(std::move(info));

View file

@ -1,5 +1,7 @@
#pragma once
#include <filesystem>
#include "canon-path.hh"
#include "hash.hh"
@ -119,7 +121,7 @@ struct SourceAccessor
* possible. This is only possible for filesystems that are
* materialized in the root filesystem.
*/
virtual std::optional<CanonPath> getPhysicalPath(const CanonPath & path)
virtual std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path)
{ return std::nullopt; }
bool operator == (const SourceAccessor & x) const

View file

@ -35,17 +35,17 @@ void SourcePath::dumpPath(
PathFilter & filter) const
{ return accessor->dumpPath(path, sink, filter); }
std::optional<CanonPath> SourcePath::getPhysicalPath() const
std::optional<std::filesystem::path> SourcePath::getPhysicalPath() const
{ return accessor->getPhysicalPath(path); }
std::string SourcePath::to_string() const
{ return accessor->showPath(path); }
SourcePath SourcePath::operator+(const CanonPath & x) const
{ return {accessor, path + x}; }
SourcePath SourcePath::operator / (const CanonPath & x) const
{ return {accessor, path / x}; }
SourcePath SourcePath::operator+(std::string_view c) const
{ return {accessor, path + c}; }
SourcePath SourcePath::operator / (std::string_view c) const
{ return {accessor, path / c}; }
bool SourcePath::operator==(const SourcePath & x) const
{
@ -62,7 +62,7 @@ bool SourcePath::operator<(const SourcePath & x) const
return std::tie(*accessor, path) < std::tie(*x.accessor, x.path);
}
SourcePath SourcePath::resolveSymlinks() const
SourcePath SourcePath::resolveSymlinks(SymlinkResolution mode) const
{
auto res = SourcePath(accessor);
@ -72,6 +72,8 @@ SourcePath SourcePath::resolveSymlinks() const
for (auto & c : path)
todo.push_back(std::string(c));
bool resolve_last = mode == SymlinkResolution::Full;
while (!todo.empty()) {
auto c = *todo.begin();
todo.pop_front();
@ -81,14 +83,16 @@ SourcePath SourcePath::resolveSymlinks() const
res.path.pop();
else {
res.path.push(c);
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", path);
auto target = res.readLink();
res.path.pop();
if (hasPrefix(target, "/"))
res.path = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
if (resolve_last || !todo.empty()) {
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", path);
auto target = res.readLink();
res.path.pop();
if (hasPrefix(target, "/"))
res.path = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}
}
}
}

View file

@ -11,6 +11,26 @@
namespace nix {
/**
* Note there is a decent chance this type soon goes away because the problem is solved another way.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/
enum class SymlinkResolution {
/**
* Resolve symlinks in the ancestors only.
*
* Only the last component of the result is possibly a symlink.
*/
Ancestors,
/**
* Resolve symlinks fully, realpath(3)-style.
*
* No component of the result will be a symlink.
*/
Full,
};
/**
* An abstraction for accessing source files during
* evaluation. Currently, it's just a wrapper around `CanonPath` that
@ -82,31 +102,35 @@ struct SourcePath
* Return the location of this path in the "real" filesystem, if
* it has a physical location.
*/
std::optional<CanonPath> getPhysicalPath() const;
std::optional<std::filesystem::path> getPhysicalPath() const;
std::string to_string() const;
/**
* Append a `CanonPath` to this path.
*/
SourcePath operator + (const CanonPath & x) const;
SourcePath operator / (const CanonPath & x) const;
/**
* Append a single component `c` to this path. `c` must not
* contain a slash. A slash is implicitly added between this path
* and `c`.
*/
SourcePath operator+(std::string_view c) const;
SourcePath operator / (std::string_view c) const;
bool operator==(const SourcePath & x) const;
bool operator!=(const SourcePath & x) const;
bool operator<(const SourcePath & x) const;
/**
* Resolve any symlinks in this `SourcePath` (including its
* parents). The result is a `SourcePath` in which no element is a
* symlink.
* Resolve any symlinks in this `SourcePath` according to the
* given resolution mode.
*
* @param mode might only be a temporary solution for this.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/
SourcePath resolveSymlinks() const;
SourcePath resolveSymlinks(
SymlinkResolution mode = SymlinkResolution::Full) const;
};
std::ostream & operator << (std::ostream & str, const SourcePath & path);

View file

@ -132,4 +132,66 @@ void unpackTarfile(const Path & tarFile, const Path & destDir)
extract_archive(archive, destDir);
}
time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSink)
{
time_t lastModified = 0;
for (;;) {
// FIXME: merge with extract_archive
struct archive_entry * entry;
int r = archive_read_next_header(archive.archive, &entry);
if (r == ARCHIVE_EOF) break;
auto path = archive_entry_pathname(entry);
if (!path)
throw Error("cannot get archive member name: %s", archive_error_string(archive.archive));
if (r == ARCHIVE_WARN)
warn(archive_error_string(archive.archive));
else
archive.check(r);
lastModified = std::max(lastModified, archive_entry_mtime(entry));
switch (archive_entry_filetype(entry)) {
case AE_IFDIR:
parseSink.createDirectory(path);
break;
case AE_IFREG: {
parseSink.createRegularFile(path, [&](auto & crf) {
if (archive_entry_mode(entry) & S_IXUSR)
crf.isExecutable();
while (true) {
std::vector<unsigned char> buf(128 * 1024);
auto n = archive_read_data(archive.archive, buf.data(), buf.size());
if (n < 0)
throw Error("cannot read file '%s' from tarball", path);
if (n == 0) break;
crf(std::string_view {
(const char *) buf.data(),
(size_t) n,
});
}
});
break;
}
case AE_IFLNK: {
auto target = archive_entry_symlink(entry);
parseSink.createSymlink(path, target);
break;
}
default:
throw Error("file '%s' in tarball has unsupported file type", path);
}
}
return lastModified;
}
}

View file

@ -2,6 +2,7 @@
///@file
#include "serialise.hh"
#include "fs-sink.hh"
#include <archive.h>
namespace nix {
@ -29,4 +30,6 @@ void unpackTarfile(Source & source, const Path & destDir);
void unpackTarfile(const Path & tarFile, const Path & destDir);
time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSink);
}