d1da9cf221
Test suite now passes with -threaded! I traced back all the hangs with -threaded to System.Cmd.Utils. It seems it's just crappy/unsafe/outdated, and should not be used. System.Process seems to be the cool new thing, so converted all the code to use it instead. In the process, --debug stopped printing commands it runs. I may try to bring that back later. Note that even SafeSystem was switched to use System.Process. Since that was a modified version of code from System.Cmd.Utils, it needed to be converted too. I also got rid of nearly all calls to forkProcess, and all calls to executeFile, which I'm also doubtful about working well with -threaded.
303 lines
9.1 KiB
Haskell
303 lines
9.1 KiB
Haskell
{- git-annex command
|
|
-
|
|
- Copyright 2010-2011 Joey Hess <joey@kitenet.net>
|
|
-
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
module Command.Fsck where
|
|
|
|
import System.Posix.Process (getProcessID)
|
|
|
|
import Common.Annex
|
|
import Command
|
|
import qualified Annex
|
|
import qualified Annex.Queue
|
|
import qualified Remote
|
|
import qualified Types.Backend
|
|
import qualified Types.Key
|
|
import qualified Backend
|
|
import Annex.Content
|
|
import Annex.Perms
|
|
import Logs.Location
|
|
import Logs.Trust
|
|
import Annex.UUID
|
|
import Utility.DataUnits
|
|
import Utility.FileMode
|
|
import Config
|
|
import qualified Option
|
|
|
|
def :: [Command]
|
|
def = [withOptions options $ command "fsck" paramPaths seek
|
|
"check for problems"]
|
|
|
|
fromOption :: Option
|
|
fromOption = Option.field ['f'] "from" paramRemote "check remote"
|
|
|
|
options :: [Option]
|
|
options = [fromOption]
|
|
|
|
seek :: [CommandSeek]
|
|
seek =
|
|
[ withField fromOption Remote.byName $ \from ->
|
|
withFilesInGit $ whenAnnexed $ start from
|
|
, withBarePresentKeys startBare
|
|
]
|
|
|
|
start :: Maybe Remote -> FilePath -> (Key, Backend) -> CommandStart
|
|
start from file (key, backend) = do
|
|
numcopies <- numCopies file
|
|
showStart "fsck" file
|
|
case from of
|
|
Nothing -> next $ perform key file backend numcopies
|
|
Just r -> next $ performRemote key file backend numcopies r
|
|
|
|
perform :: Key -> FilePath -> Backend -> Maybe Int -> CommandPerform
|
|
perform key file backend numcopies = check
|
|
-- order matters
|
|
[ fixLink key file
|
|
, verifyLocationLog key file
|
|
, checkKeySize key
|
|
, checkBackend backend key
|
|
, checkKeyNumCopies key file numcopies
|
|
]
|
|
|
|
{- To fsck a remote, the content is retrieved to a tmp file,
|
|
- and checked locally. -}
|
|
performRemote :: Key -> FilePath -> Backend -> Maybe Int -> Remote -> CommandPerform
|
|
performRemote key file backend numcopies remote =
|
|
dispatch =<< Remote.hasKey remote key
|
|
where
|
|
dispatch (Left err) = do
|
|
showNote err
|
|
stop
|
|
dispatch (Right True) = withtmp $ \tmpfile ->
|
|
ifM (getfile tmpfile)
|
|
( go True (Just tmpfile)
|
|
, go True Nothing
|
|
)
|
|
dispatch (Right False) = go False Nothing
|
|
go present localcopy = check
|
|
[ verifyLocationLogRemote key file remote present
|
|
, checkKeySizeRemote key remote localcopy
|
|
, checkBackendRemote backend key remote localcopy
|
|
, checkKeyNumCopies key file numcopies
|
|
]
|
|
withtmp a = do
|
|
pid <- liftIO getProcessID
|
|
t <- fromRepo gitAnnexTmpDir
|
|
createAnnexDirectory t
|
|
let tmp = t </> "fsck" ++ show pid ++ "." ++ keyFile key
|
|
let cleanup = liftIO $ catchIO (removeFile tmp) (const noop)
|
|
cleanup
|
|
cleanup `after` a tmp
|
|
getfile tmp =
|
|
ifM (Remote.retrieveKeyFileCheap remote key tmp)
|
|
( return True
|
|
, ifM (Annex.getState Annex.fast)
|
|
( return False
|
|
, Remote.retrieveKeyFile remote key Nothing tmp
|
|
)
|
|
)
|
|
|
|
{- To fsck a bare repository, fsck each key in the location log. -}
|
|
withBarePresentKeys :: (Key -> CommandStart) -> CommandSeek
|
|
withBarePresentKeys a params = isBareRepo >>= go
|
|
where
|
|
go False = return []
|
|
go True = do
|
|
unless (null params) $
|
|
error "fsck should be run without parameters in a bare repository"
|
|
map a <$> loggedKeys
|
|
|
|
startBare :: Key -> CommandStart
|
|
startBare key = case Backend.maybeLookupBackendName (Types.Key.keyBackendName key) of
|
|
Nothing -> stop
|
|
Just backend -> do
|
|
showStart "fsck" (show key)
|
|
next $ performBare key backend
|
|
|
|
{- Note that numcopies cannot be checked in a bare repository, because
|
|
- getting the numcopies value requires a working copy with .gitattributes
|
|
- files. -}
|
|
performBare :: Key -> Backend -> CommandPerform
|
|
performBare key backend = check
|
|
[ verifyLocationLog key (show key)
|
|
, checkKeySize key
|
|
, checkBackend backend key
|
|
]
|
|
|
|
check :: [Annex Bool] -> CommandPerform
|
|
check = sequence >=> dispatch
|
|
where
|
|
dispatch vs
|
|
| all (== True) vs = next $ return True
|
|
| otherwise = stop
|
|
|
|
|
|
{- Checks that the file's symlink points correctly to the content. -}
|
|
fixLink :: Key -> FilePath -> Annex Bool
|
|
fixLink key file = do
|
|
want <- calcGitLink file key
|
|
have <- liftIO $ readSymbolicLink file
|
|
when (want /= have) $ do
|
|
{- Version 3.20120227 had a bug that could cause content
|
|
- to be stored in the wrong hash directory. Clean up
|
|
- after the bug by moving the content.
|
|
-}
|
|
whenM (liftIO $ doesFileExist file) $
|
|
unlessM (inAnnex key) $ do
|
|
showNote "fixing content location"
|
|
dir <- liftIO $ parentDir <$> absPath file
|
|
let content = absPathFrom dir have
|
|
liftIO $ allowWrite (parentDir content)
|
|
moveAnnex key content
|
|
|
|
showNote "fixing link"
|
|
liftIO $ createDirectoryIfMissing True (parentDir file)
|
|
liftIO $ removeFile file
|
|
liftIO $ createSymbolicLink want file
|
|
Annex.Queue.addCommand "add" [Param "--force", Param "--"] [file]
|
|
return True
|
|
|
|
{- Checks that the location log reflects the current status of the key,
|
|
in this repository only. -}
|
|
verifyLocationLog :: Key -> String -> Annex Bool
|
|
verifyLocationLog key desc = do
|
|
present <- inAnnex key
|
|
|
|
-- Since we're checking that a key's file is present, throw
|
|
-- in a permission fixup here too.
|
|
when present $ do
|
|
file <- inRepo $ gitAnnexLocation key
|
|
freezeContent file
|
|
freezeContentDir file
|
|
|
|
u <- getUUID
|
|
verifyLocationLog' key desc present u (logChange key u)
|
|
|
|
verifyLocationLogRemote :: Key -> String -> Remote -> Bool -> Annex Bool
|
|
verifyLocationLogRemote key desc remote present =
|
|
verifyLocationLog' key desc present (Remote.uuid remote)
|
|
(Remote.logStatus remote key)
|
|
|
|
verifyLocationLog' :: Key -> String -> Bool -> UUID -> (LogStatus -> Annex ()) -> Annex Bool
|
|
verifyLocationLog' key desc present u bad = do
|
|
uuids <- Remote.keyLocations key
|
|
case (present, u `elem` uuids) of
|
|
(True, False) -> do
|
|
fix InfoPresent
|
|
-- There is no data loss, so do not fail.
|
|
return True
|
|
(False, True) -> do
|
|
fix InfoMissing
|
|
warning $
|
|
"** Based on the location log, " ++ desc
|
|
++ "\n** was expected to be present, " ++
|
|
"but its content is missing."
|
|
return False
|
|
_ -> return True
|
|
where
|
|
fix s = do
|
|
showNote "fixing location log"
|
|
bad s
|
|
|
|
{- The size of the data for a key is checked against the size encoded in
|
|
- the key's metadata, if available. -}
|
|
checkKeySize :: Key -> Annex Bool
|
|
checkKeySize key = do
|
|
file <- inRepo $ gitAnnexLocation key
|
|
ifM (liftIO $ doesFileExist file)
|
|
( checkKeySize' key file badContent
|
|
, return True
|
|
)
|
|
|
|
checkKeySizeRemote :: Key -> Remote -> Maybe FilePath -> Annex Bool
|
|
checkKeySizeRemote _ _ Nothing = return True
|
|
checkKeySizeRemote key remote (Just file) = checkKeySize' key file
|
|
(badContentRemote remote)
|
|
|
|
checkKeySize' :: Key -> FilePath -> (Key -> Annex String) -> Annex Bool
|
|
checkKeySize' key file bad = case Types.Key.keySize key of
|
|
Nothing -> return True
|
|
Just size -> do
|
|
size' <- fromIntegral . fileSize
|
|
<$> liftIO (getFileStatus file)
|
|
comparesizes size size'
|
|
where
|
|
comparesizes a b = do
|
|
let same = a == b
|
|
unless same $ badsize a b
|
|
return same
|
|
badsize a b = do
|
|
msg <- bad key
|
|
warning $ concat
|
|
[ "Bad file size ("
|
|
, compareSizes storageUnits True a b
|
|
, "); "
|
|
, msg
|
|
]
|
|
|
|
checkBackend :: Backend -> Key -> Annex Bool
|
|
checkBackend backend key = do
|
|
file <- inRepo (gitAnnexLocation key)
|
|
checkBackend' backend key (Just file) badContent
|
|
|
|
checkBackendRemote :: Backend -> Key -> Remote -> Maybe FilePath -> Annex Bool
|
|
checkBackendRemote backend key remote localcopy =
|
|
checkBackend' backend key localcopy (badContentRemote remote)
|
|
|
|
checkBackend' :: Backend -> Key -> Maybe FilePath -> (Key -> Annex String) -> Annex Bool
|
|
checkBackend' _ _ Nothing _ = return True
|
|
checkBackend' backend key (Just file) bad = case Types.Backend.fsckKey backend of
|
|
Nothing -> return True
|
|
Just a -> do
|
|
ok <- a key file
|
|
unless ok $ do
|
|
msg <- bad key
|
|
warning $ "Bad file content; " ++ msg
|
|
return ok
|
|
|
|
checkKeyNumCopies :: Key -> FilePath -> Maybe Int -> Annex Bool
|
|
checkKeyNumCopies key file numcopies = do
|
|
needed <- getNumCopies numcopies
|
|
(untrustedlocations, safelocations) <- trustPartition UnTrusted =<< Remote.keyLocations key
|
|
let present = length safelocations
|
|
if present < needed
|
|
then do
|
|
ppuuids <- Remote.prettyPrintUUIDs "untrusted" untrustedlocations
|
|
warning $ missingNote file present needed ppuuids
|
|
return False
|
|
else return True
|
|
|
|
missingNote :: String -> Int -> Int -> String -> String
|
|
missingNote file 0 _ [] =
|
|
"** No known copies exist of " ++ file
|
|
missingNote file 0 _ untrusted =
|
|
"Only these untrusted locations may have copies of " ++ file ++
|
|
"\n" ++ untrusted ++
|
|
"Back it up to trusted locations with git-annex copy."
|
|
missingNote file present needed [] =
|
|
"Only " ++ show present ++ " of " ++ show needed ++
|
|
" trustworthy copies exist of " ++ file ++
|
|
"\nBack it up with git-annex copy."
|
|
missingNote file present needed untrusted =
|
|
missingNote file present needed [] ++
|
|
"\nThe following untrusted locations may also have copies: " ++
|
|
"\n" ++ untrusted
|
|
|
|
{- Bad content is moved aside. -}
|
|
badContent :: Key -> Annex String
|
|
badContent key = do
|
|
dest <- moveBad key
|
|
return $ "moved to " ++ dest
|
|
|
|
badContentRemote :: Remote -> Key -> Annex String
|
|
badContentRemote remote key = do
|
|
ok <- Remote.removeKey remote key
|
|
-- better safe than sorry: assume the remote dropped the key
|
|
-- even if it seemed to fail; the failure could have occurred
|
|
-- after it really dropped it
|
|
Remote.logStatus remote key InfoMissing
|
|
return $ (if ok then "dropped from " else "failed to drop from ")
|
|
++ Remote.name remote
|