Correct way to check is path is symbolic link?

I am using both directory-1.3.8.3 and unix-compat-0.7.4.1 in a hobby project. Both libraries allow to check if a path is a symbolic link, but they throw different results. Here there is a minimal examples

#!/usr/bin/env cabal
{- cabal:
  build-depends:
     base
   , directory==1.3.8.3
   , unix-compat==0.7.4.1
   , filepath==1.5.2.0
  default-language: GHC2024
-}

import System.Directory.OsPath qualified as Directory
import System.PosixCompat qualified as Posix
import System.OsPath qualified as OsPath
import System.Exit (exitSuccess)


main :: IO()
main = do
  posix <- Posix.isSymbolicLink <$> Posix.getFileStatus "linked.txt"
  direct <- Directory.pathIsSymbolicLink $ OsPath.unsafeEncodeUtf "linked.txt"
  putStrLn "PosixCompat result is:"
  print posix
  putStrLn "Directory result is:"
  print direct

  exitSuccess

Now, create a symlink file and run the program.

> ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.10.1
> touch original.txt
> ln -s original.txt linked.txt
> cabal run main.hs
PosixCompat result is:
False
Directory result is:
True

Is this behaviour expected? to me it seems that either there is a bug or there is something I don’t understand (likely the second)

1 Like

A case of confusing API. You should use getSymbolicLinkStatus instead:

Ī»> isSymbolicLink  <$> getSymbolicLinkStatus "/tmp/original.txt"
False
Ī»> isSymbolicLink  <$> getSymbolicLinkStatus "/tmp/linked.txt"
True

You are not the first one tripping on this, see unix/#79, so I would say documentation is lacking.
If you have time, please file an issue, if not I will do it myself.

4 Likes

Edit: I hadn’t seen there was another answer that was posted while I wrote mine.

Long Story with the same conclusion as above

Confusing behaviour to say the least: After a deep dive into the source of unix-compat and unpacking and running the tests myself I can now at least offer a hint.

I’ve looked into the man page for stat, which I found to be likely to be used by unix-compat internally.
An excerpt from man fstat64(2)

       stat() and fstatat() retrieve information about the file pointed to by  pathname;  the  differences  for  fs‐
       tatat() are described below.

       lstat() is identical to stat(), except that if pathname is a symbolic link, then it returns information about
       the link itself, not the file that the link refers to.

       fstat()  is identical to stat(), except that the file about which information is to be retrieved is specified
       by the file descriptor fd.

So there seems to be a difference in how the program has to query for link file information and referenced file information.
Running the test suite gave another hint: There are tests checking that the library successfully provides link file information.
However, the tests use getSymbolicLinkStatus. The documentation also points out that it uses lstat, mentioned in the man page.

They should add a hint to getFileStatus, this is very confusing.

Regarding your question: Use getSymbolicLinkStatus to avoid dereferencing links.

2 Likes

Far from useless @VegOwOtenks, you dug the relevant manpage and gave a clearer picture of the problem.

I agree adding a small hint to getFileStatus would prevent much pain.

2 Likes

Thank you for your encouragement, I have now opened a pull request at their github repository.

3 Likes