packages feed

haskdeep (empty) → 0.1.0.0

raw patch · 13 files changed

+854/−0 lines, 13 filesdep +attoparsecdep +attoparsec-conduitdep +basesetup-changed

Dependencies added: attoparsec, attoparsec-conduit, base, bytestring, cereal, conduit, containers, crypto-api, crypto-conduit, cryptohash, filesystem-conduit, optparse-applicative, regex-tdfa, regex-tdfa-text, system-fileio, system-filepath, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012 Mauro Taraborelli++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,53 @@+HaskDeep+========++What is it?+-----------+Command line tool that computes file hashes traversing recursively through+a directory structure.+Known hashes are saved to file and they can be used to verify the original+files or a copy of them.++Quick start+-----------+Execute haskdeep without arguments and it will show you the help text:++    user@host:~$ haskdeep++    Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] [-i|--ignore RULE]+      Computes hashes and audit a set of files++    Available options:+      -c,--computation MODE    md5 | sha1 | sha256 | skein512 - default md5+      -r,--root DIRNAME        Root directory - default current directory+      -k,--known FILENAME      Known hashes file - default known.haskdeep+      -i,--ignore RULE         Regex to ignore files or directories+      -h,--help                Show this help text++    Available commands:+      compute                  Computes file hashes and saves them to known hashes file+      audit                    Audits files comparing them to known hashes++Default usage:++1. create known hashes of files contained in a root directory (traversed recursively)++        user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -i "tmp|\.log"++2. verify a copy of the same files comparing them with known hashes++        user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -i "tmp|\.log"++Licensing+---------+Please see the file called LICENSE.++Reference+---------+Heavily inspired by `hashdeep`: <http://md5deep.sourceforge.net/>++Contacts+--------+For question and comments:++- [MauroTaraborelli@gmail.com](mailto:MauroTaraborelli@gmail.com)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ haskdeep.cabal view
@@ -0,0 +1,88 @@+name:                haskdeep+version:             0.1.0.0+cabal-version:       >=1.8+build-type:          Simple+license:             BSD3+license-file:        LICENSE+copyright:           © 2012 Mauro Taraborelli+author:              Mauro Taraborelli+maintainer:          maurotaraborelli@gmail.com+stability:           experimental+homepage:            https://github.com/maurotrb/haskdeep+bug-reports:         https://github.com/maurotrb/haskdeep/issues+synopsis:            Computes and audits file hashes.+description:+    A command line application that computes file hashes traversing recursively+    through a directory structure.+    .+    The known hashes can be used to audit the same directory structure or a copy+    of it.+    .+    Execute haskdeep without arguments and it will show you the help text:+    .+    > user@host:~$ haskdeep+    >+    >  Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] [-i|--ignore RULE]+    >    Computes hashes and audit a set of files+    >+    >  Available options:+    >    -c,--computation MODE    md5 | sha1 | sha256 | skein512 - default md5+    >    -r,--root DIRNAME        Root directory - default current directory+    >    -k,--known FILENAME      Known hashes file - default known.haskdeep+    >    -i,--ignore RULE         Regex to ignore files or directories+    >    -h,--help                Show this help text+    >+    >  Available commands:+    >    compute                  Computes file hashes and saves them to known hashes file+    >    audit                    Audits files comparing them to known hashes+    .+    Default usage:+    .+    1. create known hashes of files contained in a root directory (traversed recursively)+    .+    >  user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+    .+    2. verify a copy of the same files comparing them with known hashes+    .+    >  user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+    .+    Heavily inspired by @hashdeep@: <http://md5deep.sourceforge.net/>+category:            Console, Cryptography, System+extra-source-files:+    README.md++executable haskdeep+  hs-source-dirs:   src+  main-is:          Main.hs+  other-modules:+    Options+    HaskDeep+    HaskDeep.Computation+    HaskDeep.ComputationMode+    HaskDeep.Configuration+    HaskDeep.HashSet+    HaskDeep.KnownHash.Reader+    HaskDeep.KnownHash.Writer+  build-depends:+      attoparsec           ==0.10.*+    , attoparsec-conduit   ==0.5.*+    , base                 ==4.5.*+    , bytestring           ==0.9.*+    , cereal               ==0.3.*+    , conduit              ==0.5.*+    , containers           ==0.4.*+    , cryptohash           ==0.7.*+    , crypto-api           ==0.10.*+    , crypto-conduit       ==0.4.*+    , filesystem-conduit   ==0.5.*+    , optparse-applicative ==0.4.*+    , system-fileio        ==0.3.*+    , system-filepath      ==0.4.*+    , text                 ==0.11.*+    , regex-tdfa           ==1.1.*+    , regex-tdfa-text      ==1.0.*+  ghc-options: -Wall++source-repository head+  type:     git+  location: git://github.com/maurotrb/haskdeep.git
+ src/HaskDeep.hs view
@@ -0,0 +1,44 @@+-- |+-- Module      :  HaskDeep+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.++module HaskDeep+    (+     -- * The @HashSet@ type+     HashSet()+    ,audit++     -- * Configuration+    ,HaskDeepConfiguration(..)+    ,defaultHaskDeepConfiguration++     -- * Computation modes+    ,ComputationMode (..)+    ,md5hash+    ,sha1hash+    ,sha256hash+    ,skein512hash++     -- * Computation+    ,compute++     -- * Known hashes file+    ,readHashes+    ,writeHashes+    )+where++import HaskDeep.Computation+import HaskDeep.ComputationMode+import HaskDeep.Configuration+import HaskDeep.HashSet+import HaskDeep.KnownHash.Reader+import HaskDeep.KnownHash.Writer
+ src/HaskDeep/Computation.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DoAndIfThenElse #-}+-- |+-- Module      :  HaskDeep.Computation+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.Computation+    (+     -- * Computation+     compute  -- Hash ctx a => HaskDeepConfiguration -> ComputationMode a -> IO HashSet+    )+where++import           Control.Monad (liftM)+import           Data.Maybe (fromJust)+import           Prelude hiding (FilePath)++import           Crypto.Classes (Hash)+import qualified Crypto.Conduit as CC+import           Data.Conduit (($$))+import qualified Data.Conduit as C+import qualified Data.Conduit.Filesystem as CF+import qualified Data.Conduit.List as CL+import qualified Data.Text as T+import qualified Filesystem as FS+import           Filesystem.Path.CurrentOS (FilePath)+import qualified Filesystem.Path.CurrentOS as FSC+import           Text.Regex.TDFA ((=~))+import           Text.Regex.TDFA.Text ()++import           HaskDeep.Configuration+import           HaskDeep.ComputationMode+import           HaskDeep.HashSet (HashInfo(..), HashSet)+import qualified HaskDeep.HashSet as HS++-- | Compute @HashSet@ traversing recursively through the directory structure.+compute :: Hash ctx a => HaskDeepConfiguration+        -> ComputationMode a+        -> IO HashSet+compute conf cm = CF.traverse False root $$ CL.foldM insert_hash empty_with_symbol+    where+      root              = rootDirectory conf+      rule              = ignoreRule conf+      empty_with_symbol = HS.setSymbol (symbol cm) HS.empty++      insert_hash :: HashSet -> FilePath -> IO HashSet+      insert_hash hs fp = do let fpt = fpToText $ relativize root fp+                             if ignore fpt rule+                             then return hs+                             else do s <- FS.getSize fp+                                     h <- liftM (runComputation cm) $ C.runResourceT+                                          $ CF.sourceFile fp $$ CC.sinkHash+                                     return $ HS.insert (HashInfo fpt s h) hs++relativize :: FilePath -> FilePath -> FilePath+relativize root fp = fromJust $ FSC.stripPrefix root fp++fpToText :: FilePath -> T.Text+fpToText = either id id . FSC.toText++ignore :: T.Text -> Maybe T.Text -> Bool+ignore fpt (Just rule) = fpt =~ rule+ignore _   Nothing     = False
+ src/HaskDeep/ComputationMode.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      :  HaskDeep.Computation+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.ComputationMode+    (+     -- * Computation modes+     ComputationMode (..)+    ,md5hash+    ,sha1hash+    ,sha256hash+    ,skein512hash+    )+where++import           Data.Bits ((.&.), shiftR)++import           Crypto.Hash.MD5 (MD5)+import           Crypto.Hash.SHA1 (SHA1)+import           Crypto.Hash.SHA256 (SHA256)+import           Crypto.Hash.Skein512 (Skein512)+import           Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.Serialize as S+import qualified Data.Text as T++-- | Algorithm to compute hash.+data ComputationMode a = ComputationMode+    { symbol         :: T.Text           -- ^ Computation mode symbol+    , runComputation :: a -> ByteString  -- ^ Computation function+    }++-- | MD5 computation.+md5hash  :: ComputationMode MD5+md5hash  = ComputationMode "md5" (toHex . S.encode)++-- | SHA1 computation.+sha1hash :: ComputationMode SHA1+sha1hash = ComputationMode "sha1" (toHex . S.encode)++-- | SHA256 computation.+sha256hash :: ComputationMode SHA256+sha256hash = ComputationMode "sha256" (toHex . S.encode)++-- | Skein512 computation.+skein512hash :: ComputationMode Skein512+skein512hash = ComputationMode "skein512" (toHex . S.encode)++-- Only with ByteString 0.10+--toHex :: ByteString -> ByteString+--toHex = BS.concat . BL.toChunks . BB.toLazyByteString . BA.byteStringHexFixed++-- Taken from+-- http://stackoverflow.com/questions/10099921/efficiently-turn-a-bytestring-into-a-hex-representation+toHex :: ByteString -> ByteString+toHex bs0 =+    fst $ BS.unfoldrN (BS.length bs0 * 2) go (Left bs0)+  where+    go (Left bs) =+        case BS.uncons bs of+            Nothing -> Nothing+            Just (w, bs') ->+                let w1 = w `shiftR` 4+                    w2 = w .&. 15+                    c1 = toC w1+                    c2 = toC w2+                 in Just (c1, Right (c2, bs'))+    go (Right (c, bs)) = Just (c, Left bs)++    toC w+        | w < 10 = w + 48+        | otherwise = w + 87
+ src/HaskDeep/Configuration.hs view
@@ -0,0 +1,42 @@+-- |+-- Module      :  HaskDeep.Configuration+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.Configuration+    (+     -- * Configuration+     HaskDeepConfiguration (..)+    ,defaultHaskDeepConfiguration+    )+where++import           Prelude hiding (FilePath)++import           Data.Text (Text)+import           Filesystem.Path (FilePath)+import qualified Filesystem.Path.CurrentOS as FSC++-- | HaskDeep configuration.+data HaskDeepConfiguration = HaskDeepConfiguration+    { rootDirectory   :: FilePath    -- ^ Root directory+    , knownHashes     :: FilePath    -- ^ Known hashes file+    , ignoreRule      :: Maybe Text  -- ^ Ignore rule (Regex)+    }++-- | HaskDeep default configuration.+defaultHaskDeepConfiguration :: HaskDeepConfiguration+defaultHaskDeepConfiguration = HaskDeepConfiguration+    { rootDirectory   = FSC.decodeString "."+    , knownHashes     = FSC.decodeString "known.haskdeep"+    , ignoreRule      = Nothing+    }
+ src/HaskDeep/HashSet.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      :  HaskDeep.HashSet+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.HashSet+    (+     -- * The @HashSet@ type+     HashSet(..)+    ,setSymbol     -- Text -> HashSet -> HashSet+    ,empty         -- HashSet+    ,insert        -- HashInfo -> HashSet -> HashSet+    ,fromList      -- [HashInfo] -> HashSet+    ,toAscList     -- HashSet -> [HashInfo]+    ,audit         -- HashSet -> HashSet -> (HashSet, HashSet)++     -- * The @HashInfo@ type+    ,HashInfo(..)+    ,toByteString  -- HashInfo -> ByteString+    )+where++import           Data.List (intercalate)++import           Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as B8+import           Data.Set (Set)+import qualified Data.Set as Set+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE++-- | Information about the hashed file.+data HashInfo = HashInfo+    { file :: Text        -- ^ Relative file path+    , size :: Integer     -- ^ File size in byte+    , hash :: ByteString  -- ^ File hash+    } deriving (Eq, Ord)++instance Show HashInfo where+    show (HashInfo f s h) = intercalate "," [ show s+                                            , B8.unpack h+                                            , T.unpack f ]++toByteString :: HashInfo -> ByteString+toByteString (HashInfo f s h) = BS.intercalate commaBS [ B8.pack (show s)+                                                       , h+                                                       , TE.encodeUtf8 f ]++commaBS :: ByteString+commaBS = B8.singleton ','++-- | Hashed files.+data HashSet = HashSet+    { filesCount :: Integer       -- ^ Number of hashed files+    , sizeSum    :: Integer       -- ^ Total size in bytes of hashed files+    , compSymbol :: Text          -- ^ Computation mode symbol+    , hashSet    :: Set HashInfo  -- ^ Hashes+    } deriving (Eq, Ord)++instance Show HashSet where+    show (HashSet 0  _  _  _ ) = "No files."+    show (HashSet fc ss cs hs) = unlines [ "Files count  : " ++ show fc+                                         , "Files size   : " ++ show ss+                                         , "Computed with: " ++ T.unpack cs+                                         , "--"+                                         , (unlines . map show . Set.toAscList) hs ]++-- | Set computation mode symbol.+setSymbol :: Text -> HashSet -> HashSet+setSymbol cs hs = hs { compSymbol = cs }++-- | Create an empty @HashSet@.+empty :: HashSet+empty = HashSet 0 0 T.empty Set.empty++-- | Insert an @HashInfo@ into an @HashSet@+insert :: HashInfo -> HashSet -> HashSet+insert hi hs = hs+               { filesCount = filesCount hs + 1+               , sizeSum    = sizeSum hs + size hi+               , hashSet    = Set.insert hi $ hashSet hs+               }++-- | Create an @HashSet@ from a list of @HashInfo@+fromList :: [HashInfo] -> HashSet+fromList his = HashSet fc ss T.empty hs+    where+      hs = Set.fromList his+      fc = countFiles hs+      ss = sumSize hs++-- | Create an ordered list of @HashInfo@ from an @HashSet@.+toAscList :: HashSet -> [HashInfo]+toAscList (HashSet _ _ _ hs) = Set.toAscList hs++-- | Get the number of hashed files+countFiles :: Set HashInfo -> Integer+countFiles = fromIntegral . Set.size++-- | Get the total size in bytes of hashed files+sumSize :: Set HashInfo -> Integer+sumSize = Set.foldr sumSize' 0+    where+      sumSize' hi ss = ss + size hi++-- | Compare two @HashSet@ and return the not matching @HashInfo@.+--+-- Not matching means all the HashInfo of the first HashSet not present in the second HashSet+-- and all the HashInfo of the second HashSet not present in the first HashSet.+audit :: HashSet -> HashSet -> (HashSet, HashSet)+audit (HashSet _ _ cs1 hs1) (HashSet _ _ cs2 hs2) =+    ( HashSet hs1_no_match_filescount hs1_no_match_sizesum cs1 hs1_no_match+    , HashSet hs2_no_match_filescount hs2_no_match_sizesum cs2 hs2_no_match )+        where+          matching                = hs1 `Set.intersection` hs2+          hs1_no_match            = hs1 `Set.difference` matching+          hs2_no_match            = hs2 `Set.difference` matching+          hs1_no_match_filescount = countFiles hs1_no_match+          hs1_no_match_sizesum    = sumSize hs1_no_match+          hs2_no_match_filescount = countFiles hs2_no_match+          hs2_no_match_sizesum    = sumSize hs2_no_match
+ src/HaskDeep/KnownHash/Reader.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      :  HaskDeep.Computation.Reader+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.KnownHash.Reader+    (+     -- * Read the known hashes file+     readHashes  -- HaskDeepConfiguration -> IO HashSet+    )+where++import           Control.Applicative ((<*), many)+import           Control.Monad (liftM)+import           Data.Maybe(fromJust)+import           Data.Word (Word8)+import           Prelude hiding (FilePath)++import           Data.Attoparsec (Parser)+import qualified Data.Attoparsec as A+import qualified Data.Attoparsec.Char8 as A8+import qualified Data.Attoparsec.Combinator as AC+import qualified Data.ByteString.Char8 as B8+import           Data.Conduit (($$))+import qualified Data.Conduit as C+import qualified Data.Conduit.Attoparsec as CA+import qualified Data.Conduit.Filesystem as CF+import           Data.Text ()+import qualified Data.Text.Encoding as TE+import           Filesystem.Path ()++import           HaskDeep.Configuration+import           HaskDeep.HashSet (HashSet, HashInfo(..))+import qualified HaskDeep.HashSet as HS++-- | Read known hashes file into an @HashSet@.+readHashes :: HaskDeepConfiguration -- ^ Configuration+           -> IO HashSet            -- ^ @HashSet@ red from file+readHashes conf = liftM HS.fromList $ C.runResourceT+                  $ CF.sourceFile (knownHashes conf) $$ CA.sinkParser knownHashesP+++-- Parsers++knownHashesP :: Parser [HashInfo]+knownHashesP = do _ <- AC.count 2 headerP+                  _ <- many commentP+                  AC.many1 fileinfoP++headerP :: Parser ()+headerP = do _ <- A8.string "%%%%"+             _ <- A.skipWhile (not . isNewline)+             skipNewline++commentP :: Parser ()+commentP = do _ <- A8.char8 '#'+              A.skipWhile (not . isNewline)+              skipNewline++fileinfoP :: Parser HashInfo+fileinfoP = do sizep  <- A.takeTill isComma   <* skipComma+               hashp  <- A.takeTill isComma   <* skipComma+               filep  <- A.takeTill isNewline <* skipNewline+               let filep'      = TE.decodeUtf8 filep+                   (sizep', _) = fromJust $ B8.readInteger sizep+               return $ HashInfo filep' sizep' hashp++isComma   :: Word8 -> Bool+isComma w = w == 44++skipComma :: Parser ()+skipComma = A.skip isComma++isNewline   :: Word8 -> Bool+isNewline w = w == 10++skipNewline :: Parser ()+skipNewline = A.skip isNewline
+ src/HaskDeep/KnownHash/Writer.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      :  HaskDeep.Computation.Writer+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.+--+-- Internal module.++module HaskDeep.KnownHash.Writer+    (+     -- * Write the known hashes file+     writeHashes  -- HaskDeepConfiguration -> HashSet -> IO ()+    )+where++import           Prelude hiding (FilePath)++import           Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as B8+import           Data.Conduit (($$))+import qualified Data.Conduit as C+import qualified Data.Conduit.Filesystem as CF+import qualified Data.Conduit.List as CL+import           Data.Text ()+import qualified Data.Text.Encoding as TE+import           Filesystem.Path ()+import qualified Filesystem.Path.CurrentOS as FSC++import           HaskDeep.Configuration+import           HaskDeep.HashSet (HashSet)+import qualified HaskDeep.HashSet as HS++-- | Write an @HashSet@ to a known hashes file.+writeHashes :: HaskDeepConfiguration -- ^ Configuration+            -> HashSet               -- ^ @HashSet@ to write+            -> IO ()+writeHashes conf hs = C.runResourceT $ CL.sourceList bs_known $$ CF.sinkFile (knownHashes conf)+    where+      newline       = "\n" :: ByteString+      root          =  TE.encodeUtf8 $ either id id $ FSC.toText $ rootDirectory conf+      ignore        = case ignoreRule conf of+                        (Just rule) -> TE.encodeUtf8 rule+                        Nothing     -> BS.empty+      files_count   = B8.pack $ show $ HS.filesCount hs+      size_sum      = B8.pack $ show $ HS.sizeSum hs+      known_header1 = "%%%% HASHDEEP-1.0"+                      `BS.append` newline+      known_header2 = "%%%% size,"+                      `BS.append` TE.encodeUtf8 (HS.compSymbol hs)+                      `BS.append` ",filename"+                      `BS.append` newline+      bs_comments   = map (flip BS.append newline . BS.append "## ")+                      [ "Root directory: " `BS.append` root+                      , "Ignore rule   : " `BS.append` ignore+                      , "Files count   : " `BS.append` files_count+                      , "Files size    : " `BS.append` size_sum+                      , "" ]+      bs_hash_set   = map (flip BS.append newline . HS.toByteString) $ HS.toAscList hs+      bs_known      = [known_header1, known_header2] ++ bs_comments ++ bs_hash_set
+ src/Main.hs view
@@ -0,0 +1,50 @@+-- |+-- Module      :  HaskDeep.Command.Main+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through a directory structure.+-- Uses a list of known hashes to audit a set of files.++module Main+    (+     main+    )+where++import           Prelude hiding (FilePath)++import           Crypto.Classes (Hash)+import qualified Options.Applicative as OA++import           HaskDeep+import           Options++main :: IO ()+main = OA.execParser optionsPI >>= haskdeep++haskdeep :: Options -> IO ()+haskdeep (Options OptComputation OptMD5      conf) = execComputation conf md5hash+haskdeep (Options OptComputation OptSHA1     conf) = execComputation conf sha1hash+haskdeep (Options OptComputation OptSHA256   conf) = execComputation conf sha256hash+haskdeep (Options OptComputation OptSkein512 conf) = execComputation conf skein512hash+haskdeep (Options OptAudit       OptMD5      conf) = execAudit       conf md5hash+haskdeep (Options OptAudit       OptSHA1     conf) = execAudit       conf sha1hash+haskdeep (Options OptAudit       OptSHA256   conf) = execAudit       conf sha256hash+haskdeep (Options OptAudit       OptSkein512 conf) = execAudit       conf skein512hash++execComputation :: Hash ctx a => HaskDeepConfiguration -> ComputationMode a -> IO ()+execComputation conf cm = writeHashes conf =<< compute conf cm++execAudit :: Hash ctx a => HaskDeepConfiguration -> ComputationMode a -> IO ()+execAudit conf cm = do hashset1 <- readHashes conf+                       hashset2 <- compute conf cm+                       let (hashdiff1, hashdiff2) = audit hashset1 hashset2+                       putStrLn "**** Not present in known hashes"+                       print hashdiff1+                       putStrLn "**** Present only in root directory"+                       print hashdiff2
+ src/Options.hs view
@@ -0,0 +1,101 @@+-- |+-- Module      :  HaskDeep.Command.Options+-- Copyright   :  Mauro Taraborelli 2012+-- License     :  BSD3+--+-- Maintainer  :  maurotaraborelli@gmail.com+-- Stability   :  experimental+-- Portability :  unknown+--+-- Computes hashes traversing recursively through the directory structure.+-- Use a list of known hashes to audit a set of files.++module Options+    (+     Options(..)+    ,OptExecution(..)+    ,OptCompMode(..)+    ,optionsPI+    )+where++import           Prelude hiding (FilePath)++import           Data.Text (Text)+import qualified Data.Text as T+import           Filesystem.Path.CurrentOS (FilePath)+import qualified Filesystem.Path.CurrentOS as FSC+import           Options.Applicative++import           HaskDeep++data Options = Options OptExecution OptCompMode HaskDeepConfiguration++data OptExecution = OptComputation+                  | OptAudit++data OptCompMode = OptMD5+                 | OptSHA1+                 | OptSHA256+                 | OptSkein512++optionsPI :: ParserInfo Options+optionsPI = info (optionsP <**> helper)+            ( fullDesc+              & progDesc "Computes hashes and audit a set of files"+              & header "haskdeep - file hashing and audit" )++configurationP :: Parser HaskDeepConfiguration+configurationP = HaskDeepConfiguration+                 <$> nullOption+                         ( long "root"+                           & short 'r'+                           & metavar "DIRNAME"+                           & help "Root directory - default current directory"+                           & reader fpReader+                           & value (rootDirectory defaultHaskDeepConfiguration))+                 <*> nullOption+                         ( long "known"+                           & short 'k'+                           & metavar "FILENAME"+                           & help "Known hashes file - default known.haskdeep"+                           & reader fpReader+                           & value (knownHashes defaultHaskDeepConfiguration))+                 <*> nullOption+                         ( long "ignore"+                           & short 'i'+                           & metavar "RULE"+                           & help "Regex to ignore files or directories"+                           & reader ignReader+                           & value (ignoreRule defaultHaskDeepConfiguration))++optionsP :: Parser Options+optionsP = Options+           <$> subparser+                   ( command "compute"+                     (info (pure OptComputation)+                               (progDesc "Computes file hashes and saves them to known hashes file"))+                     & command "audit"+                     (info (pure OptAudit)+                               (progDesc "Audit files comparing them to known hashes")))+           <*> nullOption+                   ( long "computation"+                     & short 'c'+                     & metavar "MODE"+                     & help "md5 | sha1 | sha256 | skein512 - default md5"+                     & reader compReader+                     & value OptMD5)+           <*> configurationP++fpReader :: String -> Maybe FilePath+fpReader fp = Just $ FSC.decodeString fp++ignReader :: String -> Maybe (Maybe Text)+ignReader = Just . Just . T.pack++compReader :: String -> Maybe OptCompMode+compReader "md5"      = Just OptMD5+compReader "sha1"     = Just OptSHA1+compReader "sha256"   = Just OptSHA256+compReader "skein512" = Just OptSkein512+compReader _          = Nothing