diff options
author | Steve Pfister <steve.pfister@microsoft.com> | 2019-10-31 05:01:51 +0300 |
---|---|---|
committer | Steve Pfister <steve.pfister@microsoft.com> | 2019-10-31 05:01:51 +0300 |
commit | d994544ba1118bfb477ded7ff9e3b40d73c0f45d (patch) | |
tree | fa60bb63bc1c198dd6555fecdd00adba7bf3072a | |
parent | 49f1c453f75e36948d0386d862378eb0dff51455 (diff) |
Backport of https://github.com/dotnet/corefx/pull/34560
Fixes https://github.com/mono/mono/issues/17304
8 files changed, 150 insertions, 11 deletions
diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs b/src/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs new file mode 100644 index 0000000000..bf0ecf72b7 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [Flags] + internal enum UserFlags : uint + { + UF_HIDDEN = 0x8000 + } + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChflags", SetLastError = true)] + internal static extern int LChflags(string path, uint flags); + + internal static readonly bool CanSetHiddenFlag = (LChflagsCanSetHiddenFlag() != 0); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChflagsCanSetHiddenFlag")] + private static extern int LChflagsCanSetHiddenFlag(); + } +}
\ No newline at end of file diff --git a/src/Native/Unix/Common/pal_config.h.in b/src/Native/Unix/Common/pal_config.h.in index c76d309940..033e390489 100644 --- a/src/Native/Unix/Common/pal_config.h.in +++ b/src/Native/Unix/Common/pal_config.h.in @@ -16,6 +16,8 @@ #cmakedefine01 HAVE_STAT_TIMESPEC #cmakedefine01 HAVE_STAT_TIM #cmakedefine01 HAVE_STAT_NSEC +#cmakedefine01 HAVE_STAT_FLAGS +#cmakedefine01 HAVE_LCHFLAGS #cmakedefine01 HAVE_GNU_STRERROR_R #cmakedefine01 HAVE_READDIR_R #cmakedefine01 HAVE_DIRENT_NAME_LEN diff --git a/src/Native/Unix/System.Native/pal_io.c b/src/Native/Unix/System.Native/pal_io.c index c3b5aff83b..ddd56b91c9 100644 --- a/src/Native/Unix/System.Native/pal_io.c +++ b/src/Native/Unix/System.Native/pal_io.c @@ -166,6 +166,12 @@ static void ConvertFileStatus(const struct stat_* src, struct FileStatus* dst) dst->BirthTime = 0; dst->BirthTimeNsec = 0; #endif + +#if defined(HAVE_STAT_FLAGS) && defined(UF_HIDDEN) + dst->UserFlags = ((src->st_flags & UF_HIDDEN) == UF_HIDDEN) ? PAL_UF_HIDDEN : 0; +#else + dst->UserFlags = 0; +#endif } // CoreCLR expects the "2" suffixes on these: they should be cleaned up in our @@ -1460,6 +1466,28 @@ int32_t SystemNative_LockFileRegion(intptr_t fd, int64_t offset, int64_t length, return ret; } +int32_t SystemNative_LChflags(const char* path, uint32_t flags) +{ +#if HAVE_LCHFLAGS + int32_t result; + while ((result = lchflags(path, flags)) < 0 && errno == EINTR); + return result; +#else + (void)path, (void)flags; + errno = ENOTSUP; + return -1; +#endif +} + +int32_t SystemNative_LChflagsCanSetHiddenFlag(void) +{ +#if defined(UF_HIDDEN) && defined(HAVE_STAT_FLAGS) && defined(HAVE_LCHFLAGS) + return true; +#else + return false; +#endif +} + int32_t SystemNative_Symlink(const char* target, const char* linkPath) { return symlink(target, linkPath); diff --git a/src/Native/Unix/System.Native/pal_io.h b/src/Native/Unix/System.Native/pal_io.h index aab12e6ed3..3a181971c6 100644 --- a/src/Native/Unix/System.Native/pal_io.h +++ b/src/Native/Unix/System.Native/pal_io.h @@ -154,6 +154,14 @@ enum }; /** + * Constants for interpreting FileStatus.UserFlags. + */ +enum +{ + PAL_UF_HIDDEN = 0x8000 +}; + +/** * Constants from dirent.h for the inode type returned from readdir variants */ enum NodeType @@ -751,6 +759,20 @@ DLLEXPORT int32_t SystemNative_GetPeerID(intptr_t socket, uid_t* euid); DLLEXPORT int32_t SystemNative_LockFileRegion(intptr_t fd, int64_t offset, int64_t length, int16_t lockType); /** +* Changes the file flags of the file whose location is specified in path +* +* Returns 0 for success, -1 for failure. Sets errno for failure. +*/ +DLLEXPORT int32_t SystemNative_LChflags(const char* path, uint32_t flags); + +/** + * Determines if the current platform supports setting UF_HIDDEN (0x8000) flag + * + * Returns true (non-zero) if supported, false (zero) if not. + */ +DLLEXPORT int32_t SystemNative_LChflagsCanSetHiddenFlag(void); + +/** * Creates a symbolic link at "linkPath", pointing at "target". * "target" may or may not exist (dangling symbolic links are valid filesystem objects) * Returns 0 on success; otherwise, returns -1 and errno is set. diff --git a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj index df9ff7154e..b7eb4c460c 100644 --- a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj +++ b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj @@ -284,6 +284,9 @@ <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Read.cs"> <Link>Common\Interop\Unix\Interop.Read.cs</Link> </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.LChflags.cs"> + <Link>Common\Interop\Unix\Interop.LChflags.cs</Link> + </Compile> <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Rename.cs"> <Link>Common\Interop\Unix\Interop.Rename.cs</Link> </Compile> diff --git a/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs b/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs index 3acb4c5747..670317d4db 100644 --- a/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs +++ b/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs @@ -84,8 +84,8 @@ namespace System.IO if (_isDirectory) attributes |= FileAttributes.Directory; - // If the filename starts with a period, it's hidden. - if (fileName.Length > 0 && fileName[0] == '.') + // If the filename starts with a period or has UF_HIDDEN flag set, it's hidden. + if (fileName.Length > 0 && (fileName[0] == '.' || (_fileStatus.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == (uint)Interop.Sys.UserFlags.UF_HIDDEN)) attributes |= FileAttributes.Hidden; return attributes != default ? attributes : FileAttributes.Normal; @@ -98,10 +98,10 @@ namespace System.IO const FileAttributes allValidFlags = FileAttributes.Archive | FileAttributes.Compressed | FileAttributes.Device | FileAttributes.Directory | FileAttributes.Encrypted | FileAttributes.Hidden | - FileAttributes.Hidden | FileAttributes.IntegrityStream | FileAttributes.Normal | - FileAttributes.NoScrubData | FileAttributes.NotContentIndexed | FileAttributes.Offline | - FileAttributes.ReadOnly | FileAttributes.ReparsePoint | FileAttributes.SparseFile | - FileAttributes.System | FileAttributes.Temporary; + FileAttributes.IntegrityStream | FileAttributes.Normal | FileAttributes.NoScrubData | + FileAttributes.NotContentIndexed | FileAttributes.Offline | FileAttributes.ReadOnly | + FileAttributes.ReparsePoint | FileAttributes.SparseFile | FileAttributes.System | + FileAttributes.Temporary; if ((attributes & ~allValidFlags) != 0) { // Using constant string for argument to match historical throw @@ -113,6 +113,26 @@ namespace System.IO if (!_exists) FileSystemInfo.ThrowNotFound(path); + if (Interop.Sys.CanSetHiddenFlag) + { + if ((attributes & FileAttributes.Hidden) != 0) + { + if ((_fileStatus.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == 0) + { + // If Hidden flag is set and cached file status does not have the flag set then set it + Interop.CheckIo(Interop.Sys.LChflags(path, (_fileStatus.UserFlags | (uint)Interop.Sys.UserFlags.UF_HIDDEN)), path, InitiallyDirectory); + } + } + else + { + if ((_fileStatus.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == (uint)Interop.Sys.UserFlags.UF_HIDDEN) + { + // If Hidden flag is not set and cached file status does have the flag set then remove it + Interop.CheckIo(Interop.Sys.LChflags(path, (_fileStatus.UserFlags & ~(uint)Interop.Sys.UserFlags.UF_HIDDEN)), path, InitiallyDirectory); + } + } + } + // The only thing we can reasonably change is whether the file object is readonly by changing permissions. int newMode = _fileStatus.Mode; diff --git a/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs b/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs index 408666e819..434af1cb5f 100644 --- a/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs +++ b/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs @@ -16,9 +16,16 @@ namespace System.IO.Tests public void SettingAttributes_Unix(FileAttributes attributes) { string path = CreateItem(); - SetAttributes(path, attributes); - Assert.Equal(attributes, GetAttributes(path)); - SetAttributes(path, 0); + AssertSettingAttributes(path, attributes); + } + + [Theory] + [InlineData(FileAttributes.Hidden)] + [PlatformSpecific(TestPlatforms.OSX | TestPlatforms.FreeBSD)] + public void SettingAttributes_OSXAndFreeBSD(FileAttributes attributes) + { + string path = CreateItem(); + AssertSettingAttributes(path, attributes); } [Theory] @@ -33,6 +40,11 @@ namespace System.IO.Tests public void SettingAttributes_Windows(FileAttributes attributes) { string path = CreateItem(); + AssertSettingAttributes(path, attributes); + } + + private void AssertSettingAttributes(string path, FileAttributes attributes) + { SetAttributes(path, attributes); Assert.Equal(attributes, GetAttributes(path)); SetAttributes(path, 0); @@ -48,8 +60,16 @@ namespace System.IO.Tests public void SettingInvalidAttributes_Unix(FileAttributes attributes) { string path = CreateItem(); - SetAttributes(path, attributes); - Assert.Equal(FileAttributes.Normal, GetAttributes(path)); + AssertSettingInvalidAttributes(path, attributes); + } + + [Theory] + [InlineData(FileAttributes.Hidden)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~(TestPlatforms.OSX | TestPlatforms.FreeBSD))] + public void SettingInvalidAttributes_UnixExceptOSXAndFreeBSD(FileAttributes attributes) + { + string path = CreateItem(); + AssertSettingInvalidAttributes(path, attributes); } [Theory] @@ -62,6 +82,11 @@ namespace System.IO.Tests public void SettingInvalidAttributes_Windows(FileAttributes attributes) { string path = CreateItem(); + AssertSettingInvalidAttributes(path, attributes); + } + + private void AssertSettingInvalidAttributes(string path, FileAttributes attributes) + { SetAttributes(path, attributes); Assert.Equal(FileAttributes.Normal, GetAttributes(path)); } diff --git a/src/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs b/src/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs index 89a92f4fb2..1bdfb98db2 100644 --- a/src/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs +++ b/src/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs @@ -28,5 +28,18 @@ namespace System.IO.Tests test.Refresh(); Assert.Equal(false, test.IsReadOnly); } + + [Theory] + [InlineData(".", true)] + [InlineData("", false)] + [PlatformSpecific(TestPlatforms.OSX)] + public void HiddenAttributeSetCorrectly_OSX(string filePrefix, bool hidden) + { + string testFilePath = Path.Combine(TestDirectory, $"{filePrefix}{GetTestFileName()}"); + FileInfo fileInfo = new FileInfo(testFilePath); + fileInfo.Create().Dispose(); + + Assert.Equal(hidden, (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden); + } } } |