1

In C#, I need to find the actual path of a file/directory in Linux that might be on the other side of a symlink.

I have found the solution to this on Windows at How to obtain the target of a symbolic link (or Reparse Point) using .Net?, but that for fairly obvious reasons won't work on Linux. I could call out to readlink, but I may need to do this a lot, and I'd hate to spend all of that time handling external processes.

I'd like something like this:

string GetFinalPathName(string inputPath);

If the symlink is something like:

/link -> /actual/path

And I call GetFinalPathName("/link/myfile.txt") it should return "/actual/path/myfile.txt".

How can I find the target of a Linux Symlink in C#?

jww
  • 97,681
  • 90
  • 411
  • 885

3 Answers3

1

I discovered a solution.

Start with the mono.posix-4.5 nuget package:

https://www.nuget.org/packages/Mono.Posix-4.5/

Then the solution was simple:

return Mono.Unix.UnixPath.GetRealPath(path);
  • 1
    Bit more info about Mono.Posix: [Mono.Posix.NETStandard](https://www.nuget.org/packages/Mono.Posix.NETStandard/) is more up-to-date and it supports .NET Standard. Despite the Mono namespace, it does seem to be the Microsoft-endorsed way of making Posix/Unix calls. Details in [this issue](https://github.com/dotnet/corefx/issues/15289). – Reilly Wood Nov 06 '19 at 18:37
1

While the NuGet Mono.Posix.NETStandard library can do the job, use it with caution. To my mind, it produces unexpected, counterintuitive results. Below are some tests with output from GetRealPath() and similar functions on Max OSX Catalina (v.10.15.7).

Based on these tests, it looks like you cannot simply provide the full path to GetRealPath() or other functions and get the final target file path with symlinks resolved as a result. Instead, you need to test each folder in the path using GetRealPath() or GetCompleteRealPath().

From the terminal:

$ pwd
/Volumes
$ ls -l 
total 0 
drwxr-xr-x  32 root    wheel  1156 Oct  7 17:12 HD2
lrwxr-xr-x   1 root    wheel     1 Nov 12 22:14 Mac HD -> /

From C# tests:

Input path: /Volumes/Mac HD <== this is the root volume

GetRealPath() => /
GetCompleteRealPath() => /
GetCanonicalPath() => /Volumes/Mac HD

Input path: /Volumes/Mac HD/ <== trailing slash produces unexpected results

GetRealPath() => /Volumes/Mac HD/
GetCompleteRealPath() => /
GetCanonicalPath() => /Volumes/Mac HD

Input path: /Volumes/Mac HD/Users/Shared/existing.txt <== existing file in symlinked volume results in "//" in GetCompleteRealPath()

GetRealPath() => /Volumes/Mac HD/Users/Shared/existing.txt
GetCompleteRealPath() => //Users/Shared/existing.txt
GetCanonicalPath() => /Volumes/Mac HD/Users/Shared/existing.txt
Martin_W
  • 1,582
  • 1
  • 19
  • 24
0

I think you'd have to create a native C/C++ project which exports a wrapper for the Linux readlink() function, and then use platform invoke to call it from C#. This appears to be how the CLR handles it. Unfortunately, I don't think you can just call their implementation: CLR Unix ReadLink code

RogerN
  • 3,761
  • 11
  • 18