~fgaz/orchid

69fd3b568fc0288b30031df9334a8f5b6f5d04d7 — Francesco Gazzetta 2 years ago 87eb100
Scan symlinks by searching for hashes in target path
3 files changed, 34 insertions(+), 16 deletions(-)

M src/Command/Pin.hs
M src/Util.hs
M src/Util/Nix.hs
M src/Command/Pin.hs => src/Command/Pin.hs +15 -5
@@ 11,7 11,7 @@ import Util.Nix

import Options.Applicative

import Data.Maybe (mapMaybe)
import Data.Maybe (mapMaybe, maybeToList)
import Data.Foldable (traverse_)
import Control.Monad (when, unless, forM_, void)



@@ 28,6 28,7 @@ import System.Directory
  , getModificationTime, setModificationTime
  , removeDirectoryRecursive
  , canonicalizePath
  , pathIsSymbolicLink
  , getSymbolicLinkTarget )

import System.Process (readProcess)


@@ 100,10 101,19 @@ processFile baseCtx pinCtx source = do
      target = gcrootsDir pinCtx </> hashed
  fileMod <- getModificationTime source
  when (fileMod > lastExecution pinCtx) $ do
    contents <- LBS.readFile source
    let storePaths = mapMaybe (lookupHash $ storeContents pinCtx)
                   $ nubOrd
                   $ findStoreHashes (storeDir baseCtx) contents
    isLink <- pathIsSymbolicLink source
    storePaths <- if isLink
      then do
        linkTarget <- getSymbolicLinkTarget source
        let maybeHash = hashFromPath (storeDir baseCtx) linkTarget
            maybeStorePath = maybeHash >>= lookupHash (storeContents pinCtx)
        pure $ maybeToList maybeStorePath
      else do
        -- TODO check that it's a normal file (could be a socket or other special file)
        contents <- LBS.readFile source
        pure $ mapMaybe (lookupHash $ storeContents pinCtx)
             $ nubOrd
             $ findStoreHashes (storeDir baseCtx) contents
    unless (null storePaths) $ do
      createDirectoryIfMissing True $ target </> "roots"
      createFileLink source $ target </> "source"

M src/Util.hs => src/Util.hs +6 -10
@@ 2,23 2,19 @@ module Util where

import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Foldable (traverse_)
import Control.Monad (unless)
import System.FilePath ((</>))
import System.Directory (doesDirectoryExist, pathIsSymbolicLink, listDirectory)
import System.Directory (doesDirectoryExist, listDirectory)
import qualified Data.ByteString as BS
import Numeric (showHex)

traverseFileTree :: MonadIO m => (FilePath -> m ()) -> FilePath -> m ()
traverseFileTree f path = do
  isDir <- liftIO $ doesDirectoryExist path
  isLink <- liftIO $ pathIsSymbolicLink path
  unless isLink $ -- TODO: decide what to do with *file* links
    if isDir
    then do
      ls <- liftIO $ listDirectory path
      traverseFileTrees f $ (path</>) <$> ls
    -- TODO check that it's a file (could be a socket!)
    else f path
  if isDir
  then do
    ls <- liftIO $ listDirectory path
    traverseFileTrees f $ (path</>) <$> ls
  else f path

traverseFileTrees :: MonadIO m => (FilePath -> m ()) -> [FilePath] -> m ()
traverseFileTrees f = traverse_ $ traverseFileTree f

M src/Util/Nix.hs => src/Util/Nix.hs +13 -1
@@ 4,6 4,8 @@ module Util.Nix where
import qualified Data.Map as Map
import Data.Map (Map)

import Data.List (stripPrefix)

import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy.Char8 as BSC8



@@ 11,13 13,15 @@ import Data.String (fromString)

import Data.ByteString.Lazy.Search (split)

import System.FilePath ((</>), addTrailingPathSeparator)
import System.FilePath ((</>), addTrailingPathSeparator, isPathSeparator)
import System.Directory (listDirectory)

type Hash = String

type StoreContents = Map Hash FilePath

-- MAYBE find bare hashes too, even without the store prefix. what does nix do?

isHash :: String -> Bool
isHash str = length str == 32
          && all isHashCharacter str


@@ 31,6 35,14 @@ findStoreHashes storePath bs =
  filter isHash $ take 32 . BSC8.unpack <$> split storePrefix bs
  where storePrefix = fromString $ addTrailingPathSeparator storePath

hashFromPath :: FilePath -> FilePath -> Maybe Hash
hashFromPath storePath path = do
  afterPrefix <- stripPrefix storePath path
  let hashCandidate = take 32 $ dropWhile isPathSeparator afterPrefix
  if isHash hashCandidate
  then pure hashCandidate
  else Nothing

lookupHash :: StoreContents -> Hash -> Maybe (Hash, FilePath)
lookupHash storeContents hash =
  (hash,) <$> Map.lookup hash storeContents