diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileOperations.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileOperations.cs index cc4896c1c52e48..c43b4fd19d21d2 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileOperations.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileOperations.cs @@ -23,7 +23,8 @@ internal static partial class FileOperations internal const int FILE_FLAG_OVERLAPPED = 0x40000000; internal const int FILE_LIST_DIRECTORY = 0x0001; - } + internal const int FILE_WRITE_ATTRIBUTES = 0x100; + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs index 23c627033b1c6f..c694a53161981f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs @@ -21,7 +21,9 @@ public abstract class BaseGetSetTimes : FileSystemTest protected static bool LowTemporalResolution => PlatformDetection.IsBrowser || isHFS; protected static bool HighTemporalResolution => !LowTemporalResolution; - protected abstract T GetExistingItem(); + protected abstract bool CanBeReadOnly { get; } + + protected abstract T GetExistingItem(bool readOnly = false); protected abstract T GetMissingItem(); protected abstract T CreateSymlink(string path, string pathToTarget); @@ -84,6 +86,18 @@ public void SettingUpdatesProperties() SettingUpdatesPropertiesCore(item); } + [Fact] + public void SettingUpdatesPropertiesWhenReadOnly() + { + if (!CanBeReadOnly) + { + return; // directories can't be read only, so automatic pass + } + + T item = GetExistingItem(readOnly: true); + SettingUpdatesPropertiesCore(item); + } + [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] [PlatformSpecific(~TestPlatforms.Browser)] // Browser is excluded as it doesn't support symlinks [InlineData(false)] @@ -164,7 +178,7 @@ public void SettingUpdatesPropertiesAfterAnother() TimeFunction function1 = functions.x; TimeFunction function2 = functions.y; bool reverse = functions.reverse; - + // Checking that milliseconds are not dropped after setter. DateTime dt1 = new DateTime(2002, 12, 1, 12, 3, 3, LowTemporalResolution ? 0 : 321, DateTimeKind.Utc); DateTime dt2 = new DateTime(2001, 12, 1, 12, 3, 3, LowTemporalResolution ? 0 : 321, DateTimeKind.Utc); diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/Directory/GetSetTimes.cs index 698fbd1d67ce08..c71832b8f76eb9 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/GetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/GetSetTimes.cs @@ -7,7 +7,9 @@ namespace System.IO.Tests { public class Directory_GetSetTimes : StaticGetSetTimes { - protected override string GetExistingItem() => Directory.CreateDirectory(GetTestFilePath()).FullName; + protected override bool CanBeReadOnly => false; + + protected override string GetExistingItem(bool _) => Directory.CreateDirectory(GetTestFilePath()).FullName; protected override string CreateSymlink(string path, string pathToTarget) => Directory.CreateSymbolicLink(path, pathToTarget).FullName; diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs index 795a8750e49bf2..7a52964f4c5af2 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs @@ -7,7 +7,9 @@ namespace System.IO.Tests { public class DirectoryInfo_GetSetTimes : InfoGetSetTimes { - protected override DirectoryInfo GetExistingItem() => Directory.CreateDirectory(GetTestFilePath()); + protected override bool CanBeReadOnly => false; + + protected override DirectoryInfo GetExistingItem(bool _) => Directory.CreateDirectory(GetTestFilePath()); protected override DirectoryInfo GetMissingItem() => new DirectoryInfo(GetTestFilePath()); diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs index 50dcf0990a0eb6..936b7021ac2794 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs @@ -11,14 +11,22 @@ namespace System.IO.Tests { public class File_GetSetTimes : StaticGetSetTimes { + protected override bool CanBeReadOnly => true; + // OSX has the limitation of setting upto 2262-04-11T23:47:16 (long.Max) date. // 32bit Unix has time_t up to ~ 2038. private static bool SupportsLongMaxDateTime => PlatformDetection.IsWindows || (!PlatformDetection.Is32BitProcess && !PlatformDetection.IsOSXLike); - protected override string GetExistingItem() + protected override string GetExistingItem(bool readOnly = false) { string path = GetTestFilePath(); File.Create(path).Dispose(); + + if (readOnly) + { + File.SetAttributes(path, FileAttributes.ReadOnly); + } + return path; } diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs index 0559edc669e5d0..78c2781eac6613 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs @@ -10,10 +10,18 @@ namespace System.IO.Tests { public class FileInfo_GetSetTimes : InfoGetSetTimes { - protected override FileInfo GetExistingItem() + protected override bool CanBeReadOnly => true; + + protected override FileInfo GetExistingItem(bool readOnly = false) { string path = GetTestFilePath(); File.Create(path).Dispose(); + + if (readOnly) + { + File.SetAttributes(path, FileAttributes.ReadOnly); + } + return new FileInfo(path); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index 4e90b98a03fd74..3436a7f1428f60 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -182,10 +182,9 @@ public static void MoveFile(string sourceFullPath, string destFullPath, bool ove } } - private static SafeFileHandle OpenHandle(string fullPath, bool asDirectory) + private static SafeFileHandle OpenHandleToWriteAttributes(string fullPath, bool asDirectory) { - string root = fullPath.Substring(0, PathInternal.GetRootLength(fullPath.AsSpan())); - if (root == fullPath && root[1] == Path.VolumeSeparatorChar) + if (fullPath.Length == PathInternal.GetRootLength(fullPath) && fullPath[1] == Path.VolumeSeparatorChar) { // intentionally not fullpath, most upstack public APIs expose this as path. throw new ArgumentException(SR.Arg_PathIsVolume, "path"); @@ -199,7 +198,7 @@ private static SafeFileHandle OpenHandle(string fullPath, bool asDirectory) SafeFileHandle handle = Interop.Kernel32.CreateFile( fullPath, - Interop.Kernel32.GenericOperations.GENERIC_WRITE, + Interop.Kernel32.FileOperations.FILE_WRITE_ATTRIBUTES, FileShare.ReadWrite | FileShare.Delete, FileMode.Open, dwFlagsAndAttributes); @@ -425,7 +424,7 @@ private static unsafe void SetFileTime( long changeTime = -1, uint fileAttributes = 0) { - using (SafeFileHandle handle = OpenHandle(fullPath, asDirectory)) + using (SafeFileHandle handle = OpenHandleToWriteAttributes(fullPath, asDirectory)) { var basicInfo = new Interop.Kernel32.FILE_BASIC_INFO() {