about summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorSilvan Mosberger <silvan.mosberger@tweag.io>2023-12-13 04:33:31 +0100
committerSilvan Mosberger <silvan.mosberger@tweag.io>2023-12-13 17:12:52 +0100
commitd4b7b154072635827dcd473131273e1a8bc54097 (patch)
treeb0855a8f9e2366d8f25c525985f328e282d06673 /lib
parentc4e2e52a2113b59cf58da441f6833fd86c682f71 (diff)
downloadnixlib-d4b7b154072635827dcd473131273e1a8bc54097.tar
nixlib-d4b7b154072635827dcd473131273e1a8bc54097.tar.gz
nixlib-d4b7b154072635827dcd473131273e1a8bc54097.tar.bz2
nixlib-d4b7b154072635827dcd473131273e1a8bc54097.tar.lz
nixlib-d4b7b154072635827dcd473131273e1a8bc54097.tar.xz
nixlib-d4b7b154072635827dcd473131273e1a8bc54097.tar.zst
nixlib-d4b7b154072635827dcd473131273e1a8bc54097.zip
lib.path.hasStorePathPrefix: init
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/path/default.nix81
-rw-r--r--lib/path/tests/unit.nix30
2 files changed, 110 insertions, 1 deletions
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. */ {
 
   /*
@@ -322,6 +347,62 @@ in /* No rec! Add dependencies on this file at the top. */ {
     };
 
   /*
+    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.
 
     A subpath string points to a specific file or directory within an absolute base directory.
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;