Skip to content

testers.testEqualContents: Fix diffoscope false positive#393381

Closed
roberth wants to merge 1 commit intoNixOS:masterfrom
roberth:issue-393375
Closed

testers.testEqualContents: Fix diffoscope false positive#393381
roberth wants to merge 1 commit intoNixOS:masterfrom
roberth:issue-393375

Conversation

@roberth
Copy link
Copy Markdown
Member

@roberth roberth commented Mar 26, 2025

Not excited about this solution, but it gets the job done.
Ideally diffoscope could implement NAR-style content equality semantics for files and directories, but until it does, we need a fix.

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 25.05 Release Notes (or backporting 24.11 and 25.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@roberth roberth requested a review from tie March 26, 2025 12:05
@github-actions github-actions bot added the 6.topic: testing Tooling for automated testing of packages and modules label Mar 26, 2025
@nix-owners nix-owners bot requested a review from philiptaron March 26, 2025 12:06
@github-actions github-actions bot added 10.rebuild-darwin: 11-100 This PR causes between 11 and 100 packages to rebuild on Darwin. 10.rebuild-linux: 11-100 This PR causes between 11 and 100 packages to rebuild on Linux. labels Mar 26, 2025
@roberth
Copy link
Copy Markdown
Member Author

roberth commented Mar 26, 2025

@ofborg build tests.testers.testEqualContents

printf '%s\n' "$assertion"
if ! diffoscope --no-progress --text-color=always --exclude-directory-metadata=no -- "$actual" "$expected"
# --exclude-directory-metadata=yes: This reports false positives such as the internal directory sizes,
# so we work around this by excluding the directory metadata, and then checking the executable bits later.
Copy link
Copy Markdown
Member

@tie tie Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check executable bit for directories? I assume directories should always have executable bit set in Nix store since it cannot be represented in NAR.

Hm, though this doesn’t seem to be the case now…

nix-repl> :b pkgs.runCommand "foo" { } "mkdir -p $out/foo && chmod -x $out/foo"

This derivation produced the following outputs:
  out -> /nix/store/1xnsf9n8ar37vhbb0w6fjciaf8bkz7w1-foo

$ ls -al /nix/store/1xnsf9n8ar37vhbb0w6fjciaf8bkz7w1-foo
total 0
dr-xr-xr-x 1 root root     6 Jan  1  1970 ./
drwxrwxr-t 1 root nixbld 11M Mar 26 15:41 ../
dr--r--r-- 1 root root     0 Jan  1  1970 foo/

$ ls -al /nix/store/1xnsf9n8ar37vhbb0w6fjciaf8bkz7w1-foo/foo
ls: cannot access '/nix/store/1xnsf9n8ar37vhbb0w6fjciaf8bkz7w1-foo/foo/.': Permission denied
ls: cannot access '/nix/store/1xnsf9n8ar37vhbb0w6fjciaf8bkz7w1-foo/foo/..': Permission denied
total 0
d????????? ? ? ? ?            ? ./
d????????? ? ? ? ?            ? ../

$ nix nar pack /nix/store/1xnsf9n8ar37vhbb0w6fjciaf8bkz7w1-foo >foo.nar
$ nix nar ls --json --recursive foo.nar /
{"entries":{"foo":{"entries":{},"type":"directory"}},"type":"directory"}
nix-repl> :b pkgs.runCommand "foo" { } "mkdir -p $out/foo && touch $out/foo/bar && chmod -x $out/foo"

This derivation produced the following outputs:
  out -> /nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo

$ ls -al  /nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo
total 0
dr-xr-xr-x 1 root root     6 Jan  1  1970 ./
drwxrwxr-t 1 root nixbld 11M Mar 26 15:49 ../
dr--r--r-- 1 root root     6 Jan  1  1970 foo/

$ ls -al  /nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo/foo
ls: cannot access '/nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo/foo/.': Permission denied
ls: cannot access '/nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo/foo/..': Permission denied
ls: cannot access '/nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo/foo/bar': Permission denied
total 0
d????????? ? ? ? ?            ? ./
d????????? ? ? ? ?            ? ../
-????????? ? ? ? ?            ? bar

$ nix nar pack /nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo >foo.nar
error: getting status of '/nix/store/6nbxwp349lfbk3yrd2mgv3nya78s7bp8-foo/foo/bar': Permission denied

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC diffoscope considers the executable bit of a file to be part of the directory, and not part of a file, similarly to the typical unix file system data model or even that of Git. This is different from the NAR data model.

directory entry:
  name  ->  mode,  content/inode
                   ^^^^^^^^^^^^^--- diffoscope unit of composition
NAR item ("File System Object")
            mode,  content
            ^^^^^^^^^^^^^^--------- NAR unit of composition 
                                    store path content root

Do we need to check executable bit for directories?

Indeed NAR does not record this bit, so we shouldn't encounter any differences there.
This seems to be a bug in Nix store path canonicalization.
What kind of store did you use to achieve this - what does nix store ping say?

Copy link
Copy Markdown
Member

@tie tie Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of store did you use to achieve this - what does nix store ping say?

This is a default NixOS installation, user is trusted.

$ nix store ping
warning: 'nix store ping' is a deprecated alias for 'nix store info'
Store URL: daemon
Version: 2.25.5
Trusted: 1
cat /etc/nix/nix.conf
# WARNING: this file is generated from the nix.* options in
# your NixOS configuration, typically
# /etc/nixos/configuration.nix.  Do not edit it!
allowed-users = *
auto-optimise-store = false
builders = 
cores = 0
experimental-features = nix-command flakes
max-jobs = auto
require-sigs = true
sandbox = true
sandbox-fallback = false
substituters = https://cache.nixos.org/
system-features = nixos-test benchmark big-parallel kvm
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
trusted-substituters = 
trusted-users = root @wheel
use-xdg-base-directories = true
warn-dirty = false
extra-sandbox-paths =

I can also reproduce this on stable Nix version from nixos-24.11:

Details
$ nix store info
Store URL: daemon
Version: 2.24.12
Trusted: 1
$ nix repl
Nix 2.24.12
Type :? for help.
nix-repl> :lf nixpkgs
Added 16 variables.

nix-repl> lib.version
"24.11.20250327.b9c013a"

nix-repl> pkgs = legacyPackages.x86_64-linux

nix-repl> :b pkgs.runCommand "foo" { } "mkdir -p $out/foo && touch $out/foo/bar && chmod -x $out/foo"

This derivation produced the following outputs:
  out -> /nix/store/q4xw50il9f6fi6xs644wisqdx0vr6x1f-foo
[1 built, 0.0 MiB DL]
$ nix nar pack /nix/store/q4xw50il9f6fi6xs644wisqdx0vr6x1f-foo >/dev/null
error: getting status of '/nix/store/q4xw50il9f6fi6xs644wisqdx0vr6x1f-foo/foo/bar': Permission denied
$ stat /nix/store/q4xw50il9f6fi6xs644wisqdx0vr6x1f-foo/foo
  File: /nix/store/q4xw50il9f6fi6xs644wisqdx0vr6x1f-foo/foo
  Size: 6               Blocks: 0          IO Block: 4096   directory
Device: 0,43    Inode: 161713277   Links: 1
Access: (0444/dr--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-03-27 19:51:13.000000000 +0300
Modify: 1970-01-01 03:00:01.000000000 +0300
Change: 2025-03-27 19:51:13.297001247 +0300
 Birth: 2025-03-27 19:51:13.292001218 +0300

Alternatively,

nix build --impure --expr 'with import ./. { };
let
  script = "mkdir -p $out/foo && touch $out/foo/bar && chmod -x $out/foo";
  badDir = runCommand "bad-directory-permissions" { } script;
in runCommand "cat-file" { inherit badDir; } "stat $badDir/foo && cat $badDir/foo/bar"'
cat-file>   File: /nix/store/99swczlgqpmkqvsd8vzzlqygxw56giqz-bad-directory-permissions/foo
cat-file>   Size: 6             Blocks: 0          IO Block: 4096   directory
cat-file> Device: 0,33  Inode: 242781643   Links: 1
cat-file> Access: (0444/dr--r--r--)  Uid: (65534/  nobody)   Gid: (65534/ nogroup)
cat-file> Access: 2025-03-27 16:59:12.000000000 +0000
cat-file> Modify: 1970-01-01 00:00:01.000000000 +0000
cat-file> Change: 2025-03-27 16:59:12.461890171 +0000
cat-file>  Birth: 2025-03-27 16:59:12.461890171 +0000
cat-file> cat: /nix/store/99swczlgqpmkqvsd8vzzlqygxw56giqz-bad-directory-permissions/foo/bar: Permission denied

Copy link
Copy Markdown
Member

@tie tie Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like permissions are set in canonicaliseTimestampAndPermissions and 0444 is not updated to 0555 for directories.

E.g. 0554 is canonicalized, but not 0444.

Details
$ nix build -L --impure --expr 'with import ./. { };
let
  script = "mkdir -p $out/foo && touch $out/foo/bar && chmod o-x $out/foo";
  badDir = runCommand "bad-directory-permissions" { } script;
in runCommand "cat-file" { inherit badDir; } "stat $badDir/foo && cat $badDir/foo/bar && touch $out"'
cat-file>   File: /nix/store/42fsalzfn075vhhxbmrvbma37v3wf5wz-bad-directory-permissions/foo
cat-file>   Size: 6             Blocks: 0          IO Block: 4096   directory
cat-file> Device: 0,33  Inode: 242782051   Links: 1
cat-file> Access: (0555/dr-xr-xr-x)  Uid: (65534/  nobody)   Gid: (65534/ nogroup)
cat-file> Access: 2025-03-27 17:18:14.000000000 +0000
cat-file> Modify: 1970-01-01 00:00:01.000000000 +0000
cat-file> Change: 2025-03-27 17:18:14.682332844 +0000
cat-file>  Birth: 2025-03-27 17:18:14.682332844 +0000

Perhaps

-if (mode != 0444 && mode != 0555) {
+bool is_dir = S_ISDIR(st.st_mode);
+if ((mode != 0444 || is_dir) && mode != 0555) {
     mode = (st.st_mode & S_IFMT)
          | 0444
-         | (st.st_mode & S_IXUSR ? 0111 : 0);
+         | (st.st_mode & S_IXUSR || is_dir ? 0111 : 0);
     if (chmod(path.c_str(), mode) == -1)
         throw SysError("changing mode of '%1%' to %2$o", path, mode);
     }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I've forwarded this to nix, NixOS/nix#12786

@tie
Copy link
Copy Markdown
Member

tie commented Mar 26, 2025

Not excited about this solution, but it gets the job done.

It’s fine as a temporary workaround, but I think we should submit a patch that removes st_size from directory stat comparison (since it’s filesystem-dependent). Note that directory entries are always diffed before stat metadata.
See

Note: I don’t have Debian Salsa account and registration confirmation can take up to a few days (see https://wiki.debian.org/Salsa/Doc#Users).

@tie
Copy link
Copy Markdown
Member

tie commented Mar 26, 2025

Submitted fix in https://salsa.debian.org/reproducible-builds/diffoscope/-/merge_requests/150

@tie
Copy link
Copy Markdown
Member

tie commented Mar 31, 2025

This issue should be fixed upstream in https://diffoscope.org/news/diffoscope-292-released (starting with version 293).
nixos-24.11 is currently at 283, unstable at 289.
Changelog: https://salsa.debian.org/reproducible-builds/diffoscope/-/blob/master/debian/changelog?ref_type=heads

@wegank wegank added the 2.status: merge conflict This PR has merge conflicts with the target branch label Apr 2, 2025
xokdvium added a commit to xokdvium/nix that referenced this pull request May 29, 2025
The underlying bug seems to have been fixed in diffoscope 293 [1] [2].
Our nixpkgs input has 295.

[1]: NixOS/nixpkgs#393381 (comment)
[2]: https://diffoscope.org/news/diffoscope-292-released/
Copy link
Copy Markdown
Contributor

@philiptaron philiptaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that both diffoscope and Nix have fixes, we can close this without merging I believe.

@roberth roberth closed this Aug 8, 2025
@roberth
Copy link
Copy Markdown
Member Author

roberth commented Aug 8, 2025

Thanks again, tie!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.status: merge conflict This PR has merge conflicts with the target branch 6.topic: testing Tooling for automated testing of packages and modules 10.rebuild-darwin: 11-100 This PR causes between 11 and 100 packages to rebuild on Darwin. 10.rebuild-linux: 11-100 This PR causes between 11 and 100 packages to rebuild on Linux.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

testEqualContents: false positive on directory size

5 participants