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. */