nix store delete/nix-store --delete cannot delete paths with self-references #494

Closed
opened 2024-08-30 05:27:39 +00:00 by jade · 10 comments
Owner

Reproducer:

builtins.derivation {
  name = "undeletable";
  system = "x86_64-linux";
  builder = "/bin/sh";
  args = ["-c" "echo $out > $out"];
}
lix/lix3 » nix build -f undeletable.nix --print-out-paths --no-link
/nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
lix/lix3 » nix-store --query --referrers /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
/nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
lix/lix3 » nix-store --query --roots /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
removing stale link from '/nix/var/nix/gcroots/auto/s5n71qi0f9l7g1bg70b5wlz8c6yinvnr' to '/home/jade/lix/lix3/result'
lix/lix3 » nix store delete /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
0 store paths deleted, 0.00 MiB freed
error: Cannot delete path '/nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable' since it is still alive. To find out why, use: nix-store --query --roots and nix-store --query --referrers

This is an instance of #282, probably not the only one.

nix store delete should just delete the path in such an instance, the "references" are nonsense.

Reproducer: ```nix builtins.derivation { name = "undeletable"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo $out > $out"]; } ``` ``` lix/lix3 » nix build -f undeletable.nix --print-out-paths --no-link /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable lix/lix3 » nix-store --query --referrers /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable lix/lix3 » nix-store --query --roots /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable removing stale link from '/nix/var/nix/gcroots/auto/s5n71qi0f9l7g1bg70b5wlz8c6yinvnr' to '/home/jade/lix/lix3/result' lix/lix3 » nix store delete /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable 0 store paths deleted, 0.00 MiB freed error: Cannot delete path '/nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable' since it is still alive. To find out why, use: nix-store --query --roots and nix-store --query --referrers ``` This is an instance of https://git.lix.systems/lix-project/lix/issues/282, probably not the only one. `nix store delete` should just delete the path in such an instance, the "references" are nonsense.
Author
Owner

Beware while fixing this: this might be more properly fixed by fixing the root cause, #495

Beware while fixing this: this might be more properly fixed by fixing the root cause, https://git.lix.systems/lix-project/lix/issues/495
Member

I think there's something off with nix-store --delete in general:

For instance, nix-store --delete /nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse fails for me with

error: Cannot delete path '/nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse' since it is still alive. To find out why, use: nix-store --query --roots and nix-store --query --referrers

Both --referrers and --roots doesn't give me anything. It's an empty store-path from pkgs.nixosTest:

$ tree
tree /nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse
/nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse

Or am I holding something wrong?

nix --version:

nix (Lix, like Nix) 2.93.0-devpre20250128_f8a5927
System type: x86_64-linux
Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux, x86_64-v4-linux
Features: gc, signed-caches
System configuration file: /etc/nix/nix.conf
User configuration files: /home/ma27/.config/nix/nix.conf:/etc/xdg/nix/nix.conf:/home/ma27/.nix-profile/etc/xdg/nix/nix.conf:/nix/profile/etc/xdg/nix/nix.conf:/home/ma27/.local/state/nix/profile/etc/xdg/nix/nix.conf:/etc/profiles/per-user/ma27/etc/xdg/nix/nix.conf:/nix/var/nix/profiles/default/etc/xdg/nix/nix.conf:/run/current-system/sw/etc/xdg/nix/nix.conf
Store directory: /nix/store
State directory: /nix/var/nix
Data directory: /nix/store/2a2wqna4gi2sg78g1mhxhqnm1sj9xlin-lix-2.93.0-devpre20250128_f8a5927/share
I think there's something off with `nix-store --delete` in general: For instance, `nix-store --delete /nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse` fails for me with ``` error: Cannot delete path '/nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse' since it is still alive. To find out why, use: nix-store --query --roots and nix-store --query --referrers ``` Both `--referrers` and `--roots` doesn't give me anything. It's an empty store-path from `pkgs.nixosTest`: ``` $ tree tree /nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse /nix/store/4m1m9i8wfs2zwapdr5c2pxppr41vik71-vm-test-run-matrix-synapse ``` Or am I holding something wrong? `nix --version`: ``` nix (Lix, like Nix) 2.93.0-devpre20250128_f8a5927 System type: x86_64-linux Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux, x86_64-v4-linux Features: gc, signed-caches System configuration file: /etc/nix/nix.conf User configuration files: /home/ma27/.config/nix/nix.conf:/etc/xdg/nix/nix.conf:/home/ma27/.nix-profile/etc/xdg/nix/nix.conf:/nix/profile/etc/xdg/nix/nix.conf:/home/ma27/.local/state/nix/profile/etc/xdg/nix/nix.conf:/etc/profiles/per-user/ma27/etc/xdg/nix/nix.conf:/nix/var/nix/profiles/default/etc/xdg/nix/nix.conf:/run/current-system/sw/etc/xdg/nix/nix.conf Store directory: /nix/store State directory: /nix/var/nix Data directory: /nix/store/2a2wqna4gi2sg78g1mhxhqnm1sj9xlin-lix-2.93.0-devpre20250128_f8a5927/share ```
Author
Owner

one of the fun ways it's fucked is because of sudo env-vars. that one's pretty evil.

one of the fun ways it's fucked is because of sudo env-vars. that one's pretty evil.
Author
Owner

okay here's a test that can be used to start on fixing this:

diff --git a/tests/functional/gc-self-ref.nix b/tests/functional/gc-self-ref.nix
new file mode 100644
index 000000000..66b8ae218
--- /dev/null
+++ b/tests/functional/gc-self-ref.nix
@@ -0,0 +1,6 @@
+with import ./config.nix;
+mkDerivation {
+  name = "self-ref";
+  outputs = [ "out" "dev" ];
+  buildCommand = "echo $out > $out; echo $out $dev > $dev";
+}
diff --git a/tests/functional/gc-self-ref.sh b/tests/functional/gc-self-ref.sh
new file mode 100644
index 000000000..c2edb30c9
--- /dev/null
+++ b/tests/functional/gc-self-ref.sh
@@ -0,0 +1,11 @@
+source common.sh
+
+clearStore
+
+# this will not be a gc root
+paths=$(nix build --json --no-link -f gc-self-ref.nix)
+
+out=$(echo "$paths" | jq -r '.[0].outputs.out')
+dev=$(echo "$paths" | jq -r '.[0].outputs.dev')
+
+nix store delete "$out" "$dev"
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index c37a32e62..59799dbde 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -51,6 +51,7 @@ functional_tests_scripts = [
   'flakes/flake-registry.sh',
   'flakes/subdir-flake.sh',
   'gc.sh',
+  'gc-self-ref.sh',
   'nix-collect-garbage-d.sh',
   'nix-collect-garbage-dry-run.sh',
   'remote-store.sh',
okay here's a test that can be used to start on fixing this: ``` diff --git a/tests/functional/gc-self-ref.nix b/tests/functional/gc-self-ref.nix new file mode 100644 index 000000000..66b8ae218 --- /dev/null +++ b/tests/functional/gc-self-ref.nix @@ -0,0 +1,6 @@ +with import ./config.nix; +mkDerivation { + name = "self-ref"; + outputs = [ "out" "dev" ]; + buildCommand = "echo $out > $out; echo $out $dev > $dev"; +} diff --git a/tests/functional/gc-self-ref.sh b/tests/functional/gc-self-ref.sh new file mode 100644 index 000000000..c2edb30c9 --- /dev/null +++ b/tests/functional/gc-self-ref.sh @@ -0,0 +1,11 @@ +source common.sh + +clearStore + +# this will not be a gc root +paths=$(nix build --json --no-link -f gc-self-ref.nix) + +out=$(echo "$paths" | jq -r '.[0].outputs.out') +dev=$(echo "$paths" | jq -r '.[0].outputs.dev') + +nix store delete "$out" "$dev" diff --git a/tests/functional/meson.build b/tests/functional/meson.build index c37a32e62..59799dbde 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -51,6 +51,7 @@ functional_tests_scripts = [ 'flakes/flake-registry.sh', 'flakes/subdir-flake.sh', 'gc.sh', + 'gc-self-ref.sh', 'nix-collect-garbage-d.sh', 'nix-collect-garbage-dry-run.sh', 'remote-store.sh', ```
pennae added this to the 2.95 milestone 2025-12-01 14:51:44 +00:00
Member

@jade wrote in #494 (comment):

okay here's a test that can be used to start on fixing this:

diff --git a/tests/functional/gc-self-ref.nix b/tests/functional/gc-self-ref.nix
new file mode 100644
index 000000000..66b8ae218
--- /dev/null
+++ b/tests/functional/gc-self-ref.nix
@@ -0,0 +1,6 @@
+with import ./config.nix;
+mkDerivation {
+  name = "self-ref";
+  outputs = [ "out" "dev" ];
+  buildCommand = "echo $out > $out; echo $out $dev > $dev";
+}
diff --git a/tests/functional/gc-self-ref.sh b/tests/functional/gc-self-ref.sh
new file mode 100644
index 000000000..c2edb30c9
--- /dev/null
+++ b/tests/functional/gc-self-ref.sh
@@ -0,0 +1,11 @@
+source common.sh
+
+clearStore
+
+# this will not be a gc root
+paths=$(nix build --json --no-link -f gc-self-ref.nix)
+
+out=$(echo "$paths" | jq -r '.[0].outputs.out')
+dev=$(echo "$paths" | jq -r '.[0].outputs.dev')
+
+nix store delete "$out" "$dev"
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index c37a32e62..59799dbde 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -51,6 +51,7 @@ functional_tests_scripts = [
   'flakes/flake-registry.sh',
   'flakes/subdir-flake.sh',
   'gc.sh',
+  'gc-self-ref.sh',
   'nix-collect-garbage-d.sh',
   'nix-collect-garbage-dry-run.sh',
   'remote-store.sh',

Fun fact, this test will never succeed, even if the logic behind the self references is fixed.

This is because the process running nix store delete will have the env var out=/nix/store/... which will create a temp root through findPlatformRoots

@jade wrote in https://git.lix.systems/lix-project/lix/issues/494#issuecomment-13946: > okay here's a test that can be used to start on fixing this: > > ```text > diff --git a/tests/functional/gc-self-ref.nix b/tests/functional/gc-self-ref.nix > new file mode 100644 > index 000000000..66b8ae218 > --- /dev/null > +++ b/tests/functional/gc-self-ref.nix > @@ -0,0 +1,6 @@ > +with import ./config.nix; > +mkDerivation { > + name = "self-ref"; > + outputs = [ "out" "dev" ]; > + buildCommand = "echo $out > $out; echo $out $dev > $dev"; > +} > diff --git a/tests/functional/gc-self-ref.sh b/tests/functional/gc-self-ref.sh > new file mode 100644 > index 000000000..c2edb30c9 > --- /dev/null > +++ b/tests/functional/gc-self-ref.sh > @@ -0,0 +1,11 @@ > +source common.sh > + > +clearStore > + > +# this will not be a gc root > +paths=$(nix build --json --no-link -f gc-self-ref.nix) > + > +out=$(echo "$paths" | jq -r '.[0].outputs.out') > +dev=$(echo "$paths" | jq -r '.[0].outputs.dev') > + > +nix store delete "$out" "$dev" > diff --git a/tests/functional/meson.build b/tests/functional/meson.build > index c37a32e62..59799dbde 100644 > --- a/tests/functional/meson.build > +++ b/tests/functional/meson.build > @@ -51,6 +51,7 @@ functional_tests_scripts = [ > 'flakes/flake-registry.sh', > 'flakes/subdir-flake.sh', > 'gc.sh', > + 'gc-self-ref.sh', > 'nix-collect-garbage-d.sh', > 'nix-collect-garbage-dry-run.sh', > 'remote-store.sh', > ``` Fun fact, this test will never succeed, even if the logic behind the self references is fixed. This is because the process running `nix store delete` will have the env var `out=/nix/store/...` which will create a temp root through `findPlatformRoots`
Member

Additionally, the undeletable.nix reproducer does not reproduce for me

Additionally, the `undeletable.nix` reproducer does not reproduce for me
Member

I don't think out will be an env var, since it's not exported?

I don't think `out` will be an env var, since it's not exported?
Owner

it will be exported inside the devshell because devshells invoke stdenv and thus exports out, which bash then uses as a hint that the assignment should reëxport out with the new value. for the same reason it'll be exposed in a build sandbox, but not when loading the required packages (but not the rest of stdenv) via some other means (like direnv would iwrc). f2 would be safe from this because f2 controls test environments very carefully, but f1 is broken by design in this regard :/

it will be exported *inside the devshell* because devshells invoke stdenv and thus exports `out`, which bash then uses as a hint that the assignment should reëxport `out` with the new value. for the same reason it'll be exposed in a build sandbox, but not when loading the required packages (but not the rest of stdenv) via some other means (like direnv would iwrc). f2 would be safe from this because f2 controls test environments very carefully, but f1 is broken by design in this regard :/
Member

Using the following diff:

diff --git c/tests/functional/gc-self-ref.nix w/tests/functional/gc-self-ref.nix
new file mode 100644
index 000000000..c62da085a
--- /dev/null
+++ w/tests/functional/gc-self-ref.nix
@@ -0,0 +1,9 @@
+with import ./config.nix;
+mkDerivation {
+  name = "self-ref";
+  outputs = [
+    "out"
+    "dev"
+  ];
+  buildCommand = "echo $out > $out; echo $out $dev > $dev";
+}
diff --git c/tests/functional/gc-self-ref.sh w/tests/functional/gc-self-ref.sh
new file mode 100644
index 000000000..263ac59cf
--- /dev/null
+++ w/tests/functional/gc-self-ref.sh
@@ -0,0 +1,11 @@
+source common.sh
+
+clearStore
+
+# this will not be a gc root
+paths=$(nix build --json --no-link -f gc-self-ref.nix)
+
+_out=$(echo "$paths" | jq -r '.[0].outputs.out')
+_dev=$(echo "$paths" | jq -r '.[0].outputs.dev')
+
+nix -vvvv store delete "$_out" "$_dev"
diff --git c/tests/functional/meson.build w/tests/functional/meson.build
index 2b8e46bd4..f2e0f7dad 100644
--- c/tests/functional/meson.build
+++ w/tests/functional/meson.build
@@ -51,6 +51,7 @@ functional_tests_scripts = [
   'flakes/flake-registry.sh',
   'flakes/subdir-flake.sh',
   'gc.sh',
+  'gc-self-ref.sh',
   'nix-collect-garbage-d.sh',
   'nix-collect-garbage-dry-run.sh',
   'remote-store.sh',

lix builds successfully, so I believe that this issue can be closed as the previous failing test was an artifact of the name out being load-bearing

Using the following diff: ```diff diff --git c/tests/functional/gc-self-ref.nix w/tests/functional/gc-self-ref.nix new file mode 100644 index 000000000..c62da085a --- /dev/null +++ w/tests/functional/gc-self-ref.nix @@ -0,0 +1,9 @@ +with import ./config.nix; +mkDerivation { + name = "self-ref"; + outputs = [ + "out" + "dev" + ]; + buildCommand = "echo $out > $out; echo $out $dev > $dev"; +} diff --git c/tests/functional/gc-self-ref.sh w/tests/functional/gc-self-ref.sh new file mode 100644 index 000000000..263ac59cf --- /dev/null +++ w/tests/functional/gc-self-ref.sh @@ -0,0 +1,11 @@ +source common.sh + +clearStore + +# this will not be a gc root +paths=$(nix build --json --no-link -f gc-self-ref.nix) + +_out=$(echo "$paths" | jq -r '.[0].outputs.out') +_dev=$(echo "$paths" | jq -r '.[0].outputs.dev') + +nix -vvvv store delete "$_out" "$_dev" diff --git c/tests/functional/meson.build w/tests/functional/meson.build index 2b8e46bd4..f2e0f7dad 100644 --- c/tests/functional/meson.build +++ w/tests/functional/meson.build @@ -51,6 +51,7 @@ functional_tests_scripts = [ 'flakes/flake-registry.sh', 'flakes/subdir-flake.sh', 'gc.sh', + 'gc-self-ref.sh', 'nix-collect-garbage-d.sh', 'nix-collect-garbage-dry-run.sh', 'remote-store.sh', ``` lix builds successfully, so I believe that this issue can be closed as the previous failing test was an artifact of the name `out` being load-bearing
Owner
❯ nix build -f undeletable.nix --print-out-paths --no-link --builders ''

❯ nix-store --query --referrers /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable

❯ nix-store --query --referrers /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable | cat
/nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable

❯ nix-store --query --roots /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable | cat

❯ nix store delete /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
1 store paths deleted, 0.00 MiB freed

❯ cat /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable
cat: /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable: No such file or directory

so this is definitely fixed now

``` ❯ nix build -f undeletable.nix --print-out-paths --no-link --builders '' ❯ nix-store --query --referrers /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable ❯ nix-store --query --referrers /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable | cat /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable ❯ nix-store --query --roots /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable | cat ❯ nix store delete /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable 1 store paths deleted, 0.00 MiB freed ❯ cat /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable cat: /nix/store/fy2kwhzh8xiiw5dx549i37nv9giyj1hj-undeletable: No such file or directory ``` so this is definitely fixed now
Sign in to join this conversation.
No milestone
No project
No assignees
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lix-project/lix#494
No description provided.