diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ChMod.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ChMod.cs
index 044f8cd414ac62..e6975fce751f2a 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ChMod.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ChMod.cs
@@ -10,5 +10,8 @@ internal static partial class Sys
{
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ChMod", CharSet = CharSet.Ansi, SetLastError = true)]
internal static partial int ChMod(string path, int mode);
+
+ [GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FChMod", CharSet = CharSet.Ansi, SetLastError = true)]
+ internal static partial int FChMod(SafeHandle fd, int mode);
}
}
diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs
index a5bc36dfa3d2fc..cbc05ad5a85b69 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChflags.cs
@@ -17,6 +17,9 @@ internal enum UserFlags : uint
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChflags", CharSet = CharSet.Ansi, SetLastError = true)]
internal static partial int LChflags(string path, uint flags);
+ [GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FChflags", CharSet = CharSet.Ansi, SetLastError = true)]
+ internal static partial int FChflags(SafeHandle fd, uint flags);
+
internal static readonly bool CanSetHiddenFlag = (LChflagsCanSetHiddenFlag() != 0);
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChflagsCanSetHiddenFlag")]
diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UTimensat.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UTimensat.cs
index dd9ab9014508ce..964e1425c1c33c 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UTimensat.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.UTimensat.cs
@@ -24,5 +24,8 @@ internal struct TimeSpec
///
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_UTimensat", CharSet = CharSet.Ansi, SetLastError = true)]
internal static unsafe partial int UTimensat(string path, TimeSpec* times);
+
+ [GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FUTimens", CharSet = CharSet.Ansi, SetLastError = true)]
+ internal static unsafe partial int FUTimens(SafeHandle fd, TimeSpec* times);
}
}
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.BY_HANDLE_FILE_INFORMATION.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.BY_HANDLE_FILE_INFORMATION.cs
new file mode 100644
index 00000000000000..d23290bbbaee30
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.BY_HANDLE_FILE_INFORMATION.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ // Even though csc will by default use a sequential layout, a CS0649 warning as error
+ // is produced for un-assigned fields when no StructLayout is specified.
+ //
+ // Explicitly saying Sequential disables that warning/error for consumers which only
+ // use Stat in debug builds.
+ internal static partial class Kernel32
+ {
+ [StructLayout(LayoutKind.Sequential, Pack=4)]
+ internal struct BY_HANDLE_FILE_INFORMATION
+ {
+ internal uint dwFileAttributes;
+ internal FILE_TIME ftCreationTime;
+ internal FILE_TIME ftLastAccessTime;
+ internal FILE_TIME ftLastWriteTime;
+ internal uint dwVolumeSerialNumber;
+ internal uint nFileSizeHigh;
+ internal uint nFileSizeLow;
+ internal uint nNumberOfLinks;
+ internal uint nFileIndexHigh;
+ internal uint nFileIndexLow;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileInformationByHandle.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileInformationByHandle.cs
new file mode 100644
index 00000000000000..4241beebf2a93a
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileInformationByHandle.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true, ExactSpelling = true)]
+ internal static extern unsafe bool GetFileInformationByHandle(SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
+ }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.WIN32_FILE_ATTRIBUTE_DATA.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.WIN32_FILE_ATTRIBUTE_DATA.cs
index b7d2e4baace4bf..10d9f8322d8ea2 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.WIN32_FILE_ATTRIBUTE_DATA.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.WIN32_FILE_ATTRIBUTE_DATA.cs
@@ -23,6 +23,16 @@ internal void PopulateFrom(ref WIN32_FIND_DATA findData)
nFileSizeHigh = findData.nFileSizeHigh;
nFileSizeLow = findData.nFileSizeLow;
}
+
+ internal void PopulateFrom(ref BY_HANDLE_FILE_INFORMATION fileInformationData)
+ {
+ dwFileAttributes = (int) fileInformationData.dwFileAttributes;
+ ftCreationTime = fileInformationData.ftCreationTime;
+ ftLastAccessTime = fileInformationData.ftLastAccessTime;
+ ftLastWriteTime = fileInformationData.ftLastWriteTime;
+ nFileSizeHigh = fileInformationData.nFileSizeHigh;
+ nFileSizeLow = fileInformationData.nFileSizeLow;
+ }
}
}
}
diff --git a/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs
index dab4cf7a8f6c5c..a38ea919aa4949 100644
--- a/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs
+++ b/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs
@@ -112,6 +112,20 @@ internal static int FillAttributeInfo(string? path, ref Interop.Kernel32.WIN32_F
return errorCode;
}
+ internal static int FillAttributeInfo(SafeFileHandle fileHandle, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data)
+ {
+
+ if (!Interop.Kernel32.GetFileInformationByHandle(
+ fileHandle,
+ out Interop.Kernel32.BY_HANDLE_FILE_INFORMATION fileInformationData))
+ {
+ return Marshal.GetLastWin32Error();
+ }
+
+ data.PopulateFrom(ref fileInformationData);
+ return Interop.Errors.ERROR_SUCCESS;
+ }
+
internal static bool IsPathUnreachableError(int errorCode)
{
switch (errorCode)
diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj b/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj
index 34200ee648be6a..9a4320c7a2b422 100644
--- a/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj
+++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj
@@ -44,6 +44,8 @@
Link="Common\Interop\Windows\Interop.GET_FILEEX_INFO_LEVELS.cs" />
+
+
File.GetAttributes(path);
+
+ protected FileAttributes GetAttributes(SafeFileHandle fileHandle) => File.GetAttributes(fileHandle);
+
protected override void SetAttributes(string path, FileAttributes attributes) => File.SetAttributes(path, attributes);
+ protected void SetAttributes(SafeFileHandle fileHandle, FileAttributes attributes) => File.SetAttributes(fileHandle, attributes);
+
// Getting only throws for File, not FileInfo
[Theory, MemberData(nameof(TrailingCharacters))]
public void GetAttributes_MissingFile(char trailingChar)
@@ -17,6 +23,18 @@ public void GetAttributes_MissingFile(char trailingChar)
Assert.Throws(() => GetAttributes(GetTestFilePath() + trailingChar));
}
+
+ [Theory, MemberData(nameof(TrailingCharacters))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void GetAttributes_MissingFile_SafeFileHandle(char trailingChar)
+ {
+ Assert.Throws(() =>
+ {
+ using var fileHandle = File.OpenHandle(GetTestFilePath() + trailingChar);
+ GetAttributes(fileHandle);
+ });
+ }
+
// Getting only throws for File, not FileInfo
[Theory,
InlineData(":bar"),
@@ -30,10 +48,37 @@ public void GetAttributes_MissingAlternateDataStream_Windows(string streamName)
Assert.Throws(() => GetAttributes(streamName));
}
+ [Theory,
+ InlineData(":bar"),
+ InlineData(":bar:$DATA")]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void GetAttributes_MissingAlternateDataStream_Windows_SafeFileHandle(string streamName)
+ {
+ string path = CreateItem();
+ streamName = path + streamName;
+
+ Assert.Throws(() =>
+ {
+ using var fileHandle = File.OpenHandle(streamName);
+ GetAttributes(fileHandle);
+ });
+ }
+
[Theory, MemberData(nameof(TrailingCharacters))]
public void GetAttributes_MissingDirectory(char trailingChar)
{
Assert.Throws(() => GetAttributes(Path.Combine(GetTestFilePath(), "dir" + trailingChar)));
}
+
+ [Theory, MemberData(nameof(TrailingCharacters))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void GetAttributes_MissingDirectory_SafeFileHandle(char trailingChar)
+ {
+ Assert.Throws(() =>
+ {
+ using var fileHandle = File.OpenHandle(Path.Combine(GetTestFilePath(), "dir" + trailingChar), access: FileAccess.ReadWrite);
+ GetAttributes(fileHandle);
+ });
+ }
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs
index 75ab865c693f54..f99beeb7c9cefa 100644
--- a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs
+++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs
@@ -1,12 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.Win32.SafeHandles;
+
namespace System.IO.Tests
{
// Concrete class to run common file attributes tests on the File class
public class File_GetSetAttributesCommon : FileGetSetAttributes
{
protected override FileAttributes GetAttributes(string path) => File.GetAttributes(path);
+ protected FileAttributes GetAttributes(SafeFileHandle fileHandle) => File.GetAttributes(fileHandle);
protected override void SetAttributes(string path, FileAttributes attributes) => File.SetAttributes(path, attributes);
+ protected void SetAttributes(SafeFileHandle fileHandle, FileAttributes attributes) => File.SetAttributes(fileHandle, attributes);
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs
index 936b7021ac2794..e776f136161603 100644
--- a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs
+++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs
@@ -15,7 +15,9 @@ public class File_GetSetTimes : StaticGetSetTimes
// 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);
+ private static bool SupportsLongMaxDateTime => PlatformDetection.IsWindows ||
+ (!PlatformDetection.Is32BitProcess &&
+ !PlatformDetection.IsOSXLike);
protected override string GetExistingItem(bool readOnly = false)
{
@@ -72,44 +74,154 @@ public async Task CreationTimeSet_GetReturnsExpected_WhenNotInFuture()
public override IEnumerable TimeFunctions(bool requiresRoundtripping = false)
{
- if (IOInputs.SupportsGettingCreationTime && (!requiresRoundtripping || IOInputs.SupportsSettingCreationTime))
+ if (IOInputs.SupportsGettingCreationTime &&
+ (!requiresRoundtripping || IOInputs.SupportsSettingCreationTime))
{
yield return TimeFunction.Create(
- ((path, time) => File.SetCreationTime(path, time)),
- ((path) => File.GetCreationTime(path)),
+ File.SetCreationTime,
+ File.GetCreationTime,
DateTimeKind.Local);
yield return TimeFunction.Create(
- ((path, time) => File.SetCreationTimeUtc(path, time)),
- ((path) => File.GetCreationTimeUtc(path)),
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetCreationTime(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetCreationTime(fileHandle);
+ },
+ DateTimeKind.Local);
+ yield return TimeFunction.Create(
+ File.SetCreationTimeUtc,
+ File.GetCreationTimeUtc,
+ DateTimeKind.Unspecified);
+ yield return TimeFunction.Create(
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetCreationTimeUtc(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetCreationTimeUtc(fileHandle);
+ },
DateTimeKind.Unspecified);
yield return TimeFunction.Create(
- ((path, time) => File.SetCreationTimeUtc(path, time)),
- ((path) => File.GetCreationTimeUtc(path)),
+ File.SetCreationTimeUtc,
+ File.GetCreationTimeUtc,
+ DateTimeKind.Utc);
+ yield return TimeFunction.Create(
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetCreationTimeUtc(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetCreationTimeUtc(fileHandle);
+ },
DateTimeKind.Utc);
}
+
yield return TimeFunction.Create(
- ((path, time) => File.SetLastAccessTime(path, time)),
- ((path) => File.GetLastAccessTime(path)),
+ File.SetLastAccessTime,
+ File.GetLastAccessTime,
DateTimeKind.Local);
yield return TimeFunction.Create(
- ((path, time) => File.SetLastAccessTimeUtc(path, time)),
- ((path) => File.GetLastAccessTimeUtc(path)),
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetLastAccessTime(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetLastAccessTime(fileHandle);
+ },
+ DateTimeKind.Local);
+ yield return TimeFunction.Create(
+ File.SetLastAccessTimeUtc,
+ File.GetLastAccessTimeUtc,
+ DateTimeKind.Unspecified);
+ yield return TimeFunction.Create(
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetLastAccessTimeUtc(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetLastAccessTimeUtc(fileHandle);
+ },
DateTimeKind.Unspecified);
yield return TimeFunction.Create(
- ((path, time) => File.SetLastAccessTimeUtc(path, time)),
- ((path) => File.GetLastAccessTimeUtc(path)),
+ File.SetLastAccessTimeUtc,
+ File.GetLastAccessTimeUtc,
DateTimeKind.Utc);
yield return TimeFunction.Create(
- ((path, time) => File.SetLastWriteTime(path, time)),
- ((path) => File.GetLastWriteTime(path)),
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetLastAccessTimeUtc(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetLastAccessTimeUtc(fileHandle);
+ },
+ DateTimeKind.Utc);
+ yield return TimeFunction.Create(
+ File.SetLastWriteTime,
+ File.GetLastWriteTime,
+ DateTimeKind.Local);
+ yield return TimeFunction.Create(
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetLastWriteTime(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetLastWriteTime(fileHandle);
+ },
DateTimeKind.Local);
yield return TimeFunction.Create(
- ((path, time) => File.SetLastWriteTimeUtc(path, time)),
- ((path) => File.GetLastWriteTimeUtc(path)),
+ File.SetLastWriteTimeUtc,
+ File.GetLastWriteTimeUtc,
DateTimeKind.Unspecified);
yield return TimeFunction.Create(
- ((path, time) => File.SetLastWriteTimeUtc(path, time)),
- ((path) => File.GetLastWriteTimeUtc(path)),
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetLastWriteTimeUtc(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetLastWriteTimeUtc(fileHandle);
+ },
+ DateTimeKind.Unspecified);
+ yield return TimeFunction.Create(
+ File.SetLastWriteTimeUtc,
+ File.GetLastWriteTimeUtc,
+ DateTimeKind.Utc);
+ yield return TimeFunction.Create(
+ (path, time) =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ File.SetLastWriteTimeUtc(fileHandle, time);
+ },
+ path =>
+ {
+ using var fileHandle = File.OpenHandle(path, access: FileAccess.ReadWrite);
+ return File.GetLastWriteTimeUtc(fileHandle);
+ },
DateTimeKind.Utc);
}
@@ -144,6 +256,25 @@ public void SetLastWriteTimeTicks()
Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}");
}
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SetLastWriteTimeTicks_SafeFileHandle()
+ {
+ string firstFilePath = GetTestFilePath();
+ string secondFilePath = GetTestFilePath();
+
+ File.WriteAllText(firstFilePath, "");
+ File.WriteAllText(secondFilePath, "");
+
+ using var secondFileHandle = File.OpenHandle(secondFilePath, access: FileAccess.ReadWrite);
+ using var firstFileHandle = File.OpenHandle(firstFilePath, access: FileAccess.ReadWrite);
+
+ File.SetLastAccessTimeUtc(secondFileHandle, DateTime.UtcNow);
+ long firstFileTicks = File.GetLastWriteTimeUtc(firstFileHandle).Ticks;
+ long secondFileTicks = File.GetLastWriteTimeUtc(secondFileHandle).Ticks;
+ Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}");
+ }
+
[ConditionalFact(nameof(HighTemporalResolution))] // OSX HFS driver format/Browser Platform do not support nanosecond granularity.
public void SetUptoNanoseconds()
{
@@ -174,6 +305,23 @@ public void SetDateTimeMax()
Assert.Equal(ticks, dateTime.Ticks);
}
+ // Linux kernels no longer have long max date time support. Discussed in https://github.com/dotnet/runtime/issues/43166.
+ [PlatformSpecific(~TestPlatforms.Windows)]
+ [ConditionalFact(nameof(SupportsLongMaxDateTime))]
+ public void SetDateTimeMax_SafeFileHandle()
+ {
+ string file = GetTestFilePath();
+ File.WriteAllText(file, "");
+
+ using var fileHandle = File.OpenHandle(file, access: FileAccess.ReadWrite);
+ DateTime dateTime = new(9999, 4, 11, 23, 47, 17, 21, DateTimeKind.Utc);
+ File.SetLastWriteTimeUtc(fileHandle, dateTime);
+ long ticks = File.GetLastWriteTimeUtc(fileHandle).Ticks;
+
+ Assert.Equal(dateTime, File.GetLastWriteTimeUtc(fileHandle));
+ Assert.Equal(ticks, dateTime.Ticks);
+ }
+
[Fact]
public void SetLastAccessTimeTicks()
{
@@ -188,5 +336,24 @@ public void SetLastAccessTimeTicks()
long secondFileTicks = File.GetLastAccessTimeUtc(secondFile).Ticks;
Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}");
}
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SetLastAccessTimeTicks_SafeFileHandle()
+ {
+ string firstFilePath = GetTestFilePath();
+ string secondFilePath = GetTestFilePath();
+
+ File.WriteAllText(firstFilePath, "");
+ File.WriteAllText(secondFilePath, "");
+
+ using var firstFileHandle = File.OpenHandle(firstFilePath, access: FileAccess.ReadWrite);
+ using var secondFileHandle = File.OpenHandle(secondFilePath, access: FileAccess.ReadWrite);
+
+ File.SetLastWriteTimeUtc(secondFileHandle, DateTime.UtcNow);
+ long firstFileTicks = File.GetLastAccessTimeUtc(firstFileHandle).Ticks;
+ long secondFileTicks = File.GetLastAccessTimeUtc(secondFileHandle).Ticks;
+ Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}");
+ }
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
index 83f7bf47ea78e0..67f979695667b1 100644
--- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
+++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
@@ -90,6 +90,8 @@
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 7b47a57ac9632e..4c95e4d31c9548 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -1053,6 +1053,9 @@
Must be an array type.
+
+ The handle has no associated path.
+
Left to right characters may not be mixed with right to left characters in IDN labels.
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 321e2c0477ea87..b2ddfac32f1436 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -1548,9 +1548,15 @@
Common\Interop\Windows\Kernel32\Interop.GetFileAttributesEx.cs
+
+ Common\Interop\Windows\Kernel32\Interop.GetFileInformationByHandle.cs
+
Common\Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs
+
+ Common\Interop\Windows\Kernel32\Interop.BY_HANDLE_FILE_INFORMATION.cs
+
Common\Interop\Windows\Kernel32\Interop.GetFileType_SafeHandle.cs
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs
index d8b1124ed9b1b9..148182db5644c9 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs
@@ -177,45 +177,87 @@ internal static DateTimeOffset GetUtcDateTimeOffset(DateTime dateTime)
public static void SetCreationTime(string path, DateTime creationTime)
=> FileSystem.SetCreationTime(Path.GetFullPath(path), creationTime, asDirectory: false);
+ public static void SetCreationTime(SafeFileHandle fileHandle, DateTime creationTime)
+ => FileSystem.SetCreationTime(fileHandle, creationTime);
+
public static void SetCreationTimeUtc(string path, DateTime creationTimeUtc)
=> FileSystem.SetCreationTime(Path.GetFullPath(path), GetUtcDateTimeOffset(creationTimeUtc), asDirectory: false);
+ public static void SetCreationTimeUtc(SafeFileHandle fileHandle, DateTime creationTimeUtc)
+ => FileSystem.SetCreationTime(fileHandle, GetUtcDateTimeOffset(creationTimeUtc));
+
public static DateTime GetCreationTime(string path)
=> FileSystem.GetCreationTime(Path.GetFullPath(path)).LocalDateTime;
+ public static DateTime GetCreationTime(SafeFileHandle fileHandle)
+ => FileSystem.GetCreationTime(fileHandle).LocalDateTime;
+
public static DateTime GetCreationTimeUtc(string path)
=> FileSystem.GetCreationTime(Path.GetFullPath(path)).UtcDateTime;
+ public static DateTime GetCreationTimeUtc(SafeFileHandle fileHandle)
+ => FileSystem.GetCreationTime(fileHandle).UtcDateTime;
+
public static void SetLastAccessTime(string path, DateTime lastAccessTime)
- => FileSystem.SetLastAccessTime(Path.GetFullPath(path), lastAccessTime, asDirectory: false);
+ => FileSystem.SetLastAccessTime(Path.GetFullPath(path), lastAccessTime, false);
+
+ public static void SetLastAccessTime(SafeFileHandle fileHandle, DateTime lastAccessTime)
+ => FileSystem.SetLastAccessTime(fileHandle, lastAccessTime);
public static void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc)
- => FileSystem.SetLastAccessTime(Path.GetFullPath(path), GetUtcDateTimeOffset(lastAccessTimeUtc), asDirectory: false);
+ => FileSystem.SetLastAccessTime(Path.GetFullPath(path), GetUtcDateTimeOffset(lastAccessTimeUtc), false);
+
+ public static void SetLastAccessTimeUtc(SafeFileHandle fileHandle, DateTime lastAccessTimeUtc)
+ => FileSystem.SetLastAccessTime(fileHandle, GetUtcDateTimeOffset(lastAccessTimeUtc));
public static DateTime GetLastAccessTime(string path)
=> FileSystem.GetLastAccessTime(Path.GetFullPath(path)).LocalDateTime;
+ public static DateTime GetLastAccessTime(SafeFileHandle fileHandle)
+ => FileSystem.GetLastAccessTime(fileHandle).LocalDateTime;
+
public static DateTime GetLastAccessTimeUtc(string path)
=> FileSystem.GetLastAccessTime(Path.GetFullPath(path)).UtcDateTime;
+ public static DateTime GetLastAccessTimeUtc(SafeFileHandle fileHandle)
+ => FileSystem.GetLastAccessTime(fileHandle).UtcDateTime;
+
public static void SetLastWriteTime(string path, DateTime lastWriteTime)
- => FileSystem.SetLastWriteTime(Path.GetFullPath(path), lastWriteTime, asDirectory: false);
+ => FileSystem.SetLastWriteTime(Path.GetFullPath(path), lastWriteTime, false);
+
+ public static void SetLastWriteTime(SafeFileHandle fileHandle, DateTime lastWriteTime)
+ => FileSystem.SetLastWriteTime(fileHandle, lastWriteTime);
public static void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc)
- => FileSystem.SetLastWriteTime(Path.GetFullPath(path), GetUtcDateTimeOffset(lastWriteTimeUtc), asDirectory: false);
+ => FileSystem.SetLastWriteTime(Path.GetFullPath(path), GetUtcDateTimeOffset(lastWriteTimeUtc), false);
+
+ public static void SetLastWriteTimeUtc(SafeFileHandle fileHandle, DateTime lastWriteTimeUtc)
+ => FileSystem.SetLastWriteTime(fileHandle, GetUtcDateTimeOffset(lastWriteTimeUtc));
public static DateTime GetLastWriteTime(string path)
=> FileSystem.GetLastWriteTime(Path.GetFullPath(path)).LocalDateTime;
+ public static DateTime GetLastWriteTime(SafeFileHandle fileHandle)
+ => FileSystem.GetLastWriteTime(fileHandle).LocalDateTime;
+
public static DateTime GetLastWriteTimeUtc(string path)
=> FileSystem.GetLastWriteTime(Path.GetFullPath(path)).UtcDateTime;
+ public static DateTime GetLastWriteTimeUtc(SafeFileHandle fileHandle)
+ => FileSystem.GetLastWriteTime(fileHandle).UtcDateTime;
+
public static FileAttributes GetAttributes(string path)
=> FileSystem.GetAttributes(Path.GetFullPath(path));
+ public static FileAttributes GetAttributes(SafeFileHandle fileHandle)
+ => FileSystem.GetAttributes(fileHandle);
+
public static void SetAttributes(string path, FileAttributes fileAttributes)
=> FileSystem.SetAttributes(Path.GetFullPath(path), fileAttributes);
+ public static void SetAttributes(SafeFileHandle fileHandle, FileAttributes fileAttributes)
+ => FileSystem.SetAttributes(fileHandle, fileAttributes);
+
public static FileStream OpenRead(string path)
=> new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OSX.cs
index 40a887e7210be8..05b70f1356b513 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OSX.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OSX.cs
@@ -2,12 +2,19 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
namespace System.IO
{
internal partial struct FileStatus
{
internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory)
+ => SetCreationTime(handle: null, path, time, asDirectory);
+
+ internal void SetCreationTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory)
+ => SetCreationTime(handle, handle.Path, time, asDirectory);
+
+ private void SetCreationTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool asDirectory)
{
// Try to set the attribute on the file system entry using setattrlist,
// if we get ENOTSUP then it means that "The volume does not support
@@ -19,7 +26,7 @@ internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory
// great care.
long seconds = time.ToUnixTimeSeconds();
long nanoseconds = UnixTimeSecondsToNanoseconds(time, seconds);
- Interop.Error error = SetCreationTimeCore(path, seconds, nanoseconds);
+ Interop.Error error = SetCreationTimeCore(handle, path, seconds, nanoseconds);
if (error == Interop.Error.SUCCESS)
{
@@ -27,7 +34,7 @@ internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory
}
else if (error == Interop.Error.ENOTSUP)
{
- SetAccessOrWriteTimeCore(path, time, isAccessTime: false, checkCreationTime: false, asDirectory);
+ SetAccessOrWriteTimeCore(handle, path, time, isAccessTime: false, checkCreationTime: false, asDirectory);
}
else
{
@@ -35,7 +42,7 @@ internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory
}
}
- private unsafe Interop.Error SetCreationTimeCore(string path, long seconds, long nanoseconds)
+ private unsafe Interop.Error SetCreationTimeCore(SafeFileHandle? handle, string path, long seconds, long nanoseconds)
{
Interop.Sys.TimeSpec timeSpec = default;
@@ -46,15 +53,18 @@ private unsafe Interop.Error SetCreationTimeCore(string path, long seconds, long
attrList.bitmapCount = Interop.libc.AttrList.ATTR_BIT_MAP_COUNT;
attrList.commonAttr = Interop.libc.AttrList.ATTR_CMN_CRTIME;
+ // Follow links when using SafeFileHandle API.
+ int flags = handle is null ? new CULong(Interop.libc.FSOPT_NOFOLLOW) : 0;
+
Interop.Error error =
- Interop.libc.setattrlist(path, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), new CULong(Interop.libc.FSOPT_NOFOLLOW)) == 0 ?
+ Interop.libc.setattrlist(path, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), flags) == 0 ?
Interop.Error.SUCCESS :
Interop.Sys.GetLastErrorInfo().Error;
return error;
}
- private void SetAccessOrWriteTime(string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
- SetAccessOrWriteTimeCore(path, time, isAccessTime, checkCreationTime: true, asDirectory);
+ private void SetAccessOrWriteTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
+ SetAccessOrWriteTimeCore(handle, path, time, isAccessTime, checkCreationTime: true, asDirectory);
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OtherUnix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OtherUnix.cs
index 85b01fde17d7c1..90bafee097a7d5 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OtherUnix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OtherUnix.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
namespace System.IO
{
@@ -10,11 +11,14 @@ internal partial struct FileStatus
internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory) =>
SetLastWriteTime(path, time, asDirectory);
- private void SetAccessOrWriteTime(string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
- SetAccessOrWriteTimeCore(path, time, isAccessTime, checkCreationTime: false, asDirectory);
+ internal void SetCreationTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory) =>
+ SetLastWriteTime(handle, time, asDirectory);
+
+ private void SetAccessOrWriteTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
+ SetAccessOrWriteTimeCore(handle, path, time, isAccessTime, checkCreationTime: false, asDirectory);
// This is not used on these platforms, but is needed for source compat
- private Interop.Error SetCreationTimeCore(string path, long seconds, long nanoseconds) =>
+ private Interop.Error SetCreationTimeCore(SafeFileHandle? handle, string path, long seconds, long nanoseconds) =>
throw new InvalidOperationException();
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs
index 91e67d1b9f74c6..571cede66789a0 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
namespace System.IO
{
@@ -180,8 +181,18 @@ internal bool IsSymbolicLink(ReadOnlySpan path, bool continueOnError = fal
}
internal FileAttributes GetAttributes(ReadOnlySpan path, ReadOnlySpan fileName, bool continueOnError = false)
+ => GetAttributes(handle: null, path, fileName, continueOnError);
+
+ internal FileAttributes GetAttributes(SafeFileHandle handle, bool continueOnError = false)
{
- EnsureCachesInitialized(path, continueOnError);
+ ReadOnlySpan path = handle.Path;
+ ReadOnlySpan fileName = Path.GetFileName(path);
+ return GetAttributes(handle, path, fileName, continueOnError);
+ }
+
+ private FileAttributes GetAttributes(SafeFileHandle? handle, ReadOnlySpan path, ReadOnlySpan fileName, bool continueOnError = false)
+ {
+ EnsureCachesInitialized(handle, path, continueOnError);
if (!EntryExists)
return (FileAttributes)(-1);
@@ -204,6 +215,12 @@ internal FileAttributes GetAttributes(ReadOnlySpan path, ReadOnlySpan SetAttributes(handle: null, path, attributes, asDirectory);
+
+ internal void SetAttributes(SafeFileHandle handle, FileAttributes attributes, bool asDirectory)
+ => SetAttributes(handle, GetHandlePath(handle), attributes, asDirectory);
+
+ private void SetAttributes(SafeFileHandle? handle, string path, FileAttributes attributes, bool asDirectory)
{
// Validate that only flags from the attribute are being provided. This is an
// approximation for the validation done by the Win32 function.
@@ -220,22 +237,21 @@ internal void SetAttributes(string path, FileAttributes attributes, bool asDirec
throw new ArgumentException(SR.Arg_InvalidFileAttrs, "Attributes");
}
- EnsureCachesInitialized(path);
+ EnsureCachesInitialized(handle, path);
if (!EntryExists)
FileSystemInfo.ThrowNotFound(path);
if (Interop.Sys.CanSetHiddenFlag)
{
- if ((attributes & FileAttributes.Hidden) != 0 && (_fileCache.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, (_fileCache.UserFlags | (uint)Interop.Sys.UserFlags.UF_HIDDEN)), path, asDirectory);
- }
- else if (HasHiddenFlag)
+ bool hidden = (attributes & FileAttributes.Hidden) != 0;
+ if (hidden ^ HasHiddenFlag)
{
- // If Hidden flag is not set and cached file status does have the flag set then remove it
- Interop.CheckIo(Interop.Sys.LChflags(path, (_fileCache.UserFlags & ~(uint)Interop.Sys.UserFlags.UF_HIDDEN)), path, asDirectory);
+ uint flags = hidden ? _fileCache.UserFlags | (uint)Interop.Sys.UserFlags.UF_HIDDEN :
+ _fileCache.UserFlags & ~(uint)Interop.Sys.UserFlags.UF_HIDDEN;
+ int rv = handle is not null ? Interop.Sys.FChflags(handle, flags) :
+ Interop.Sys.LChflags(path!, flags);
+ Interop.CheckIo(rv, path, asDirectory);
}
}
@@ -256,7 +272,9 @@ internal void SetAttributes(string path, FileAttributes attributes, bool asDirec
// Change the permissions on the file
if (newMode != _fileCache.Mode)
{
- Interop.CheckIo(Interop.Sys.ChMod(path, newMode), path, asDirectory);
+ int rv = handle is not null ? Interop.Sys.FChMod(handle, newMode) :
+ Interop.Sys.ChMod(path!, newMode);
+ Interop.CheckIo(rv, path, asDirectory);
}
InvalidateCaches();
@@ -269,8 +287,14 @@ internal bool GetExists(ReadOnlySpan path, bool asDirectory)
}
internal DateTimeOffset GetCreationTime(ReadOnlySpan path, bool continueOnError = false)
+ => GetCreationTime(handle: null, path, continueOnError);
+
+ internal DateTimeOffset GetCreationTime(SafeFileHandle handle, bool continueOnError = false)
+ => GetCreationTime(handle, GetHandlePath(handle), continueOnError);
+
+ private DateTimeOffset GetCreationTime(SafeFileHandle? handle, ReadOnlySpan path, bool continueOnError = false)
{
- EnsureCachesInitialized(path, continueOnError);
+ EnsureCachesInitialized(handle, path, continueOnError);
if (!EntryExists)
return new DateTimeOffset(DateTime.FromFileTimeUtc(0));
@@ -287,8 +311,14 @@ internal DateTimeOffset GetCreationTime(ReadOnlySpan path, bool continueOn
}
internal DateTimeOffset GetLastAccessTime(ReadOnlySpan path, bool continueOnError = false)
+ => GetLastAccessTime(handle: null, path, continueOnError);
+
+ internal DateTimeOffset GetLastAccessTime(SafeFileHandle handle, bool continueOnError = false)
+ => GetLastAccessTime(handle, handle.Path, continueOnError);
+
+ private DateTimeOffset GetLastAccessTime(SafeFileHandle? handle, ReadOnlySpan path, bool continueOnError = false)
{
- EnsureCachesInitialized(path, continueOnError);
+ EnsureCachesInitialized(handle, path, continueOnError);
if (!EntryExists)
return new DateTimeOffset(DateTime.FromFileTimeUtc(0));
@@ -297,11 +327,23 @@ internal DateTimeOffset GetLastAccessTime(ReadOnlySpan path, bool continue
}
internal void SetLastAccessTime(string path, DateTimeOffset time, bool asDirectory)
- => SetAccessOrWriteTime(path, time, isAccessTime: true, asDirectory);
+ => SetLastAccessTime(handle: null, path, time, asDirectory);
+
+ internal void SetLastAccessTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory)
+ => SetLastAccessTime(handle, GetHandlePath(handle), time, asDirectory);
+
+ private void SetLastAccessTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool asDirectory)
+ => SetAccessOrWriteTime(handle, path, time, isAccessTime: true, asDirectory);
internal DateTimeOffset GetLastWriteTime(ReadOnlySpan path, bool continueOnError = false)
+ => GetLastWriteTime(handle: null, path, continueOnError);
+
+ internal DateTimeOffset GetLastWriteTime(SafeFileHandle handle, bool continueOnError = false)
+ => GetLastWriteTime(handle, handle.Path, continueOnError);
+
+ private DateTimeOffset GetLastWriteTime(SafeFileHandle? handle, ReadOnlySpan path, bool continueOnError = false)
{
- EnsureCachesInitialized(path, continueOnError);
+ EnsureCachesInitialized(handle, path, continueOnError);
if (!EntryExists)
return new DateTimeOffset(DateTime.FromFileTimeUtc(0));
@@ -310,14 +352,20 @@ internal DateTimeOffset GetLastWriteTime(ReadOnlySpan path, bool continueO
}
internal void SetLastWriteTime(string path, DateTimeOffset time, bool asDirectory)
- => SetAccessOrWriteTime(path, time, isAccessTime: false, asDirectory);
+ => SetLastWriteTime(handle: null, path, time, asDirectory);
+
+ internal void SetLastWriteTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory)
+ => SetLastWriteTime(handle, GetHandlePath(handle), time, asDirectory);
+
+ internal void SetLastWriteTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool asDirectory)
+ => SetAccessOrWriteTime(handle, path, time, isAccessTime: false, asDirectory);
private DateTimeOffset UnixTimeToDateTimeOffset(long seconds, long nanoseconds)
{
return DateTimeOffset.FromUnixTimeSeconds(seconds).AddTicks(nanoseconds / NanosecondsPerTick);
}
- private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, bool isAccessTime, bool checkCreationTime, bool asDirectory)
+ private unsafe void SetAccessOrWriteTimeCore(SafeFileHandle? handle, string path, DateTimeOffset time, bool isAccessTime, bool checkCreationTime, bool asDirectory)
{
// This api is used to set creation time on non OSX platforms, and as a fallback for OSX platforms.
// The reason why we use it to set 'creation time' is the below comment:
@@ -333,7 +381,7 @@ private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, b
// force a refresh so that we have an up-to-date times for values not being overwritten
InvalidateCaches();
- EnsureCachesInitialized(path);
+ EnsureCachesInitialized(handle, path);
if (!EntryExists)
FileSystemInfo.ThrowNotFound(path);
@@ -365,7 +413,9 @@ private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, b
buf[1].TvNsec = nanoseconds;
}
#endif
- Interop.CheckIo(Interop.Sys.UTimensat(path, buf), path, asDirectory);
+ int rv = handle is not null ? Interop.Sys.FUTimens(handle, buf) :
+ Interop.Sys.UTimensat(path!, buf);
+ Interop.CheckIo(rv, path, asDirectory);
// On OSX-like platforms, when the modification time is less than the creation time (including
// when the modification time is already less than but access time is being set), the creation
@@ -382,7 +432,7 @@ private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, b
if (updateCreationTime)
{
- Interop.Error error = SetCreationTimeCore(path, _fileCache.BirthTime, _fileCache.BirthTimeNsec);
+ Interop.Error error = SetCreationTimeCore(handle, path, _fileCache.BirthTime, _fileCache.BirthTimeNsec);
if (error != Interop.Error.SUCCESS && error != Interop.Error.ENOTSUP)
{
Interop.CheckIo(error, path, asDirectory);
@@ -399,17 +449,27 @@ internal long GetLength(ReadOnlySpan path, bool continueOnError = false)
return EntryExists ? _fileCache.Size : 0;
}
+ internal void RefreshCaches(ReadOnlySpan path)
+ => RefreshCaches(handle: null, path);
+
// Tries to refresh the lstat cache (_fileCache).
// This method should not throw. Instead, we store the results, and we will throw when the user attempts to access any of the properties when there was a failure
- internal void RefreshCaches(ReadOnlySpan path)
+ internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan path)
{
- path = Path.TrimEndingDirectorySeparator(path);
-
#if !TARGET_BROWSER
_isReadOnlyCache = -1;
#endif
+ int rv;
+ if (handle is not null)
+ {
+ rv = Interop.Sys.FStat(handle, out _fileCache);
+ }
+ else
+ {
+ path = Path.TrimEndingDirectorySeparator(path);
+ rv = Interop.Sys.LStat(path, out _fileCache);
+ }
- int rv = Interop.Sys.LStat(path, out _fileCache);
if (rv < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
@@ -431,20 +491,24 @@ internal void RefreshCaches(ReadOnlySpan path)
// Check if the main path is a directory, or a link to a directory.
int fileType = _fileCache.Mode & Interop.Sys.FileTypes.S_IFMT;
bool isDirectory = fileType == Interop.Sys.FileTypes.S_IFDIR ||
- (fileType == Interop.Sys.FileTypes.S_IFLNK
- && Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0
- && (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
+ (handle is null && // Don't follow links for SafeHandle APIs.
+ fileType == Interop.Sys.FileTypes.S_IFLNK &&
+ Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0 &&
+ (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
_state = isDirectory ? InitializedExistsDir : InitializedExistsFile;
}
+ internal void EnsureCachesInitialized(ReadOnlySpan path, bool continueOnError = false)
+ => EnsureCachesInitialized(handle: null, path, continueOnError);
+
// Checks if the file cache is uninitialized and refreshes it's value.
// If it failed, and continueOnError is set to true, this method will throw.
- internal void EnsureCachesInitialized(ReadOnlySpan path, bool continueOnError = false)
+ internal void EnsureCachesInitialized(SafeFileHandle? handle, ReadOnlySpan path, bool continueOnError = false)
{
if (_state == Uninitialized)
{
- RefreshCaches(path);
+ RefreshCaches(handle, path);
}
if (!continueOnError)
@@ -471,5 +535,19 @@ private static long UnixTimeSecondsToNanoseconds(DateTimeOffset time, long secon
const long TicksPerSecond = TicksPerMillisecond * 1000;
return (time.UtcDateTime.Ticks - DateTimeOffset.UnixEpoch.Ticks - seconds * TicksPerSecond) * NanosecondsPerTick;
}
+
+ private static string GetHandlePath(SafeFileHandle handle)
+ {
+ if (handle.Path is null)
+ {
+ ThrowHandleHasNoPath();
+ }
+ return handle.Path!;
+
+ static void ThrowHandleHasNoPath()
+ {
+ throw new ArgumentException(SR.Arg_HandleHasNoPath);
+ }
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs
index 3cff6ccd16e2bc..0e62768bf7e54e 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs
@@ -553,27 +553,51 @@ public static FileAttributes GetAttributes(string fullPath)
return attributes;
}
+ public static FileAttributes GetAttributes(SafeFileHandle fileHandle)
+ => default(FileStatus).GetAttributes(fileHandle);
+
public static void SetAttributes(string fullPath, FileAttributes attributes)
=> default(FileStatus).SetAttributes(fullPath, attributes, asDirectory: false);
+ public static void SetAttributes(SafeFileHandle fileHandle, FileAttributes attributes)
+ => default(FileStatus).SetAttributes(fileHandle, attributes, asDirectory: false);
+
public static DateTimeOffset GetCreationTime(string fullPath)
=> default(FileStatus).GetCreationTime(fullPath).UtcDateTime;
+ public static DateTimeOffset GetCreationTime(SafeFileHandle fileHandle)
+ => default(FileStatus).GetCreationTime(fileHandle).UtcDateTime;
+
public static void SetCreationTime(string fullPath, DateTimeOffset time, bool asDirectory)
=> default(FileStatus).SetCreationTime(fullPath, time, asDirectory);
+ public static void SetCreationTime(SafeFileHandle fileHandle, DateTimeOffset time)
+ => default(FileStatus).SetCreationTime(fileHandle, time, asDirectory: false);
+
public static DateTimeOffset GetLastAccessTime(string fullPath)
=> default(FileStatus).GetLastAccessTime(fullPath).UtcDateTime;
+ public static DateTimeOffset GetLastAccessTime(SafeFileHandle fileHandle)
+ => default(FileStatus).GetLastAccessTime(fileHandle).UtcDateTime;
+
public static void SetLastAccessTime(string fullPath, DateTimeOffset time, bool asDirectory)
=> default(FileStatus).SetLastAccessTime(fullPath, time, asDirectory);
+ public static unsafe void SetLastAccessTime(SafeFileHandle fileHandle, DateTimeOffset time)
+ => default(FileStatus).SetLastAccessTime(fileHandle, time, asDirectory: false);
+
public static DateTimeOffset GetLastWriteTime(string fullPath)
=> default(FileStatus).GetLastWriteTime(fullPath).UtcDateTime;
+ public static DateTimeOffset GetLastWriteTime(SafeFileHandle fileHandle)
+ => default(FileStatus).GetLastWriteTime(fileHandle).UtcDateTime;
+
public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory)
=> default(FileStatus).SetLastWriteTime(fullPath, time, asDirectory);
+ public static unsafe void SetLastWriteTime(SafeFileHandle fileHandle, DateTimeOffset time)
+ => default(FileStatus).SetLastWriteTime(fileHandle, time, asDirectory: false);
+
public static string[] GetLogicalDrives()
{
return DriveInfoInternal.GetLogicalDrives();
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 d554994b9d1006..4a083cc492d99c 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
@@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Buffers;
+using System.ComponentModel;
namespace System.IO
{
@@ -110,21 +111,35 @@ public static void DeleteFile(string fullPath)
public static FileAttributes GetAttributes(string fullPath)
{
Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
- int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: true);
- if (errorCode != 0)
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ int errorCode = FillAttributeInfo(fullPath, ref data, true);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath)
+ : (FileAttributes)data.dwFileAttributes;
+ }
- return (FileAttributes)data.dwFileAttributes;
+ public static FileAttributes GetAttributes(SafeFileHandle fileHandle)
+ {
+ Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
+ int errorCode = FillAttributeInfo(fileHandle, ref data);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fileHandle.Path)
+ : (FileAttributes)data.dwFileAttributes;
}
public static DateTimeOffset GetCreationTime(string fullPath)
{
Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
- int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: false);
- if (errorCode != 0)
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ int errorCode = FillAttributeInfo(fullPath, ref data, false);
+ return errorCode != 0 ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath) : data.ftCreationTime.ToDateTimeOffset();
+ }
- return data.ftCreationTime.ToDateTimeOffset();
+ public static DateTimeOffset GetCreationTime(SafeFileHandle fileHandle)
+ {
+ Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
+ int errorCode = FillAttributeInfo(fileHandle, ref data);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fileHandle.Path)
+ : data.ftCreationTime.ToDateTimeOffset();
}
public static FileSystemInfo GetFileSystemInfo(string fullPath, bool asDirectory)
@@ -138,20 +153,36 @@ public static DateTimeOffset GetLastAccessTime(string fullPath)
{
Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: false);
- if (errorCode != 0)
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath)
+ : data.ftLastAccessTime.ToDateTimeOffset();
+ }
- return data.ftLastAccessTime.ToDateTimeOffset();
+ public static DateTimeOffset GetLastAccessTime(SafeFileHandle fileHandle)
+ {
+ Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
+ int errorCode = FillAttributeInfo(fileHandle, ref data);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fileHandle.Path)
+ : data.ftLastAccessTime.ToDateTimeOffset();
}
public static DateTimeOffset GetLastWriteTime(string fullPath)
{
Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
- int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: false);
- if (errorCode != 0)
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ int errorCode = FillAttributeInfo(fullPath, ref data, false);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath)
+ : data.ftLastWriteTime.ToDateTimeOffset();
+ }
- return data.ftLastWriteTime.ToDateTimeOffset();
+ public static DateTimeOffset GetLastWriteTime(SafeFileHandle fileHandle)
+ {
+ Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
+ int errorCode = FillAttributeInfo(fileHandle, ref data);
+ return errorCode != 0
+ ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fileHandle.Path)
+ : data.ftLastWriteTime.ToDateTimeOffset();
}
public static void MoveDirectory(string sourceFullPath, string destFullPath)
@@ -405,17 +436,39 @@ private static void RemoveDirectoryInternal(string fullPath, bool topLevel, bool
public static void SetAttributes(string fullPath, FileAttributes attributes)
{
- if (!Interop.Kernel32.SetFileAttributes(fullPath, (int)attributes))
+ if (Interop.Kernel32.SetFileAttributes(fullPath, (int)attributes))
{
- int errorCode = Marshal.GetLastWin32Error();
- if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
- throw new ArgumentException(SR.Arg_InvalidFileAttrs, nameof(attributes));
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ return;
}
+
+ int errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
+ throw new ArgumentException(SR.Arg_InvalidFileAttrs, nameof(attributes));
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
}
- // Default values indicate "no change". Use defaults so that we don't force callsites to be aware of the default values
- private static unsafe void SetFileTime(
+ public static unsafe void SetAttributes(SafeFileHandle fileHandle, FileAttributes attributes)
+ {
+ var basicInfo = new Interop.Kernel32.FILE_BASIC_INFO
+ {
+ FileAttributes = (uint)attributes
+ };
+
+ bool success = Interop.Kernel32.SetFileInformationByHandle(
+ fileHandle,
+ Interop.Kernel32.FileBasicInfo,
+ &basicInfo,
+ (uint)sizeof(Interop.Kernel32.FILE_BASIC_INFO)
+ );
+
+ if (!success)
+ {
+ throw Win32Marshal.GetExceptionForLastWin32Error(fileHandle.Path);
+ }
+ }
+
+ // Default values indicate "no change". Use defaults so that we don't force callsites to be aware of the default values
+ private static void SetFileTime(
string fullPath,
bool asDirectory,
long creationTime = -1,
@@ -424,33 +477,51 @@ private static unsafe void SetFileTime(
long changeTime = -1,
uint fileAttributes = 0)
{
- using (SafeFileHandle handle = OpenHandleToWriteAttributes(fullPath, asDirectory))
+ using SafeFileHandle handle = OpenHandleToWriteAttributes(fullPath, asDirectory);
+ SetFileTime(handle, creationTime, lastAccessTime, lastWriteTime, changeTime, fileAttributes);
+ }
+
+ private static unsafe void SetFileTime(
+ SafeFileHandle fileHandle,
+ long creationTime = -1,
+ long lastAccessTime = -1,
+ long lastWriteTime = -1,
+ long changeTime = -1,
+ uint fileAttributes = 0)
+ {
+ var basicInfo = new Interop.Kernel32.FILE_BASIC_INFO
{
- var basicInfo = new Interop.Kernel32.FILE_BASIC_INFO()
- {
- CreationTime = creationTime,
- LastAccessTime = lastAccessTime,
- LastWriteTime = lastWriteTime,
- ChangeTime = changeTime,
- FileAttributes = fileAttributes
- };
-
- if (!Interop.Kernel32.SetFileInformationByHandle(handle, Interop.Kernel32.FileBasicInfo, &basicInfo, (uint)sizeof(Interop.Kernel32.FILE_BASIC_INFO)))
- {
- throw Win32Marshal.GetExceptionForLastWin32Error(fullPath);
- }
+ CreationTime = creationTime,
+ LastAccessTime = lastAccessTime,
+ LastWriteTime = lastWriteTime,
+ ChangeTime = changeTime,
+ FileAttributes = fileAttributes
+ };
+
+ if (!Interop.Kernel32.SetFileInformationByHandle(fileHandle, Interop.Kernel32.FileBasicInfo, &basicInfo, (uint)sizeof(Interop.Kernel32.FILE_BASIC_INFO)))
+ {
+ throw Win32Marshal.GetExceptionForLastWin32Error(fileHandle.Path);
}
}
public static void SetCreationTime(string fullPath, DateTimeOffset time, bool asDirectory)
- => SetFileTime(fullPath, asDirectory, creationTime: time.ToFileTime());
+ => SetFileTime(fullPath, asDirectory, time.ToFileTime());
+
+ public static void SetCreationTime(SafeFileHandle fileHandle, DateTimeOffset time) =>
+ SetFileTime(fileHandle, time.ToFileTime());
public static void SetLastAccessTime(string fullPath, DateTimeOffset time, bool asDirectory)
=> SetFileTime(fullPath, asDirectory, lastAccessTime: time.ToFileTime());
+ public static void SetLastAccessTime(SafeFileHandle fileHandle, DateTimeOffset time)
+ => SetFileTime(fileHandle, lastAccessTime: time.ToFileTime());
+
public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory)
=> SetFileTime(fullPath, asDirectory, lastWriteTime: time.ToFileTime());
+ public static void SetLastWriteTime(SafeFileHandle fileHandle, DateTimeOffset time)
+ => SetFileTime(fileHandle, lastWriteTime: time.ToFileTime());
+
public static string[] GetLogicalDrives()
=> DriveInfoInternal.GetLogicalDrives();
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index cf8690cc422e83..b0e071fb0f4177 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -10420,6 +10420,20 @@ public static void WriteAllText(string path, string? contents) { }
public static void WriteAllText(string path, string? contents, System.Text.Encoding encoding) { }
public static System.Threading.Tasks.Task WriteAllTextAsync(string path, string? contents, System.Text.Encoding encoding, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task WriteAllTextAsync(string path, string? contents, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+ public static void SetCreationTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, DateTime creationTime) { }
+ public static void SetCreationTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, DateTime creationTimeUtc) { }
+ public static DateTime GetCreationTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static DateTime GetCreationTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static void SetLastAccessTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, DateTime lastAccessTime) { }
+ public static void SetLastAccessTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, DateTime lastAccessTimeUtc) { }
+ public static DateTime GetLastAccessTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static DateTime GetLastAccessTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static void SetLastWriteTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, DateTime lastWriteTime) { }
+ public static void SetLastWriteTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, DateTime lastWriteTimeUtc) { }
+ public static DateTime GetLastWriteTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static DateTime GetLastWriteTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static FileAttributes GetAttributes(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; }
+ public static void SetAttributes(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, FileAttributes fileAttributes) { }
}
[System.FlagsAttribute]
public enum FileAccess
diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c
index 9ac04de3e0d66f..b02a2ad1e6ca90 100644
--- a/src/native/libs/System.Native/entrypoints.c
+++ b/src/native/libs/System.Native/entrypoints.c
@@ -239,6 +239,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_Exit)
DllImportEntry(SystemNative_Abort)
DllImportEntry(SystemNative_UTimensat)
+ DllImportEntry(SystemNative_FUTimens)
DllImportEntry(SystemNative_GetTimestamp)
DllImportEntry(SystemNative_GetCpuUtilization)
DllImportEntry(SystemNative_GetPwUidR)
diff --git a/src/native/libs/System.Native/pal_time.c b/src/native/libs/System.Native/pal_time.c
index 4f9b5326cb73c3..8aaffe115fb46c 100644
--- a/src/native/libs/System.Native/pal_time.c
+++ b/src/native/libs/System.Native/pal_time.c
@@ -53,6 +53,21 @@ int32_t SystemNative_UTimensat(const char* path, TimeSpec* times)
return result;
}
+int32_t SystemNative_FUTimens(intptr_t fd, TimeSpec* times)
+{
+ int32_t result;
+
+ struct timespec updatedTimes[2];
+ updatedTimes[0].tv_sec = (time_t)times[0].tv_sec;
+ updatedTimes[0].tv_nsec = (long)times[0].tv_nsec;
+ updatedTimes[1].tv_sec = (time_t)times[1].tv_sec;
+ updatedTimes[1].tv_nsec = (long)times[1].tv_nsec;
+
+ while (CheckInterrupted(result = futimens(ToFileDescriptor(fd), updatedTimes)));
+
+ return result;
+}
+
uint64_t SystemNative_GetTimestamp()
{
#if HAVE_CLOCK_GETTIME_NSEC_NP
diff --git a/src/native/libs/System.Native/pal_time.h b/src/native/libs/System.Native/pal_time.h
index 12fe5001334183..c7ef77da1e5bd5 100644
--- a/src/native/libs/System.Native/pal_time.h
+++ b/src/native/libs/System.Native/pal_time.h
@@ -27,6 +27,13 @@ typedef struct ProcessCpuInformation
*/
PALEXPORT int32_t SystemNative_UTimensat(const char* path, TimeSpec* times);
+/**
+ * Sets the last access and last modified time of a file
+ *
+ * Returns 0 on success; otherwise, returns -1 and errno is set.
+ */
+PALEXPORT int32_t SystemNative_FUTimens(intptr_t fd, TimeSpec* times);
+
/**
* Gets a high-resolution timestamp that can be used for time-interval measurements.
*/