From d4b7b154072635827dcd473131273e1a8bc54097 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Wed, 13 Dec 2023 04:33:31 +0100 Subject: [PATCH] lib.path.hasStorePathPrefix: init Co-authored-by: Robert Hensing --- lib/path/default.nix | 81 +++++++++++++++++++++++++++++++++++++++++ lib/path/tests/unit.nix | 30 ++++++++++++++- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/lib/path/default.nix b/lib/path/default.nix index ab8a9e2202e9..e6b385c0aee0 100644 --- a/lib/path/default.nix +++ b/lib/path/default.nix @@ -9,6 +9,7 @@ let split match typeOf + storeDir ; inherit (lib.lists) @@ -24,6 +25,8 @@ let drop ; + listHasPrefix = lib.lists.hasPrefix; + inherit (lib.strings) concatStringsSep substring @@ -120,6 +123,28 @@ let else recurse ([ (baseNameOf base) ] ++ components) (dirOf base); in recurse []; + # The components of the store directory, typically [ "nix" "store" ] + storeDirComponents = splitRelPath ("./" + storeDir); + # The number of store directory components, typically 2 + storeDirLength = length storeDirComponents; + + # Type: [ String ] -> Bool + # + # Whether path components have a store path as a prefix, according to + # https://nixos.org/manual/nix/stable/store/store-path.html#store-path. + componentsHaveStorePathPrefix = components: + # path starts with the store directory (typically /nix/store) + listHasPrefix storeDirComponents components + # is not the store directory itself, meaning there's at least one extra component + && storeDirComponents != components + # and the first component after the store directory has the expected format. + # NOTE: We could change the hash regex to be [0-9a-df-np-sv-z], + # because these are the actual ASCII characters used by Nix's base32 implementation, + # but this is not fully specified, so let's tie this too much to the currently implemented concept of store paths. + # Similar reasoning applies to the validity of the name part. + # We care more about discerning store path-ness on realistic values. Making it airtight would be fragile and slow. + && match ".{32}-.+" (elemAt components storeDirLength) != null; + in /* No rec! Add dependencies on this file at the top. */ { /* @@ -321,6 +346,62 @@ in /* No rec! Add dependencies on this file at the top. */ { subpath = joinRelPath deconstructed.components; }; + /* + Whether a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path) + has a [store path](https://nixos.org/manual/nix/stable/store/store-path.html#store-path) + as a prefix. + + :::{.note} + As with all functions of this `lib.path` library, it does not work on paths in strings, + which is how you'd typically get store paths. + + Instead, this function only handles path values themselves, + which occur when Nix files in the store use relative path expressions. + ::: + + Type: + hasStorePathPrefix :: Path -> Bool + + Example: + # Subpaths of derivation outputs have a store path as a prefix + hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo/bar/baz + => true + + # The store directory itself is not a store path + hasStorePathPrefix /nix/store + => false + + # Derivation outputs are store paths themselves + hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo + => true + + # Paths outside the Nix store don't have a store path prefix + hasStorePathPrefix /home/user + => false + + # Not all paths under the Nix store are store paths + hasStorePathPrefix /nix/store/.links/10gg8k3rmbw8p7gszarbk7qyd9jwxhcfq9i6s5i0qikx8alkk4hq + => false + + # Store derivations are also store paths themselves + hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo.drv + => true + */ + hasStorePathPrefix = path: + let + deconstructed = deconstructPath path; + in + assert assertMsg + (isPath path) + "lib.path.hasStorePathPrefix: Argument is of type ${typeOf path}, but a path was expected"; + assert assertMsg + # This function likely breaks or needs adjustment if used with other filesystem roots, if they ever get implemented. + # Let's try to error nicely in such a case, though it's unclear how an implementation would work even and whether this could be detected. + # See also https://github.com/NixOS/nix/pull/6530#discussion_r1422843117 + (deconstructed.root == /. && toString deconstructed.root == "/") + "lib.path.hasStorePathPrefix: Argument has a filesystem root (${toString deconstructed.root}) that's not /, which is currently not supported."; + componentsHaveStorePathPrefix deconstructed.components; + /* Whether a value is a valid subpath string. diff --git a/lib/path/tests/unit.nix b/lib/path/tests/unit.nix index bad6560f13a9..9b0a0b2714aa 100644 --- a/lib/path/tests/unit.nix +++ b/lib/path/tests/unit.nix @@ -3,7 +3,10 @@ { libpath }: let lib = import libpath; - inherit (lib.path) hasPrefix removePrefix append splitRoot subpath; + inherit (lib.path) hasPrefix removePrefix append splitRoot hasStorePathPrefix subpath; + + # This is not allowed generally, but we're in the tests here, so we'll allow ourselves. + storeDirPath = /. + builtins.storeDir; cases = lib.runTests { # Test examples from the lib.path.append documentation @@ -91,6 +94,31 @@ let expected = false; }; + testHasStorePathPrefixExample1 = { + expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo/bar/baz"); + expected = true; + }; + testHasStorePathPrefixExample2 = { + expr = hasStorePathPrefix storeDirPath; + expected = false; + }; + testHasStorePathPrefixExample3 = { + expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo"); + expected = true; + }; + testHasStorePathPrefixExample4 = { + expr = hasStorePathPrefix /home/user; + expected = false; + }; + testHasStorePathPrefixExample5 = { + expr = hasStorePathPrefix (storeDirPath + "/.links/10gg8k3rmbw8p7gszarbk7qyd9jwxhcfq9i6s5i0qikx8alkk4hq"); + expected = false; + }; + testHasStorePathPrefixExample6 = { + expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo.drv"); + expected = true; + }; + # Test examples from the lib.path.subpath.isValid documentation testSubpathIsValidExample1 = { expr = subpath.isValid null;