From 8e92aa51e8374c136e9f75f213d7f91220d4e0fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 03:36:55 +0000 Subject: [PATCH 1/6] Initial plan From 08b95f6c4d7397e5d72215df8b154cf420c71b5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 03:53:53 +0000 Subject: [PATCH 2/6] Fix UF_HIDDEN dropping st_flags in ConvertFileStatus; add static assert for PAL_UF_HIDDEN == UF_HIDDEN Co-authored-by: danmoseley <6385855+danmoseley@users.noreply.github.com> --- src/native/libs/System.Native/pal_io.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index cd488c01c2c339..c77f31b7d2f733 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -201,6 +201,11 @@ c_static_assert(PAL_IN_EXCL_UNLINK == IN_EXCL_UNLINK); c_static_assert(PAL_IN_ISDIR == IN_ISDIR); #endif // HAVE_INOTIFY +// Validate that our UserFlags enum value are correct for the platform +#if HAVE_STAT_FLAGS && defined(UF_HIDDEN) +c_static_assert(PAL_UF_HIDDEN == UF_HIDDEN); +#endif + static void ConvertFileStatus(const struct stat_* src, FileStatus* dst) { dst->Dev = (int64_t)src->st_dev; @@ -230,8 +235,8 @@ static void ConvertFileStatus(const struct stat_* src, FileStatus* dst) dst->BirthTimeNsec = 0; #endif -#if HAVE_STAT_FLAGS && defined(UF_HIDDEN) - dst->UserFlags = ((src->st_flags & UF_HIDDEN) == UF_HIDDEN) ? PAL_UF_HIDDEN : 0; +#if HAVE_STAT_FLAGS + dst->UserFlags = (uint32_t)src->st_flags; #else dst->UserFlags = 0; #endif From 1a6d59ac4ad05ab6d07fe3bf910674f9fd589a74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:13:56 +0000 Subject: [PATCH 3/6] Address PR review feedback: fix comment grammar and gate UserFlags on UF_HIDDEN being defined Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- src/native/libs/System.Native/pal_io.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index c77f31b7d2f733..6ecae029baa908 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -201,7 +201,7 @@ c_static_assert(PAL_IN_EXCL_UNLINK == IN_EXCL_UNLINK); c_static_assert(PAL_IN_ISDIR == IN_ISDIR); #endif // HAVE_INOTIFY -// Validate that our UserFlags enum value are correct for the platform +// Validate that our UserFlags enum values are correct for the platform #if HAVE_STAT_FLAGS && defined(UF_HIDDEN) c_static_assert(PAL_UF_HIDDEN == UF_HIDDEN); #endif @@ -235,7 +235,7 @@ static void ConvertFileStatus(const struct stat_* src, FileStatus* dst) dst->BirthTimeNsec = 0; #endif -#if HAVE_STAT_FLAGS +#if HAVE_STAT_FLAGS && defined(UF_HIDDEN) dst->UserFlags = (uint32_t)src->st_flags; #else dst->UserFlags = 0; From 682f85f4943538b1b0dd6a59ccbaf5bce04559fa Mon Sep 17 00:00:00 2001 From: Dan Moseley Date: Wed, 18 Mar 2026 12:00:04 -0600 Subject: [PATCH 4/6] Add test verifying toggling Hidden preserves other BSD user flags Adds a macOS-specific test that sets UF_NODUMP on a file, toggles Hidden via FileInfo.Attributes, and verifies UF_NODUMP survives the round-trip through ConvertFileStatus and lchflags. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../FileInfo/GetSetAttributes.cs | 33 +++++++++++++++++++ .../System.IO.FileSystem.Tests.csproj | 4 +++ 2 files changed, 37 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs index eff7bf758b6ca2..c2184f1d66e1bc 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs @@ -41,5 +41,38 @@ public void HiddenAttributeSetCorrectly_OSX(string filePrefix, bool hidden) Assert.Equal(hidden, (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden); } + + [Fact] + [PlatformSpecific(TestPlatforms.OSX)] + public void TogglingHiddenAttribute_PreservesOtherUserFlags() + { + // UF_NODUMP (0x01) is a harmless BSD user flag that any file owner can set/clear. + const uint UF_NODUMP = 0x01; + const uint UF_HIDDEN = (uint)Interop.Sys.UserFlags.UF_HIDDEN; + + string path = GetTestFilePath(); + File.Create(path).Dispose(); + + // Set UF_NODUMP on the file directly via lchflags. + Assert.Equal(0, Interop.Sys.LChflags(path, UF_NODUMP)); + Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus before)); + Assert.NotEqual(0u, before.UserFlags & UF_NODUMP); + + // Toggle Hidden ON via the public API — this must preserve UF_NODUMP. + var fi = new FileInfo(path); + fi.Attributes |= FileAttributes.Hidden; + + Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus afterSet)); + Assert.NotEqual(0u, afterSet.UserFlags & UF_HIDDEN); + Assert.NotEqual(0u, afterSet.UserFlags & UF_NODUMP); + + // Toggle Hidden OFF — UF_NODUMP must still survive. + fi.Refresh(); + fi.Attributes &= ~FileAttributes.Hidden; + + Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus afterClear)); + Assert.Equal(0u, afterClear.UserFlags & UF_HIDDEN); + Assert.NotEqual(0u, afterClear.UserFlags & UF_NODUMP); + } } } diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj index a81429675984db..dbf26849083924 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj @@ -107,6 +107,10 @@ Link="Common\Interop\Unix\Interop.OpenFlags.cs" /> + + From 9405d542fbafcc803894b856b779dc274c05919e Mon Sep 17 00:00:00 2001 From: Dan Moseley Date: Wed, 18 Mar 2026 12:15:22 -0600 Subject: [PATCH 5/6] Move test to Unix-only file to fix cross-platform compilation The test references Interop.Sys.* types that are only available in the unix ItemGroup. Split the test into GetSetAttributes.Unix.cs (compiled only for unix TFM) and make the main class partial. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../FileInfo/GetSetAttributes.Unix.cs | 43 +++++++++++++++++++ .../FileInfo/GetSetAttributes.cs | 35 +-------------- .../System.IO.FileSystem.Tests.csproj | 1 + 3 files changed, 45 insertions(+), 34 deletions(-) create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.Unix.cs diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.Unix.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.Unix.cs new file mode 100644 index 00000000000000..60a75eda883a9c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.Unix.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Tests +{ + public partial class FileInfo_GetSetAttributes + { + [Fact] + [PlatformSpecific(TestPlatforms.OSX)] + public void TogglingHiddenAttribute_PreservesOtherUserFlags() + { + // UF_NODUMP (0x01) is a harmless BSD user flag that any file owner can set/clear. + const uint UF_NODUMP = 0x01; + const uint UF_HIDDEN = (uint)Interop.Sys.UserFlags.UF_HIDDEN; + + string path = GetTestFilePath(); + File.Create(path).Dispose(); + + // Set UF_NODUMP on the file directly via lchflags. + Assert.Equal(0, Interop.Sys.LChflags(path, UF_NODUMP)); + Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus before)); + Assert.NotEqual(0u, before.UserFlags & UF_NODUMP); + + // Toggle Hidden ON via the public API — this must preserve UF_NODUMP. + var fi = new FileInfo(path); + fi.Attributes |= FileAttributes.Hidden; + + Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus afterSet)); + Assert.NotEqual(0u, afterSet.UserFlags & UF_HIDDEN); + Assert.NotEqual(0u, afterSet.UserFlags & UF_NODUMP); + + // Toggle Hidden OFF — UF_NODUMP must still survive. + fi.Refresh(); + fi.Attributes &= ~FileAttributes.Hidden; + + Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus afterClear)); + Assert.Equal(0u, afterClear.UserFlags & UF_HIDDEN); + Assert.NotEqual(0u, afterClear.UserFlags & UF_NODUMP); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs index c2184f1d66e1bc..84f3cc5269a732 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/GetSetAttributes.cs @@ -5,7 +5,7 @@ namespace System.IO.Tests { - public class FileInfo_GetSetAttributes : InfoGetSetAttributes + public partial class FileInfo_GetSetAttributes : InfoGetSetAttributes { protected override bool CanBeReadOnly => true; protected override FileAttributes GetAttributes(string path) => new FileInfo(path).Attributes; @@ -41,38 +41,5 @@ public void HiddenAttributeSetCorrectly_OSX(string filePrefix, bool hidden) Assert.Equal(hidden, (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden); } - - [Fact] - [PlatformSpecific(TestPlatforms.OSX)] - public void TogglingHiddenAttribute_PreservesOtherUserFlags() - { - // UF_NODUMP (0x01) is a harmless BSD user flag that any file owner can set/clear. - const uint UF_NODUMP = 0x01; - const uint UF_HIDDEN = (uint)Interop.Sys.UserFlags.UF_HIDDEN; - - string path = GetTestFilePath(); - File.Create(path).Dispose(); - - // Set UF_NODUMP on the file directly via lchflags. - Assert.Equal(0, Interop.Sys.LChflags(path, UF_NODUMP)); - Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus before)); - Assert.NotEqual(0u, before.UserFlags & UF_NODUMP); - - // Toggle Hidden ON via the public API — this must preserve UF_NODUMP. - var fi = new FileInfo(path); - fi.Attributes |= FileAttributes.Hidden; - - Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus afterSet)); - Assert.NotEqual(0u, afterSet.UserFlags & UF_HIDDEN); - Assert.NotEqual(0u, afterSet.UserFlags & UF_NODUMP); - - // Toggle Hidden OFF — UF_NODUMP must still survive. - fi.Refresh(); - fi.Attributes &= ~FileAttributes.Hidden; - - Assert.Equal(0, Interop.Sys.Stat(path, out Interop.Sys.FileStatus afterClear)); - Assert.Equal(0u, afterClear.UserFlags & UF_HIDDEN); - Assert.NotEqual(0u, afterClear.UserFlags & UF_NODUMP); - } } } diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj index dbf26849083924..2c5993f0db6118 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj @@ -111,6 +111,7 @@ Link="Common\Interop\Unix\Interop.Stat.cs" /> + From 73756092b573e09f033e691ade5e93e102c06f09 Mon Sep 17 00:00:00 2001 From: Dan Moseley Date: Wed, 18 Mar 2026 13:21:16 -0600 Subject: [PATCH 6/6] Improve static assert comment per reviewer feedback Clarify that the static assert exists because LChflags and FChflags pass UserFlags values directly to the OS without remapping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/libs/System.Native/pal_io.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 6ecae029baa908..55e3147d09225a 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -201,7 +201,8 @@ c_static_assert(PAL_IN_EXCL_UNLINK == IN_EXCL_UNLINK); c_static_assert(PAL_IN_ISDIR == IN_ISDIR); #endif // HAVE_INOTIFY -// Validate that our UserFlags enum values are correct for the platform +// Validate that our UserFlags enum values match the platform, since +// SystemNative_LChflags and SystemNative_FChflags pass them directly to the OS. #if HAVE_STAT_FLAGS && defined(UF_HIDDEN) c_static_assert(PAL_UF_HIDDEN == UF_HIDDEN); #endif