2020-05-29 15:40:53 +03:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
{-# LANGUAGE TypeApplications #-}
|
|
|
|
|
|
|
|
module NixDaemon where
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
import Data.Bool ( bool )
|
|
|
|
import Control.Monad ( void )
|
|
|
|
import Control.Monad.IO.Class ( liftIO )
|
|
|
|
import Control.Exception ( bracket )
|
|
|
|
import Control.Concurrent ( threadDelay )
|
|
|
|
import Data.Either ( isRight
|
|
|
|
, isLeft
|
|
|
|
)
|
|
|
|
import Data.Text ( Text )
|
|
|
|
import qualified Data.HashSet as HS
|
|
|
|
import qualified Data.Map.Strict as M
|
2020-05-29 15:40:53 +03:00
|
|
|
import System.Directory
|
|
|
|
import qualified System.Environment
|
|
|
|
import System.IO.Temp
|
2021-02-03 13:44:58 +03:00
|
|
|
import qualified System.Process as P
|
|
|
|
import System.Posix.User as U
|
|
|
|
import System.Linux.Namespaces as NS
|
|
|
|
import Test.Tasty.Hspec ( Spec
|
|
|
|
, describe
|
|
|
|
, context
|
|
|
|
)
|
|
|
|
import qualified Test.Tasty.Hspec as Hspec
|
2020-05-29 15:40:53 +03:00
|
|
|
import Test.Hspec.Expectations.Lifted
|
|
|
|
|
|
|
|
import System.FilePath
|
|
|
|
|
|
|
|
import System.Nix.Build
|
|
|
|
import System.Nix.Hash
|
|
|
|
import System.Nix.StorePath
|
|
|
|
import System.Nix.Store.Remote
|
|
|
|
import System.Nix.Store.Remote.Protocol
|
|
|
|
|
2020-05-19 12:12:36 +03:00
|
|
|
import Derivation
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
createProcessEnv :: FilePath -> String -> [String] -> IO P.ProcessHandle
|
2020-05-29 15:40:53 +03:00
|
|
|
createProcessEnv fp proc args = do
|
2021-02-03 13:44:58 +03:00
|
|
|
mPath <- System.Environment.lookupEnv "PATH"
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
(_, _, _, ph) <-
|
|
|
|
P.createProcess (P.proc proc args)
|
|
|
|
{ P.cwd = Just $ fp
|
|
|
|
, P.env = Just $ mockedEnv mPath fp
|
|
|
|
}
|
2020-05-29 15:40:53 +03:00
|
|
|
return ph
|
|
|
|
|
|
|
|
mockedEnv :: Maybe String -> FilePath -> [(String, FilePath)]
|
2021-02-03 13:44:58 +03:00
|
|
|
mockedEnv mEnvPath fp =
|
|
|
|
[ ("NIX_STORE_DIR" , fp </> "store")
|
2020-05-29 15:40:53 +03:00
|
|
|
, ("NIX_LOCALSTATE_DIR", fp </> "var")
|
2021-02-03 13:44:58 +03:00
|
|
|
, ("NIX_LOG_DIR" , fp </> "var" </> "log")
|
|
|
|
, ("NIX_STATE_DIR" , fp </> "var" </> "nix")
|
|
|
|
, ("NIX_CONF_DIR" , fp </> "etc")
|
2020-05-29 15:40:53 +03:00
|
|
|
-- , ("NIX_REMOTE", "daemon")
|
2021-02-03 13:44:58 +03:00
|
|
|
] <> (maybe [] (\x -> [("PATH", x)]) mEnvPath)
|
2020-05-29 15:40:53 +03:00
|
|
|
|
|
|
|
waitSocket :: FilePath -> Int -> IO ()
|
2021-01-14 16:15:47 +03:00
|
|
|
waitSocket _ 0 = fail "No socket"
|
2020-05-29 15:40:53 +03:00
|
|
|
waitSocket fp x = do
|
|
|
|
ex <- doesFileExist fp
|
2021-02-03 13:44:58 +03:00
|
|
|
bool
|
|
|
|
(threadDelay 100000 >> waitSocket fp (x - 1))
|
|
|
|
(return ())
|
|
|
|
ex
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
writeConf :: FilePath -> IO ()
|
2021-02-03 13:44:58 +03:00
|
|
|
writeConf fp =
|
|
|
|
writeFile fp $ unlines
|
|
|
|
[ "build-users-group = "
|
2020-05-29 15:40:53 +03:00
|
|
|
, "trusted-users = root"
|
|
|
|
, "allowed-users = *"
|
|
|
|
, "fsync-metadata = false"
|
|
|
|
]
|
|
|
|
|
|
|
|
{-
|
|
|
|
- we run in user namespace as root but groups are failed
|
|
|
|
- => build-users-group has to be empty but we still
|
|
|
|
- get an error (maybe older nix-daemon)
|
|
|
|
-
|
|
|
|
uid=0(root) gid=65534(nobody) groups=65534(nobody)
|
|
|
|
|
|
|
|
drwxr-xr-x 3 0 65534 60 Nov 29 05:53 store
|
|
|
|
|
|
|
|
accepted connection from pid 22959, user root (trusted)
|
|
|
|
error: changing ownership of path '/run/user/1000/test-nix-store-06b0d249e5616122/store': Invalid argument
|
|
|
|
-}
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
startDaemon
|
|
|
|
:: FilePath
|
|
|
|
-> IO (P.ProcessHandle, MonadStore a -> IO (Either String a, [Logger]))
|
2020-05-29 15:40:53 +03:00
|
|
|
startDaemon fp = do
|
|
|
|
writeConf (fp </> "etc" </> "nix.conf")
|
|
|
|
p <- createProcessEnv fp "nix-daemon" []
|
|
|
|
waitSocket sockFp 30
|
|
|
|
return (p, runStoreOpts sockFp (fp </> "store"))
|
2021-02-03 13:44:58 +03:00
|
|
|
where
|
|
|
|
sockFp = fp </> "var/nix/daemon-socket/socket"
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
enterNamespaces :: IO ()
|
2020-05-29 15:40:53 +03:00
|
|
|
enterNamespaces = do
|
|
|
|
uid <- getEffectiveUserID
|
|
|
|
gid <- getEffectiveGroupID
|
|
|
|
|
|
|
|
unshare [User, Network, Mount]
|
|
|
|
-- map our (parent) uid to root
|
|
|
|
writeUserMappings Nothing [UserMapping 0 uid 1]
|
|
|
|
-- map our (parent) gid to root group
|
|
|
|
writeGroupMappings Nothing [GroupMapping 0 gid 1] True
|
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
withNixDaemon
|
|
|
|
:: ((MonadStore a -> IO (Either String a, [Logger])) -> IO a) -> IO a
|
2021-02-03 13:44:58 +03:00
|
|
|
withNixDaemon action =
|
2020-05-29 15:40:53 +03:00
|
|
|
withSystemTempDirectory "test-nix-store" $ \path -> do
|
|
|
|
|
|
|
|
mapM_ (createDirectory . snd)
|
2021-02-03 13:44:58 +03:00
|
|
|
(filter ((/= "NIX_REMOTE") . fst) $ mockedEnv Nothing path)
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
ini <- createProcessEnv path "nix-store" ["--init"]
|
2021-01-14 16:15:47 +03:00
|
|
|
void $ P.waitForProcess ini
|
2020-05-29 15:40:53 +03:00
|
|
|
|
|
|
|
writeFile (path </> "dummy") "Hello World"
|
|
|
|
|
|
|
|
setCurrentDirectory path
|
|
|
|
|
|
|
|
bracket (startDaemon path)
|
|
|
|
(P.terminateProcess . fst)
|
|
|
|
(\x -> action . snd $ x)
|
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
checks :: (Show a, Show b) => IO (a, b) -> (a -> Bool) -> IO ()
|
2020-05-29 15:40:53 +03:00
|
|
|
checks action check = action >>= (`Hspec.shouldSatisfy` (check . fst))
|
2021-01-14 16:15:47 +03:00
|
|
|
|
|
|
|
it
|
|
|
|
:: (Show a, Show b, Monad m)
|
|
|
|
=> String
|
|
|
|
-> m c
|
|
|
|
-> (a -> Bool)
|
|
|
|
-> Hspec.SpecWith (m () -> IO (a, b))
|
2021-02-03 13:44:58 +03:00
|
|
|
it name action check =
|
|
|
|
Hspec.it name $ \run -> (run (action >> return ())) `checks` check
|
2021-01-14 16:15:47 +03:00
|
|
|
|
|
|
|
itRights
|
|
|
|
:: (Show a, Show b, Show c, Monad m)
|
|
|
|
=> String
|
|
|
|
-> m d
|
|
|
|
-> Hspec.SpecWith (m () -> IO (Either a b, c))
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights name action = it name action isRight
|
2021-01-14 16:15:47 +03:00
|
|
|
|
|
|
|
itLefts
|
|
|
|
:: (Show a, Show b, Show c, Monad m)
|
|
|
|
=> String
|
|
|
|
-> m d
|
|
|
|
-> Hspec.SpecWith (m () -> IO (Either a b, c))
|
2020-05-29 15:40:53 +03:00
|
|
|
itLefts name action = it name action isLeft
|
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
withPath :: (StorePath -> MonadStore a) -> MonadStore a
|
2020-05-29 15:40:53 +03:00
|
|
|
withPath action = do
|
|
|
|
path <- addTextToStore "hnix-store" "test" (HS.fromList []) False
|
|
|
|
action path
|
|
|
|
|
|
|
|
-- | dummy path, adds <tmp>/dummpy with "Hello World" contents
|
2021-01-14 16:15:47 +03:00
|
|
|
dummy :: MonadStore StorePath
|
2020-05-29 15:40:53 +03:00
|
|
|
dummy = do
|
|
|
|
let Right n = makeStorePathName "dummy"
|
2021-01-14 16:15:47 +03:00
|
|
|
res <- addToStore @'SHA256 n "dummy" False (pure True) False
|
2020-05-29 15:40:53 +03:00
|
|
|
return res
|
|
|
|
|
|
|
|
invalidPath :: StorePath
|
|
|
|
invalidPath =
|
|
|
|
let Right n = makeStorePathName "invalid"
|
|
|
|
in StorePath (hash "invalid") n "no_such_root"
|
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
withBuilder :: (StorePath -> MonadStore a) -> MonadStore a
|
2020-05-29 15:40:53 +03:00
|
|
|
withBuilder action = do
|
|
|
|
path <- addTextToStore "builder" builderSh (HS.fromList []) False
|
|
|
|
action path
|
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
builderSh :: Text
|
2021-02-03 13:44:58 +03:00
|
|
|
builderSh = "declare -xpexport > $out"
|
2020-05-29 15:40:53 +03:00
|
|
|
|
|
|
|
spec_protocol :: Spec
|
2021-02-03 13:44:58 +03:00
|
|
|
spec_protocol = Hspec.around withNixDaemon $
|
2020-05-29 15:40:53 +03:00
|
|
|
|
|
|
|
describe "store" $ do
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "syncWithGC" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "syncs with garbage collector" syncWithGC
|
|
|
|
|
|
|
|
context "verifyStore" $ do
|
2021-02-03 13:44:58 +03:00
|
|
|
itRights "check=False repair=False" $
|
2020-05-29 15:40:53 +03:00
|
|
|
verifyStore False False `shouldReturn` False
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
itRights "check=True repair=False" $
|
2020-05-29 15:40:53 +03:00
|
|
|
verifyStore True False `shouldReturn` False
|
|
|
|
|
|
|
|
--privileged
|
2021-02-03 13:44:58 +03:00
|
|
|
itRights "check=True repair=True" $
|
2020-05-29 15:40:53 +03:00
|
|
|
verifyStore True True `shouldReturn` False
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "addTextToStore" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "adds text to store" $ withPath $ const return ()
|
|
|
|
|
|
|
|
context "isValidPathUncached" $ do
|
|
|
|
itRights "validates path" $ withPath $ \path -> do
|
|
|
|
liftIO $ putStrLn $ show path
|
|
|
|
(isValidPathUncached path) `shouldReturn` True
|
|
|
|
itLefts "fails on invalid path" $ isValidPathUncached $ invalidPath
|
|
|
|
|
|
|
|
context "queryAllValidPaths" $ do
|
|
|
|
itRights "empty query" $ queryAllValidPaths
|
2021-02-03 13:44:58 +03:00
|
|
|
itRights "non-empty query" $ withPath $ \path ->
|
|
|
|
queryAllValidPaths `shouldReturn` (HS.fromList [path])
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "queryPathInfoUncached" $
|
2020-11-12 01:26:26 +03:00
|
|
|
itRights "queries path info" $ withPath $ queryPathInfoUncached
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "ensurePath" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "simple ensure" $ withPath $ ensurePath
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "addTempRoot" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "simple addition" $ withPath $ addTempRoot
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "addIndirectRoot" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "simple addition" $ withPath $ addIndirectRoot
|
|
|
|
|
|
|
|
context "buildPaths" $ do
|
|
|
|
itRights "build Normal" $ withPath $ \path -> do
|
|
|
|
let pathSet = HS.fromList [path]
|
|
|
|
buildPaths pathSet Normal
|
|
|
|
|
|
|
|
itRights "build Check" $ withPath $ \path -> do
|
|
|
|
let pathSet = HS.fromList [path]
|
|
|
|
buildPaths pathSet Check
|
|
|
|
|
|
|
|
itLefts "build Repair" $ withPath $ \path -> do
|
|
|
|
let pathSet = HS.fromList [path]
|
|
|
|
buildPaths pathSet Repair
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "roots" $ context "findRoots" $ do
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "empty roots" $ (findRoots `shouldReturn` M.empty)
|
|
|
|
|
2021-01-14 16:15:47 +03:00
|
|
|
itRights "path added as a temp root" $ withPath $ \_ -> do
|
2020-05-29 15:40:53 +03:00
|
|
|
roots <- findRoots
|
2021-02-03 13:44:58 +03:00
|
|
|
roots `shouldSatisfy` ((== 1) . M.size)
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "optimiseStore" $ itRights "optimises" $ optimiseStore
|
2020-05-29 15:40:53 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "queryMissing" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "queries" $ withPath $ \path -> do
|
|
|
|
let pathSet = HS.fromList [path]
|
|
|
|
queryMissing pathSet `shouldReturn` (HS.empty, HS.empty, HS.empty, 0, 0)
|
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "addToStore" $
|
2020-05-29 15:40:53 +03:00
|
|
|
itRights "adds file to store" $ do
|
|
|
|
fp <- liftIO $ writeSystemTempFile "addition" "lal"
|
|
|
|
let Right n = makeStorePathName "tmp-addition"
|
2021-02-03 13:44:58 +03:00
|
|
|
res <- addToStore @ 'SHA256 n fp False (pure True) False
|
2020-05-29 15:40:53 +03:00
|
|
|
liftIO $ print res
|
|
|
|
|
|
|
|
context "with dummy" $ do
|
|
|
|
itRights "adds dummy" dummy
|
|
|
|
|
|
|
|
itRights "valid dummy" $ do
|
|
|
|
path <- dummy
|
|
|
|
liftIO $ putStrLn $ show path
|
|
|
|
(isValidPathUncached path) `shouldReturn` True
|
2020-05-19 12:12:36 +03:00
|
|
|
|
2021-02-03 13:44:58 +03:00
|
|
|
context "derivation" $
|
|
|
|
itRights "build derivation" $
|
2020-05-19 12:12:36 +03:00
|
|
|
withDerivation $ \path drv -> do
|
|
|
|
result <- buildDerivation path drv Normal
|
2021-02-03 13:44:58 +03:00
|
|
|
result `shouldSatisfy` ((== AlreadyValid) . status)
|