2020-11-16 15:21:33 +00:00
|
|
|
{- git-annex object content presence
|
|
|
|
-
|
2022-01-20 15:33:14 +00:00
|
|
|
- Copyright 2010-2022 Joey Hess <id@joeyh.name>
|
2020-11-16 15:21:33 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE CPP #-}
|
2022-01-21 16:56:07 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2020-11-16 15:21:33 +00:00
|
|
|
|
|
|
|
module Annex.Content.Presence (
|
|
|
|
inAnnex,
|
|
|
|
inAnnex',
|
|
|
|
inAnnexSafe,
|
|
|
|
inAnnexCheck,
|
|
|
|
objectFileExists,
|
|
|
|
withObjectLoc,
|
|
|
|
isUnmodified,
|
2021-07-26 21:33:49 +00:00
|
|
|
isUnmodified',
|
2020-11-16 15:21:33 +00:00
|
|
|
isUnmodifiedCheap,
|
2022-01-20 15:33:14 +00:00
|
|
|
withContentLockFile,
|
2022-05-16 19:19:48 +00:00
|
|
|
contentLockFile,
|
2020-11-16 15:21:33 +00:00
|
|
|
) where
|
|
|
|
|
2021-07-27 18:21:09 +00:00
|
|
|
import Annex.Content.Presence.LowLevel
|
2020-11-16 15:21:33 +00:00
|
|
|
import Annex.Common
|
|
|
|
import qualified Annex
|
|
|
|
import Annex.LockPool
|
2022-01-20 15:33:14 +00:00
|
|
|
import Annex.LockFile
|
2022-01-11 21:01:11 +00:00
|
|
|
import Annex.Version
|
2022-01-20 15:33:14 +00:00
|
|
|
import Types.RepoVersion
|
2020-11-16 15:21:33 +00:00
|
|
|
import qualified Database.Keys
|
|
|
|
import Annex.InodeSentinal
|
|
|
|
import Utility.InodeCache
|
|
|
|
import qualified Utility.RawFilePath as R
|
2022-01-21 16:56:07 +00:00
|
|
|
import qualified Git
|
|
|
|
import Config
|
2020-11-16 15:21:33 +00:00
|
|
|
|
2020-11-23 17:53:12 +00:00
|
|
|
#ifdef mingw32_HOST_OS
|
|
|
|
import Annex.Perms
|
|
|
|
#endif
|
|
|
|
|
2022-01-21 16:56:07 +00:00
|
|
|
import qualified System.FilePath.ByteString as P
|
|
|
|
|
2020-11-16 15:21:33 +00:00
|
|
|
{- Checks if a given key's content is currently present. -}
|
|
|
|
inAnnex :: Key -> Annex Bool
|
|
|
|
inAnnex key = inAnnexCheck key $ liftIO . R.doesPathExist
|
|
|
|
|
|
|
|
{- Runs an arbitrary check on a key's content. -}
|
|
|
|
inAnnexCheck :: Key -> (RawFilePath -> Annex Bool) -> Annex Bool
|
|
|
|
inAnnexCheck key check = inAnnex' id False check key
|
|
|
|
|
|
|
|
{- inAnnex that performs an arbitrary check of the key's content. -}
|
|
|
|
inAnnex' :: (a -> Bool) -> a -> (RawFilePath -> Annex a) -> Key -> Annex a
|
|
|
|
inAnnex' isgood bad check key = withObjectLoc key $ \loc -> do
|
|
|
|
r <- check loc
|
|
|
|
if isgood r
|
|
|
|
then ifM (annexThin <$> Annex.getGitConfig)
|
|
|
|
-- When annex.thin is set, the object file
|
|
|
|
-- could be modified; make sure it's not.
|
|
|
|
-- (Suppress any messages about
|
|
|
|
-- checksumming, to avoid them cluttering
|
|
|
|
-- the display.)
|
|
|
|
( ifM (doQuietAction $ isUnmodified key loc)
|
|
|
|
( return r
|
|
|
|
, return bad
|
|
|
|
)
|
|
|
|
, return r
|
|
|
|
)
|
|
|
|
else return bad
|
|
|
|
|
|
|
|
{- Like inAnnex, checks if the object file for a key exists,
|
|
|
|
- but there are no guarantees it has the right content. -}
|
|
|
|
objectFileExists :: Key -> Annex Bool
|
|
|
|
objectFileExists key =
|
|
|
|
calcRepo (gitAnnexLocation key)
|
|
|
|
>>= liftIO . R.doesPathExist
|
|
|
|
|
|
|
|
{- A safer check; the key's content must not only be present, but
|
|
|
|
- is not in the process of being removed. -}
|
|
|
|
inAnnexSafe :: Key -> Annex (Maybe Bool)
|
|
|
|
inAnnexSafe key = inAnnex' (fromMaybe True) (Just False) go key
|
|
|
|
where
|
|
|
|
is_locked = Nothing
|
|
|
|
is_unlocked = Just True
|
|
|
|
is_missing = Just False
|
|
|
|
|
2022-01-20 15:33:14 +00:00
|
|
|
go contentfile = withContentLockFile key $ flip checklock contentfile
|
2020-11-16 15:21:33 +00:00
|
|
|
|
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
checklock Nothing contentfile = checkOr is_missing contentfile
|
|
|
|
{- The content file must exist, but the lock file generally
|
|
|
|
- won't exist unless a removal is in process. -}
|
|
|
|
checklock (Just lockfile) contentfile =
|
|
|
|
ifM (liftIO $ doesFileExist (fromRawFilePath contentfile))
|
|
|
|
( checkOr is_unlocked lockfile
|
|
|
|
, return is_missing
|
|
|
|
)
|
|
|
|
checkOr d lockfile = checkLocked lockfile >>= return . \case
|
|
|
|
Nothing -> d
|
|
|
|
Just True -> is_locked
|
|
|
|
Just False -> is_unlocked
|
|
|
|
#else
|
|
|
|
checklock Nothing contentfile = liftIO $ ifM (doesFileExist (fromRawFilePath contentfile))
|
|
|
|
( lockShared contentfile >>= \case
|
|
|
|
Nothing -> return is_locked
|
|
|
|
Just lockhandle -> do
|
|
|
|
dropLock lockhandle
|
|
|
|
return is_unlocked
|
|
|
|
, return is_missing
|
|
|
|
)
|
|
|
|
{- In Windows, see if we can take a shared lock. If so,
|
|
|
|
- remove the lock file to clean up after ourselves. -}
|
|
|
|
checklock (Just lockfile) contentfile =
|
|
|
|
ifM (liftIO $ doesFileExist (fromRawFilePath contentfile))
|
revert windows-specific locking changes that broke tests
This reverts windows-specific parts of 5a98f2d50913682c4ebe0e0c4ce695c450a96091
There were no code paths in common between windows and unix, so this
will return Windows to the old behavior.
The problem that the commit talks about has to do with multiple different
locations where git-annex can store annex object files, but that is not
too relevant to Windows anyway, because on windows the filesystem is always
treated as criplled and/or symlinks are not supported, so it will only
use one object location. It would need to be using a repo populated
in another OS to have the other object location in use probably.
Then a drop and get could possibly lead to a dangling lock file.
And, I was not able to actually reproduce that situation happening
before making that commit, even when I forced a race. So making these
changes on windows was just begging trouble..
I suspect that the change that caused the reversion is in
Annex/Content/Presence.hs. It checks if the content file exists,
and then called modifyContentDirWhenExists, which seems like it would
not fail, but if something deleted the content file at that point,
that call would fail. Which would result in an exception being thrown,
which should not normally happen from a call to inAnnexSafe. That was a
windows-specific change; the unix side did not have an equivilant
change.
Sponsored-by: Dartmouth College's Datalad project
2022-05-23 17:04:39 +00:00
|
|
|
( modifyContentDir lockfile $ liftIO $
|
2020-11-16 15:21:33 +00:00
|
|
|
lockShared lockfile >>= \case
|
|
|
|
Nothing -> return is_locked
|
|
|
|
Just lockhandle -> do
|
|
|
|
dropLock lockhandle
|
2020-11-25 10:24:49 +00:00
|
|
|
void $ tryIO $ removeWhenExistsWith R.removeLink lockfile
|
2020-11-16 15:21:33 +00:00
|
|
|
return is_unlocked
|
|
|
|
, return is_missing
|
|
|
|
)
|
|
|
|
#endif
|
|
|
|
|
2022-01-20 15:33:14 +00:00
|
|
|
{- Runs an action with the lock file to use to lock a key's content.
|
|
|
|
- When the content file itself should be locked, runs the action with
|
|
|
|
- Nothing.
|
|
|
|
-
|
|
|
|
- In v9 and below, while the action is running, a shared lock is held of the
|
|
|
|
- gitAnnexContentLockLock. That prevents the v10 upgrade, which changes how
|
|
|
|
- content locking works, from running at the same time as content is locked
|
|
|
|
- using the old method.
|
|
|
|
-}
|
|
|
|
withContentLockFile :: Key -> (Maybe RawFilePath -> Annex a) -> Annex a
|
|
|
|
withContentLockFile k a = do
|
|
|
|
v <- getVersion
|
|
|
|
if versionNeedsWritableContentFiles v
|
2022-08-11 20:57:44 +00:00
|
|
|
then fromRepo gitAnnexContentLockLock >>= \lck -> withSharedLock lck $ do
|
2022-01-21 16:56:07 +00:00
|
|
|
{- While the lock is held, check to see if the git
|
|
|
|
- config has changed, and reload it if so. This
|
|
|
|
- updates the annex.version after the v10 upgrade,
|
|
|
|
- so that a process that started in a v9 repository
|
|
|
|
- will switch over to v10 content lock files at the
|
|
|
|
- right time. -}
|
|
|
|
gitdir <- fromRepo Git.localGitDir
|
|
|
|
let gitconfig = gitdir P.</> "config"
|
|
|
|
ic <- withTSDelta (liftIO . genInodeCache gitconfig)
|
|
|
|
oldic <- Annex.getState Annex.gitconfiginodecache
|
|
|
|
v' <- if fromMaybe False (compareStrong <$> ic <*> oldic)
|
|
|
|
then pure v
|
|
|
|
else do
|
|
|
|
Annex.changeState $ \s ->
|
|
|
|
s { Annex.gitconfiginodecache = ic }
|
|
|
|
reloadConfig
|
|
|
|
getVersion
|
|
|
|
go (v')
|
2022-08-11 20:57:44 +00:00
|
|
|
else go v
|
2022-01-21 16:56:07 +00:00
|
|
|
where
|
|
|
|
go v = contentLockFile k v >>= a
|
2022-01-20 15:33:14 +00:00
|
|
|
|
|
|
|
contentLockFile :: Key -> Maybe RepoVersion -> Annex (Maybe RawFilePath)
|
2020-11-16 15:21:33 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2022-01-11 21:01:11 +00:00
|
|
|
{- Older versions of git-annex locked content files themselves, but newer
|
|
|
|
- versions use a separate lock file, to better support repos shared
|
|
|
|
- amoung users in eg a group. -}
|
2022-01-20 15:33:14 +00:00
|
|
|
contentLockFile key v
|
|
|
|
| versionNeedsWritableContentFiles v = pure Nothing
|
|
|
|
| otherwise = Just <$> calcRepo (gitAnnexContentLock key)
|
2020-11-16 15:21:33 +00:00
|
|
|
#else
|
2022-01-11 21:01:11 +00:00
|
|
|
{- Windows always has to use a separate lock file from the content, since
|
|
|
|
- locking the actual content file would interfere with the user's
|
|
|
|
- use of it. -}
|
2022-01-20 15:33:14 +00:00
|
|
|
contentLockFile key _ = Just <$> calcRepo (gitAnnexContentLock key)
|
2020-11-16 15:21:33 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
{- Performs an action, passing it the location to use for a key's content. -}
|
|
|
|
withObjectLoc :: Key -> (RawFilePath -> Annex a) -> Annex a
|
|
|
|
withObjectLoc key a = a =<< calcRepo (gitAnnexLocation key)
|
|
|
|
|
|
|
|
{- Check if a file contains the unmodified content of the key.
|
|
|
|
-
|
|
|
|
- The expensive way to tell is to do a verification of its content.
|
|
|
|
- The cheaper way is to see if the InodeCache for the key matches the
|
|
|
|
- file. -}
|
|
|
|
isUnmodified :: Key -> RawFilePath -> Annex Bool
|
2021-07-26 21:33:49 +00:00
|
|
|
isUnmodified key f =
|
|
|
|
withTSDelta (liftIO . genInodeCache f) >>= \case
|
|
|
|
Just fc -> do
|
|
|
|
ic <- Database.Keys.getInodeCaches key
|
|
|
|
isUnmodified' key f fc ic
|
|
|
|
Nothing -> return False
|
|
|
|
|
|
|
|
isUnmodified' :: Key -> RawFilePath -> InodeCache -> [InodeCache] -> Annex Bool
|
2021-07-27 18:21:09 +00:00
|
|
|
isUnmodified' = isUnmodifiedLowLevel Database.Keys.addInodeCaches
|
2020-11-16 15:21:33 +00:00
|
|
|
|
|
|
|
{- Cheap check if a file contains the unmodified content of the key,
|
|
|
|
- only checking the InodeCache of the key.
|
|
|
|
-
|
2021-07-29 17:14:03 +00:00
|
|
|
- When the InodeCache is stale, this may incorrectly report that a file is
|
|
|
|
- modified.
|
|
|
|
-
|
2020-11-16 15:21:33 +00:00
|
|
|
- Note that, on systems not supporting high-resolution mtimes,
|
|
|
|
- this may report a false positive when repeated edits are made to a file
|
|
|
|
- within a small time window (eg 1 second).
|
|
|
|
-}
|
|
|
|
isUnmodifiedCheap :: Key -> RawFilePath -> Annex Bool
|
|
|
|
isUnmodifiedCheap key f = maybe (return False) (isUnmodifiedCheap' key)
|
|
|
|
=<< withTSDelta (liftIO . genInodeCache f)
|
|
|
|
|
|
|
|
isUnmodifiedCheap' :: Key -> InodeCache -> Annex Bool
|
2021-07-27 18:21:09 +00:00
|
|
|
isUnmodifiedCheap' key fc = isUnmodifiedCheapLowLevel fc
|
2021-07-26 21:33:49 +00:00
|
|
|
=<< Database.Keys.getInodeCaches key
|