Skip to content

Commit f65c469

Browse files
authored
Fix setting timestamp on Windows on readonly files (#62638)
1 parent e19619a commit f65c469

7 files changed

Lines changed: 46 additions & 12 deletions

File tree

src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileOperations.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ internal static partial class FileOperations
2323
internal const int FILE_FLAG_OVERLAPPED = 0x40000000;
2424

2525
internal const int FILE_LIST_DIRECTORY = 0x0001;
26-
}
2726

27+
internal const int FILE_WRITE_ATTRIBUTES = 0x100;
28+
}
2829
}
2930
}

src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public abstract class BaseGetSetTimes<T> : FileSystemTest
2121
protected static bool LowTemporalResolution => PlatformDetection.IsBrowser || isHFS;
2222
protected static bool HighTemporalResolution => !LowTemporalResolution;
2323

24-
protected abstract T GetExistingItem();
24+
protected abstract bool CanBeReadOnly { get; }
25+
26+
protected abstract T GetExistingItem(bool readOnly = false);
2527
protected abstract T GetMissingItem();
2628

2729
protected abstract T CreateSymlink(string path, string pathToTarget);
@@ -84,6 +86,18 @@ public void SettingUpdatesProperties()
8486
SettingUpdatesPropertiesCore(item);
8587
}
8688

89+
[Fact]
90+
public void SettingUpdatesPropertiesWhenReadOnly()
91+
{
92+
if (!CanBeReadOnly)
93+
{
94+
return; // directories can't be read only, so automatic pass
95+
}
96+
97+
T item = GetExistingItem(readOnly: true);
98+
SettingUpdatesPropertiesCore(item);
99+
}
100+
87101
[ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
88102
[PlatformSpecific(~TestPlatforms.Browser)] // Browser is excluded as it doesn't support symlinks
89103
[InlineData(false)]
@@ -164,7 +178,7 @@ public void SettingUpdatesPropertiesAfterAnother()
164178
TimeFunction function1 = functions.x;
165179
TimeFunction function2 = functions.y;
166180
bool reverse = functions.reverse;
167-
181+
168182
// Checking that milliseconds are not dropped after setter.
169183
DateTime dt1 = new DateTime(2002, 12, 1, 12, 3, 3, LowTemporalResolution ? 0 : 321, DateTimeKind.Utc);
170184
DateTime dt2 = new DateTime(2001, 12, 1, 12, 3, 3, LowTemporalResolution ? 0 : 321, DateTimeKind.Utc);

src/libraries/System.IO.FileSystem/tests/Directory/GetSetTimes.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ namespace System.IO.Tests
77
{
88
public class Directory_GetSetTimes : StaticGetSetTimes
99
{
10-
protected override string GetExistingItem() => Directory.CreateDirectory(GetTestFilePath()).FullName;
10+
protected override bool CanBeReadOnly => false;
11+
12+
protected override string GetExistingItem(bool _) => Directory.CreateDirectory(GetTestFilePath()).FullName;
1113

1214
protected override string CreateSymlink(string path, string pathToTarget) => Directory.CreateSymbolicLink(path, pathToTarget).FullName;
1315

src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ namespace System.IO.Tests
77
{
88
public class DirectoryInfo_GetSetTimes : InfoGetSetTimes<DirectoryInfo>
99
{
10-
protected override DirectoryInfo GetExistingItem() => Directory.CreateDirectory(GetTestFilePath());
10+
protected override bool CanBeReadOnly => false;
11+
12+
protected override DirectoryInfo GetExistingItem(bool _) => Directory.CreateDirectory(GetTestFilePath());
1113

1214
protected override DirectoryInfo GetMissingItem() => new DirectoryInfo(GetTestFilePath());
1315

src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,22 @@ namespace System.IO.Tests
1111
{
1212
public class File_GetSetTimes : StaticGetSetTimes
1313
{
14+
protected override bool CanBeReadOnly => true;
15+
1416
// OSX has the limitation of setting upto 2262-04-11T23:47:16 (long.Max) date.
1517
// 32bit Unix has time_t up to ~ 2038.
1618
private static bool SupportsLongMaxDateTime => PlatformDetection.IsWindows || (!PlatformDetection.Is32BitProcess && !PlatformDetection.IsOSXLike);
1719

18-
protected override string GetExistingItem()
20+
protected override string GetExistingItem(bool readOnly = false)
1921
{
2022
string path = GetTestFilePath();
2123
File.Create(path).Dispose();
24+
25+
if (readOnly)
26+
{
27+
File.SetAttributes(path, FileAttributes.ReadOnly);
28+
}
29+
2230
return path;
2331
}
2432

src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,18 @@ namespace System.IO.Tests
1010
{
1111
public class FileInfo_GetSetTimes : InfoGetSetTimes<FileInfo>
1212
{
13-
protected override FileInfo GetExistingItem()
13+
protected override bool CanBeReadOnly => true;
14+
15+
protected override FileInfo GetExistingItem(bool readOnly = false)
1416
{
1517
string path = GetTestFilePath();
1618
File.Create(path).Dispose();
19+
20+
if (readOnly)
21+
{
22+
File.SetAttributes(path, FileAttributes.ReadOnly);
23+
}
24+
1725
return new FileInfo(path);
1826
}
1927

src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,9 @@ public static void MoveFile(string sourceFullPath, string destFullPath, bool ove
182182
}
183183
}
184184

185-
private static SafeFileHandle OpenHandle(string fullPath, bool asDirectory)
185+
private static SafeFileHandle OpenHandleToWriteAttributes(string fullPath, bool asDirectory)
186186
{
187-
string root = fullPath.Substring(0, PathInternal.GetRootLength(fullPath.AsSpan()));
188-
if (root == fullPath && root[1] == Path.VolumeSeparatorChar)
187+
if (fullPath.Length == PathInternal.GetRootLength(fullPath) && fullPath[1] == Path.VolumeSeparatorChar)
189188
{
190189
// intentionally not fullpath, most upstack public APIs expose this as path.
191190
throw new ArgumentException(SR.Arg_PathIsVolume, "path");
@@ -199,7 +198,7 @@ private static SafeFileHandle OpenHandle(string fullPath, bool asDirectory)
199198

200199
SafeFileHandle handle = Interop.Kernel32.CreateFile(
201200
fullPath,
202-
Interop.Kernel32.GenericOperations.GENERIC_WRITE,
201+
Interop.Kernel32.FileOperations.FILE_WRITE_ATTRIBUTES,
203202
FileShare.ReadWrite | FileShare.Delete,
204203
FileMode.Open,
205204
dwFlagsAndAttributes);
@@ -425,7 +424,7 @@ private static unsafe void SetFileTime(
425424
long changeTime = -1,
426425
uint fileAttributes = 0)
427426
{
428-
using (SafeFileHandle handle = OpenHandle(fullPath, asDirectory))
427+
using (SafeFileHandle handle = OpenHandleToWriteAttributes(fullPath, asDirectory))
429428
{
430429
var basicInfo = new Interop.Kernel32.FILE_BASIC_INFO()
431430
{

0 commit comments

Comments
 (0)