summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Command/Fsck.hs157
-rw-r--r--Locations.hs5
-rw-r--r--Utility/FileMode.hs14
-rw-r--r--doc/git-annex.mdwn8
4 files changed, 155 insertions, 29 deletions
diff --git a/Command/Fsck.hs b/Command/Fsck.hs
index 1e49fd4d3..d231972f2 100644
--- a/Command/Fsck.hs
+++ b/Command/Fsck.hs
@@ -7,8 +7,6 @@
module Command.Fsck where
-import System.Posix.Process (getProcessID)
-
import Common.Annex
import Command
import qualified Annex
@@ -28,6 +26,12 @@ import Config
import qualified Option
import Types.Key
+import System.Posix.Process (getProcessID)
+import Data.Time.Clock.POSIX
+import Data.Time
+import System.Posix.Types (EpochTime)
+import System.Locale
+
def :: [Command]
def = [withOptions options $ command "fsck" paramPaths seek
"check for problems"]
@@ -35,25 +39,40 @@ def = [withOptions options $ command "fsck" paramPaths seek
fromOption :: Option
fromOption = Option.field ['f'] "from" paramRemote "check remote"
+startIncrementalOption :: Option
+startIncrementalOption = Option.flag ['S'] "incremental" "start an incremental fsck"
+
+incrementalOption :: Option
+incrementalOption = Option.flag ['m'] "more" "continue an incremental fsck"
+
options :: [Option]
-options = [fromOption]
+options = [fromOption, startIncrementalOption, incrementalOption]
seek :: [CommandSeek]
seek =
[ withField fromOption Remote.byName $ \from ->
- withFilesInGit $ whenAnnexed $ start from
- , withBarePresentKeys startBare
+ withIncremental $ \i -> withFilesInGit $ whenAnnexed $ start from i
+ , withIncremental $ \i -> withBarePresentKeys $ startBare i
]
-start :: Maybe Remote -> FilePath -> (Key, Backend) -> CommandStart
-start from file (key, backend) = do
+withIncremental :: (Incremental -> CommandSeek) -> CommandSeek
+withIncremental a = withFlag startIncrementalOption $ \startincremental ->
+ withFlag incrementalOption $ \incremental ->
+ a $ case (startincremental, incremental) of
+ (False, False) -> NonIncremental
+ (True, _) -> StartIncremental
+ (False, True) -> ContIncremental
+
+start :: Maybe Remote -> Incremental -> FilePath -> (Key, Backend) -> CommandStart
+start from inc 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
+ Nothing -> go $ perform key file backend numcopies
+ Just r -> go $ performRemote key file backend numcopies r
+ where
+ go = runFsck inc file key
-perform :: Key -> FilePath -> Backend -> Maybe Int -> CommandPerform
+perform :: Key -> FilePath -> Backend -> Maybe Int -> Annex Bool
perform key file backend numcopies = check
-- order matters
[ fixLink key file
@@ -65,13 +84,13 @@ perform key file backend numcopies = check
{- 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 -> FilePath -> Backend -> Maybe Int -> Remote -> Annex Bool
performRemote key file backend numcopies remote =
dispatch =<< Remote.hasKey remote key
where
dispatch (Left err) = do
showNote err
- stop
+ return False
dispatch (Right True) = withtmp $ \tmpfile ->
ifM (getfile tmpfile)
( go True (Just tmpfile)
@@ -111,30 +130,23 @@ withBarePresentKeys a params = isBareRepo >>= go
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
+startBare :: Incremental -> Key -> CommandStart
+startBare inc key = case Backend.maybeLookupBackendName (Types.Key.keyBackendName key) of
Nothing -> stop
- Just backend -> do
- showStart "fsck" (key2file key)
- next $ performBare key backend
+ Just backend -> runFsck inc (key2file key) key $ 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 -> Annex Bool
performBare key backend = check
[ verifyLocationLog key (key2file key)
, checkKeySize key
, checkBackend backend key
]
-check :: [Annex Bool] -> CommandPerform
-check = sequence >=> dispatch
- where
- dispatch vs
- | all (== True) vs = next $ return True
- | otherwise = stop
-
+check :: [Annex Bool] -> Annex Bool
+check cs = all id <$> sequence cs
{- Checks that the file's symlink points correctly to the content. -}
fixLink :: Key -> FilePath -> Annex Bool
@@ -303,3 +315,96 @@ badContentRemote remote key = do
Remote.logStatus remote key InfoMissing
return $ (if ok then "dropped from " else "failed to drop from ")
++ Remote.name remote
+
+data Incremental = StartIncremental | ContIncremental | NonIncremental
+ deriving (Eq)
+
+runFsck :: Incremental -> FilePath -> Key -> Annex Bool -> CommandStart
+runFsck inc file key a = do
+ starttime <- getstart
+ ifM (needFsck inc starttime key)
+ ( do
+ showStart "fsck" file
+ next $ do
+ ok <- a
+ when ok $
+ recordFsckTime key
+ next $ return ok
+ , stop
+ )
+ where
+ getstart
+ | inc == StartIncremental = Just <$> recordStartTime
+ | inc == ContIncremental = getStartTime
+ | otherwise = return Nothing
+
+{- Check if a key needs to be fscked, with support for incremental fscks. -}
+needFsck :: Incremental -> Maybe EpochTime -> Key -> Annex Bool
+needFsck ContIncremental Nothing _ = return True
+needFsck ContIncremental starttime key = do
+ fscktime <- getFsckTime key
+ return $ fscktime < starttime
+needFsck _ _ _ = return True
+
+{- To record the time that a key was last fscked, without
+ - modifying its mtime, we set the timestamp of its parent directory.
+ - Each annexed file is the only thing in its directory, so this is fine.
+ -
+ - To record that the file was fscked, the directory's sticky bit is set.
+ - (None of the normal unix behaviors of the sticky bit should matter, so
+ - we can reuse this permission bit.)
+ -
+ - Note that this relies on the parent directory being deleted when a file
+ - is dropped. That way, if it's later added back, the fsck record
+ - won't still be present.
+ -}
+recordFsckTime :: Key -> Annex ()
+recordFsckTime key = do
+ parent <- parentDir <$> inRepo (gitAnnexLocation key)
+ liftIO $ void $ tryIO $ do
+ touchFile parent
+ setSticky parent
+
+getFsckTime :: Key -> Annex (Maybe EpochTime)
+getFsckTime key = do
+ parent <- parentDir <$> inRepo (gitAnnexLocation key)
+ liftIO $ catchDefaultIO Nothing $ do
+ s <- getFileStatus parent
+ return $ if isSticky $ fileMode s
+ then Just $ modificationTime s
+ else Nothing
+
+{- Records the start time of an interactive fsck, also returning it.
+ -
+ - To guard against time stamp damange (for example, if an annex directory
+ - is copied without -a), the fsckstate file contains a time that should
+ - be identical to its modification time. -}
+recordStartTime :: Annex (EpochTime)
+recordStartTime = do
+ f <- fromRepo gitAnnexFsckState
+ createAnnexDirectory $ parentDir f
+ liftIO $ do
+ nukeFile f
+ h <- openFile f WriteMode
+ t <- modificationTime <$> getFileStatus f
+ hPutStr h $ showTime $ realToFrac t
+ hClose h
+ return t
+ where
+ showTime :: POSIXTime -> String
+ showTime = show
+
+{- Gets the incremental fsck start time. -}
+getStartTime :: Annex (Maybe EpochTime)
+getStartTime = do
+ f <- fromRepo gitAnnexFsckState
+ liftIO $ catchDefaultIO Nothing $ do
+ timestamp <- modificationTime <$> getFileStatus f
+ t <- readishTime <$> readFile f
+ return $ if Just (realToFrac timestamp) == t
+ then Just timestamp
+ else Nothing
+ where
+ readishTime :: String -> Maybe POSIXTime
+ readishTime s = utcTimeToPOSIXSeconds <$>
+ parseTime defaultTimeLocale "%s%Qs" s
diff --git a/Locations.hs b/Locations.hs
index 397081cc4..98eabb172 100644
--- a/Locations.hs
+++ b/Locations.hs
@@ -18,6 +18,7 @@ module Locations (
gitAnnexBadDir,
gitAnnexBadLocation,
gitAnnexUnusedLog,
+ gitAnnexFsckState,
gitAnnexTransferDir,
gitAnnexJournalDir,
gitAnnexJournalLock,
@@ -130,6 +131,10 @@ gitAnnexBadLocation key r = gitAnnexBadDir r </> keyFile key
gitAnnexUnusedLog :: FilePath -> Git.Repo -> FilePath
gitAnnexUnusedLog prefix r = gitAnnexDir r </> (prefix ++ "unused")
+{- .git/annex/fsckstate is used to store information about incremental fscks. -}
+gitAnnexFsckState :: Git.Repo -> FilePath
+gitAnnexFsckState r = gitAnnexDir r </> "fsckstate"
+
{- .git/annex/transfer/ is used is used to record keys currently
- being transferred, and other transfer bookkeeping info. -}
gitAnnexTransferDir :: Git.Repo -> FilePath
diff --git a/Utility/FileMode.hs b/Utility/FileMode.hs
index 353de7b92..c742c690b 100644
--- a/Utility/FileMode.hs
+++ b/Utility/FileMode.hs
@@ -63,9 +63,12 @@ groupWriteRead f = modifyFileMode f $ addModes
, ownerReadMode, groupReadMode
]
+checkMode :: FileMode -> FileMode -> Bool
+checkMode checkfor mode = checkfor `intersectFileModes` mode == checkfor
+
{- Checks if a file mode indicates it's a symlink. -}
isSymLink :: FileMode -> Bool
-isSymLink mode = symbolicLinkMode `intersectFileModes` mode == symbolicLinkMode
+isSymLink = checkMode symbolicLinkMode
{- Checks if a file has any executable bits set. -}
isExecutable :: FileMode -> Bool
@@ -88,3 +91,12 @@ combineModes :: [FileMode] -> FileMode
combineModes [] = undefined
combineModes [m] = m
combineModes (m:ms) = foldl unionFileModes m ms
+
+stickyMode :: FileMode
+stickyMode = 512
+
+isSticky :: FileMode -> Bool
+isSticky = checkMode stickyMode
+
+setSticky :: FilePath -> IO ()
+setSticky f = modifyFileMode f $ addModes [stickyMode]
diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn
index ce7c0be3c..cf6a0c6bd 100644
--- a/doc/git-annex.mdwn
+++ b/doc/git-annex.mdwn
@@ -258,9 +258,13 @@ subdirectories).
With parameters, only the specified files are checked.
To check a remote to fsck, specify --from.
-
+
+ To start a new incremental fsck, specify --incremental. Then
+ the next time you fsck, you can specify --more to skip over
+ files that have already been checked, and continue where it left off.
+
To avoid expensive checksum calculations (and expensive transfers when
- fscking a remote), specify --fast
+ fscking a remote), specify --fast.
* unused