From 6e1e6fc6426ff3d7323153dc7f042e3dbe455b90 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 21 Mar 2019 00:01:40 -0400 Subject: [PATCH] Add System.Nix.StorePath, successor to System.Nix.Path. The new type acknowledges the store root, and is better named. Future work will migrate all dependents and retire the old module. --- hnix-store-core/hnix-store-core.cabal | 4 +- .../src/System/Nix/Internal/StorePath.hs | 141 ++++++++++++++++++ hnix-store-core/src/System/Nix/StorePath.hs | 19 +++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 hnix-store-core/src/System/Nix/Internal/StorePath.hs create mode 100644 hnix-store-core/src/System/Nix/StorePath.hs diff --git a/hnix-store-core/hnix-store-core.cabal b/hnix-store-core/hnix-store-core.cabal index 41c865c..cf73f2e 100644 --- a/hnix-store-core/hnix-store-core.cabal +++ b/hnix-store-core/hnix-store-core.cabal @@ -1,5 +1,5 @@ name: hnix-store-core -version: 0.1.0.0 +version: 0.1.1.0 synopsis: Core effects for interacting with the Nix store. description: This package contains types and functions needed to describe @@ -23,10 +23,12 @@ library , System.Nix.GC , System.Nix.Hash , System.Nix.Internal.Hash + , System.Nix.Internal.StorePath , System.Nix.Nar , System.Nix.Path , System.Nix.ReadonlyStore , System.Nix.Store + , System.Nix.StorePath , System.Nix.Util build-depends: base >=4.10 && <5 , base16-bytestring diff --git a/hnix-store-core/src/System/Nix/Internal/StorePath.hs b/hnix-store-core/src/System/Nix/Internal/StorePath.hs new file mode 100644 index 0000000..b3be3d4 --- /dev/null +++ b/hnix-store-core/src/System/Nix/Internal/StorePath.hs @@ -0,0 +1,141 @@ +{-| +Description : Representation of Nix store paths. +-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE RecordWildCards #-} +module System.Nix.Internal.StorePath where +import System.Nix.Hash (HashAlgorithm(Truncated, SHA256), Digest, encodeBase32) +import Text.Regex.Base.RegexLike (makeRegex, matchTest) +import Text.Regex.TDFA.Text (Regex) +import Data.Text (Text) +import Data.Text.Encoding (encodeUtf8) +import GHC.TypeLits (Symbol, KnownSymbol, symbolVal) +import Data.ByteString (ByteString) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as BC + +-- | A path in a Nix store. +-- +-- From the Nix thesis: A store path is the full path of a store +-- object. It has the following anatomy: storeDir/hashPart-name. +-- +-- @storeDir@: The root of the Nix store (e.g. \/nix\/store). +-- +-- See the 'StoreDir' haddocks for details on why we represent this at +-- the type level. +data StorePath (storeDir :: StoreDir) = StorePath + { -- | The 160-bit hash digest reflecting the "address" of the name. + -- Currently, this is a truncated SHA256 hash. + storePathHash :: !(Digest StorePathHashAlgo) + , -- | The (typically human readable) name of the path. For packages + -- this is typically the package name and version (e.g. + -- hello-1.2.3). + storePathName :: !StorePathName + } + +-- | The name portion of a Nix path. +-- +-- 'unStorePathName' must only contain a-zA-Z0-9+._?=-, can't start +-- with a -, and must have at least one character (i.e. it must match +-- 'storePathNameRegex'). +newtype StorePathName = StorePathName + { -- | Extract the contents of the name. + unStorePathName :: Text + } + +-- | The hash algorithm used for store path hashes. +type StorePathHashAlgo = 'Truncated 20 'SHA256 + +-- | A type-level representation of the root directory of a Nix store. +-- +-- The extra complexity of type indices requires justification. +-- Fundamentally, this boils down to the fact that there is little +-- meaningful sense in which 'StorePath's rooted at different +-- directories are of the same type, i.e. there are few if any +-- non-trivial non-contrived functions or data types that could +-- equally well accept 'StorePath's from different stores. In current +-- practice, any real application dealing with Nix stores (including, +-- in particular, the Nix expression language) only operates over one +-- store root and only cares about 'StorePath's belonging to that +-- root. One could imagine a use case that cares about multiple store +-- roots at once (e.g. the normal \/nix\/store along with some private +-- store at \/root\/nix\/store to contain secrets), but in that case +-- distinguishing 'StorePath's that belong to one store or the other +-- is even /more/ critical: Most operations will only be correct over +-- one of the stores or another, and it would be an error to mix and +-- match (e.g. a 'StorePath' in one store could not legitimately refer +-- to one in another). +-- +-- As of @5886bc5996537fbf00d1fcfbb29595b8ccc9743e@, the C++ Nix +-- codebase contains 30 separate places where we assert that a given +-- store dir is, in fact, in the store we care about; those run-time +-- assertions could be completely removed if we had stronger types +-- there. Moreover, there are dozens of other cases where input coming +-- from the user, from serializations, etc. is parsed and then +-- required to be in the appropriate store; this case is the +-- equivalent of an existentially quantified version of 'StorePath' +-- and, notably, requiring at runtime that the index matches the +-- ambient store directory we're working in. In every case where a +-- path is treated as a store path, there is exactly one legitimate +-- candidate for the store directory it belongs to. +-- +-- It may be instructive to consider the example of "chroot stores". +-- Since Nix 2.0, it has been possible to have a store actually live +-- at one directory (say, $HOME\/nix\/store) with a different +-- effective store directory (say, \/nix\/store). Nix can build into +-- a chroot store by running the builds in a mount namespace where the +-- store is at the effective store directory, can download from a +-- binary cache containing paths for the effective store directory, +-- and can run programs in the store that expect to be living at the +-- effective store directory (via nix run). When viewed as store paths +-- (rather than random files in the filesystem), paths in a chroot +-- store have nothing in common with paths in a non-chroot store that +-- lives in the same directory, and a lot in common with paths in a +-- non-chroot store that lives in the effective store directory of the +-- store in question. Store paths in stores with the same effective +-- store directory share the same hashing scheme, can be copied +-- between each other, etc. Store paths in stores with different +-- effective store directories have no relationship to each other that +-- they don't have to arbitrary other files. +type StoreDir = Symbol + +-- | Smart constructor for 'StorePathName' that ensures the underlying +-- content invariant is met. +makeStorePathName :: Text -> Maybe StorePathName +makeStorePathName n = case matchTest storePathNameRegex n of + True -> Just $ StorePathName n + False -> Nothing + +-- | Regular expression to match valid store path names. +storePathNameRegex :: Regex +storePathNameRegex = makeRegex r + where + r :: String + r = "[a-zA-Z0-9\\+\\-\\_\\?\\=][a-zA-Z0-9\\+\\-\\.\\_\\?\\=]*" + +-- | Copied from @RawFilePath@ in the @unix@ package, duplicated here +-- to avoid the dependency. +type RawFilePath = ByteString + +-- | Render a 'StorePath' as a 'RawFilePath'. +storePathToRawFilePath + :: (KnownStoreDir storeDir) + => StorePath storeDir + -> RawFilePath +storePathToRawFilePath s@(StorePath {..}) = BS.concat + [ root + , "/" + , hashPart + , "-" + , name + ] + where + root = BC.pack $ symbolVal s + hashPart = encodeUtf8 $ encodeBase32 storePathHash + name = encodeUtf8 $ unStorePathName storePathName + +-- | A 'StoreDir' whose value is known at compile time. +type KnownStoreDir = KnownSymbol diff --git a/hnix-store-core/src/System/Nix/StorePath.hs b/hnix-store-core/src/System/Nix/StorePath.hs new file mode 100644 index 0000000..7aab6f7 --- /dev/null +++ b/hnix-store-core/src/System/Nix/StorePath.hs @@ -0,0 +1,19 @@ +{-| +Description : Representation of Nix store paths. +-} +module System.Nix.StorePath + ( -- * Basic store path types + StorePath(..) + , StorePathName + , StorePathHashAlgo + , StoreDir + , -- * Manipulating 'StorePathName' + makeStorePathName + , unStorePathName + , storePathNameRegex + , -- * Rendering out 'StorePath's + storePathToRawFilePath + , KnownStoreDir + ) where + +import System.Nix.Internal.StorePath