Advanced Topics
+
diff --git a/doc/manual/advanced-topics/diff-hook.xml b/doc/manual/advanced-topics/diff-hook.xml
new file mode 100644
index 000000000..fb4bf819f
--- /dev/null
+++ b/doc/manual/advanced-topics/diff-hook.xml
@@ -0,0 +1,205 @@
+
+
+Verifying Build Reproducibility with
+
+Check build reproducibility by running builds multiple times
+and comparing their results.
+
+Specify a program with Nix's to
+compare build results when two builds produce different results. Note:
+this hook is only executed if the results are not the same, this hook
+is not used for determining if the results are the same.
+
+For purposes of demonstration, we'll use the following Nix file,
+deterministic.nix for testing:
+
+
+let
+ inherit (import <nixpkgs> {}) runCommand;
+in {
+ stable = runCommand "stable" {} ''
+ touch $out
+ '';
+
+ unstable = runCommand "unstable" {} ''
+ echo $RANDOM > $out
+ '';
+}
+
+
+Additionally, nix.conf contains:
+
+
+diff-hook = /etc/nix/my-diff-hook
+run-diff-hook = true
+
+
+where /etc/nix/my-diff-hook is an executable
+file containing:
+
+
+#!/bin/sh
+exec >&2
+echo "For derivation $3:"
+/run/current-system/sw/bin/diff -r "$1" "$2"
+
+
+
+
+The diff hook is executed by the same user and group who ran the
+build. However, the diff hook does not have write access to the store
+path just built.
+
+
+
+ Spot-Checking Build Determinism
+
+
+
+ Verify a path which already exists in the Nix store by passing
+ to the build command.
+
+
+ If the build passes and is deterministic, Nix will exit with a
+ status code of 0:
+
+
+$ nix-build ./deterministic.nix -A stable
+these derivations will be built:
+ /nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv
+building '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'...
+/nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable
+
+$ nix-build ./deterministic.nix -A stable --check
+checking outputs of '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'...
+/nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable
+
+
+ If the build is not deterministic, Nix will exit with a status
+ code of 1:
+
+
+$ nix-build ./deterministic.nix -A unstable
+these derivations will be built:
+ /nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv
+building '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
+/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable
+
+$ nix-build ./deterministic.nix -A unstable --check
+checking outputs of '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
+error: derivation '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv' may not be deterministic: output '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable' differs
+
+
+In the Nix daemon's log, we will now see:
+
+For derivation /nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv:
+1c1
+< 8108
+---
+> 30204
+
+
+
+ Using with
+ will cause Nix to keep the second build's output in a special,
+ .check path:
+
+
+$ nix-build ./deterministic.nix -A unstable --check --keep-failed
+checking outputs of '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
+note: keeping build directory '/tmp/nix-build-unstable.drv-0'
+error: derivation '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv' may not be deterministic: output '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable' differs from '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check'
+
+
+ In particular, notice the
+ /nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check
+ output. Nix has copied the build results to that directory where you
+ can examine it.
+
+
+ .check paths are not registered store paths
+
+ Check paths are not protected against garbage collection,
+ and this path will be deleted on the next garbage collection.
+
+ The path is guaranteed to be alive for the duration of
+ 's execution, but may be deleted
+ any time after.
+
+ If the comparison is performed as part of automated tooling,
+ please use the diff-hook or author your tooling to handle the case
+ where the build was not deterministic and also a check path does
+ not exist.
+
+
+
+ is only usable if the derivation has
+ been built on the system already. If the derivation has not been
+ built Nix will fail with the error:
+
+error: some outputs of '/nix/store/hzi1h60z2qf0nb85iwnpvrai3j2w7rr6-unstable.drv' are not valid, so checking is not possible
+
+
+ Run the build without , and then try with
+ again.
+
+
+
+
+
+ Automatic and Optionally Enforced Determinism Verification
+
+
+
+ Automatically verify every build at build time by executing the
+ build multiple times.
+
+
+
+ Setting and
+ in your
+ nix.conf permits the automated verification
+ of every build Nix performs.
+
+
+
+ The following configuration will run each build three times, and
+ will require the build to be deterministic:
+
+
+enforce-determinism = true
+repeat = 2
+
+
+
+
+ Setting to false as in
+ the following configuration will run the build multiple times,
+ execute the build hook, but will allow the build to succeed even
+ if it does not build reproducibly:
+
+
+enforce-determinism = false
+repeat = 1
+
+
+
+
+ An example output of this configuration:
+
+$ nix-build ./test.nix -A unstable
+these derivations will be built:
+ /nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv
+building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 1/2)...
+building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 2/2)...
+output '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable' of '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' differs from '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable.check' from previous round
+/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable
+
+
+
+
diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index f0da1f612..24fbf28cf 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -1,7 +1,9 @@
+
+ xml:id="sec-conf-file"
+ version="5">
nix.conf
@@ -240,6 +242,71 @@ false.
+ diff-hook
+
+
+ Absolute path to an executable capable of diffing build results.
+ The hook executes if is
+ true, and the output of a build is known to not be the same.
+ This program is not executed to determine if two results are the
+ same.
+
+
+
+ The diff hook is executed by the same user and group who ran the
+ build. However, the diff hook does not have write access to the
+ store path just built.
+
+
+ The diff hook program receives three parameters:
+
+
+
+
+ A path to the previous build's results
+
+
+
+
+
+ A path to the current build's results
+
+
+
+
+
+ The path to the build's derivation
+
+
+
+
+
+ The path to the build's scratch directory. This directory
+ will exist only if the build was run with
+ .
+
+
+
+
+
+ The stderr and stdout output from the diff hook will not be
+ displayed to the user. Instead, it will print to the nix-daemon's
+ log.
+
+
+ When using the Nix daemon, diff-hook must
+ be set in the nix.conf configuration file, and
+ cannot be passed at the command line.
+
+
+
+
+
+ enforce-determinism
+
+ See .
+
+
extra-sandbox-paths
@@ -595,9 +662,9 @@ password my-password
they are deterministic. The default value is 0. If the value is
non-zero, every build is repeated the specified number of
times. If the contents of any of the runs differs from the
- previous ones, the build is rejected and the resulting store paths
- are not registered as “valid” in Nix’s database.
-
+ previous ones and is
+ true, the build is rejected and the resulting store paths are not
+ registered as “valid” in Nix’s database.
require-sigs
@@ -628,6 +695,19 @@ password my-password
+ run-diff-hook
+
+
+ If true, enable the execution of .
+
+
+
+ When using the Nix daemon, run-diff-hook must
+ be set in the nix.conf configuration file,
+ and cannot be passed at the command line.
+
+
+ sandbox
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 91eb97dfb..b07461013 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -461,6 +461,28 @@ static void commonChildInit(Pipe & logPipe)
close(fdDevNull);
}
+void handleDiffHook(uid_t uid, uid_t gid, Path tryA, Path tryB, Path drvPath, Path tmpDir)
+{
+ auto diffHook = settings.diffHook;
+ if (diffHook != "" && settings.runDiffHook) {
+ try {
+ RunOptions diffHookOptions(diffHook,{tryA, tryB, drvPath, tmpDir});
+ diffHookOptions.searchPath = true;
+ diffHookOptions.uid = uid;
+ diffHookOptions.gid = gid;
+ diffHookOptions.chdir = "/";
+
+ auto diffRes = runProgram(diffHookOptions);
+ if (!statusOk(diffRes.first))
+ throw ExecError(diffRes.first, fmt("diff-hook program '%1%' %2%", diffHook, statusToString(diffRes.first)));
+
+ if (diffRes.second != "")
+ printError(chomp(diffRes.second));
+ } catch (Error & error) {
+ printError("diff hook execution failed: %s", error.what());
+ }
+ }
+}
//////////////////////////////////////////////////////////////////////
@@ -3039,8 +3061,7 @@ void DerivationGoal::registerOutputs()
InodesSeen inodesSeen;
Path checkSuffix = ".check";
- bool runDiffHook = settings.runDiffHook;
- bool keepPreviousRound = settings.keepFailed || runDiffHook;
+ bool keepPreviousRound = settings.keepFailed || settings.runDiffHook;
std::exception_ptr delayedException;
@@ -3185,11 +3206,17 @@ void DerivationGoal::registerOutputs()
if (!worker.store.isValidPath(path)) continue;
auto info = *worker.store.queryPathInfo(path);
if (hash.first != info.narHash) {
- if (settings.keepFailed) {
+ if (settings.runDiffHook || settings.keepFailed) {
Path dst = worker.store.toRealPath(path + checkSuffix);
deletePath(dst);
if (rename(actualPath.c_str(), dst.c_str()))
throw SysError(format("renaming '%1%' to '%2%'") % actualPath % dst);
+
+ handleDiffHook(
+ buildUser ? buildUser->getUID() : getuid(),
+ buildUser ? buildUser->getGID() : getgid(),
+ path, dst, drvPath, tmpDir);
+
throw Error(format("derivation '%1%' may not be deterministic: output '%2%' differs from '%3%'")
% drvPath % path % dst);
} else
@@ -3254,16 +3281,10 @@ void DerivationGoal::registerOutputs()
? fmt("output '%1%' of '%2%' differs from '%3%' from previous round", i->second.path, drvPath, prev)
: fmt("output '%1%' of '%2%' differs from previous round", i->second.path, drvPath);
- auto diffHook = settings.diffHook;
- if (prevExists && diffHook != "" && runDiffHook) {
- try {
- auto diff = runProgram(diffHook, true, {prev, i->second.path});
- if (diff != "")
- printError(chomp(diff));
- } catch (Error & error) {
- printError("diff hook execution failed: %s", error.what());
- }
- }
+ handleDiffHook(
+ buildUser ? buildUser->getUID() : getuid(),
+ buildUser ? buildUser->getGID() : getgid(),
+ prev, i->second.path, drvPath, tmpDir);
if (settings.enforceDeterminism)
throw NotDeterministic(msg);
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index a71705665..17aee2d5c 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -16,6 +16,7 @@
#include
#include
+#include
#include
#include
#include
@@ -1025,6 +1026,16 @@ void runProgram2(const RunOptions & options)
if (source && dup2(in.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin");
+ if (options.chdir && chdir((*options.chdir).c_str()) == -1)
+ throw SysError("chdir failed");
+ if (options.gid && setgid(*options.gid) == -1)
+ throw SysError("setgid failed");
+ /* Drop all other groups if we're setgid. */
+ if (options.gid && setgroups(0, 0) == -1)
+ throw SysError("setgroups failed");
+ if (options.uid && setuid(*options.uid) == -1)
+ throw SysError("setuid failed");
+
Strings args_(options.args);
args_.push_front(options.program);
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 54936a5cb..7c57d0afa 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -267,6 +267,9 @@ string runProgram(Path program, bool searchPath = false,
struct RunOptions
{
+ std::optional uid;
+ std::optional gid;
+ std::optional chdir;
Path program;
bool searchPath = true;
Strings args;