]> dgit.raspbian.org Git - git-annex.git/commitdiff
Added annex.freezecontent-command and annex.thawcontent-command configs
authorJoey Hess <joeyh@joeyh.name>
Mon, 21 Jun 2021 18:40:20 +0000 (14:40 -0400)
committerJoey Hess <joeyh@joeyh.name>
Mon, 21 Jun 2021 18:40:52 +0000 (14:40 -0400)
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
Annex/Init.hs
Annex/Perms.hs
CHANGELOG
Test.hs
Types/GitConfig.hs
doc/git-annex.mdwn
doc/internals/lockdown.mdwn
doc/todo/lockdown_hooks.mdwn

index 0da934889b08d7dd1f0e5071cbe00f43f173db6d..a8f260edab2365445463200304af4a87cb7e408e 100644 (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. -}
index cae5a94a31846a8bef9982b7a75422b86db49ff7..4e71b2c78ca9be60a041cb6ce3e59380af7d5baa 100644 (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)) ]
index c0629ad3b87710ad73f762cf5edcbfbcc356aff6..9c32f4ad8987bb00bfbb7aabebfd4381a368beac 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
 
diff --git a/Test.hs b/Test.hs
index c5d419074a9fe5e31cdde8bd7afe1d434e51a127..39dd88c099af4c7869b0dafa2be07f9d6ae8b0a4 100644 (file)
--- a/Test.hs
+++ b/Test.hs
@@ -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!?"
index 90e9eb54a0ff0efd0316ab5a633b27dd512a6ede..c058fc1619a345e60ae2414c6840068d1b7d7b55 100644 (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")
index 7cf8b43760cfd2b44ecf80a643cd8fd883f79611..4c2fa4ff3a628773b924512540da945ee3132972 100644 (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.
index 5e1160de16aa5aea6e4f7fcaad9f765b1abab1f2..86b5260a126ab389d9c2504312375c882b6ee91e 100644 (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.
index 9c45883aeb04edf6e24ba0b7243ea1d3d884e283..c8f812bb2185cbc2b4425e2f27efcee2206cdf90 100644 (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.