Added annex.freezecontent-command and annex.thawcontent-command configs

Freeze first sets the file perms, and then runs
freezecontent-command. Thaw runs thawcontent-command before
restoring file permissions. This is in case the freeze command
prevents changing file perms, as eg setting a file immutable does.
Also, changing file perms tends to mess up previously set ACLs.

git-annex init's probe for crippled filesystem uses them, so if file perms
don't work, but freezecontent-command manages to prevent write to a file,
it won't treat the filesystem as crippled.

When the the filesystem has been probed as crippled, the hooks are not
used, because there seems to be no point then; git-annex won't be relying
on locking annex objects down. Also, this avoids them being run when the
file perms have not been changed, in case they somehow rely on
git-annex's setting of the file perms in order to work.

Sponsored-by: Dartmouth College's Datalad project
This commit is contained in:
Joey Hess 2021-06-21 14:40:20 -04:00
parent ba62c3467b
commit 4b1b9d7a83
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
8 changed files with 88 additions and 31 deletions

View file

@ -1,6 +1,6 @@
{- git-annex repository initialization
-
- Copyright 2011-2020 Joey Hess <id@joeyh.name>
- Copyright 2011-2021 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
@ -59,6 +59,7 @@ import qualified Utility.LockFile.Posix as Posix
#endif
import qualified Data.Map as M
import Control.Monad.IO.Class (MonadIO)
#ifndef mingw32_HOST_OS
import Data.Either
import qualified System.FilePath.ByteString as P
@ -241,32 +242,40 @@ isInitialized = maybe Annex.Branch.hasSibling (const $ return True) =<< getVersi
- or removing write access from files. -}
probeCrippledFileSystem :: Annex Bool
probeCrippledFileSystem = withEventuallyCleanedOtherTmp $ \tmp -> do
(r, warnings) <- liftIO $ probeCrippledFileSystem' tmp
(r, warnings) <- probeCrippledFileSystem' tmp
(Just freezeContent)
(Just thawContent)
mapM_ warning warnings
return r
probeCrippledFileSystem' :: RawFilePath -> IO (Bool, [String])
probeCrippledFileSystem'
:: (MonadIO m, MonadCatch m)
=> RawFilePath
-> Maybe (RawFilePath -> m ())
-> Maybe (RawFilePath -> m ())
-> m (Bool, [String])
#ifdef mingw32_HOST_OS
probeCrippledFileSystem' _ = return (True, [])
probeCrippledFileSystem' _ _ = return (True, [])
#else
probeCrippledFileSystem' tmp = do
let f = fromRawFilePath (tmp P.</> "gaprobe")
writeFile f ""
r <- probe f
void $ tryIO $ allowWrite (toRawFilePath f)
removeFile f
probeCrippledFileSystem' tmp freezecontent thawcontent = do
let f = tmp P.</> "gaprobe"
let f' = fromRawFilePath f
liftIO $ writeFile f' ""
r <- probe f'
void $ tryNonAsync $ (fromMaybe (liftIO . allowWrite) thawcontent) f
liftIO $ removeFile f'
return r
where
probe f = catchDefaultIO (True, []) $ do
let f2 = f ++ "2"
removeWhenExistsWith R.removeLink (toRawFilePath f2)
createSymbolicLink f f2
removeWhenExistsWith R.removeLink (toRawFilePath f2)
preventWrite (toRawFilePath f)
liftIO $ removeWhenExistsWith R.removeLink (toRawFilePath f2)
liftIO $ createSymbolicLink f f2
liftIO $ removeWhenExistsWith R.removeLink (toRawFilePath f2)
(fromMaybe (liftIO . preventWrite) freezecontent) (toRawFilePath f)
-- Should be unable to write to the file, unless
-- running as root, but some crippled
-- filesystems ignore write bit removals.
ifM ((== 0) <$> getRealUserID)
liftIO $ ifM ((== 0) <$> getRealUserID)
( return (False, [])
, do
r <- catchBoolIO $ do
@ -283,7 +292,8 @@ checkCrippledFileSystem = whenM probeCrippledFileSystem $ do
warning "Detected a crippled filesystem."
setCrippledFileSystem True
{- Normally git disables core.symlinks itself when the
{- Normally git disables core.symlinks itself when the:w
-
- filesystem does not support them. But, even if symlinks are
- supported, we don't use them by default in a crippled
- filesystem. -}

View file

@ -1,6 +1,6 @@
{- git-annex file permissions
-
- Copyright 2012-2020 Joey Hess <id@joeyh.name>
- Copyright 2012-2021 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
@ -131,8 +131,9 @@ createWorkTreeDirectory dir = do
- owned by another user, so failure to set this mode is ignored.
-}
freezeContent :: RawFilePath -> Annex ()
freezeContent file = unlessM crippledFileSystem $
freezeContent file = unlessM crippledFileSystem $ do
withShared go
freezeHook file
where
go GroupShared = liftIO $ void $ tryIO $ modifyFileMode file $
addModes [ownerReadMode, groupReadMode, ownerWriteMode, groupWriteMode]
@ -159,7 +160,7 @@ isContentWritePermOk file = ifM crippledFileSystem
{- Allows writing to an annexed file that freezeContent was called on
- before. -}
thawContent :: RawFilePath -> Annex ()
thawContent file = thawPerms $ withShared go
thawContent file = thawPerms (withShared go) (thawHook file)
where
go GroupShared = liftIO $ void $ tryIO $ groupWriteRead file
go AllShared = liftIO $ void $ tryIO $ groupWriteRead file
@ -167,11 +168,12 @@ thawContent file = thawPerms $ withShared go
{- Runs an action that thaws a file's permissions. This will probably
- fail on a crippled filesystem. But, if file modes are supported on a
- crippled filesystem, the file may be frozen, so try to thaw it. -}
thawPerms :: Annex () -> Annex ()
thawPerms a = ifM crippledFileSystem
( void $ tryNonAsync a
, a
- crippled filesystem, the file may be frozen, so try to thaw its
- permissions. -}
thawPerms :: Annex () -> Annex () -> Annex ()
thawPerms a hook = ifM crippledFileSystem
( void (tryNonAsync a)
, hook >> a
)
{- Blocks writing to the directory an annexed file is in, to prevent the
@ -180,8 +182,9 @@ thawPerms a = ifM crippledFileSystem
- file.
-}
freezeContentDir :: RawFilePath -> Annex ()
freezeContentDir file = unlessM crippledFileSystem $
freezeContentDir file = unlessM crippledFileSystem $ do
withShared go
freezeHook dir
where
dir = parentDir file
go GroupShared = liftIO $ void $ tryIO $ groupWriteRead dir
@ -189,8 +192,9 @@ freezeContentDir file = unlessM crippledFileSystem $
go _ = liftIO $ preventWrite dir
thawContentDir :: RawFilePath -> Annex ()
thawContentDir file =
thawPerms $ liftIO $ allowWrite $ parentDir file
thawContentDir file = thawPerms (liftIO $ allowWrite dir) (thawHook dir)
where
dir = parentDir file
{- Makes the directory tree to store an annexed file's content,
- with appropriate permissions on each level. -}
@ -199,7 +203,8 @@ createContentDir dest = do
unlessM (liftIO $ R.doesPathExist dir) $
createAnnexDirectory dir
-- might have already existed with restricted perms
unlessM crippledFileSystem $
unlessM crippledFileSystem $ do
thawHook dir
liftIO $ allowWrite dir
where
dir = parentDir dest
@ -213,3 +218,17 @@ modifyContent f a = do
v <- tryNonAsync a
freezeContentDir f
either throwM return v
freezeHook :: RawFilePath -> Annex ()
freezeHook p = maybe noop go =<< annexFreezeContentCommand <$> Annex.getGitConfig
where
go basecmd = void $ liftIO $
boolSystem "sh" [Param "-c", Param $ gencmd basecmd]
gencmd = massReplace [ ("%path", shellEscape (fromRawFilePath p)) ]
thawHook :: RawFilePath -> Annex ()
thawHook p = maybe noop go =<< annexThawContentCommand <$> Annex.getGitConfig
where
go basecmd = void $ liftIO $
boolSystem "sh" [Param "-c", Param $ gencmd basecmd]
gencmd = massReplace [ ("%path", shellEscape (fromRawFilePath p)) ]

View file

@ -3,6 +3,8 @@ git-annex (8.20210622) UNRELEASED; urgency=medium
* sync: Partly work around github behavior that first branch to be pushed
to a new repository is assumed to be the head branch, by not pushing
synced/git-annex first.
* Added annex.freezecontent-command and annex.thawcontent-command
configs.
-- Joey Hess <id@joeyh.name> Mon, 21 Jun 2021 12:25:25 -0400

View file

@ -142,7 +142,9 @@ runner opts
exitWith exitcode
runsubprocesstests (Just _) = isolateGitConfig $ do
ensuretmpdir
crippledfilesystem <- fst <$> Annex.Init.probeCrippledFileSystem' (toRawFilePath tmpdir)
crippledfilesystem <- fst <$> Annex.Init.probeCrippledFileSystem'
(toRawFilePath tmpdir)
Nothing Nothing
adjustedbranchok <- Annex.AdjustedBranch.isGitVersionSupported
case tryIngredients ingredients (tastyOptionSet opts) (tests crippledfilesystem adjustedbranchok opts) of
Nothing -> error "No tests found!?"

View file

@ -104,6 +104,8 @@ data GitConfig = GitConfig
, annexFsckNudge :: Bool
, annexAutoUpgrade :: AutoUpgrade
, annexExpireUnused :: Maybe (Maybe Duration)
, annexFreezeContentCommand :: Maybe String
, annexThawContentCommand :: Maybe String
, annexSecureEraseCommand :: Maybe String
, annexGenMetaData :: Bool
, annexListen :: Maybe String
@ -191,6 +193,8 @@ extractGitConfig configsource r = GitConfig
getmaybe (annexConfig "autoupgrade")
, annexExpireUnused = either (const Nothing) Just . parseDuration
<$> getmaybe (annexConfig "expireunused")
, annexFreezeContentCommand = getmaybe (annexConfig "freezecontent-command")
, annexThawContentCommand = getmaybe (annexConfig "thawcontent-command")
, annexSecureEraseCommand = getmaybe (annexConfig "secure-erase-command")
, annexGenMetaData = getbool (annexConfig "genmetadata") False
, annexListen = getmaybe (annexConfig "listen")

View file

@ -1213,6 +1213,16 @@ repository, using [[git-annex-config]]. See its man page for a list.)
For example, to use the wipe command, set it to `wipe -f %file`.
* `annex.freezecontent-command`, `annex.thawcontent-command`
Usually the write permission bits are unset to protect annexed objects
from being modified or deleted. The freezecontent-command is run after
git-annex has removed the write bit. The thawcontent-command should undo
its effect, and is run before git-annex restores the write bit.
In the command line, %path is replaced with the file or directory to
operate on.
* `annex.tune.objecthash1`, `annex.tune.objecthashlower`, `annex.tune.branchhash1`
These can be passed to `git annex init` to tune the repository.

View file

@ -42,3 +42,7 @@ it is that if you explicitly run `chmod +w` on an annexed file in the working
tree, this follows the symlink and allows writing to the file. It would be
better to make the files fully immutable. But most systems either don't
support immutable attributes, or only let root make files immutable.
The git configs `annex.freezecontent-command` and `annex.thawcontent-command`
can be used to run additional commands to further lock down and later thaw
the annex object and directory.

View file

@ -15,7 +15,7 @@ Use cases include:
Design:
Configs: annex.lockdown-command, annex.unlockdown-command
Configs: annex.freezecontent-command, annex.thawcontent-command
In these, "%path" is replaced with the file/directory to act on.
Locking down a directory only needs to do the equivilant of removing its
@ -49,5 +49,11 @@ fed the names of files to operate on via stdin.
> hook could also support things like [[storing_xattrs|support_for_storing_xattrs]]
> --[[Joey]]
[[!tag needsthought]]
[[!tag projects/datalad]]
> [[done]].. `git-annex init` does run annex.freezecontent-command and if it
> prevents writing to a file, it will avoid setting
> annex.crippledfilesystem.
>
> I didn't make `git-annex test` use the global git config of the hooks
> though, not sure if that really makes sense or is needed.