aboutsummaryrefslogtreecommitdiff
path: root/Utility/InodeCache.hs
diff options
context:
space:
mode:
authorGravatar Joey Hess <joey@kitenet.net>2014-06-11 17:51:12 -0400
committerGravatar Joey Hess <joey@kitenet.net>2014-06-12 13:42:21 -0400
commit34c9eab951a73f06de70170cc2c2d40ce490ea70 (patch)
treeb74bc50d26a7667b6ef9c4826fb575252d655103 /Utility/InodeCache.hs
parent8550dd4bb75f03700fbebd7cf1b38cb2ef8b29f5 (diff)
fix for Windows file timestamp timezone madness
On Windows, changing the time zone causes the apparent mtime of files to change. This confuses git-annex, which natually thinks this means the files have actually been modified (since THAT'S WHAT A MTIME IS FOR, BILL <sheesh>). Work around this stupidity, by using the inode sentinal file to detect if the timezone has changed, and calculate a TSDelta, which will be applied when generating InodeCaches. This should add no overhead at all on unix. Indeed, I sped up a few things slightly in the refactoring. Seems to basically work! But it has a big known problem: If the timezone changes while the assistant (or a long-running command) runs, it won't notice, since it only checks the inode cache once, and so will use the old delta for all new inode caches it generates for new files it's added. Which will result in them seeming changed the next time it runs. This commit was sponsored by Vincent Demeester.
Diffstat (limited to 'Utility/InodeCache.hs')
-rw-r--r--Utility/InodeCache.hs96
1 files changed, 90 insertions, 6 deletions
diff --git a/Utility/InodeCache.hs b/Utility/InodeCache.hs
index 9bcb6d4f8..b0718e0be 100644
--- a/Utility/InodeCache.hs
+++ b/Utility/InodeCache.hs
@@ -6,19 +6,33 @@
- License: BSD-2-clause
-}
+{-# LANGUAGE CPP #-}
+
module Utility.InodeCache (
InodeCache,
InodeComparisonType(..),
+
compareStrong,
compareWeak,
compareBy,
+
readInodeCache,
showInodeCache,
genInodeCache,
toInodeCache,
+
InodeCacheKey,
inodeCacheToKey,
inodeCacheToMtime,
+
+ SentinalFile(..),
+ SentinalStatus(..),
+ TSDelta,
+ noTSDelta,
+ writeSentinalFile,
+ checkSentinalFile,
+ sentinalFileExists,
+
prop_read_show_inodecache
) where
@@ -32,7 +46,6 @@ data InodeCachePrim = InodeCachePrim FileID FileOffset EpochTime
newtype InodeCache = InodeCache InodeCachePrim
deriving (Show)
-
{- Inode caches can be compared in two different ways, either weakly
- or strongly. -}
data InodeComparisonType = Weakly | Strongly
@@ -92,17 +105,88 @@ readInodeCache s = case words s of
in InodeCache <$> prim
_ -> Nothing
-genInodeCache :: FilePath -> IO (Maybe InodeCache)
-genInodeCache f = catchDefaultIO Nothing $ toInodeCache <$> getFileStatus f
+genInodeCache :: FilePath -> TSDelta -> IO (Maybe InodeCache)
+genInodeCache f delta = catchDefaultIO Nothing $
+ toInodeCache delta <$> getFileStatus f
-toInodeCache :: FileStatus -> Maybe InodeCache
-toInodeCache s
+toInodeCache :: TSDelta -> FileStatus -> Maybe InodeCache
+toInodeCache (TSDelta delta) s
| isRegularFile s = Just $ InodeCache $ InodeCachePrim
(fileID s)
(fileSize s)
- (modificationTime s)
+ (modificationTime s + delta)
| otherwise = Nothing
+{- Some filesystem get new random inodes each time they are mounted.
+ - To detect this and other problems, a sentinal file can be created.
+ - Its InodeCache at the time of its creation is written to the cache file,
+ - so changes can later be detected. -}
+data SentinalFile = SentinalFile
+ { sentinalFile :: FilePath
+ , sentinalCacheFile :: FilePath
+ }
+ deriving (Show)
+
+{- On Windows, the mtime of a file appears to change when the time zone is
+ - changed. To deal with this, a TSDelta can be used; the delta is added to
+ - the mtime when generating an InodeCache. The current delta can be found
+ - by looking at the SentinalFile. -}
+newtype TSDelta = TSDelta EpochTime
+ deriving (Show)
+
+noTSDelta :: TSDelta
+noTSDelta = TSDelta 0
+
+writeSentinalFile :: SentinalFile -> IO ()
+writeSentinalFile s = do
+ writeFile (sentinalFile s) ""
+ maybe noop (writeFile (sentinalCacheFile s) . showInodeCache)
+ =<< genInodeCache (sentinalFile s) noTSDelta
+
+data SentinalStatus = SentinalStatus
+ { sentinalInodesChanged :: Bool
+ , sentinalTSDelta :: TSDelta
+ }
+ deriving (Show)
+
+{- Checks if the InodeCache of the sentinal file is the same
+ - as it was when it was originally created.
+ -
+ - On Windows, there's no change even when there is a nonzero
+ - TSDelta between the original and current InodeCaches.
+ -
+ - If the sential does not exist, returns a dummy value indicating
+ - that it's apparently changed.
+ -}
+checkSentinalFile :: SentinalFile -> IO SentinalStatus
+checkSentinalFile s = do
+ mold <- loadoldcache
+ case mold of
+ Nothing -> return dummy
+ (Just old) -> do
+ mnew <- gennewcache
+ case mnew of
+ Nothing -> return dummy
+ Just new -> return $ calc old new
+ where
+ loadoldcache = catchDefaultIO Nothing $
+ readInodeCache <$> readFile (sentinalCacheFile s)
+ gennewcache = genInodeCache (sentinalFile s) noTSDelta
+ calc (InodeCache (InodeCachePrim inode1 size1 mtime1)) (InodeCache (InodeCachePrim inode2 size2 mtime2)) =
+ SentinalStatus (not unchanged) tsdelta
+ where
+#ifdef mingw32_HOST_OS
+ unchanged = inode1 == inode2 && size1 == size2
+ tsdelta = TSDelta (mtime1 - mtime2)
+#else
+ unchanged = inode1 == inode2 && size1 == size2 && mtime1 == mtime2
+ tsdelta = noTSDelta
+#endif
+ dummy = SentinalStatus True noTSDelta
+
+sentinalFileExists :: SentinalFile -> IO Bool
+sentinalFileExists s = allM doesFileExist [sentinalCacheFile s, sentinalFile s]
+
instance Arbitrary InodeCache where
arbitrary =
let prim = InodeCachePrim