77
88module System.Nix.Internal.Nar.Parser
99 ( runParser
10+ , runParserWithOptions
1011 , parseNar
1112 , testParser
1213 , testParser'
@@ -26,6 +27,8 @@ import qualified Control.Monad.State as State
2627import qualified Control.Monad.Trans as Trans
2728import qualified Control.Monad.Trans.Control as Base
2829import qualified Data.ByteString as Bytes
30+ import qualified Data.CaseInsensitive as CI
31+ import qualified Data.HashMap.Strict as HashMap
2932import qualified Data.List as List
3033import qualified Data.Map as Map
3134import qualified Data.Serialize as Serialize
@@ -35,6 +38,7 @@ import System.FilePath as FilePath
3538import qualified System.IO as IO
3639
3740import qualified System.Nix.Internal.Nar.Effects as Nar
41+ import qualified System.Nix.Internal.Nar.Options as Nar
3842
3943
4044-- | NarParser is a monad for parsing a Nar file as a byte stream
@@ -48,19 +52,34 @@ newtype NarParser m a = NarParser
4852 ParserState
4953 (Except. ExceptT
5054 String
51- (Reader. ReaderT
52- (Nar. NarEffects m )
53- m
54- )
55+ (Reader. ReaderT (ParserEnv m ) m )
5556 )
5657 a
5758 }
5859 deriving ( Functor , Applicative , Monad , Fail.MonadFail
5960 , Trans.MonadIO , State.MonadState ParserState
6061 , Except.MonadError String
61- , Reader.MonadReader (Nar.NarEffects m)
62+ , Reader.MonadReader (ParserEnv m)
6263 )
6364
65+
66+ data ParserEnv m = ParserEnv
67+ { envNarEffects :: Nar. NarEffects m
68+ , envNarOptions :: Nar. NarOptions
69+ }
70+
71+
72+ getNarEffects :: Monad m => NarParser m (Nar. NarEffects m )
73+ getNarEffects = fmap envNarEffects ask
74+
75+
76+ getNarEffect :: Monad m => (Nar. NarEffects m -> a ) -> NarParser m a
77+ getNarEffect eff = fmap eff getNarEffects
78+
79+
80+ getNarOptions :: Monad m => NarParser m Nar. NarOptions
81+ getNarOptions = fmap envNarOptions ask
82+
6483-- | Run a @NarParser@ over a byte stream
6584-- This is suitable for testing the top-level NAR parser, or any of the
6685-- smaller utilities parsers, if you have bytes appropriate for them
@@ -77,9 +96,26 @@ runParser
7796 -> FilePath
7897 -- ^ The root file system object to be created by the NAR
7998 -> m (Either String a )
80- runParser effs (NarParser action) h target = do
99+ runParser effs parser h target = do
100+ runParserWithOptions Nar. defaultNarOptions effs parser h target
101+
102+ runParserWithOptions
103+ :: forall m a
104+ . (IO. MonadIO m , Base. MonadBaseControl IO m )
105+ => Nar. NarOptions
106+ -> Nar. NarEffects m
107+ -- ^ Provide the effects set, usually @narEffectsIO@
108+ -> NarParser m a
109+ -- ^ A parser to run, such as @parseNar@
110+ -> IO. Handle
111+ -- ^ A handle the stream containg the NAR. It should already be
112+ -- open and in @ReadMode@
113+ -> FilePath
114+ -- ^ The root file system object to be created by the NAR
115+ -> m (Either String a )
116+ runParserWithOptions opts effs (NarParser action) h target = do
81117 unpackResult <-
82- runReaderT (runExceptT $ State. evalStateT action state0) effs
118+ runReaderT (runExceptT $ State. evalStateT action state0) ( ParserEnv effs opts)
83119 `Exception.Lifted.catch` exceptionHandler
84120 when (isLeft unpackResult) cleanup
85121 pure unpackResult
@@ -92,6 +128,7 @@ runParser effs (NarParser action) h target = do
92128 , handle = h
93129 , directoryStack = [target]
94130 , links = []
131+ , filePaths = HashMap. empty
95132 }
96133
97134 exceptionHandler :: Exception.Lifted. SomeException -> m (Either String a )
@@ -126,6 +163,9 @@ data ParserState = ParserState
126163 , links :: [LinkInfo ]
127164 -- ^ Unlike with files and directories, we collect symlinks
128165 -- from the NAR on
166+ , filePaths :: HashMap. HashMap (CI. CI FilePath ) Int
167+ -- ^ A map of case-insensitive files paths to the number of collisions encountered.
168+ -- See @Nar.NarOptions.optUseCaseHack@ for details.
129169 }
130170
131171
@@ -230,11 +270,11 @@ parseFile = do
230270 pure $ Just chunk
231271
232272 target <- currentFile
233- streamFile <- asks Nar. narStreamFile
273+ streamFile <- getNarEffect Nar. narStreamFile
234274 lift (streamFile target getChunk)
235275
236276 when (s == " executable" ) $ do
237- effs :: Nar. NarEffects m <- ask
277+ effs :: Nar. NarEffects m <- getNarEffects
238278 lift $ do
239279 p <- Nar. narGetPerms effs target
240280 Nar. narSetPerms effs target (p { Directory. executable = True })
@@ -246,34 +286,50 @@ parseFile = do
246286-- handles for target files longer than needed
247287parseDirectory :: (IO. MonadIO m , Fail. MonadFail m ) => NarParser m ()
248288parseDirectory = do
249- createDirectory <- asks Nar. narCreateDir
289+ createDirectory <- getNarEffect Nar. narCreateDir
250290 target <- currentFile
251291 lift $ createDirectory target
252- parseEntryOrFinish
292+ parseEntryOrFinish target
253293
254294 where
255295
256- parseEntryOrFinish :: (IO. MonadIO m , Fail. MonadFail m ) => NarParser m ()
257- parseEntryOrFinish =
296+ parseEntryOrFinish :: (IO. MonadIO m , Fail. MonadFail m ) => FilePath -> NarParser m ()
297+ parseEntryOrFinish path =
258298 -- If we reach a ")", we finished the directory's entries, and we have
259299 -- to put ")" back into the stream, because the outer call to @parens@
260300 -- expects to consume it.
261301 -- Otherwise, parse an entry as a fresh file system object
262302 matchStr
263303 [ ( " )" , pushStr " )" )
264- , (" entry" , parseEntry )
304+ , (" entry" , parseEntry path )
265305 ]
266306
267- parseEntry :: (IO. MonadIO m , Fail. MonadFail m ) => NarParser m ()
268- parseEntry = do
307+ parseEntry :: (IO. MonadIO m , Fail. MonadFail m ) => FilePath -> NarParser m ()
308+ parseEntry path = do
309+ opts <- getNarOptions
269310 parens $ do
270311 expectStr " name"
271- fName <- parseStr
312+ fName <-
313+ if Nar. optUseCaseHack opts then
314+ addCaseHack path =<< parseStr
315+ else
316+ parseStr
272317 pushFileName (toString fName)
273318 expectStr " node"
274319 parens parseFSO
275320 popFileName
276- parseEntryOrFinish
321+ parseEntryOrFinish path
322+
323+ addCaseHack :: (IO. MonadIO m , Fail. MonadFail m ) => FilePath -> Text -> NarParser m Text
324+ addCaseHack path fName = do
325+ let key = path </> Text. unpack fName
326+ recordFilePath key
327+ conflictCount <- getFilePathConflictCount key
328+ pure $
329+ if conflictCount > 0 then
330+ fName <> Nar. caseHackSuffix <> show conflictCount
331+ else
332+ fName
277333
278334
279335
@@ -373,7 +429,7 @@ parens act = do
373429-- (Targets must be created before the links that target them)
374430createLinks :: IO. MonadIO m => NarParser m ()
375431createLinks = do
376- createLink <- asks Nar. narCreateLink
432+ createLink <- getNarEffect Nar. narCreateLink
377433 allLinks <- State. gets links
378434 sortedLinks <- IO. liftIO $ sortLinksIO allLinks
379435 forM_ sortedLinks $ \ li -> do
@@ -473,6 +529,16 @@ pushLink :: Monad m => LinkInfo -> NarParser m ()
473529pushLink linkInfo = State. modify (\ s -> s { links = linkInfo : links s })
474530
475531
532+ -- | Add a file path to the collection of encountered file paths
533+ recordFilePath :: Monad m => FilePath -> NarParser m ()
534+ recordFilePath fPath =
535+ State. modify (\ s -> s { filePaths = HashMap. insertWith (\ _ v -> v + 1 ) (CI. mk fPath) 0 (filePaths s) })
536+
537+ getFilePathConflictCount :: Monad m => FilePath -> NarParser m Int
538+ getFilePathConflictCount fPath = do
539+ fileMap <- State. gets filePaths
540+ pure $ HashMap. findWithDefault 0 (CI. mk fPath) fileMap
541+
476542------------------------------------------------------------------------------
477543-- * Utilities
478544
0 commit comments