Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Xamarin.MacDev/PathUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ static int lstat (string path, out Stat buf)
}
}

const int ENOENT = 2;
const int EACCES = 13;
const int ENOTDIR = 20;

/// <summary>
/// Returns whether the given path is a symlink.
/// Returns false (rather than throwing) when the path cannot be examined
/// due to non-existence, permissions, or a non-directory path component.
/// </summary>
public static bool IsSymlink (string file)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
Expand All @@ -56,8 +65,12 @@ public static bool IsSymlink (string file)
}
Stat buf;
var rv = lstat (file, out buf);
if (rv != 0)
throw new Exception (string.Format ("Could not lstat '{0}': {1}", file, Marshal.GetLastWin32Error ()));
if (rv != 0) {
var errno = Marshal.GetLastWin32Error ();
if (errno == ENOENT || errno == EACCES || errno == ENOTDIR)
return false;
throw new Exception (string.Format ("Could not lstat '{0}': {1}", file, errno));
Comment thread
rmarinho marked this conversation as resolved.
}
const int S_IFLNK = 40960;
return (buf.st_mode & S_IFLNK) == S_IFLNK;
}
Expand Down
3 changes: 3 additions & 0 deletions Xamarin.MacDev/Xamarin.MacDev.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard2.0' ">
<Compile Remove="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="tests" />
</ItemGroup>
Comment thread
rmarinho marked this conversation as resolved.
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.5" Condition=" '$(TargetFramework)' == 'netstandard2.0' " />
</ItemGroup>
Expand Down
105 changes: 105 additions & 0 deletions tests/PathUtilsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

using System.IO;
using NUnit.Framework;
using Xamarin.MacDev;

namespace tests {

[TestFixture]
public class PathUtilsTests {

[Test]
[Platform ("MacOsX")]
public void IsSymlink_ReturnsFalse_ForNonExistentFile ()
{
var path = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ());
// Should not throw; returns false for ENOENT
Assert.That (PathUtils.IsSymlink (path), Is.False);
}

[Test]
[Platform ("MacOsX")]
public void IsSymlink_ReturnsFalse_ForRegularFile ()
{
var path = Path.GetTempFileName ();
try {
Assert.That (PathUtils.IsSymlink (path), Is.False);
} finally {
File.Delete (path);
}
}

[Test]
[Platform ("MacOsX")]
public void IsSymlink_ReturnsTrue_ForSymlink ()
{
var target = Path.GetTempFileName ();
var link = target + ".link";
try {
#if NET
File.CreateSymbolicLink (link, target);
#else
// File.CreateSymbolicLink is not available on net472.
// Use a shell command to create the symlink on macOS.
var psi = new System.Diagnostics.ProcessStartInfo ("ln", $"-s \"{target}\" \"{link}\"") {
UseShellExecute = false,
};
System.Diagnostics.Process.Start (psi)!.WaitForExit ();
#endif
Assert.That (PathUtils.IsSymlink (link), Is.True);
} finally {
File.Delete (link);
File.Delete (target);
}
}
Comment thread
rmarinho marked this conversation as resolved.

[Test]
[Platform ("MacOsX")]
public void IsSymlinkOrHasParentSymlink_ReturnsFalse_ForNonExistentPath ()
{
var path = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ());
Assert.That (PathUtils.IsSymlinkOrHasParentSymlink (path), Is.False);
}
Comment thread
rmarinho marked this conversation as resolved.

[Test]
[Platform ("MacOsX")]
public void IsSymlink_ReturnsFalse_WhenPathComponentIsNotDirectory ()
{
// /etc/hosts is a file, so /etc/hosts/bogus triggers ENOTDIR
var path = Path.Combine ("/etc/hosts", "bogus");
Assert.That (PathUtils.IsSymlink (path), Is.False);
}

[Test]
[Platform ("MacOsX")]
public void IsSymlinkOrHasParentSymlink_ReturnsTrue_WhenParentIsSymlink ()
{
var realDir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ());
Directory.CreateDirectory (realDir);
var childDir = Path.Combine (realDir, "subdir");
Directory.CreateDirectory (childDir);

var linkDir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ());
try {
#if NET
Directory.CreateSymbolicLink (linkDir, realDir);
#else
var psi = new System.Diagnostics.ProcessStartInfo ("ln", $"-s \"{realDir}\" \"{linkDir}\"") {
UseShellExecute = false,
};
System.Diagnostics.Process.Start (psi)!.WaitForExit ();
#endif
var childViaLink = Path.Combine (linkDir, "subdir");
Assert.That (PathUtils.IsSymlinkOrHasParentSymlink (childViaLink), Is.True);
} finally {
if (Directory.Exists (linkDir))
Directory.Delete (linkDir);
Directory.Delete (realDir, recursive: true);
}
}
}
}
Loading