diff --git a/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj b/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj index 6a912da0..67b1e517 100644 --- a/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj +++ b/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj @@ -18,6 +18,7 @@ SPDX-License-Identifier: MIT + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Cesium.Sdk.Tests/FileUtilTests.cs b/Cesium.Sdk.Tests/FileUtilTests.cs new file mode 100644 index 00000000..ef6a5c98 --- /dev/null +++ b/Cesium.Sdk.Tests/FileUtilTests.cs @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using System.Runtime.InteropServices; +using TruePath; +using TruePath.SystemIo; + +namespace Cesium.Sdk.Tests; + +public class FileUtilTests +{ + private static void CreateUnixFile(AbsolutePath file, AbsolutePath? link = null, UnixFileMode? mode = null) + { + file.WriteAllText("empty"); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && + !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return; + if (mode is { } m) + File.SetUnixFileMode(file.Value, m); + if (link is { } linkFile) + { + linkFile.Delete(); + File.CreateSymbolicLink(linkFile.Value, file.Value); + } + } + + private AbsolutePath TestFile = Temporary.CreateTempFile(); + private AbsolutePath TestLink = Temporary.CreateTempFile(); + + [Fact] + public void ExecutablePermissionsCheckOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var file = TestFile; + CreateUnixFile(file, mode: UnixFileMode.UserExecute); + + Assert.True(FileSystemUtil.IsUnixFileExecutable(file.Value)); + } + } + + [Fact] + public void ExecutableLinkPermissionsCheckOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var file = TestFile; + var link = TestLink; + CreateUnixFile(file, link: link, mode: UnixFileMode.UserExecute); + + Assert.True(FileSystemUtil.IsUnixFileExecutable(link.Value)); + } + } + + [Fact] + public void NoExecutablePermissionsCheckOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var file = TestFile; + CreateUnixFile(file, mode: UnixFileMode.UserRead | UnixFileMode.UserWrite); + + Assert.False(FileSystemUtil.IsUnixFileExecutable(file.Value)); + } + } + + [Fact] + public void InvalidForDirOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var dir = "/etc"; + Assert.False(FileSystemUtil.IsUnixFileExecutable(dir)); + } + } +} diff --git a/Cesium.Sdk/Cesium.Sdk.csproj b/Cesium.Sdk/Cesium.Sdk.csproj index 667f0885..073bb49a 100644 --- a/Cesium.Sdk/Cesium.Sdk.csproj +++ b/Cesium.Sdk/Cesium.Sdk.csproj @@ -18,6 +18,7 @@ SPDX-License-Identifier: MIT + diff --git a/Cesium.Sdk/CesiumCompile.cs b/Cesium.Sdk/CesiumCompile.cs index 222f96fa..92d4f45e 100644 --- a/Cesium.Sdk/CesiumCompile.cs +++ b/Cesium.Sdk/CesiumCompile.cs @@ -285,11 +285,11 @@ private static bool ExecutableFileExists(string path) var pathExtWithDot = new Lazy(() => Environment.GetEnvironmentVariable("PATHEXT")?.Split(Path.PathSeparator) ?? []); - if (IsExecutable(path)) return true; + if (File.Exists(path) && IsExecutable(path)) return true; foreach (var pathEntry in Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? []) { - var fullPath = Path.Combine(pathEntry, pathEntry); + var fullPath = Path.Combine(pathEntry, path); if (IsExecutable(fullPath)) return true; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -305,14 +305,12 @@ private static bool ExecutableFileExists(string path) bool IsExecutable(string exePath) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var extension = Path.GetExtension(exePath); - return pathExtWithDot.Value.Contains(extension); - } - - return true; // TODO[#840]: Proper executable check for Unix + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return FileSystemUtil.IsUnixFileExecutable(exePath); + var extension = Path.GetExtension(exePath); + return pathExtWithDot.Value.Contains(extension); } } diff --git a/Cesium.Sdk/FileSystemUtil.cs b/Cesium.Sdk/FileSystemUtil.cs new file mode 100644 index 00000000..5e5948f9 --- /dev/null +++ b/Cesium.Sdk/FileSystemUtil.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using System.IO; +using System.Runtime.InteropServices; + +namespace Cesium.Sdk; + +internal static class FileSystemUtil +{ + [DllImport("libc", EntryPoint = "access", SetLastError = true)] + private static extern int Access(string path, int mode); + + private const int X_OK = 1; + + public static bool IsUnixFileExecutable(string path) + { + if (Directory.Exists(path)) return false; + return Access(Path.GetFullPath(path), X_OK) == 0; + } +} diff --git a/Directory.Packages.props b/Directory.Packages.props index 4821b01b..71f78430 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,6 +19,7 @@ SPDX-License-Identifier: MIT +