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:
commit
cb4f85f11c
253 changed files with 5623 additions and 2830 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
7
src/libutil/exit.cc
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include "exit.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
Exit::~Exit() {}
|
||||
|
||||
}
|
19
src/libutil/exit.hh
Normal file
19
src/libutil/exit.hh
Normal 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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
176
src/libutil/file-path-impl.hh
Normal file
176
src/libutil/file-path-impl.hh
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 don’t want trailing slashes here, which dirOf only produces if s = /
|
||||
s.clear();
|
||||
result = dirOf(result);
|
||||
if (result == "/") {
|
||||
/* we don’t 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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -26,6 +26,8 @@ struct CreateRegularFileSink : Sink
|
|||
|
||||
struct FileSystemObjectSink
|
||||
{
|
||||
virtual ~FileSystemObjectSink() = default;
|
||||
|
||||
virtual void createDirectory(const Path & path) = 0;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue