darcs-beta-2.2.98.1: src/Darcs/Commands/GZCRCs.lhs
% Copyright (C) 2009 Ganesh Sittampalam
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2, or (at your option)
% any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; see the file COPYING. If not, write to
% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
% Boston, MA 02110-1301, USA.
\subsection{darcs gzcrcs}
\darcsCommand{gzcrcs}
\begin{code}
module Darcs.Commands.GZCRCs ( gzcrcs, doCRCWarnings ) where
import Control.Arrow ( (***) )
import Control.Monad ( when )
import Control.Monad.Trans ( liftIO )
import Control.Monad.Writer ( runWriter, runWriterT, tell )
import Data.List ( intersperse )
import Data.Monoid ( Any(..), Sum(..) )
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import System.Directory ( getDirectoryContents, doesFileExist, doesDirectoryExist )
import System.Exit ( ExitCode(..), exitWith )
import Darcs.Commands ( DarcsCommand(..), nodefaults )
import Darcs.Arguments ( DarcsFlag( Quiet, Verbose, Check, Repair ),
check_or_repair, working_repo_dir,
)
import Darcs.Repository ( Repository, amInRepository, withRepository )
import Darcs.Patch ( RepoPatch )
import Printer ( putDocLn, text )
import ByteStringUtils ( isGZFile )
import Darcs.Lock ( gzWriteAtomicFilePSs )
-- This command needs access beyond the normal repository APIs (to
-- get at the caches and inspect them directly)
-- Could move the relevant code into Darcs.Repository modules
-- but it doesn't really seem worth it.
import Darcs.Repository.InternalTypes ( extractCache )
import Darcs.Repository.Cache ( Cache(..), writable, hashedFilePath, allHashedDirs )
#ifdef HAVE_HASKELL_ZLIB
import Darcs.Global ( getCRCWarnings, resetCRCWarnings )
import ByteStringUtils ( gzDecompress )
#else
-- These functions aren't available unless we have the Haskell zlib.
-- The gzcrcs command shouldn't be enabled in this case, but we would
-- still like to typecheck this module as much as possible so we include
-- dummy versions
noChecking :: String -> a
noChecking what = error $ "Darcs.Commands.GZCRCs." ++ what ++ ": gz CRC checking is not possible unless " ++
"darcs has been built with the Haskell zlib. This code should be unreachable."
getCRCWarnings :: IO [FilePath]
getCRCWarnings = noChecking "getCRCWarnings"
resetCRCWarnings :: IO ()
resetCRCWarnings = noChecking "resetCRCWarnings"
gzDecompress :: forall m . (Functor m, Monad m) => m () -> Maybe Int -> BL.ByteString -> m [B.ByteString]
gzDecompress = noChecking "gzDecompress"
#endif
gzcrcs_description :: String
gzcrcs_description = "Check or repair the CRCs of compressed files in the repository."
gzcrcs_help :: String
gzcrcs_help = formatText
[
"Versions of darcs >=1.0.4 and <2.2.0 had a bug that caused compressed files " ++
"with bad CRCs (but valid data) to be written out. CRCs were not checked on " ++
"reading, so this bug wasn't noticed.",
"This command inspects your repository and caches for this corruption and optionally " ++
"repairs it.",
"You may wish to also run 'darcs check --complete' before repairing the corruption. " ++
"This is not done automatically because it might result in needing to fetch extra " ++
"patches if the repository is lazy.",
"If there are any other problems with your repository, you can still repair the CRCs, " ++
"but you are advised to first make a backup copy in case the CRC errors are actually " ++
"caused by bad data and the old CRCs might be useful in recovering that data.",
"If you were warned about CRC errors during an operation involving another repository, " ++
"then it is possible that the other repository contains the corrupt CRCs, so you " ++
"should arrange for that repository to also be checked/repaired.",
"Note that readonly caches, or other repositories that were sources for lazy gets, " ++
"will be checked but not repaired. Also, this command will abort if it encounters " ++
"any non-CRC corruption in compressed files."
]
formatText :: [String] -> String
formatText = unlines . concat . intersperse [""] . map (map unwords . para 80 . words)
-- |Take a list of words and split it up so that each chunk fits into the specified width
-- when spaces are included. Any words longer than the specified width end up in a chunk
-- of their own.
para :: Int -> [[a]] -> [[[a]]]
para w = para'
where para' [] = []
para' xs = uncurry (:) $ para'' w xs
para'' r (x:xs) | w == r || length x < r = ((x:) *** id) $ para'' (r - length x - 1) xs
para'' _ xs = ([], para' xs)
-- |This is designed for use in an atexit handler, e.g. in Darcs.RunCommand
doCRCWarnings :: Bool -> IO ()
doCRCWarnings verbose = do
files <- getCRCWarnings
resetCRCWarnings
when (not . null $ files) $ do
putStr . formatText $
["",
"Warning: CRC errors found. These are probably harmless but should " ++
"be repaired. See 'darcs gzcrcs --help' for more information.",
""]
when verbose $ putStrLn $ unlines ("The following corrupt files were found:":files)
gzcrcs :: DarcsCommand
gzcrcs = DarcsCommand {command_name = "gzcrcs",
command_help = gzcrcs_help,
command_description = gzcrcs_description,
command_extra_args = 0,
command_extra_arg_help = [],
command_command = gzcrcs_cmd,
command_prereq = amInRepository,
command_get_arg_possibilities = return [],
command_argdefaults = nodefaults,
command_advanced_options = [],
command_basic_options = [check_or_repair,
working_repo_dir
]}
gzcrcs_cmd :: [DarcsFlag] -> [String] -> IO ()
gzcrcs_cmd opts _ | Check `elem` opts || Repair `elem` opts = withRepository opts (gzcrcs' opts)
gzcrcs_cmd _ _ = error "You must specify --check or --repair for gzcrcs"
gzcrcs' :: (RepoPatch p) => [DarcsFlag] -> Repository p -> IO ()
gzcrcs' opts repo = do
let Ca locs = extractCache repo
((), Any checkFailed) <- runWriterT $ flip mapM_ locs $ \loc -> do
let w = writable loc
flip mapM_ allHashedDirs $ \hdir -> do
let dir = hashedFilePath loc hdir ""
exists <- liftIO $ doesDirectoryExist dir
when exists $ do
liftIO $ putInfo $ text $ "Checking " ++ dir ++ (if w then "" else " (readonly)")
files <- liftIO $ getDirectoryContents dir
((), Sum (count :: Int)) <- runWriterT $ flip mapM_ files $ \file -> do
let fn = dir ++ file
isfile <- liftIO $ doesFileExist fn
when isfile $ do
gz <- liftIO $ isGZFile fn
case gz of
Nothing -> return ()
Just len -> do
contents <- liftIO $ B.readFile fn
let (uncompressed, Any isCorrupt)
= runWriter . gzDecompress (tell (Any True)) (Just len) . BL.fromChunks $ [contents]
when isCorrupt $ do
tell (Sum 1) -- count of files in current directory
liftIO $ putVerbose $ text $ "Corrupt: " ++ fn
when (w && Repair `elem` opts) $ liftIO $ gzWriteAtomicFilePSs fn uncompressed
when (count > 0) $ do
liftIO $ putInfo $ text $
"Found " ++ show count ++ " corrupt file" ++ (if count > 1 then "s" else "") ++
(if Repair `elem` opts then (if w then " (repaired)" else " (not repaired") else "")
tell (Any True) -- something corrupt somewhere
when (Check `elem` opts && checkFailed) $ exitWith $ ExitFailure 1
where
putInfo s = when (not $ Quiet `elem` opts) $ putDocLn s
putVerbose s = when (Verbose `elem` opts) $ putDocLn s
\end{code}