crystal-lang

Is it possible to get the resolved path of a broken symbolic link?


I've been unable to find a way to get the target path of a broken symbolic link in Crystal:

Dir.cd "/tmp"
`ln -s a b`
puts File.exists?(b)  # false
puts File.symlink?(b) # true

Is there anything in the standard library to get the address the broken link points to?


Solution

  • Update: As of July 2019, this has been implemented in the standard library as File.readlink.


    Original answer:

    This is done on Linux/MacOS via readlink, which unfortunately hasn't been implemented in the standard library yet. This is tracked in issue #3284 on GitHub, and kostya has posted a version that seems to work in that issue.

    Using his code:

    lib LibC
      fun readlink(pathname : Char*, buf : Char*, bufsiz : SizeT) : SizeT
    end
    
    def File.readlink(path)
      buf = uninitialized UInt8[1024]
      size = LibC.readlink(path.to_unsafe, buf.to_unsafe, 1024).to_i32
    
      if size == -1
        raise Errno.new("readlink")
      elsif size > 1024
        raise "buffer too small"
      else
        return String.new(buf.to_unsafe, size)
      end
    end
    

    and then calling that function:

    File.symlink("a", "b")
    puts File.readlink("b") # => a
    

    Note however that you'll probably need to do some work to get an absolute path. It might just be more feasible to use the command line version directly, since you can pass the -f flag which gives an absolute path:

    File.symlink("a", "b")
    puts `readlink -f b` # => /path/to/a
    

    The -f-flag isn't available on MacOS though.