aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-13 03:41:54 +0000
committertv <tv@krebsco.de>2026-11-13 03:41:54 +0000
commit0842652921304ba76fbd321756b6b545abc81b01 (patch)
tree0b9679cd9a231728175e3efdbc0834eb2ce2b6dd /tests
parent4ee857a9f1c9153b944a2f48a89f35d9657b2ca5 (diff)
populate: never write through files or change hard links elsewhere
Populate writes some files in place, and makes files writable, which reaches beyond the target directory where such a file is a symlink or a hard link to one elsewhere: - The key of a derived copy gets written to a new file, which then replaces it, instead of written through a symlink at its place. - Lock files get copied to a new file before Nix rewrites them, instead of to flake.lock.populate, which a source may contain as a symlink. - Before rsync syncs a source, each file with other hard links gets replaced by a copy of its own. rsync fixes the mode and time of a file it takes for unchanged in place, which changes every hard link to it: one elsewhere, e.g. in /etc/nixos, or a backup's. - Removing state makes only directories writable, which is what removing what's in them needs, instead of everything, including files that are hard links to ones elsewhere. - The sentinel file only gets created if it's missing, instead of touched on every run. - A link only counts as pointing into the store if its resolved path is in the store, so a link like /nix/store/../.. gets replaced. Safety scenarios cover a symlink in place of a key, symlinks in a source named like the lock file's copies, a hard link to a read-only file outside in the target directory, and in stale state.
Diffstat (limited to 'tests')
-rw-r--r--tests/scenarios.nix96
1 files changed, 96 insertions, 0 deletions
diff --git a/tests/scenarios.nix b/tests/scenarios.nix
index 6e1f89b..89a3b09 100644
--- a/tests/scenarios.nix
+++ b/tests/scenarios.nix
@@ -2013,6 +2013,102 @@ assert !(krops.isLocalTarget (targets.remote "/"));
}
];
}))
+ # A file in populate's state replaced by a symlink to a file outside.
+ (s "state-key" (let
+ sources.x.flake-input = {
+ url = "/fixtures/top"; name = "dep"; inherit fetchOnTarget;
+ patches = hello;
+ };
+ in {
+ isolate = true;
+ phases = [
+ {
+ inherit sources;
+ mutate = ''
+ rm "$T"/.populate/derived/x/key
+ ln -s ${outside}/file "$T"/.populate/derived/x/key
+ '';
+ }
+ {
+ inherit sources;
+ check = checkFlake { attr = "greeting"; expected = "hello+inner"; };
+ }
+ ];
+ }))
+ # Stale state that contains a hard link to a read-only file outside,
+ # whose mode stays.
+ (s "state-hard-link" (let
+ sources.x.flake-input = {
+ url = "/fixtures/top"; name = "dep"; inherit fetchOnTarget;
+ patches = hello;
+ };
+ in {
+ isolate = true;
+ phases = [
+ {
+ inherit sources;
+ mutate = ''
+ mkdir -p "$T"/.populate/derived/gone "$T"/.populate/derived/x/work
+ ln ${outside}/dir/sub/file "$T"/.populate/derived/gone/file
+ ln ${outside}/dir/sub/file "$T"/.populate/derived/x/work/file
+ '';
+ }
+ {
+ inherit sources;
+ check = ''
+ test ! -e "$T"/.populate/derived/gone || fail "gone remains"
+ '';
+ }
+ ];
+ }))
+ # The source contains flake.lock.populate and a file like the temporary
+ # copy of the lock file, symlinks to a file outside.
+ (s "lock-temporary-link" {
+ isolate = true;
+ prepareDeployer = ''
+ rm -fR /var/krops-src/lock-temporary-link
+ mkdir -p /var/krops-src
+ cp -R /fixtures/filesrc /var/krops-src/lock-temporary-link
+ ln -s ${outside}/file /var/krops-src/lock-temporary-link/flake.lock.populate
+ ln -s ${outside}/file /var/krops-src/lock-temporary-link/.flake.lock.XXXXXX
+ '';
+ phases = twice {
+ sources = alt // {
+ x.file = {
+ path = "/var/krops-src/lock-temporary-link";
+ inputs.dep.follows = "alt";
+ };
+ };
+ check = ''
+ expect_eq msg "$(nix_eval_online path:"$T"/x#msg)" top:alt,sub
+ '';
+ };
+ })
+ # A file in the target directory is a hard link to a read-only file
+ # outside with the same contents, which rsync takes for unchanged, and
+ # would fix the mode and time of in place.
+ (s "rsync-hard-link" {
+ isolate = true;
+ prepareDeployer = ''
+ rm -fR /var/krops-src/rsync-hard-link
+ mkdir -p /var/krops-src/rsync-hard-link
+ echo outside > /var/krops-src/rsync-hard-link/file
+ '';
+ prepare = ''
+ mkdir -p "$T"/x
+ ln ${outside}/dir/sub/file "$T"/x/file
+ '';
+ phases = twice {
+ sources.x.file = {
+ path = "/var/krops-src/rsync-hard-link";
+ useChecksum = true;
+ };
+ check = ''
+ expect_eq file "$(cat "$T"/x/file)" outside
+ expect_eq links "$(stat -c %h "$T"/x/file)" 1
+ '';
+ };
+ })
(lib.optionals (targetKind == "local") [
# Rewriting a lock file changes no other hard link to it. The lock
# file is like the source's, with its modification time, so rsync