2013-05-17 19:59:37 +00:00
|
|
|
{- git-annex file replacing
|
|
|
|
-
|
2020-03-06 15:31:01 +00:00
|
|
|
- Copyright 2013-2020 Joey Hess <id@joeyh.name>
|
2013-05-17 19:59:37 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2013-05-17 19:59:37 +00:00
|
|
|
-}
|
|
|
|
|
2016-05-06 16:49:41 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
2020-03-06 15:31:01 +00:00
|
|
|
module Annex.ReplaceFile (
|
|
|
|
replaceGitAnnexDirFile,
|
|
|
|
replaceGitDirFile,
|
|
|
|
replaceWorkTreeFile,
|
|
|
|
replaceFile,
|
|
|
|
) where
|
2013-05-17 19:59:37 +00:00
|
|
|
|
2016-01-20 20:36:33 +00:00
|
|
|
import Annex.Common
|
2019-01-17 19:40:44 +00:00
|
|
|
import Annex.Tmp
|
2020-03-06 15:31:01 +00:00
|
|
|
import Annex.Perms
|
|
|
|
import Git
|
2017-12-31 20:08:31 +00:00
|
|
|
import Utility.Tmp.Dir
|
2020-10-29 16:02:46 +00:00
|
|
|
import Utility.Directory.Create
|
2019-05-23 16:13:56 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2017-12-31 20:08:31 +00:00
|
|
|
import Utility.Path.Max
|
2019-05-23 16:13:56 +00:00
|
|
|
#endif
|
2013-05-17 19:59:37 +00:00
|
|
|
|
2020-03-06 15:31:01 +00:00
|
|
|
{- replaceFile on a file located inside the gitAnnexDir. -}
|
|
|
|
replaceGitAnnexDirFile :: FilePath -> (FilePath -> Annex a) -> Annex a
|
|
|
|
replaceGitAnnexDirFile = replaceFile createAnnexDirectory
|
|
|
|
|
|
|
|
{- replaceFile on a file located inside the .git directory. -}
|
|
|
|
replaceGitDirFile :: FilePath -> (FilePath -> Annex a) -> Annex a
|
|
|
|
replaceGitDirFile = replaceFile $ \dir -> do
|
2020-10-29 16:02:46 +00:00
|
|
|
top <- fromRepo localGitDir
|
2020-03-06 15:31:01 +00:00
|
|
|
liftIO $ createDirectoryUnder top dir
|
|
|
|
|
|
|
|
{- replaceFile on a worktree file. -}
|
|
|
|
replaceWorkTreeFile :: FilePath -> (FilePath -> Annex a) -> Annex a
|
2020-03-06 15:40:20 +00:00
|
|
|
replaceWorkTreeFile = replaceFile createWorkTreeDirectory
|
2020-03-06 15:31:01 +00:00
|
|
|
|
2013-05-17 19:59:37 +00:00
|
|
|
{- Replaces a possibly already existing file with a new version,
|
|
|
|
- atomically, by running an action.
|
|
|
|
-
|
fix replaceFile makeAnnexLink race
replaceFile created a temp file, which was guaranteed to not overlap with
another temp file. However, makeAnnexLink then deleted that file, in
preparation for making the symlink in its place. This caused a race, since
some other replaceFile could create a temp file, using the same name!
I was able to reproduce the race easily running git-annex add -J10 in a
directory with 100 files (all with different contents). Some files would
get ingested into the annex, but their annex links would fail to be added.
There could be other situations where this same problem could occur.
Perhaps when the assistant is adding a file, if the user manually also ran
git-annex add. Perhaps in cases not involving adding a file.
The new replaceFile makes a temprary directory, which is guaranteed to be
unique, and doesn't make a temp file in there. makeAnnexLink can thus
create the symlink without problem and the race is avoided.
Audited all calls to replaceFile to make sure that the old behavior of
providing an empty temp file was not relied on.
The general problem of asking for a temp file and deleting it as part of
the process of using it could reach beyond replaceFile. Did some quick
audits and didn't find other cases of it. Probably only symlink creation
stuff would tend to make that mistake, mostly.
2015-11-06 19:08:19 +00:00
|
|
|
- The action is passed the name of temp file, in a temp directory,
|
|
|
|
- which it can write to, and once done the temp file is moved into place
|
|
|
|
- and anything else in the temp directory is deleted.
|
2013-05-23 00:58:27 +00:00
|
|
|
-
|
2015-12-27 19:59:59 +00:00
|
|
|
- The action can throw an exception, in which case the temp directory
|
2013-05-23 00:58:27 +00:00
|
|
|
- will be deleted, and the existing file will be preserved.
|
|
|
|
-
|
|
|
|
- Throws an IO exception when it was unable to replace the file.
|
2020-03-06 15:31:01 +00:00
|
|
|
-
|
|
|
|
- The createdirectory action is only run when moving the file into place
|
|
|
|
- fails, and can create any parent directory structure needed.
|
2013-05-17 19:59:37 +00:00
|
|
|
-}
|
2020-10-29 16:02:46 +00:00
|
|
|
replaceFile :: (RawFilePath -> Annex ()) -> FilePath -> (FilePath -> Annex a) -> Annex a
|
2020-03-06 15:31:01 +00:00
|
|
|
replaceFile createdirectory file action = withOtherTmp $ \othertmpdir -> do
|
2020-10-29 16:02:46 +00:00
|
|
|
let othertmpdir' = fromRawFilePath othertmpdir
|
2016-05-06 16:49:41 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
-- Use part of the filename as the template for the temp
|
|
|
|
-- directory. This does not need to be unique, but it
|
|
|
|
-- makes it more clear what this temp directory is for.
|
2020-10-29 16:02:46 +00:00
|
|
|
filemax <- liftIO $ fileNameLengthLimit othertmpdir'
|
2015-12-06 20:42:40 +00:00
|
|
|
let basetmp = take (filemax `div` 2) (takeFileName file)
|
2016-05-06 16:49:41 +00:00
|
|
|
#else
|
|
|
|
-- Windows has limits on the whole path length, so keep
|
|
|
|
-- it short.
|
|
|
|
let basetmp = "t"
|
|
|
|
#endif
|
2020-10-29 16:02:46 +00:00
|
|
|
withTmpDirIn othertmpdir' basetmp $ \tmpdir -> do
|
2015-12-06 20:50:37 +00:00
|
|
|
let tmpfile = tmpdir </> basetmp
|
2018-08-16 19:15:20 +00:00
|
|
|
r <- action tmpfile
|
2020-03-06 15:31:01 +00:00
|
|
|
replaceFileFrom tmpfile file createdirectory
|
2018-08-16 19:15:20 +00:00
|
|
|
return r
|
2014-08-15 17:38:05 +00:00
|
|
|
|
2020-10-29 16:02:46 +00:00
|
|
|
replaceFileFrom :: FilePath -> FilePath -> (RawFilePath -> Annex ()) -> Annex ()
|
2020-03-06 15:31:01 +00:00
|
|
|
replaceFileFrom src dest createdirectory = go `catchIO` fallback
|
2014-08-15 17:38:05 +00:00
|
|
|
where
|
2020-03-06 15:31:01 +00:00
|
|
|
go = liftIO $ moveFile src dest
|
2014-08-15 17:38:05 +00:00
|
|
|
fallback _ = do
|
2020-10-29 16:02:46 +00:00
|
|
|
createdirectory (parentDir (toRawFilePath dest))
|
2014-08-15 17:38:05 +00:00
|
|
|
go
|