handle edge case of symlink to something that is not really a pointer file
authorJoey Hess <joeyh@joeyh.name>
Mon, 14 Jun 2021 15:29:49 +0000 (11:29 -0400)
committerJoey Hess <joeyh@joeyh.name>
Mon, 14 Jun 2021 15:35:52 +0000 (11:35 -0400)
That seems very unlikely to happen, but still, it's possible it could.
And with the recent addition of locked files to the keys db, this could
be called by places that did not call it before, so it seems even more
important it's correct.

Adds an extra stat of the file, and is potentially racy, but both
problems are fixed by the unix-2.8.0 path. I have not tested that path
builds because that package is not yet released and it would be difficult
to install it since it's tightly tied to a ghc version.

Annex/Link.hs

index 5853eb000379c40f16d216a1aac05260b34ed661..50f2717fcbbd87833ceb042153984e73ab20c45a 100644 (file)
@@ -7,7 +7,7 @@
  -
  - Pointer files are used instead of symlinks for unlocked files.
  -
- - Copyright 2013-2019 Joey Hess <id@joeyh.name>
+ - Copyright 2013-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -294,10 +294,36 @@ unpaddedMaxPointerSz = 8192
 
 {- Checks if a worktree file is a pointer to a key.
  -
- - Unlocked files whose content is present are not detected by this. -}
+ - Unlocked files whose content is present are not detected by this.
+ -
+ - It's possible, though unlikely, that an annex symlink points to
+ - an object that looks like a pointer file. Or that a non-annex
+ - symlink does. Avoids a false positive in those cases.
+ - -}
 isPointerFile :: RawFilePath -> IO (Maybe Key)
-isPointerFile f = catchDefaultIO Nothing $ withFile (fromRawFilePath f) ReadMode $ \h ->
-       parseLinkTargetOrPointer <$> S.hGet h unpaddedMaxPointerSz
+isPointerFile f = catchDefaultIO Nothing $ do
+#if defined(mingw32_HOST_OS)
+       checkcontentfollowssymlinks -- no symlinks supported on windows
+#else
+#if MIN_VERSION_unix(2,8,0)
+       bracket
+               (openFd (fromRawFilePath f) ReadOnly (defaultFileFlags { nofollow = True }) Nothing)
+               closeFd
+               (\fd -> readhandle =<< fdToHandle fd)
+#else
+       pointercontent <- checkcontentfollowssymlinks
+       if isJust pointercontent
+               then ifM (isSymbolicLink <$> R.getSymbolicLinkStatus f)
+                       ( return Nothing
+                       , return pointercontent
+                       )
+               else return Nothing
+#endif
+#endif
+  where
+       checkcontentfollowssymlinks = 
+               withFile (fromRawFilePath f) ReadMode readhandle
+       readhandle h = parseLinkTargetOrPointer <$> S.hGet h unpaddedMaxPointerSz
 
 {- Checks a symlink target or pointer file first line to see if it
  - appears to point to annexed content.