Skip to content

Commit 67f1f0b

Browse files
committed
Migrate System.Nix.Path to System.Nix.StorePath
2 parents d26c592 + 2ad76d2 commit 67f1f0b

File tree

17 files changed

+228
-677
lines changed

17 files changed

+228
-677
lines changed

default.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010
in {
1111
haskellPackages =
1212
pkgs.haskellPackages.override overrideHaskellPackages;
13+
haskell844Packages =
14+
pkgs.haskell.packages.ghc844.override overrideHaskellPackages;
1315
inherit pkgs;
1416
}

hnix-store-core/hnix-store-core.cabal

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: hnix-store-core
2-
version: 0.1.0.0
2+
version: 0.2.0.0
33
synopsis: Core effects for interacting with the Nix store.
44
description:
55
This package contains types and functions needed to describe
@@ -19,14 +19,12 @@ cabal-version: >=1.10
1919
library
2020
exposed-modules: System.Nix.Base32
2121
, System.Nix.Build
22-
, System.Nix.Derivation
23-
, System.Nix.GC
2422
, System.Nix.Hash
2523
, System.Nix.Internal.Hash
24+
, System.Nix.Internal.StorePath
2625
, System.Nix.Nar
27-
, System.Nix.Path
2826
, System.Nix.ReadonlyStore
29-
, System.Nix.Store
27+
, System.Nix.StorePath
3028
, System.Nix.Util
3129
build-depends: base >=4.10 && <5
3230
, base16-bytestring

hnix-store-core/src/System/Nix/Build.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ module System.Nix.Build (
1212

1313
import Data.Text (Text)
1414
import Data.HashSet (HashSet)
15-
import System.Nix.Path (Path)
1615

1716
data BuildMode = Normal | Repair | Check
1817
deriving (Eq, Ord, Enum, Show)

hnix-store-core/src/System/Nix/Derivation.hs

Lines changed: 0 additions & 30 deletions
This file was deleted.

hnix-store-core/src/System/Nix/GC.hs

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
{-|
2+
Description : Representation of Nix store paths.
3+
-}
4+
{-# LANGUAGE DataKinds #-}
5+
{-# LANGUAGE OverloadedStrings #-}
6+
{-# LANGUAGE KindSignatures #-}
7+
{-# LANGUAGE ConstraintKinds #-}
8+
{-# LANGUAGE RecordWildCards #-}
9+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
10+
{-# LANGUAGE TypeApplications #-}
11+
{-# LANGUAGE ScopedTypeVariables #-}
12+
{-# LANGUAGE AllowAmbiguousTypes #-}
13+
{-# LANGUAGE TypeInType #-} -- Needed for GHC 8.4.4 for some reason
14+
module System.Nix.Internal.StorePath where
15+
import System.Nix.Hash
16+
( HashAlgorithm(Truncated, SHA256)
17+
, Digest
18+
, encodeBase32
19+
)
20+
import Text.Regex.Base.RegexLike (makeRegex, matchTest)
21+
import Text.Regex.TDFA.Text (Regex)
22+
import Data.Text (Text)
23+
import Data.Text.Encoding (encodeUtf8)
24+
import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
25+
import Data.ByteString (ByteString)
26+
import qualified Data.ByteString as BS
27+
import qualified Data.ByteString.Char8 as BC
28+
import Data.Hashable (Hashable(..))
29+
import Data.HashSet (HashSet)
30+
import Data.Proxy (Proxy(..))
31+
32+
-- | A path in a Nix store.
33+
--
34+
-- From the Nix thesis: A store path is the full path of a store
35+
-- object. It has the following anatomy: storeDir/hashPart-name.
36+
--
37+
-- @storeDir@: The root of the Nix store (e.g. \/nix\/store).
38+
--
39+
-- See the 'StoreDir' haddocks for details on why we represent this at
40+
-- the type level.
41+
data StorePath (storeDir :: StoreDir) = StorePath
42+
{ -- | The 160-bit hash digest reflecting the "address" of the name.
43+
-- Currently, this is a truncated SHA256 hash.
44+
storePathHash :: !(Digest StorePathHashAlgo)
45+
, -- | The (typically human readable) name of the path. For packages
46+
-- this is typically the package name and version (e.g.
47+
-- hello-1.2.3).
48+
storePathName :: !StorePathName
49+
}
50+
51+
instance Hashable (StorePath storeDir) where
52+
hashWithSalt s (StorePath {..}) =
53+
s `hashWithSalt` storePathHash `hashWithSalt` storePathName
54+
55+
-- | The name portion of a Nix path.
56+
--
57+
-- 'unStorePathName' must only contain a-zA-Z0-9+._?=-, can't start
58+
-- with a -, and must have at least one character (i.e. it must match
59+
-- 'storePathNameRegex').
60+
newtype StorePathName = StorePathName
61+
{ -- | Extract the contents of the name.
62+
unStorePathName :: Text
63+
} deriving (Hashable)
64+
65+
-- | The hash algorithm used for store path hashes.
66+
type StorePathHashAlgo = 'Truncated 20 'SHA256
67+
68+
-- | A set of 'StorePath's.
69+
type StorePathSet storeDir = HashSet (StorePath storeDir)
70+
71+
-- | A type-level representation of the root directory of a Nix store.
72+
--
73+
-- The extra complexity of type indices requires justification.
74+
-- Fundamentally, this boils down to the fact that there is little
75+
-- meaningful sense in which 'StorePath's rooted at different
76+
-- directories are of the same type, i.e. there are few if any
77+
-- non-trivial non-contrived functions or data types that could
78+
-- equally well accept 'StorePath's from different stores. In current
79+
-- practice, any real application dealing with Nix stores (including,
80+
-- in particular, the Nix expression language) only operates over one
81+
-- store root and only cares about 'StorePath's belonging to that
82+
-- root. One could imagine a use case that cares about multiple store
83+
-- roots at once (e.g. the normal \/nix\/store along with some private
84+
-- store at \/root\/nix\/store to contain secrets), but in that case
85+
-- distinguishing 'StorePath's that belong to one store or the other
86+
-- is even /more/ critical: Most operations will only be correct over
87+
-- one of the stores or another, and it would be an error to mix and
88+
-- match (e.g. a 'StorePath' in one store could not legitimately refer
89+
-- to one in another).
90+
--
91+
-- As of @5886bc5996537fbf00d1fcfbb29595b8ccc9743e@, the C++ Nix
92+
-- codebase contains 30 separate places where we assert that a given
93+
-- store dir is, in fact, in the store we care about; those run-time
94+
-- assertions could be completely removed if we had stronger types
95+
-- there. Moreover, there are dozens of other cases where input coming
96+
-- from the user, from serializations, etc. is parsed and then
97+
-- required to be in the appropriate store; this case is the
98+
-- equivalent of an existentially quantified version of 'StorePath'
99+
-- and, notably, requiring at runtime that the index matches the
100+
-- ambient store directory we're working in. In every case where a
101+
-- path is treated as a store path, there is exactly one legitimate
102+
-- candidate for the store directory it belongs to.
103+
--
104+
-- It may be instructive to consider the example of "chroot stores".
105+
-- Since Nix 2.0, it has been possible to have a store actually live
106+
-- at one directory (say, $HOME\/nix\/store) with a different
107+
-- effective store directory (say, \/nix\/store). Nix can build into
108+
-- a chroot store by running the builds in a mount namespace where the
109+
-- store is at the effective store directory, can download from a
110+
-- binary cache containing paths for the effective store directory,
111+
-- and can run programs in the store that expect to be living at the
112+
-- effective store directory (via nix run). When viewed as store paths
113+
-- (rather than random files in the filesystem), paths in a chroot
114+
-- store have nothing in common with paths in a non-chroot store that
115+
-- lives in the same directory, and a lot in common with paths in a
116+
-- non-chroot store that lives in the effective store directory of the
117+
-- store in question. Store paths in stores with the same effective
118+
-- store directory share the same hashing scheme, can be copied
119+
-- between each other, etc. Store paths in stores with different
120+
-- effective store directories have no relationship to each other that
121+
-- they don't have to arbitrary other files.
122+
type StoreDir = Symbol
123+
124+
-- | Smart constructor for 'StorePathName' that ensures the underlying
125+
-- content invariant is met.
126+
makeStorePathName :: Text -> Maybe StorePathName
127+
makeStorePathName n = case matchTest storePathNameRegex n of
128+
True -> Just $ StorePathName n
129+
False -> Nothing
130+
131+
-- | Regular expression to match valid store path names.
132+
storePathNameRegex :: Regex
133+
storePathNameRegex = makeRegex r
134+
where
135+
r :: String
136+
r = "[a-zA-Z0-9\\+\\-\\_\\?\\=][a-zA-Z0-9\\+\\-\\.\\_\\?\\=]*"
137+
138+
-- | Copied from @RawFilePath@ in the @unix@ package, duplicated here
139+
-- to avoid the dependency.
140+
type RawFilePath = ByteString
141+
142+
-- | Render a 'StorePath' as a 'RawFilePath'.
143+
storePathToRawFilePath
144+
:: forall storeDir . (KnownStoreDir storeDir)
145+
=> StorePath storeDir
146+
-> RawFilePath
147+
storePathToRawFilePath (StorePath {..}) = BS.concat
148+
[ root
149+
, "/"
150+
, hashPart
151+
, "-"
152+
, name
153+
]
154+
where
155+
root = storeDirVal @storeDir
156+
hashPart = encodeUtf8 $ encodeBase32 storePathHash
157+
name = encodeUtf8 $ unStorePathName storePathName
158+
159+
-- | Get a value-level representation of a 'KnownStoreDir'
160+
storeDirVal :: forall storeDir . (KnownStoreDir storeDir)
161+
=> ByteString
162+
storeDirVal = BC.pack $ symbolVal @storeDir Proxy
163+
164+
-- | A 'StoreDir' whose value is known at compile time.
165+
--
166+
-- A valid instance of 'KnownStoreDir' should represent a valid path,
167+
-- i.e. all "characters" fit into bytes (as determined by the logic of
168+
-- 'BC.pack') and there are no 0 "characters". Currently this is not
169+
-- enforced, but it should be.
170+
type KnownStoreDir = KnownSymbol

hnix-store-core/src/System/Nix/Nar.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ module System.Nix.Nar (
1818
, localUnpackNar
1919
, narEffectsIO
2020
, putNar
21+
, FilePathPart(..)
22+
, filePathPart
2123
) where
2224

2325
import Control.Applicative
@@ -42,7 +44,6 @@ import System.FilePath
4244
import System.Posix.Files (createSymbolicLink, fileSize, getFileStatus,
4345
isDirectory, readSymbolicLink)
4446

45-
import System.Nix.Path
4647

4748
data NarEffects (m :: * -> *) = NarEffects {
4849
narReadFile :: FilePath -> m BSL.ByteString
@@ -65,6 +66,17 @@ data NarEffects (m :: * -> *) = NarEffects {
6566
data Nar = Nar { narFile :: FileSystemObject }
6667
deriving (Eq, Show)
6768

69+
-- | A valid filename or directory name
70+
newtype FilePathPart = FilePathPart { unFilePathPart :: BSC.ByteString }
71+
deriving (Eq, Ord, Show)
72+
73+
-- | Construct FilePathPart from Text by checking that there
74+
-- are no '/' or '\\NUL' characters
75+
filePathPart :: BSC.ByteString -> Maybe FilePathPart
76+
filePathPart p = case BSC.any (`elem` ['/', '\NUL']) p of
77+
False -> Just $ FilePathPart p
78+
True -> Nothing
79+
6880
-- | A FileSystemObject (FSO) is an anonymous entity that can be NAR archived
6981
data FileSystemObject =
7082
Regular IsExecutable Int64 BSL.ByteString

0 commit comments

Comments
 (0)