Filesystem.hs (2972B)
1 module ContentStore.Filesystem 2 ( putObject 3 , getVerifiedObject 4 , getObject 5 , objectPath 6 , resolveStorePath 7 , ensureStore 8 ) where 9 10 import ContentStore.Object 11 12 import Control.Monad (unless, when) 13 import Data.Text (unpack) 14 import System.Directory (getHomeDirectory, createDirectoryIfMissing, doesFileExist, removeFile, renameFile) 15 import System.FilePath ((</>)) 16 import System.IO (hClose, openBinaryTempFile) 17 18 import qualified Data.ByteString as BS 19 20 ensureStore :: StorePath -> IO () 21 ensureStore (StorePath root) = do 22 createDirectoryIfMissing True (root </> "objects") 23 createDirectoryIfMissing True (root </> "aliases" </> "names") 24 createDirectoryIfMissing True (root </> "aliases" </> "modules") 25 createDirectoryIfMissing True (root </> "aliases" </> "packages") 26 createDirectoryIfMissing True (root </> "manifests") 27 createDirectoryIfMissing True (root </> "tmp") 28 29 objectPath :: StorePath -> ObjectHash -> FilePath 30 objectPath (StorePath root) h = case validateObjectHash h of 31 Left err -> errorWithoutStackTrace err 32 Right () -> root </> "objects" </> shardForHash h </> unpack h 33 34 putObject :: StorePath -> Domain -> BS.ByteString -> IO ObjectHash 35 putObject store@(StorePath root) domain payload = do 36 ensureStore store 37 let h = hashObject domain payload 38 shardDir = root </> "objects" </> shardForHash h 39 finalPath = objectPath store h 40 createDirectoryIfMissing True shardDir 41 exists <- doesFileExist finalPath 42 if exists 43 then verifyExisting finalPath 44 else do 45 let tmpDir = root </> "tmp" 46 (tmpPath, handle) <- openBinaryTempFile tmpDir (unpack h ++ ".tmp") 47 BS.hPut handle payload 48 hClose handle 49 raced <- doesFileExist finalPath 50 if raced 51 then removeFile tmpPath >> verifyExisting finalPath 52 else renameFile tmpPath finalPath 53 return h 54 where 55 verifyExisting path = do 56 existing <- BS.readFile path 57 when (existing /= payload) $ 58 fail $ "content-addressed object exists with mismatched bytes: " ++ path 59 60 getObject :: StorePath -> ObjectHash -> IO (Maybe BS.ByteString) 61 -- Raw storage read: checks the filename, not a content domain. Typed consumers 62 -- must use getVerifiedObject (or authenticate the returned bytes themselves). 63 getObject store h = do 64 either fail pure (validateObjectHash h) 65 let path = objectPath store h 66 exists <- doesFileExist path 67 if exists then Just <$> BS.readFile path else return Nothing 68 69 getVerifiedObject :: StorePath -> Domain -> ObjectHash -> IO (Maybe BS.ByteString) 70 getVerifiedObject store domain h = do 71 payload <- getObject store h 72 case payload of 73 Nothing -> pure Nothing 74 Just bytes -> do 75 either fail pure (verifyObjectBytes domain h bytes) 76 pure (Just bytes) 77 78 resolveStorePath :: Maybe FilePath -> IO StorePath 79 resolveStorePath (Just path) = return (StorePath path) 80 resolveStorePath Nothing = do 81 home <- getHomeDirectory 82 return (StorePath (home </> ".tricu" </> "store"))