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 eff7bf758b6ca2..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; 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..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 @@ -107,6 +107,11 @@ Link="Common\Interop\Unix\Interop.OpenFlags.cs" /> + + + diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index cd488c01c2c339..55e3147d09225a 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -201,6 +201,12 @@ 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 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 + static void ConvertFileStatus(const struct stat_* src, FileStatus* dst) { dst->Dev = (int64_t)src->st_dev; @@ -231,7 +237,7 @@ static void ConvertFileStatus(const struct stat_* src, FileStatus* dst) #endif #if HAVE_STAT_FLAGS && defined(UF_HIDDEN) - dst->UserFlags = ((src->st_flags & UF_HIDDEN) == UF_HIDDEN) ? PAL_UF_HIDDEN : 0; + dst->UserFlags = (uint32_t)src->st_flags; #else dst->UserFlags = 0; #endif