From 54bfd8bcd4b2517e6ff936a69c1129dbebd2c015 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Oct 2025 00:27:34 +0500 Subject: [PATCH 01/10] Verify that the file is executable for Unix --- Cesium.Sdk/CesiumCompile.cs | 13 ++--- Cesium.Sdk/FileInterop.cs | 51 +++++++++++++++++ Cesium.Sdk/FileSystemUtil.cs | 108 +++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 Cesium.Sdk/FileInterop.cs create mode 100644 Cesium.Sdk/FileSystemUtil.cs diff --git a/Cesium.Sdk/CesiumCompile.cs b/Cesium.Sdk/CesiumCompile.cs index 222f96fa..ae3c5421 100644 --- a/Cesium.Sdk/CesiumCompile.cs +++ b/Cesium.Sdk/CesiumCompile.cs @@ -305,14 +305,13 @@ 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.CheckUnixFilePermissions(exePath, + FileSystemUtil.ExecutablePermissions); + var extension = Path.GetExtension(exePath); + return pathExtWithDot.Value.Contains(extension); } } diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs new file mode 100644 index 00000000..df89393f --- /dev/null +++ b/Cesium.Sdk/FileInterop.cs @@ -0,0 +1,51 @@ +using System.Runtime.InteropServices; + +namespace Cesium.Sdk; + +internal enum Error +{ + ENOENT = 0x1002D, // No such file or directory. + ENOTDIR = 0x10039 // Not a directory. +} + +[StructLayout(LayoutKind.Sequential)] +internal struct FileStatus +{ + public ulong st_dev; + public ulong st_ino; + public ulong st_nlink; + public uint st_mode; + public uint st_uid; + public uint st_gid; + public ulong st_rdev; + public long st_size; + public long st_blksize; + public long st_blocks; + public long st_atime; + public ulong st_atime_nsec; + public long st_mtime; + public ulong st_mtime_nsec; + public long st_ctime; + public ulong st_ctime_nsec; +} + +internal static class FileTypes +{ + internal const uint S_IFMT = 0xF000; + internal const uint S_IFIFO = 0x1000; + internal const uint S_IFCHR = 0x2000; + internal const uint S_IFDIR = 0x4000; + internal const uint S_IFBLK = 0x6000; + internal const uint S_IFREG = 0x8000; + internal const uint S_IFLNK = 0xA000; + internal const uint S_IFSOCK = 0xC000; +} + +internal static class FileInterop +{ + [DllImport("libc", EntryPoint = "stat", SetLastError = true, CharSet = CharSet.Ansi)] + internal static extern int Stat(string path, out FileStatus output); + + [DllImport("libc", EntryPoint = "lstat", SetLastError = true, CharSet = CharSet.Ansi)] + internal static extern int LStat(string path, out FileStatus output); +} diff --git a/Cesium.Sdk/FileSystemUtil.cs b/Cesium.Sdk/FileSystemUtil.cs new file mode 100644 index 00000000..d4359c59 --- /dev/null +++ b/Cesium.Sdk/FileSystemUtil.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Cesium.Sdk; + +[Flags] +internal enum FilePermissions +{ + None = 0, + OtherExecute = 1, + OtherWrite = 2, + OtherRead = 4, + GroupExecute = 8, + GroupWrite = 16, + GroupRead = 32, + UserExecute = 64, + UserWrite = 128, + UserRead = 256, + StickyBit = 512, + SetGroup = 1024, + SetUser = 2048, +} + +internal class UnixFileInfo +{ + private FileStatus _status; + + public UnixFileInfo(string path) + { + LoadFileStatus(path); + } + + private void LoadFileStatus(string path) + { + int rv = FileInterop.LStat(path, out _status); + + if (rv < 0) + { + var error = Marshal.GetLastWin32Error(); + + throw (Error)error switch + { + Error.ENOENT => + new ArgumentException("No such file or directory", nameof(path)), + Error.ENOTDIR => + new ArgumentException("A component of the path is not a directory", nameof(path)), + _ => + new InvalidOperationException($"lstat failed for {path} with error {error}") + }; + } + + uint fileType = _status.st_mode & FileTypes.S_IFMT; + + if (fileType != FileTypes.S_IFLNK) + return; + if (FileInterop.Stat(path, out var target) == 0) + _status.st_mode = FileTypes.S_IFLNK | (target.st_mode & (int)ValidUnixFileModes); + else + throw new InvalidOperationException($"Stat failed for {path}"); + } + + private uint FileTypeCode => _status.st_mode & FileTypes.S_IFMT; + + public FilePermissions FilePermissions => + (FilePermissions)(_status.st_mode & (int)ValidUnixFileModes); + + public bool IsDirectory => FileTypeCode == FileTypes.S_IFDIR; + + internal const FilePermissions ValidUnixFileModes = + FilePermissions.UserRead | + FilePermissions.UserWrite | + FilePermissions.UserExecute | + FilePermissions.GroupRead | + FilePermissions.GroupWrite | + FilePermissions.GroupExecute | + FilePermissions.OtherRead | + FilePermissions.OtherWrite | + FilePermissions.OtherExecute | + FilePermissions.StickyBit | + FilePermissions.SetGroup | + FilePermissions.SetUser; +} + +internal static class FileSystemUtil +{ + public static FilePermissions ExecutablePermissions => + FilePermissions.UserExecute + | FilePermissions.GroupExecute + | FilePermissions.OtherExecute; + + public static bool CheckUnixFilePermissions(string path, FilePermissions permissions) + { + try + { + var info = new UnixFileInfo(path); + return (info.FilePermissions & permissions) != 0 && !info.IsDirectory; + } + catch (Exception) + { + return false; + } + } +} From a22332eb4feee9a49e0cef2b745a40832ee1fcd5 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Oct 2025 00:30:06 +0500 Subject: [PATCH 02/10] Add license info at the top of file --- Cesium.Sdk/FileInterop.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs index df89393f..88ad4be5 100644 --- a/Cesium.Sdk/FileInterop.cs +++ b/Cesium.Sdk/FileInterop.cs @@ -1,3 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT + + using System.Runtime.InteropServices; namespace Cesium.Sdk; From afcc7c7b1b9ae3a72926c1bc8ab1232ac22a7bdc Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Oct 2025 16:05:23 +0500 Subject: [PATCH 03/10] Native invoke: Replace "libc" with the native library. Fix the output struct annotation. Add logging for tests --- Cesium.Sdk/FileInterop.cs | 61 +++++++++++++++++++++--------------- Cesium.Sdk/FileSystemUtil.cs | 25 ++++++++++----- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs index 88ad4be5..d5de8e33 100644 --- a/Cesium.Sdk/FileInterop.cs +++ b/Cesium.Sdk/FileInterop.cs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT +using System; using System.Runtime.InteropServices; namespace Cesium.Sdk; @@ -16,41 +17,49 @@ internal enum Error [StructLayout(LayoutKind.Sequential)] internal struct FileStatus { - public ulong st_dev; - public ulong st_ino; - public ulong st_nlink; - public uint st_mode; - public uint st_uid; - public uint st_gid; - public ulong st_rdev; - public long st_size; - public long st_blksize; - public long st_blocks; - public long st_atime; - public ulong st_atime_nsec; - public long st_mtime; - public ulong st_mtime_nsec; - public long st_ctime; - public ulong st_ctime_nsec; + internal FileStatusFlags Flags; + internal int Mode; + internal uint Uid; + internal uint Gid; + internal long Size; + internal long ATime; + internal long ATimeNsec; + internal long MTime; + internal long MTimeNsec; + internal long CTime; + internal long CTimeNsec; + internal long BirthTime; + internal long BirthTimeNsec; + internal long Dev; + internal long Ino; + internal uint UserFlags; } internal static class FileTypes { - internal const uint S_IFMT = 0xF000; - internal const uint S_IFIFO = 0x1000; - internal const uint S_IFCHR = 0x2000; - internal const uint S_IFDIR = 0x4000; - internal const uint S_IFBLK = 0x6000; - internal const uint S_IFREG = 0x8000; - internal const uint S_IFLNK = 0xA000; - internal const uint S_IFSOCK = 0xC000; + internal const int S_IFMT = 0xF000; + internal const int S_IFIFO = 0x1000; + internal const int S_IFCHR = 0x2000; + internal const int S_IFDIR = 0x4000; + internal const int S_IFREG = 0x8000; + internal const int S_IFLNK = 0xA000; + internal const int S_IFSOCK = 0xC000; +} + +[Flags] +internal enum FileStatusFlags +{ + None = 0, + HasBirthTime = 1, } internal static class FileInterop { - [DllImport("libc", EntryPoint = "stat", SetLastError = true, CharSet = CharSet.Ansi)] + internal const string SystemNative = "libSystem.Native"; + + [DllImport(SystemNative, EntryPoint = "SystemNative_Stat", SetLastError = true, CharSet = CharSet.Ansi)] internal static extern int Stat(string path, out FileStatus output); - [DllImport("libc", EntryPoint = "lstat", SetLastError = true, CharSet = CharSet.Ansi)] + [DllImport(SystemNative, EntryPoint = "SystemNative_LStat", SetLastError = true, CharSet = CharSet.Ansi)] internal static extern int LStat(string path, out FileStatus output); } diff --git a/Cesium.Sdk/FileSystemUtil.cs b/Cesium.Sdk/FileSystemUtil.cs index d4359c59..21e96ae8 100644 --- a/Cesium.Sdk/FileSystemUtil.cs +++ b/Cesium.Sdk/FileSystemUtil.cs @@ -37,8 +37,12 @@ public UnixFileInfo(string path) private void LoadFileStatus(string path) { + Console.WriteLine("Loading file: " + path); + int rv = FileInterop.LStat(path, out _status); + Console.WriteLine($"LStat returned {rv}"); + if (rv < 0) { var error = Marshal.GetLastWin32Error(); @@ -54,20 +58,22 @@ private void LoadFileStatus(string path) }; } - uint fileType = _status.st_mode & FileTypes.S_IFMT; + int fileType = _status.Mode & FileTypes.S_IFMT; + + Console.WriteLine($"File type: {fileType}"); if (fileType != FileTypes.S_IFLNK) return; - if (FileInterop.Stat(path, out var target) == 0) - _status.st_mode = FileTypes.S_IFLNK | (target.st_mode & (int)ValidUnixFileModes); + if (FileInterop.Stat(path, out var target) >= 0) + _status.Mode = FileTypes.S_IFLNK | (target.Mode & (int)ValidUnixFileModes); else throw new InvalidOperationException($"Stat failed for {path}"); } - private uint FileTypeCode => _status.st_mode & FileTypes.S_IFMT; + private int FileTypeCode => _status.Mode & FileTypes.S_IFMT; public FilePermissions FilePermissions => - (FilePermissions)(_status.st_mode & (int)ValidUnixFileModes); + (FilePermissions)(_status.Mode & (int)ValidUnixFileModes); public bool IsDirectory => FileTypeCode == FileTypes.S_IFDIR; @@ -97,11 +103,16 @@ public static bool CheckUnixFilePermissions(string path, FilePermissions permiss { try { - var info = new UnixFileInfo(path); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return true; // TODO[#840]: Proper executable check for MacOS + + var info = new UnixFileInfo(Path.GetFullPath(path)); + Console.WriteLine($"FilePermissions: {info.FilePermissions} ({(int)info.FilePermissions}) IsDir: {info.IsDirectory}"); return (info.FilePermissions & permissions) != 0 && !info.IsDirectory; } - catch (Exception) + catch (Exception ex) { + Console.WriteLine($"UnixFileInfo has thrown: {ex.Message}"); return false; } } From 83ddc0dede9e2a66b294e6876601da10818f13b0 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Thu, 2 Oct 2025 17:04:03 +0500 Subject: [PATCH 04/10] Replace native internal lib to libc, fix checking for symbolic link files --- Cesium.Sdk.Tests/FileUtilTests.cs | 78 +++++++++++++++++++++++++++++++ Cesium.Sdk/FileInterop.cs | 45 ++++++++---------- Cesium.Sdk/FileSystemUtil.cs | 34 +++++++------- 3 files changed, 113 insertions(+), 44 deletions(-) create mode 100644 Cesium.Sdk.Tests/FileUtilTests.cs diff --git a/Cesium.Sdk.Tests/FileUtilTests.cs b/Cesium.Sdk.Tests/FileUtilTests.cs new file mode 100644 index 00000000..473acdd7 --- /dev/null +++ b/Cesium.Sdk.Tests/FileUtilTests.cs @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2025 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Cesium.Sdk.Tests; + +public class FileUtilTests +{ + private static void CreateUnixFile(string filePath, string? link = null, UnixFileMode? mode = null) + { + File.WriteAllText(filePath, "empty"); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && + !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return; + if (mode is { } m) + File.SetUnixFileMode(filePath, m); + if (link != null) + File.CreateSymbolicLink(link, filePath); + } + + [Fact] + public void ExecutablePermissionsCheckOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var fileName = "testfile"; + CreateUnixFile(fileName, mode: UnixFileMode.UserExecute); + + Assert.True(FileSystemUtil.CheckUnixFilePermissions(fileName, FileSystemUtil.ExecutablePermissions)); + } + } + + [Fact] + public void ExecutableLinkPermissionsCheckOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var fileName = "testfile"; + var linkName = "testlink"; + CreateUnixFile(fileName, link: linkName, mode: UnixFileMode.UserExecute); + + Assert.True(FileSystemUtil.CheckUnixFilePermissions(linkName, FileSystemUtil.ExecutablePermissions)); + } + } + + [Fact] + public void NoExecutablePermissionsCheckOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var fileName = "testfile"; + CreateUnixFile(fileName, mode: UnixFileMode.UserRead | UnixFileMode.UserWrite); + + Assert.False(FileSystemUtil.CheckUnixFilePermissions(fileName, FileSystemUtil.ExecutablePermissions)); + } + } + + [Fact] + public void InvalidForDirOnUnix() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Assert.True(true); + else + { + var dir = "/etc"; + Assert.False(FileSystemUtil.CheckUnixFilePermissions(dir, FileSystemUtil.ExecutablePermissions)); + } + } +} diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs index d5de8e33..54c24e69 100644 --- a/Cesium.Sdk/FileInterop.cs +++ b/Cesium.Sdk/FileInterop.cs @@ -17,22 +17,22 @@ internal enum Error [StructLayout(LayoutKind.Sequential)] internal struct FileStatus { - internal FileStatusFlags Flags; - internal int Mode; - internal uint Uid; - internal uint Gid; - internal long Size; - internal long ATime; - internal long ATimeNsec; - internal long MTime; - internal long MTimeNsec; - internal long CTime; - internal long CTimeNsec; - internal long BirthTime; - internal long BirthTimeNsec; - internal long Dev; - internal long Ino; - internal uint UserFlags; + public ulong st_dev; + public UIntPtr st_ino; + public ulong st_nlink; + public uint st_mode; + public uint st_uid; + public uint st_gid; + public ulong st_rdev; + public IntPtr st_size; + public long st_blksize; + public long st_blocks; + public long st_atime; + public ulong st_atime_nsec; + public long st_mtime; + public ulong st_mtime_nsec; + public long st_ctime; + public ulong st_ctime_nsec; } internal static class FileTypes @@ -46,20 +46,13 @@ internal static class FileTypes internal const int S_IFSOCK = 0xC000; } -[Flags] -internal enum FileStatusFlags -{ - None = 0, - HasBirthTime = 1, -} - internal static class FileInterop { - internal const string SystemNative = "libSystem.Native"; + internal const string LibcLibrary = "libc"; - [DllImport(SystemNative, EntryPoint = "SystemNative_Stat", SetLastError = true, CharSet = CharSet.Ansi)] + [DllImport(LibcLibrary, EntryPoint = "stat", SetLastError = true)] internal static extern int Stat(string path, out FileStatus output); - [DllImport(SystemNative, EntryPoint = "SystemNative_LStat", SetLastError = true, CharSet = CharSet.Ansi)] + [DllImport(LibcLibrary, EntryPoint = "lstat", SetLastError = true)] internal static extern int LStat(string path, out FileStatus output); } diff --git a/Cesium.Sdk/FileSystemUtil.cs b/Cesium.Sdk/FileSystemUtil.cs index 21e96ae8..d1dff819 100644 --- a/Cesium.Sdk/FileSystemUtil.cs +++ b/Cesium.Sdk/FileSystemUtil.cs @@ -9,7 +9,7 @@ namespace Cesium.Sdk; [Flags] -internal enum FilePermissions +public enum FilePermissions { None = 0, OtherExecute = 1, @@ -37,12 +37,8 @@ public UnixFileInfo(string path) private void LoadFileStatus(string path) { - Console.WriteLine("Loading file: " + path); - int rv = FileInterop.LStat(path, out _status); - Console.WriteLine($"LStat returned {rv}"); - if (rv < 0) { var error = Marshal.GetLastWin32Error(); @@ -58,22 +54,26 @@ private void LoadFileStatus(string path) }; } - int fileType = _status.Mode & FileTypes.S_IFMT; - - Console.WriteLine($"File type: {fileType}"); - + uint fileType = _status.st_mode & FileTypes.S_IFMT; if (fileType != FileTypes.S_IFLNK) return; - if (FileInterop.Stat(path, out var target) >= 0) - _status.Mode = FileTypes.S_IFLNK | (target.Mode & (int)ValidUnixFileModes); + + // It's a symlink, we need to get the target file's mode + + int ret; + FileStatus target; + while ((ret = FileInterop.Stat(path, out target)) < 0); + + if (ret == 0) + _status.st_mode = FileTypes.S_IFLNK | (target.st_mode & (int)ValidUnixFileModes); else throw new InvalidOperationException($"Stat failed for {path}"); } - private int FileTypeCode => _status.Mode & FileTypes.S_IFMT; + private uint FileTypeCode => _status.st_mode & FileTypes.S_IFMT; public FilePermissions FilePermissions => - (FilePermissions)(_status.Mode & (int)ValidUnixFileModes); + (FilePermissions)(_status.st_mode & (int)ValidUnixFileModes); public bool IsDirectory => FileTypeCode == FileTypes.S_IFDIR; @@ -92,7 +92,7 @@ private void LoadFileStatus(string path) FilePermissions.SetUser; } -internal static class FileSystemUtil +public static class FileSystemUtil { public static FilePermissions ExecutablePermissions => FilePermissions.UserExecute @@ -103,16 +103,14 @@ public static bool CheckUnixFilePermissions(string path, FilePermissions permiss { try { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return true; // TODO[#840]: Proper executable check for MacOS + // if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + // return true; // TODO[#840]: Proper executable check for MacOS var info = new UnixFileInfo(Path.GetFullPath(path)); - Console.WriteLine($"FilePermissions: {info.FilePermissions} ({(int)info.FilePermissions}) IsDir: {info.IsDirectory}"); return (info.FilePermissions & permissions) != 0 && !info.IsDirectory; } catch (Exception ex) { - Console.WriteLine($"UnixFileInfo has thrown: {ex.Message}"); return false; } } From 97ff6ba6768f9dc8ba144bbd913f584235201ef7 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Thu, 2 Oct 2025 17:17:44 +0500 Subject: [PATCH 05/10] Fix tests --- Cesium.Sdk.Tests/FileUtilTests.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Cesium.Sdk.Tests/FileUtilTests.cs b/Cesium.Sdk.Tests/FileUtilTests.cs index 473acdd7..192c148b 100644 --- a/Cesium.Sdk.Tests/FileUtilTests.cs +++ b/Cesium.Sdk.Tests/FileUtilTests.cs @@ -21,6 +21,9 @@ private static void CreateUnixFile(string filePath, string? link = null, UnixFil File.CreateSymbolicLink(link, filePath); } + private string TestFile => $"testfile-{Process.GetCurrentProcess().Id}-{Guid.NewGuid()}"; + private string TestLink => $"testlink-{Process.GetCurrentProcess().Id}-{Guid.NewGuid()}"; + [Fact] public void ExecutablePermissionsCheckOnUnix() { @@ -28,7 +31,7 @@ public void ExecutablePermissionsCheckOnUnix() Assert.True(true); else { - var fileName = "testfile"; + var fileName = TestFile; CreateUnixFile(fileName, mode: UnixFileMode.UserExecute); Assert.True(FileSystemUtil.CheckUnixFilePermissions(fileName, FileSystemUtil.ExecutablePermissions)); @@ -42,8 +45,8 @@ public void ExecutableLinkPermissionsCheckOnUnix() Assert.True(true); else { - var fileName = "testfile"; - var linkName = "testlink"; + var fileName = TestFile; + var linkName = TestLink; CreateUnixFile(fileName, link: linkName, mode: UnixFileMode.UserExecute); Assert.True(FileSystemUtil.CheckUnixFilePermissions(linkName, FileSystemUtil.ExecutablePermissions)); @@ -57,7 +60,7 @@ public void NoExecutablePermissionsCheckOnUnix() Assert.True(true); else { - var fileName = "testfile"; + var fileName = TestFile; CreateUnixFile(fileName, mode: UnixFileMode.UserRead | UnixFileMode.UserWrite); Assert.False(FileSystemUtil.CheckUnixFilePermissions(fileName, FileSystemUtil.ExecutablePermissions)); From b2828a1b991295925bf23e0e9186895ca4ab3f64 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 5 Oct 2025 14:07:46 +0500 Subject: [PATCH 06/10] Fix fields of FileStatus structure --- Cesium.Sdk/FileInterop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs index 54c24e69..bfd98939 100644 --- a/Cesium.Sdk/FileInterop.cs +++ b/Cesium.Sdk/FileInterop.cs @@ -18,13 +18,13 @@ internal enum Error internal struct FileStatus { public ulong st_dev; - public UIntPtr st_ino; + public ulong st_ino; public ulong st_nlink; public uint st_mode; public uint st_uid; public uint st_gid; public ulong st_rdev; - public IntPtr st_size; + public long st_size; public long st_blksize; public long st_blocks; public long st_atime; From 99fce6b20868ead43daae5ef3c36e73de2e39511 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Mon, 6 Oct 2025 16:33:11 +0500 Subject: [PATCH 07/10] Add OSX specific fields --- Cesium.Sdk/FileInterop.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs index bfd98939..f62fa91b 100644 --- a/Cesium.Sdk/FileInterop.cs +++ b/Cesium.Sdk/FileInterop.cs @@ -33,6 +33,13 @@ internal struct FileStatus public ulong st_mtime_nsec; public long st_ctime; public ulong st_ctime_nsec; + public long st_birthtimespec; + public ulong st_birthtimespec_nsec; + public ulong st_flags; + public ulong st_gen; + private int _reserved1_; + private long _reserved2_; + private long _reserved3_; } internal static class FileTypes From 0d5ea2e5b017fd70ddfb101b0a982f2e9dd85965 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Mon, 6 Oct 2025 16:39:11 +0500 Subject: [PATCH 08/10] Add Console logs --- Cesium.Sdk/FileSystemUtil.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cesium.Sdk/FileSystemUtil.cs b/Cesium.Sdk/FileSystemUtil.cs index d1dff819..4b40f105 100644 --- a/Cesium.Sdk/FileSystemUtil.cs +++ b/Cesium.Sdk/FileSystemUtil.cs @@ -39,6 +39,7 @@ private void LoadFileStatus(string path) { int rv = FileInterop.LStat(path, out _status); + Console.WriteLine($"LStat: {rv}"); if (rv < 0) { var error = Marshal.GetLastWin32Error(); @@ -111,6 +112,7 @@ public static bool CheckUnixFilePermissions(string path, FilePermissions permiss } catch (Exception ex) { + Console.WriteLine("Error checking file permissions: " + ex.Message); return false; } } From 29b1920e9fbf034acbdae0d80c1968a2993e6abe Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 10 Oct 2025 00:41:19 +0200 Subject: [PATCH 09/10] (#840) SDK.FileSystemUtil: simplify the executable check, call access() This restores the macOS compatibility. --- Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj | 1 + Cesium.Sdk.Tests/FileUtilTests.cs | 42 +++++---- Cesium.Sdk/Cesium.Sdk.csproj | 1 + Cesium.Sdk/CesiumCompile.cs | 3 +- Cesium.Sdk/FileInterop.cs | 65 ------------- Cesium.Sdk/FileSystemUtil.cs | 111 ++--------------------- Directory.Packages.props | 1 + 7 files changed, 34 insertions(+), 190 deletions(-) delete mode 100644 Cesium.Sdk/FileInterop.cs 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 index 192c148b..ef6a5c98 100644 --- a/Cesium.Sdk.Tests/FileUtilTests.cs +++ b/Cesium.Sdk.Tests/FileUtilTests.cs @@ -2,27 +2,31 @@ // // SPDX-License-Identifier: MIT -using System.Diagnostics; using System.Runtime.InteropServices; +using TruePath; +using TruePath.SystemIo; namespace Cesium.Sdk.Tests; public class FileUtilTests { - private static void CreateUnixFile(string filePath, string? link = null, UnixFileMode? mode = null) + private static void CreateUnixFile(AbsolutePath file, AbsolutePath? link = null, UnixFileMode? mode = null) { - File.WriteAllText(filePath, "empty"); + file.WriteAllText("empty"); if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return; if (mode is { } m) - File.SetUnixFileMode(filePath, m); - if (link != null) - File.CreateSymbolicLink(link, filePath); + File.SetUnixFileMode(file.Value, m); + if (link is { } linkFile) + { + linkFile.Delete(); + File.CreateSymbolicLink(linkFile.Value, file.Value); + } } - private string TestFile => $"testfile-{Process.GetCurrentProcess().Id}-{Guid.NewGuid()}"; - private string TestLink => $"testlink-{Process.GetCurrentProcess().Id}-{Guid.NewGuid()}"; + private AbsolutePath TestFile = Temporary.CreateTempFile(); + private AbsolutePath TestLink = Temporary.CreateTempFile(); [Fact] public void ExecutablePermissionsCheckOnUnix() @@ -31,10 +35,10 @@ public void ExecutablePermissionsCheckOnUnix() Assert.True(true); else { - var fileName = TestFile; - CreateUnixFile(fileName, mode: UnixFileMode.UserExecute); + var file = TestFile; + CreateUnixFile(file, mode: UnixFileMode.UserExecute); - Assert.True(FileSystemUtil.CheckUnixFilePermissions(fileName, FileSystemUtil.ExecutablePermissions)); + Assert.True(FileSystemUtil.IsUnixFileExecutable(file.Value)); } } @@ -45,11 +49,11 @@ public void ExecutableLinkPermissionsCheckOnUnix() Assert.True(true); else { - var fileName = TestFile; - var linkName = TestLink; - CreateUnixFile(fileName, link: linkName, mode: UnixFileMode.UserExecute); + var file = TestFile; + var link = TestLink; + CreateUnixFile(file, link: link, mode: UnixFileMode.UserExecute); - Assert.True(FileSystemUtil.CheckUnixFilePermissions(linkName, FileSystemUtil.ExecutablePermissions)); + Assert.True(FileSystemUtil.IsUnixFileExecutable(link.Value)); } } @@ -60,10 +64,10 @@ public void NoExecutablePermissionsCheckOnUnix() Assert.True(true); else { - var fileName = TestFile; - CreateUnixFile(fileName, mode: UnixFileMode.UserRead | UnixFileMode.UserWrite); + var file = TestFile; + CreateUnixFile(file, mode: UnixFileMode.UserRead | UnixFileMode.UserWrite); - Assert.False(FileSystemUtil.CheckUnixFilePermissions(fileName, FileSystemUtil.ExecutablePermissions)); + Assert.False(FileSystemUtil.IsUnixFileExecutable(file.Value)); } } @@ -75,7 +79,7 @@ public void InvalidForDirOnUnix() else { var dir = "/etc"; - Assert.False(FileSystemUtil.CheckUnixFilePermissions(dir, FileSystemUtil.ExecutablePermissions)); + 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 ae3c5421..45392d25 100644 --- a/Cesium.Sdk/CesiumCompile.cs +++ b/Cesium.Sdk/CesiumCompile.cs @@ -307,8 +307,7 @@ bool IsExecutable(string exePath) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return FileSystemUtil.CheckUnixFilePermissions(exePath, - FileSystemUtil.ExecutablePermissions); + return FileSystemUtil.IsUnixFileExecutable(exePath); var extension = Path.GetExtension(exePath); return pathExtWithDot.Value.Contains(extension); diff --git a/Cesium.Sdk/FileInterop.cs b/Cesium.Sdk/FileInterop.cs deleted file mode 100644 index f62fa91b..00000000 --- a/Cesium.Sdk/FileInterop.cs +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Cesium contributors -// -// SPDX-License-Identifier: MIT - - -using System; -using System.Runtime.InteropServices; - -namespace Cesium.Sdk; - -internal enum Error -{ - ENOENT = 0x1002D, // No such file or directory. - ENOTDIR = 0x10039 // Not a directory. -} - -[StructLayout(LayoutKind.Sequential)] -internal struct FileStatus -{ - public ulong st_dev; - public ulong st_ino; - public ulong st_nlink; - public uint st_mode; - public uint st_uid; - public uint st_gid; - public ulong st_rdev; - public long st_size; - public long st_blksize; - public long st_blocks; - public long st_atime; - public ulong st_atime_nsec; - public long st_mtime; - public ulong st_mtime_nsec; - public long st_ctime; - public ulong st_ctime_nsec; - public long st_birthtimespec; - public ulong st_birthtimespec_nsec; - public ulong st_flags; - public ulong st_gen; - private int _reserved1_; - private long _reserved2_; - private long _reserved3_; -} - -internal static class FileTypes -{ - internal const int S_IFMT = 0xF000; - internal const int S_IFIFO = 0x1000; - internal const int S_IFCHR = 0x2000; - internal const int S_IFDIR = 0x4000; - internal const int S_IFREG = 0x8000; - internal const int S_IFLNK = 0xA000; - internal const int S_IFSOCK = 0xC000; -} - -internal static class FileInterop -{ - internal const string LibcLibrary = "libc"; - - [DllImport(LibcLibrary, EntryPoint = "stat", SetLastError = true)] - internal static extern int Stat(string path, out FileStatus output); - - [DllImport(LibcLibrary, EntryPoint = "lstat", SetLastError = true)] - internal static extern int LStat(string path, out FileStatus output); -} diff --git a/Cesium.Sdk/FileSystemUtil.cs b/Cesium.Sdk/FileSystemUtil.cs index 4b40f105..5e5948f9 100644 --- a/Cesium.Sdk/FileSystemUtil.cs +++ b/Cesium.Sdk/FileSystemUtil.cs @@ -2,118 +2,21 @@ // // SPDX-License-Identifier: MIT -using System; using System.IO; using System.Runtime.InteropServices; namespace Cesium.Sdk; -[Flags] -public enum FilePermissions +internal static class FileSystemUtil { - None = 0, - OtherExecute = 1, - OtherWrite = 2, - OtherRead = 4, - GroupExecute = 8, - GroupWrite = 16, - GroupRead = 32, - UserExecute = 64, - UserWrite = 128, - UserRead = 256, - StickyBit = 512, - SetGroup = 1024, - SetUser = 2048, -} - -internal class UnixFileInfo -{ - private FileStatus _status; - - public UnixFileInfo(string path) - { - LoadFileStatus(path); - } - - private void LoadFileStatus(string path) - { - int rv = FileInterop.LStat(path, out _status); - - Console.WriteLine($"LStat: {rv}"); - if (rv < 0) - { - var error = Marshal.GetLastWin32Error(); - - throw (Error)error switch - { - Error.ENOENT => - new ArgumentException("No such file or directory", nameof(path)), - Error.ENOTDIR => - new ArgumentException("A component of the path is not a directory", nameof(path)), - _ => - new InvalidOperationException($"lstat failed for {path} with error {error}") - }; - } - - uint fileType = _status.st_mode & FileTypes.S_IFMT; - if (fileType != FileTypes.S_IFLNK) - return; + [DllImport("libc", EntryPoint = "access", SetLastError = true)] + private static extern int Access(string path, int mode); - // It's a symlink, we need to get the target file's mode + private const int X_OK = 1; - int ret; - FileStatus target; - while ((ret = FileInterop.Stat(path, out target)) < 0); - - if (ret == 0) - _status.st_mode = FileTypes.S_IFLNK | (target.st_mode & (int)ValidUnixFileModes); - else - throw new InvalidOperationException($"Stat failed for {path}"); - } - - private uint FileTypeCode => _status.st_mode & FileTypes.S_IFMT; - - public FilePermissions FilePermissions => - (FilePermissions)(_status.st_mode & (int)ValidUnixFileModes); - - public bool IsDirectory => FileTypeCode == FileTypes.S_IFDIR; - - internal const FilePermissions ValidUnixFileModes = - FilePermissions.UserRead | - FilePermissions.UserWrite | - FilePermissions.UserExecute | - FilePermissions.GroupRead | - FilePermissions.GroupWrite | - FilePermissions.GroupExecute | - FilePermissions.OtherRead | - FilePermissions.OtherWrite | - FilePermissions.OtherExecute | - FilePermissions.StickyBit | - FilePermissions.SetGroup | - FilePermissions.SetUser; -} - -public static class FileSystemUtil -{ - public static FilePermissions ExecutablePermissions => - FilePermissions.UserExecute - | FilePermissions.GroupExecute - | FilePermissions.OtherExecute; - - public static bool CheckUnixFilePermissions(string path, FilePermissions permissions) + public static bool IsUnixFileExecutable(string path) { - try - { - // if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - // return true; // TODO[#840]: Proper executable check for MacOS - - var info = new UnixFileInfo(Path.GetFullPath(path)); - return (info.FilePermissions & permissions) != 0 && !info.IsDirectory; - } - catch (Exception ex) - { - Console.WriteLine("Error checking file permissions: " + ex.Message); - return false; - } + 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 + From ea975cf24b92e01ffa919c4baeb04b98380996e0 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 10 Oct 2025 00:55:02 +0200 Subject: [PATCH 10/10] SDK: fix issue with proper determining of the runtime file location --- Cesium.Sdk/CesiumCompile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium.Sdk/CesiumCompile.cs b/Cesium.Sdk/CesiumCompile.cs index 45392d25..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))