Skip to content

Updating the nix input (e.g. #170) breaks runtime with "Symbolic link loop" #171

Description

@Hayao0819

The documented way to upgrade Nix in nix-portable is to override the nix flake input (see #27). With recent Nix versions (2.34, and likely any post-split-outputs release) this produces a binary that builds fine but fails at runtime:

error: unable to exec '~/.nix-portable/nix/store/q7f0d…-nix-2.34.6/bin/nix-store': Symbolic link loop

#170 is the most obvious trigger — it does exactly this input bump — but the underlying breakage is in the runtime, not the PR. Anyone following the #27 guidance on a modern Nix will hit it.

Reproduce

git fetch origin pull/170/head:pr-170
git checkout main && git merge pr-170
nix build .#nix-portable
rm -rf ~/.nix-portable
./result/bin/nix-portable nix --version

main alone works; main with the nix input bumped (or #170 merged) fails.

Cause

Modern Nix uses split outputs. bin/nix is an absolute symlink to another store path:

$ readlink /nix/store/q7f0d…-nix-2.34.6/bin/nix
/nix/store/3km45…-nix-2.34.6/bin/nix

The runtime extracts the embedded tarball with busybox tar (PATH is locked to \$dir/busybox/bin). busybox tar silently strips the leading / from symlink targets; GNU tar does not:

$ ln -sf /tmp/foo /tmp/symtest/link
$ tar cf t.tar -C /tmp symtest
$ busybox tar -xf t.tar -C /tmp/a && readlink /tmp/a/symtest/link
tmp/foo
$ tar       -xf t.tar -C /tmp/b && readlink /tmp/b/symtest/link
/tmp/foo

After extraction:

~/.nix-portable/nix/store/q7f0d…/bin/nix -> nix/store/3km45…/bin/nix   # relative, broken

Combined with the sibling nix-store -> nix, the kernel returns ELOOP.

The tarball itself is correct — the breakage is at extraction time:

$ zstd -dc storeTar/tar | tar -tvf - | grep 'bin/nix '
lrwxrwxrwx  nix/store/q7f0d…/bin/nix -> /nix/store/3km45…/bin/nix

The shipped 2.20.6 happens to dodge this because all its commands were sibling relative symlinks (`nix-build -> nix`) in one directory. Any Nix version that uses split outputs will trip it.

Impact

  • Upgrade nix version #170 cannot be merged as-is.
  • The flake-input-override upgrade path documented in How to upgrade nix? #27 is broken against current Nix.
  • Users who pin an older Nix won't see this, but newer pins (including upstream `master`) will.

Fix options

  1. Rewrite `nix/store/...` symlink targets to `/nix/store/...` after extraction.
  2. Use GNU static tar at runtime. `gnutar = pkgs.pkgsStatic.gnutar` is already an input but never installed into `$dir/bin`.
  3. Pin Nix to a pre-split-outputs version in the flake until the runtime is fixed.

Env

Arch Linux x86_64, kernel 7.0.5-zen.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions