Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: NixOS/nix
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: aabf5c86c9df
Choose a base ref
...
head repository: NixOS/nix
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 629b9b004936
Choose a head ref
  • 4 commits
  • 16 files changed
  • 1 contributor

Commits on Oct 21, 2019

  1. Allow content-addressable paths to have references

    This adds a command 'nix make-content-addressable' that rewrites the
    specified store paths into content-addressable paths. The advantage of
    such paths is that 1) they can be imported without signatures; 2) they
    can enable deduplication in cases where derivation changes do not
    cause output changes (apart from store path hashes).
    
    For example,
    
      $ nix make-content-addressable -r nixpkgs.cowsay
      rewrote '/nix/store/g1g31ah55xdia1jdqabv1imf6mcw0nb1-glibc-2.25-49' to '/nix/store/48jfj7bg78a8n4f2nhg269rgw1936vj4-glibc-2.25-49'
      ...
      rewrote '/nix/store/qbi6rzpk0bxjw8lw6azn2mc7ynnn455q-cowsay-3.03+dfsg1-16' to '/nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16'
    
    We can then copy the resulting closure to another store without
    signatures:
    
      $ nix copy --trusted-public-keys '' ---to ~/my-nix /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16
    
    In order to support self-references in content-addressable paths,
    these paths are hashed "modulo" self-references, meaning that
    self-references are zeroed out during hashing. Somewhat annoyingly,
    this means that the NAR hash stored in the Nix database is no longer
    necessarily equal to the output of "nix hash-path"; for
    content-addressable paths, you need to pass the --modulo flag:
    
      $ nix path-info --json /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16  | jq -r .[].narHash
      sha256:0ri611gdilz2c9rsibqhsipbfs9vwcqvs811a52i2bnkhv7w9mgw
    
      $ nix hash-path --type sha256 --base32 /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16
      1ggznh07khq0hz6id09pqws3a8q9pn03ya3c03nwck1kwq8rclzs
    
      $ nix hash-path --type sha256 --base32 /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16 --modulo iq6g2x4q62xp7y7493bibx0qn5w7xz67
      0ri611gdilz2c9rsibqhsipbfs9vwcqvs811a52i2bnkhv7w9mgw
    edolstra committed Oct 21, 2019
    Copy the full SHA
    0abb3ad View commit details
  2. Fix build

    edolstra committed Oct 21, 2019
    Copy the full SHA
    d77970f View commit details
  3. Copy the full SHA
    e687369 View commit details
  4. 1
    Copy the full SHA
    629b9b0 View commit details
19 changes: 1 addition & 18 deletions src/libstore/build.cc
Original file line number Diff line number Diff line change
@@ -727,23 +727,6 @@ HookInstance::~HookInstance()
//////////////////////////////////////////////////////////////////////


typedef map<std::string, std::string> StringRewrites;


std::string rewriteStrings(std::string s, const StringRewrites & rewrites)
{
for (auto & i : rewrites) {
size_t j = 0;
while ((j = s.find(i.first, j)) != string::npos)
s.replace(j, i.first.size(), i.second);
}
return s;
}


//////////////////////////////////////////////////////////////////////


typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;

class SubstitutionGoal;
@@ -865,7 +848,7 @@ class DerivationGoal : public Goal
#endif

/* Hash rewriting. */
StringRewrites inputRewrites, outputRewrites;
StringMap inputRewrites, outputRewrites;
typedef map<Path, Path> RedirectedOutputs;
RedirectedOutputs redirectedOutputs;

24 changes: 20 additions & 4 deletions src/libstore/local-store.cc
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#include "worker-protocol.hh"
#include "derivations.hh"
#include "nar-info.hh"
#include "references.hh"

#include <iostream>
#include <algorithm>
@@ -1009,17 +1010,24 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,

/* While restoring the path from the NAR, compute the hash
of the NAR. */
HashSink hashSink(htSHA256);
std::unique_ptr<AbstractHashSink> hashSink;
if (info.ca == "")
hashSink = std::make_unique<HashSink>(htSHA256);
else {
if (!info.references.empty())
settings.requireExperimentalFeature("ca-references");
hashSink = std::make_unique<HashModuloSink>(htSHA256, storePathToHash(info.path));
}

LambdaSource wrapperSource([&](unsigned char * data, size_t len) -> size_t {
size_t n = source.read(data, len);
hashSink(data, n);
(*hashSink)(data, n);
return n;
});

restorePath(realPath, wrapperSource);

auto hashResult = hashSink.finish();
auto hashResult = hashSink->finish();

if (hashResult.first != info.narHash)
throw Error("hash mismatch importing path '%s';\n wanted: %s\n got: %s",
@@ -1241,7 +1249,15 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)

/* Check the content hash (optionally - slow). */
printMsg(lvlTalkative, format("checking contents of '%1%'") % i);
HashResult current = hashPath(info->narHash.type, toRealPath(i));

std::unique_ptr<AbstractHashSink> hashSink;
if (info->ca == "")
hashSink = std::make_unique<HashSink>(info->narHash.type);
else
hashSink = std::make_unique<HashModuloSink>(info->narHash.type, storePathToHash(info->path));

dumpPath(toRealPath(i), *hashSink);
auto current = hashSink->finish();

if (info->narHash != nullHash && info->narHash != current.first) {
printError(format("path '%1%' was modified! "
62 changes: 62 additions & 0 deletions src/libstore/references.cc
Original file line number Diff line number Diff line change
@@ -118,4 +118,66 @@ PathSet scanForReferences(const string & path,
}


RewritingSink::RewritingSink(const std::string & from, const std::string & to, Sink & nextSink)
: from(from), to(to), nextSink(nextSink)
{
assert(from.size() == to.size());
}

void RewritingSink::operator () (const unsigned char * data, size_t len)
{
std::string s(prev);
s.append((const char *) data, len);

size_t j = 0;
while ((j = s.find(from, j)) != string::npos) {
matches.push_back(pos + j);
s.replace(j, from.size(), to);
}

prev = s.size() < from.size() ? s : std::string(s, s.size() - from.size() + 1, from.size() - 1);

auto consumed = s.size() - prev.size();

pos += consumed;

if (consumed) nextSink((unsigned char *) s.data(), consumed);
}

void RewritingSink::flush()
{
if (prev.empty()) return;
pos += prev.size();
nextSink((unsigned char *) prev.data(), prev.size());
prev.clear();
}

HashModuloSink::HashModuloSink(HashType ht, const std::string & modulus)
: hashSink(ht)
, rewritingSink(modulus, std::string(modulus.size(), 0), hashSink)
{
}

void HashModuloSink::operator () (const unsigned char * data, size_t len)
{
rewritingSink(data, len);
}

HashResult HashModuloSink::finish()
{
rewritingSink.flush();

/* Hash the positions of the self-references. This ensures that a
NAR with self-references and a NAR with some of the
self-references already zeroed out do not produce a hash
collision. FIXME: proof. */
for (auto & pos : rewritingSink.matches) {
auto s = fmt("|%d", pos);
hashSink((unsigned char *) s.data(), s.size());
}

auto h = hashSink.finish();
return {h.first, rewritingSink.pos};
}

}
29 changes: 28 additions & 1 deletion src/libstore/references.hh
Original file line number Diff line number Diff line change
@@ -7,5 +7,32 @@ namespace nix {

PathSet scanForReferences(const Path & path, const PathSet & refs,
HashResult & hash);


struct RewritingSink : Sink
{
std::string from, to, prev;
Sink & nextSink;
uint64_t pos = 0;

std::vector<uint64_t> matches;

RewritingSink(const std::string & from, const std::string & to, Sink & nextSink);

void operator () (const unsigned char * data, size_t len) override;

void flush();
};

struct HashModuloSink : AbstractHashSink
{
HashSink hashSink;
RewritingSink rewritingSink;

HashModuloSink(HashType ht, const std::string & modulus);

void operator () (const unsigned char * data, size_t len) override;

HashResult finish() override;
};

}
36 changes: 22 additions & 14 deletions src/libstore/store-api.cc
Original file line number Diff line number Diff line change
@@ -205,15 +205,27 @@ Path Store::makeOutputPath(const string & id,
}


static std::string makeType(string && type, const PathSet & references)
{
for (auto & i : references) {
type += ":";
type += i;
}
return type;
}


Path Store::makeFixedOutputPath(bool recursive,
const Hash & hash, const string & name) const
const Hash & hash, const string & name, const PathSet & references) const
{
return hash.type == htSHA256 && recursive
? makeStorePath("source", hash, name)
: makeStorePath("output:out", hashString(htSHA256,
if (hash.type == htSHA256 && recursive) {
return makeStorePath(makeType("source", references), hash, name);
} else {
assert(references.empty());
return makeStorePath("output:out", hashString(htSHA256,
"fixed:out:" + (recursive ? (string) "r:" : "") +
hash.to_string(Base16) + ":"),
name);
hash.to_string(Base16) + ":"), name);
}
}


@@ -224,12 +236,7 @@ Path Store::makeTextPath(const string & name, const Hash & hash,
/* Stuff the references (if any) into the type. This is a bit
hacky, but we can't put them in `s' since that would be
ambiguous. */
string type = "text";
for (auto & i : references) {
type += ":";
type += i;
}
return makeStorePath(type, hash, name);
return makeStorePath(makeType("text", references), hash, name);
}


@@ -785,8 +792,9 @@ bool ValidPathInfo::isContentAddressed(const Store & store) const
else if (hasPrefix(ca, "fixed:")) {
bool recursive = ca.compare(6, 2, "r:") == 0;
Hash hash(std::string(ca, recursive ? 8 : 6));
if (references.empty() &&
store.makeFixedOutputPath(recursive, hash, storePathToName(path)) == path)
auto refs = references;
replaceInSet(refs, path, std::string("self"));
if (store.makeFixedOutputPath(recursive, hash, storePathToName(path), refs) == path)
return true;
else
warn();
3 changes: 2 additions & 1 deletion src/libstore/store-api.hh
Original file line number Diff line number Diff line change
@@ -303,7 +303,8 @@ public:
const Hash & hash, const string & name) const;

Path makeFixedOutputPath(bool recursive,
const Hash & hash, const string & name) const;
const Hash & hash, const string & name,
const PathSet & references = {}) const;

Path makeTextPath(const string & name, const Hash & hash,
const PathSet & references) const;
20 changes: 3 additions & 17 deletions src/libutil/hash.cc
Original file line number Diff line number Diff line change
@@ -256,23 +256,9 @@ Hash hashString(HashType ht, const string & s)

Hash hashFile(HashType ht, const Path & path)
{
Ctx ctx;
Hash hash(ht);
start(ht, ctx);

AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd) throw SysError(format("opening file '%1%'") % path);

std::vector<unsigned char> buf(8192);
ssize_t n;
while ((n = read(fd.get(), buf.data(), buf.size()))) {
checkInterrupt();
if (n == -1) throw SysError(format("reading file '%1%'") % path);
update(ht, ctx, buf.data(), n);
}

finish(ht, ctx, hash.hash);
return hash;
HashSink sink(ht);
readFile(path, sink);
return sink.finish().first;
}


11 changes: 8 additions & 3 deletions src/libutil/hash.hh
Original file line number Diff line number Diff line change
@@ -111,7 +111,12 @@ string printHashType(HashType ht);

union Ctx;

class HashSink : public BufferedSink
struct AbstractHashSink : virtual Sink
{
virtual HashResult finish() = 0;
};

class HashSink : public BufferedSink, public AbstractHashSink
{
private:
HashType ht;
@@ -122,8 +127,8 @@ public:
HashSink(HashType ht);
HashSink(const HashSink & h);
~HashSink();
void write(const unsigned char * data, size_t len);
HashResult finish();
void write(const unsigned char * data, size_t len) override;
HashResult finish() override;
HashResult currentHash();
};

2 changes: 1 addition & 1 deletion src/libutil/serialise.hh
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ struct Sink


/* A buffered abstract sink. */
struct BufferedSink : Sink
struct BufferedSink : virtual Sink
{
size_t bufSize, bufPos;
std::unique_ptr<unsigned char[]> buffer;
13 changes: 13 additions & 0 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
@@ -1260,6 +1260,19 @@ string replaceStrings(const std::string & s,
}


std::string rewriteStrings(const std::string & _s, const StringMap & rewrites)
{
auto s = _s;
for (auto & i : rewrites) {
if (i.first == i.second) continue;
size_t j = 0;
while ((j = s.find(i.first, j)) != string::npos)
s.replace(j, i.first.size(), i.second);
}
return s;
}


string statusToString(int status)
{
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
14 changes: 14 additions & 0 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
@@ -362,6 +362,20 @@ string replaceStrings(const std::string & s,
const std::string & from, const std::string & to);


std::string rewriteStrings(const std::string & s, const StringMap & rewrites);


/* If a set contains 'from', remove it and insert 'to'. */
template<typename T>
void replaceInSet(std::set<T> & set, const T & from, const T & to)
{
auto i = set.find(from);
if (i == set.end()) return;
set.erase(i);
set.insert(to);
}


/* Convert the exit status of a child as returned by wait() into an
error string. */
string statusToString(int status);
2 changes: 1 addition & 1 deletion src/nix/command.cc
Original file line number Diff line number Diff line change
@@ -129,7 +129,7 @@ void StorePathsCommand::run(ref<Store> store)
}

else {
for (auto & p : toStorePaths(store, NoBuild, installables))
for (auto & p : toStorePaths(store, realiseMode, installables))
storePaths.push_back(p);

if (recursive) {
4 changes: 4 additions & 0 deletions src/nix/command.hh
Original file line number Diff line number Diff line change
@@ -139,6 +139,10 @@ private:
bool recursive = false;
bool all = false;

protected:

RealiseMode realiseMode = NoBuild;

public:

StorePathsCommand(bool recursive = false);
Loading