diff --git a/src/libraries/Common/src/Interop/OSX/Interop.libc.cs b/src/libraries/Common/src/Interop/OSX/Interop.libc.cs index 9b65332570b07..44717c56d0ba8 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.libc.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.libc.cs @@ -27,5 +27,22 @@ internal struct AttrList internal static unsafe partial int setattrlist(string path, AttrList* attrList, void* attrBuf, nint attrBufSize, CULong options); internal const uint FSOPT_NOFOLLOW = 0x00000001; + [LibraryImport(Libraries.libc, EntryPoint = "fsetattrlist", SetLastError = true)] + private static unsafe partial int fsetattrlist(int fd, AttrList* attrList, void* attrBuf, nint attrBufSize, CULong options); + + internal static unsafe int fsetattrlist(SafeHandle handle, AttrList* attrList, void* attrBuf, nint attrBufSize, CULong options) + { + bool refAdded = false; + try + { + handle.DangerousAddRef(ref refAdded); + return fsetattrlist(handle.DangerousGetHandle().ToInt32(), attrList, attrBuf, attrBufSize, options); + } + finally + { + if (refAdded) + handle.DangerousRelease(); + } + } } } 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 941227564a01b..b436a821ad83c 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 [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChflags", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] internal static partial int LChflags(string path, uint flags); + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_FChflags", SetLastError = true)] + internal static partial int FChflags(SafeHandle fd, uint flags); + internal static readonly bool CanSetHiddenFlag = (LChflagsCanSetHiddenFlag() != 0); [LibraryImport(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 87359ffcbe0dc..261a5ae8562df 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 /// [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_UTimensat", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] internal static unsafe partial int UTimensat(string path, TimeSpec* times); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_FUTimens", 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 0000000000000..2f43f60c98c95 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.BY_HANDLE_FILE_INFORMATION.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [StructLayout(LayoutKind.Sequential)] + 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; + } + } +} 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 0000000000000..4e3b349327845 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileInformationByHandle.cs @@ -0,0 +1,15 @@ +// 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 + { + [LibraryImport(Libraries.Kernel32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static unsafe partial bool GetFileInformationByHandle(SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs index e58a3f89fe25f..4943a079c9898 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs @@ -25,12 +25,14 @@ public void InvalidParameters() [Theory, MemberData(nameof(TrailingCharacters))] public void SetAttributes_MissingFile(char trailingChar) { + if (!CanBeReadOnly) return; Assert.Throws(() => SetAttributes(GetTestFilePath() + trailingChar, FileAttributes.ReadOnly)); } [Theory, MemberData(nameof(TrailingCharacters))] public void SetAttributes_MissingDirectory(char trailingChar) { + if (!CanBeReadOnly) return; Assert.Throws(() => SetAttributes(Path.Combine(GetTestFilePath(), "file" + trailingChar), FileAttributes.ReadOnly)); } diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetAttributes.cs index db99a0b2f1032..2b27088d9004d 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetAttributes.cs @@ -7,6 +7,7 @@ namespace System.IO.Tests { public abstract class BaseGetSetAttributes : FileSystemTest { + protected abstract bool CanBeReadOnly { get; } protected abstract FileAttributes GetAttributes(string path); protected abstract void SetAttributes(string path, FileAttributes attributes); diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs index ac0f4c11a4244..72f2809d1666f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs @@ -28,6 +28,9 @@ public abstract class BaseGetSetTimes : FileSystemTest protected abstract T CreateSymlink(string path, string pathToTarget); + // When the item is a link, indicates whether the .NET API will get/set the link itself, or its target. + protected virtual bool ApiTargetsLink => true; + protected T CreateSymlinkToItem(T item) { // Creates a Symlink to 'item' (target may or may not exist) @@ -37,6 +40,7 @@ protected T CreateSymlinkToItem(T item) protected abstract string GetItemPath(T item); + // requiresRoundtripping defines whether to convert DateTimeFormat 'a' to 'b' and then back to 'a' to verify the DateTimeFormat-conversion public abstract IEnumerable TimeFunctions(bool requiresRoundtripping = false); public class TimeFunction : Tuple @@ -52,17 +56,27 @@ public static TimeFunction Create(SetTime setter, GetTime getter, DateTimeKind k public SetTime Setter => Item1; public GetTime Getter => Item2; public DateTimeKind Kind => Item3; + + public override string ToString() + { + return $"TimeFunction DateTimeKind.{Kind} Setter: {Setter.Method.Name} Getter: {Getter.Method.Name}"; + } } - private void SettingUpdatesPropertiesCore(T item) + private void SettingUpdatesPropertiesCore(T item, T? linkTarget = default) { Assert.All(TimeFunctions(requiresRoundtripping: true), (function) => { + bool isLink = linkTarget is not null; + // Checking that milliseconds are not dropped after setter. // Emscripten drops milliseconds in Browser DateTime dt = new DateTime(2014, 12, 1, 12, 3, 3, LowTemporalResolution ? 0 : 321, function.Kind); function.Setter(item, dt); - DateTime result = function.Getter(item); + + T getTarget = !isLink || ApiTargetsLink ? item : linkTarget; + DateTime result = function.Getter(getTarget); + Assert.Equal(dt, result); Assert.Equal(dt.ToLocalTime(), result.ToLocalTime()); @@ -102,7 +116,7 @@ public void SettingUpdatesPropertiesWhenReadOnly() [PlatformSpecific(~TestPlatforms.Browser)] // Browser is excluded as it doesn't support symlinks [InlineData(false)] [InlineData(true)] - public void SettingUpdatesPropertiesOnSymlink(bool targetExists) + public void SettingPropertiesOnSymlink(bool targetExists) { // This test is in this class since it needs all of the time functions. // This test makes sure that the times are set on the symlink itself. @@ -119,25 +133,37 @@ public void SettingUpdatesPropertiesOnSymlink(bool targetExists) T link = CreateSymlinkToItem(target); if (!targetExists) { - SettingUpdatesPropertiesCore(link); + // Don't check when settings update the target. + if (ApiTargetsLink) + { + SettingUpdatesPropertiesCore(link, target); + } } else { - // Get the target's initial times - IEnumerable timeFunctions = TimeFunctions(requiresRoundtripping: true); - DateTime[] initialTimes = timeFunctions.Select((funcs) => funcs.Getter(target)).ToArray(); + // When properties update link, verify the target properties don't change. + IEnumerable? timeFunctions = null; + DateTime[]? initialTimes = null; + if (ApiTargetsLink) + { + timeFunctions = TimeFunctions(requiresRoundtripping: true); + initialTimes = timeFunctions.Select((funcs) => funcs.Getter(target)).ToArray(); + } - SettingUpdatesPropertiesCore(link); + SettingUpdatesPropertiesCore(link, target); - // Ensure that we have the latest times. - if (target is FileSystemInfo fsi) + // Ensure target properties haven't changed. + if (ApiTargetsLink) { - fsi.Refresh(); + // Ensure that we have the latest times. + if (target is FileSystemInfo fsi) + { + fsi.Refresh(); + } + + DateTime[] updatedTimes = timeFunctions.Select((funcs) => funcs.Getter(target)).ToArray(); + Assert.Equal(initialTimes, updatedTimes); } - - // Ensure the target's times haven't changed. - DateTime[] updatedTimes = timeFunctions.Select((funcs) => funcs.Getter(target)).ToArray(); - Assert.Equal(initialTimes, updatedTimes); } } diff --git a/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs index 4a43aabfb574c..13f06d0f65a5f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs @@ -8,14 +8,21 @@ namespace System.IO.Tests // Tests that are valid for File and FileInfo public abstract class FileGetSetAttributes : BaseGetSetAttributes { - [Theory] - [InlineData(FileAttributes.ReadOnly)] - [InlineData(FileAttributes.Normal)] + [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] - public void SettingAttributes_Unix(FileAttributes attributes) + public void SettingAttributes_Unix_Normal() { string path = CreateItem(); - AssertSettingAttributes(path, attributes); + AssertSettingAttributes(path, FileAttributes.Normal); + } + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void SettingAttributes_Unix_ReadOnly() + { + if (!CanBeReadOnly) return; + string path = CreateItem(); + AssertSettingAttributes(path, FileAttributes.ReadOnly); } [Theory] @@ -28,13 +35,11 @@ public void SettingAttributes_OSXAndFreeBSD(FileAttributes attributes) } [Theory] - [InlineData(FileAttributes.ReadOnly)] [InlineData(FileAttributes.Hidden)] [InlineData(FileAttributes.System)] [InlineData(FileAttributes.Archive)] [InlineData(FileAttributes.Normal)] [InlineData(FileAttributes.Temporary)] - [InlineData(FileAttributes.ReadOnly | FileAttributes.Hidden)] [PlatformSpecific(TestPlatforms.Windows)] public void SettingAttributes_Windows(FileAttributes attributes) { @@ -42,6 +47,17 @@ public void SettingAttributes_Windows(FileAttributes attributes) AssertSettingAttributes(path, attributes); } + [Theory] + [InlineData(FileAttributes.ReadOnly)] + [InlineData(FileAttributes.ReadOnly | FileAttributes.Hidden)] + [PlatformSpecific(TestPlatforms.Windows)] + public void SettingAttributes_Windows_ReadOnly(FileAttributes attributes) + { + if (!CanBeReadOnly) return; + string path = CreateItem(); + AssertSettingAttributes(path, attributes); + } + private void AssertSettingAttributes(string path, FileAttributes attributes) { SetAttributes(path, attributes); diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetAttributes.cs index e35bfa62dab02..178c113addcaf 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetAttributes.cs @@ -15,6 +15,7 @@ protected override string CreateItem(string path = null, [CallerMemberName] stri protected override DirectoryInfo CreateInfo(string path) => new DirectoryInfo(path); protected override void DeleteItem(string path) => Directory.Delete(path); protected override bool IsDirectory => true; + protected override bool CanBeReadOnly => true; [Theory] [InlineData(FileAttributes.ReadOnly)] diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes.cs index 454b5ace12c94..eb8ad0bd30255 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes.cs @@ -9,6 +9,7 @@ public class File_GetSetAttributes : BaseGetSetAttributes { protected override FileAttributes GetAttributes(string path) => File.GetAttributes(path); protected override void SetAttributes(string path, FileAttributes attributes) => File.SetAttributes(path, attributes); + protected override bool CanBeReadOnly => false; // Getting only throws for File, not FileInfo [Theory, MemberData(nameof(TrailingCharacters))] diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes_SafeFileHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes_SafeFileHandle.cs new file mode 100644 index 0000000000000..f6b72510d2e0d --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes_SafeFileHandle.cs @@ -0,0 +1,39 @@ +// 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 Xunit; + +namespace System.IO.Tests +{ + public class GetSetAttributes_SafeFileHandle : FileGetSetAttributes + { + protected virtual SafeFileHandle OpenFileHandle(string path, FileAccess fileAccess) => + File.OpenHandle( + path, + FileMode.OpenOrCreate, + fileAccess, + FileShare.None); + + protected override bool CanBeReadOnly => false; + + protected override FileAttributes GetAttributes(string path) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetAttributes(fileHandle); + } + + protected override void SetAttributes(string path, FileAttributes attributes) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetAttributes(fileHandle, attributes); + } + + [Fact] + public void NullArgumentValidation() + { + Assert.Throws("fileHandle", static () => File.GetAttributes(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetAttributes(default(SafeFileHandle)!, (FileAttributes)0)); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes_String.cs similarity index 78% rename from src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs rename to src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes_String.cs index 75ab865c693f5..d042bd4b4610a 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributesCommon.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetAttributes_String.cs @@ -4,9 +4,10 @@ namespace System.IO.Tests { // Concrete class to run common file attributes tests on the File class - public class File_GetSetAttributesCommon : FileGetSetAttributes + public sealed class File_GetSetAttributes_String : FileGetSetAttributes { protected override FileAttributes GetAttributes(string path) => File.GetAttributes(path); protected override void SetAttributes(string path, FileAttributes attributes) => File.SetAttributes(path, attributes); + protected override bool CanBeReadOnly => true; } } diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs index 936b7021ac279..e622c7d22010d 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes.cs @@ -1,21 +1,19 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; namespace System.IO.Tests { - public class File_GetSetTimes : StaticGetSetTimes + public abstract class File_GetSetTimes : StaticGetSetTimes { - protected override bool CanBeReadOnly => true; - // OSX has the limitation of setting upto 2262-04-11T23:47:16 (long.Max) date. // 32bit Unix has time_t up to ~ 2038. - private static bool SupportsLongMaxDateTime => PlatformDetection.IsWindows || (!PlatformDetection.Is32BitProcess && !PlatformDetection.IsOSXLike); + protected static bool SupportsLongMaxDateTime => PlatformDetection.IsWindows || (!PlatformDetection.Is32BitProcess && !PlatformDetection.IsOSXLike); + + protected override bool CanBeReadOnly => true; protected override string GetExistingItem(bool readOnly = false) { @@ -32,6 +30,31 @@ protected override string GetExistingItem(bool readOnly = false) protected override string CreateSymlink(string path, string pathToTarget) => File.CreateSymbolicLink(path, pathToTarget).FullName; + protected abstract void SetCreationTime(string path, DateTime creationTime); + + protected abstract DateTime GetCreationTime(string path); + + protected abstract void SetCreationTimeUtc(string path, DateTime creationTimeUtc); + + protected abstract DateTime GetCreationTimeUtc(string path); + + protected abstract void SetLastAccessTime(string path, DateTime lastAccessTime); + + protected abstract DateTime GetLastAccessTime(string path); + + protected abstract void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc); + + protected abstract DateTime GetLastAccessTimeUtc(string path); + + protected abstract void SetLastWriteTime(string path, DateTime lastWriteTime); + + protected abstract DateTime GetLastWriteTime(string path); + + protected abstract void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc); + + protected abstract DateTime GetLastWriteTimeUtc(string path); + + [Fact] [PlatformSpecific(TestPlatforms.Linux)] public void BirthTimeIsNotNewerThanLowestOfAccessModifiedTimes() @@ -42,11 +65,12 @@ public void BirthTimeIsNotNewerThanLowestOfAccessModifiedTimes() // Set different values for all three // Status changed time will be when the file was first created, in this case) string path = GetExistingItem(); - File.SetLastWriteTime(path, DateTime.Now.AddMinutes(1)); - File.SetLastAccessTime(path, DateTime.Now.AddMinutes(2)); + + SetLastWriteTime(path, DateTime.Now.AddMinutes(1)); + SetLastAccessTime(path, DateTime.Now.AddMinutes(2)); // Assert.InRange is inclusive. - Assert.InRange(File.GetCreationTimeUtc(path), DateTime.MinValue, File.GetLastWriteTimeUtc(path)); + Assert.InRange(GetCreationTimeUtc(path), DateTime.MinValue, GetLastWriteTimeUtc(path)); } [Fact] @@ -62,12 +86,12 @@ public async Task CreationTimeSet_GetReturnsExpected_WhenNotInFuture() // Set the creation time to a value in the past that is between ctime and now. await Task.Delay(600); - DateTime newCreationTimeUTC = System.DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(300)); - File.SetCreationTimeUtc(path, newCreationTimeUTC); + DateTime newCreationTimeUtc = DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(300)); - Assert.Equal(newCreationTimeUTC, File.GetLastWriteTimeUtc(path)); + SetCreationTimeUtc(path, newCreationTimeUtc); - Assert.Equal(newCreationTimeUTC, File.GetCreationTimeUtc(path)); + Assert.Equal(newCreationTimeUtc, GetLastWriteTimeUtc(path)); + Assert.Equal(newCreationTimeUtc, GetCreationTimeUtc(path)); } public override IEnumerable TimeFunctions(bool requiresRoundtripping = false) @@ -75,60 +99,44 @@ public override IEnumerable TimeFunctions(bool requiresRoundtrippi if (IOInputs.SupportsGettingCreationTime && (!requiresRoundtripping || IOInputs.SupportsSettingCreationTime)) { yield return TimeFunction.Create( - ((path, time) => File.SetCreationTime(path, time)), - ((path) => File.GetCreationTime(path)), + SetCreationTime, + GetCreationTime, DateTimeKind.Local); yield return TimeFunction.Create( - ((path, time) => File.SetCreationTimeUtc(path, time)), - ((path) => File.GetCreationTimeUtc(path)), + SetCreationTimeUtc, + GetCreationTimeUtc, DateTimeKind.Unspecified); yield return TimeFunction.Create( - ((path, time) => File.SetCreationTimeUtc(path, time)), - ((path) => File.GetCreationTimeUtc(path)), + SetCreationTimeUtc, + GetCreationTimeUtc, DateTimeKind.Utc); } yield return TimeFunction.Create( - ((path, time) => File.SetLastAccessTime(path, time)), - ((path) => File.GetLastAccessTime(path)), + SetLastAccessTime, + GetLastAccessTime, DateTimeKind.Local); yield return TimeFunction.Create( - ((path, time) => File.SetLastAccessTimeUtc(path, time)), - ((path) => File.GetLastAccessTimeUtc(path)), + SetLastAccessTimeUtc, + GetLastAccessTimeUtc, DateTimeKind.Unspecified); yield return TimeFunction.Create( - ((path, time) => File.SetLastAccessTimeUtc(path, time)), - ((path) => File.GetLastAccessTimeUtc(path)), + SetLastAccessTimeUtc, + GetLastAccessTimeUtc, DateTimeKind.Utc); yield return TimeFunction.Create( - ((path, time) => File.SetLastWriteTime(path, time)), - ((path) => File.GetLastWriteTime(path)), + SetLastWriteTime, + GetLastWriteTime, DateTimeKind.Local); yield return TimeFunction.Create( - ((path, time) => File.SetLastWriteTimeUtc(path, time)), - ((path) => File.GetLastWriteTimeUtc(path)), + SetLastWriteTimeUtc, + GetLastWriteTimeUtc, DateTimeKind.Unspecified); yield return TimeFunction.Create( - ((path, time) => File.SetLastWriteTimeUtc(path, time)), - ((path) => File.GetLastWriteTimeUtc(path)), + SetLastWriteTimeUtc, + GetLastWriteTimeUtc, DateTimeKind.Utc); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInAppContainer))] // Can't read root in appcontainer - [PlatformSpecific(TestPlatforms.Windows)] - public void PageFileHasTimes() - { - // Typically there is a page file on the C: drive, if not, don't bother trying to track it down. - string pageFilePath = Directory.EnumerateFiles(@"C:\", "pagefile.sys").FirstOrDefault(); - if (pageFilePath != null) - { - Assert.All(TimeFunctions(), (item) => - { - var time = item.Getter(pageFilePath); - Assert.NotEqual(DateTime.FromFileTime(0), time); - }); - } - } - [Fact] public void SetLastWriteTimeTicks() { @@ -138,9 +146,9 @@ public void SetLastWriteTimeTicks() File.WriteAllText(firstFile, ""); File.WriteAllText(secondFile, ""); - File.SetLastAccessTimeUtc(secondFile, DateTime.UtcNow); - long firstFileTicks = File.GetLastWriteTimeUtc(firstFile).Ticks; - long secondFileTicks = File.GetLastWriteTimeUtc(secondFile).Ticks; + SetLastAccessTimeUtc(firstFile, DateTime.UtcNow); + long firstFileTicks = GetLastWriteTimeUtc(firstFile).Ticks; + long secondFileTicks = GetLastWriteTimeUtc(secondFile).Ticks; Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}"); } @@ -151,10 +159,11 @@ public void SetUptoNanoseconds() File.WriteAllText(file, ""); DateTime dateTime = DateTime.UtcNow; - File.SetLastWriteTimeUtc(file, dateTime); - long ticks = File.GetLastWriteTimeUtc(file).Ticks; - Assert.Equal(dateTime, File.GetLastWriteTimeUtc(file)); + SetLastWriteTimeUtc(file, dateTime); + long ticks = GetLastWriteTimeUtc(file).Ticks; + + Assert.Equal(dateTime, GetLastWriteTimeUtc(file)); Assert.Equal(ticks, dateTime.Ticks); } @@ -166,11 +175,11 @@ public void SetDateTimeMax() string file = GetTestFilePath(); File.WriteAllText(file, ""); - DateTime dateTime = new DateTime(9999, 4, 11, 23, 47, 17, 21, DateTimeKind.Utc); - File.SetLastWriteTimeUtc(file, dateTime); - long ticks = File.GetLastWriteTimeUtc(file).Ticks; + DateTime dateTime = new(9999, 4, 11, 23, 47, 17, 21, DateTimeKind.Utc); + SetLastWriteTimeUtc(file, dateTime); + long ticks = GetLastWriteTimeUtc(file).Ticks; - Assert.Equal(dateTime, File.GetLastWriteTimeUtc(file)); + Assert.Equal(dateTime, GetLastWriteTimeUtc(file)); Assert.Equal(ticks, dateTime.Ticks); } @@ -183,9 +192,9 @@ public void SetLastAccessTimeTicks() File.WriteAllText(firstFile, ""); File.WriteAllText(secondFile, ""); - File.SetLastWriteTimeUtc(secondFile, DateTime.UtcNow); - long firstFileTicks = File.GetLastAccessTimeUtc(firstFile).Ticks; - long secondFileTicks = File.GetLastAccessTimeUtc(secondFile).Ticks; + SetLastWriteTimeUtc(firstFile, DateTime.UtcNow); + long firstFileTicks = GetLastAccessTimeUtc(firstFile).Ticks; + long secondFileTicks = GetLastAccessTimeUtc(secondFile).Ticks; Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}"); } } diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_SafeFileHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_SafeFileHandle.cs new file mode 100644 index 0000000000000..47147428379d5 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_SafeFileHandle.cs @@ -0,0 +1,116 @@ +// 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 Xunit; + +namespace System.IO.Tests +{ + public class File_GetSetTimes_SafeFileHandle : File_GetSetTimes + { + protected virtual SafeFileHandle OpenFileHandle(string path, FileAccess fileAccess) => + File.OpenHandle( + path, + FileMode.OpenOrCreate, + fileAccess, + FileShare.ReadWrite); + + protected override bool CanBeReadOnly => false; + + protected override bool ApiTargetsLink => false; + + protected override void SetCreationTime(string path, DateTime creationTime) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetCreationTime(fileHandle, creationTime); + } + + protected override DateTime GetCreationTime(string path) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetCreationTime(fileHandle); + } + + protected override void SetCreationTimeUtc(string path, DateTime creationTimeUtc) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetCreationTimeUtc(fileHandle, creationTimeUtc); + } + + protected override DateTime GetCreationTimeUtc(string path) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetCreationTimeUtc(fileHandle); + } + + protected override void SetLastAccessTime(string path, DateTime lastAccessTime) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetLastAccessTime(fileHandle, lastAccessTime); + } + + protected override DateTime GetLastAccessTime(string path) + { + using var fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetLastAccessTime(fileHandle); + } + + protected override void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetLastAccessTimeUtc(fileHandle, lastAccessTimeUtc); + } + + protected override DateTime GetLastAccessTimeUtc(string path) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetLastAccessTimeUtc(fileHandle); + } + + protected override void SetLastWriteTime(string path, DateTime lastWriteTime) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetLastWriteTime(fileHandle, lastWriteTime); + } + + protected override DateTime GetLastWriteTime(string path) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetLastWriteTime(fileHandle); + } + + protected override void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.ReadWrite); + File.SetLastWriteTimeUtc(fileHandle, lastWriteTimeUtc); + } + + protected override DateTime GetLastWriteTimeUtc(string path) + { + using SafeFileHandle fileHandle = OpenFileHandle(path, FileAccess.Read); + return File.GetLastWriteTimeUtc(fileHandle); + } + + [Fact] + public void NullArgumentValidation() + { + Assert.Throws("fileHandle", static () => File.GetCreationTime(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetCreationTime(default(SafeFileHandle)!, DateTime.Now)); + + Assert.Throws("fileHandle", static () => File.GetCreationTimeUtc(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetCreationTimeUtc(default(SafeFileHandle)!, DateTime.Now)); + + Assert.Throws("fileHandle", static () => File.GetLastAccessTime(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetLastAccessTime(default(SafeFileHandle)!, DateTime.Now)); + + Assert.Throws("fileHandle", static () => File.GetLastAccessTimeUtc(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetLastAccessTimeUtc(default(SafeFileHandle)!, DateTime.Now)); + + Assert.Throws("fileHandle", static () => File.GetLastWriteTime(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetLastWriteTime(default(SafeFileHandle)!, DateTime.Now)); + + Assert.Throws("fileHandle", static () => File.GetLastWriteTimeUtc(default(SafeFileHandle)!)); + Assert.Throws("fileHandle", static () => File.SetLastWriteTimeUtc(default(SafeFileHandle)!, DateTime.Now)); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_SafeFileHandle_Pathless.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_SafeFileHandle_Pathless.cs new file mode 100644 index 0000000000000..6ac9da6fbf3c7 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_SafeFileHandle_Pathless.cs @@ -0,0 +1,21 @@ +// 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 +{ + public sealed class File_GetSetTimes_SafeFileHandle_Pathless : File_GetSetTimes_SafeFileHandle + { + protected override SafeFileHandle OpenFileHandle(string path, FileAccess fileAccess) + { + SafeFileHandle originHandle = base.OpenFileHandle(path, fileAccess); + + // Create handle by ptr to force that `SafeFileHandle.Path` is `null` + SafeFileHandle newHandle = new(originHandle.DangerousGetHandle(), true); + originHandle.SetHandleAsInvalid(); + + return newHandle; + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_String.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_String.cs new file mode 100644 index 0000000000000..3c11151a2aa03 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetTimes_String.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Xunit; + +namespace System.IO.Tests +{ + public class File_GetSetTimes_String : File_GetSetTimes + { + protected override bool CanBeReadOnly => true; + + protected override string CreateSymlink(string path, string pathToTarget) => File.CreateSymbolicLink(path, pathToTarget).FullName; + + protected override void SetCreationTime(string path, DateTime creationTime) => File.SetCreationTime(path, creationTime); + + protected override DateTime GetCreationTime(string path) => File.GetCreationTime(path); + + protected override void SetCreationTimeUtc(string path, DateTime creationTimeUtc) => File.SetCreationTimeUtc(path, creationTimeUtc); + + protected override DateTime GetCreationTimeUtc(string path) => File.GetCreationTimeUtc(path); + + protected override void SetLastAccessTime(string path, DateTime lastAccessTime) => File.SetLastAccessTime(path, lastAccessTime); + + protected override DateTime GetLastAccessTime(string path) => File.GetLastAccessTime(path); + + protected override void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc) => File.SetLastAccessTimeUtc(path, lastAccessTimeUtc); + + protected override DateTime GetLastAccessTimeUtc(string path) => File.GetLastAccessTimeUtc(path); + + protected override void SetLastWriteTime(string path, DateTime lastWriteTime) => File.SetLastWriteTime(path, lastWriteTime); + + protected override DateTime GetLastWriteTime(string path) => File.GetLastWriteTime(path); + + protected override void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc) => File.SetLastWriteTimeUtc(path, lastWriteTimeUtc); + + protected override DateTime GetLastWriteTimeUtc(string path) => File.GetLastWriteTimeUtc(path); + + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInAppContainer))] // Can't read root in appcontainer + [PlatformSpecific(TestPlatforms.Windows)] + public void PageFileHasTimes() + { + // Typically there is a page file on the C: drive, if not, don't bother trying to track it down. + string pageFilePath = Directory.EnumerateFiles(@"C:\", "pagefile.sys", new EnumerationOptions + { + AttributesToSkip = 0 + }).FirstOrDefault(); + if (pageFilePath != null) + { + Assert.All(TimeFunctions(), (item) => + { + var time = item.Getter(pageFilePath); + Assert.NotEqual(DateTime.FromFileTime(0), time); + }); + } + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs index 995c7df6fa82c..eff7bf758b6ca 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributes.cs @@ -7,6 +7,7 @@ namespace System.IO.Tests { public class FileInfo_GetSetAttributes : InfoGetSetAttributes { + protected override bool CanBeReadOnly => true; protected override FileAttributes GetAttributes(string path) => new FileInfo(path).Attributes; protected override void SetAttributes(string path, FileAttributes attributes) => new FileInfo(path).Attributes = attributes; protected override FileInfo CreateInfo(string path) => new FileInfo(path); diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributesCommon.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributesCommon.cs index 40c9a4ff1ff1a..3fd32387a8c40 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributesCommon.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetAttributesCommon.cs @@ -6,6 +6,7 @@ namespace System.IO.Tests // Concrete class to run common file attributes tests on the FileInfo class public class FileInfo_GetSetAttributesCommon : FileGetSetAttributes { + protected override bool CanBeReadOnly => true; protected override FileAttributes GetAttributes(string path) => new FileInfo(path).Attributes; protected override void SetAttributes(string path, FileAttributes attributes) => new FileInfo(path).Attributes = attributes; } 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 1db5a016e3685..a548e0b7cc90e 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 @@ -34,7 +34,11 @@ - + + + + + @@ -99,6 +103,8 @@ + + @@ -202,7 +208,7 @@ - + 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 623c28f92bde6..e5cfb22fd0f5f 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 @@ -1492,6 +1492,9 @@ Common\Interop\Windows\NtDll\Interop.RtlNtStatusToDosError.cs + + Common\Interop\Windows\Kernel32\Interop.BY_HANDLE_FILE_INFORMATION.cs + Common\Interop\Windows\Kernel32\Interop.DeleteFile.cs @@ -1588,6 +1591,9 @@ Common\Interop\Windows\Kernel32\Interop.GetFileAttributesEx.cs + + Common\Interop\Windows\Kernel32\Interop.GetFileInformationByHandle.cs + Common\Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs @@ -2001,6 +2007,9 @@ Common\Interop\Unix\System.Native\Interop.ChMod.cs + + Common\Interop\Unix\System.Native\Interop.FChMod.cs + Common\Interop\Unix\System.Native\Interop.Close.cs @@ -2016,9 +2025,6 @@ Common\Interop\Unix\System.Native\Interop.UnixFileSystemTypes.cs - - Common\Interop\Unix\System.Native\Interop.FChMod.cs - Common\Interop\Unix\System.Native\Interop.FLock.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 9c8916f8090f2..48e53e0c7af91 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -181,45 +181,392 @@ internal static DateTimeOffset GetUtcDateTimeOffset(DateTime dateTime) public static void SetCreationTime(string path, DateTime creationTime) => FileSystem.SetCreationTime(Path.GetFullPath(path), creationTime, asDirectory: false); + /// + /// Sets the date and time the file or directory was created. + /// + /// + /// A to the file or directory for which to set the creation date and time information. + /// + /// + /// A containing the value to set for the creation date and time of . + /// This value is expressed in local time. + /// + /// + /// is . + /// + /// + /// specifies a value outside the range of dates, times, or both permitted for this operation. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// An I/O error occurred while performing the operation. + /// + public static void SetCreationTime(SafeFileHandle fileHandle, DateTime creationTime) + { + ArgumentNullException.ThrowIfNull(fileHandle); + FileSystem.SetCreationTime(fileHandle, creationTime); + } + public static void SetCreationTimeUtc(string path, DateTime creationTimeUtc) => FileSystem.SetCreationTime(Path.GetFullPath(path), GetUtcDateTimeOffset(creationTimeUtc), asDirectory: false); + + /// + /// Sets the date and time, in coordinated universal time (UTC), that the file or directory was created. + /// + /// + /// A to the file or directory for which to set the creation date and time information. + /// + /// + /// A containing the value to set for the creation date and time of . + /// This value is expressed in UTC time. + /// + /// + /// is . + /// + /// + /// specifies a value outside the range of dates, times, or both permitted for this operation. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// An I/O error occurred while performing the operation. + /// + public static void SetCreationTimeUtc(SafeFileHandle fileHandle, DateTime creationTimeUtc) + { + ArgumentNullException.ThrowIfNull(fileHandle); + FileSystem.SetCreationTime(fileHandle, GetUtcDateTimeOffset(creationTimeUtc)); + } + public static DateTime GetCreationTime(string path) => FileSystem.GetCreationTime(Path.GetFullPath(path)).LocalDateTime; + /// + /// Returns the creation date and time of the specified file or directory. + /// + /// + /// A to the file or directory for which to obtain creation date and time information. + /// + /// + /// A structure set to the creation date and time for the specified file or + /// directory. This value is expressed in local time. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static DateTime GetCreationTime(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return FileSystem.GetCreationTime(fileHandle).LocalDateTime; + } + public static DateTime GetCreationTimeUtc(string path) => FileSystem.GetCreationTime(Path.GetFullPath(path)).UtcDateTime; + /// + /// Returns the creation date and time, in coordinated universal time (UTC), of the specified file or directory. + /// + /// + /// A to the file or directory for which to obtain creation date and time information. + /// + /// + /// A structure set to the creation date and time for the specified file or + /// directory. This value is expressed in UTC time. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static DateTime GetCreationTimeUtc(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return 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); + + /// + /// Sets the date and time the specified file or directory was last accessed. + /// + /// + /// A to the file or directory for which to set the last access date and time information. + /// + /// + /// A containing the value to set for the last access date and time of . + /// This value is expressed in local time. + /// + /// + /// is . + /// + /// + /// specifies a value outside the range of dates, times, or both permitted for this operation. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// An I/O error occurred while performing the operation. + /// + public static void SetLastAccessTime(SafeFileHandle fileHandle, DateTime lastAccessTime) + { + ArgumentNullException.ThrowIfNull(fileHandle); + 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); + + /// + /// Sets the date and time, in coordinated universal time (UTC), that the specified file or directory was last accessed. + /// + /// + /// A to the file or directory for which to set the last access date and time information. + /// + /// + /// A containing the value to set for the last access date and time of . + /// This value is expressed in UTC time. + /// + /// + /// is . + /// + /// + /// specifies a value outside the range of dates, times, or both permitted for this operation. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// An I/O error occurred while performing the operation. + /// + public static void SetLastAccessTimeUtc(SafeFileHandle fileHandle, DateTime lastAccessTimeUtc) + { + ArgumentNullException.ThrowIfNull(fileHandle); + FileSystem.SetLastAccessTime(fileHandle, GetUtcDateTimeOffset(lastAccessTimeUtc)); + } public static DateTime GetLastAccessTime(string path) => FileSystem.GetLastAccessTime(Path.GetFullPath(path)).LocalDateTime; + /// + /// Returns the last access date and time of the specified file or directory. + /// + /// + /// A to the file or directory for which to obtain last access date and time information. + /// + /// + /// A structure set to the last access date and time for the specified file or + /// directory. This value is expressed in local time. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static DateTime GetLastAccessTime(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return FileSystem.GetLastAccessTime(fileHandle).LocalDateTime; + } + public static DateTime GetLastAccessTimeUtc(string path) => FileSystem.GetLastAccessTime(Path.GetFullPath(path)).UtcDateTime; + /// + /// Returns the last access date and time, in coordinated universal time (UTC), of the specified file or directory. + /// + /// + /// A to the file or directory for which to obtain last access date and time information. + /// + /// + /// A structure set to the last access date and time for the specified file or + /// directory. This value is expressed in UTC time. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static DateTime GetLastAccessTimeUtc(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return 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); + + /// + /// Sets the date and time that the specified file or directory was last written to. + /// + /// + /// A to the file or directory for which to set the last write date and time information. + /// + /// + /// A containing the value to set for the last write date and time of . + /// This value is expressed in local time. + /// + /// + /// is . + /// + /// + /// specifies a value outside the range of dates, times, or both permitted for this operation. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// An I/O error occurred while performing the operation. + /// + public static void SetLastWriteTime(SafeFileHandle fileHandle, DateTime lastWriteTime) + { + ArgumentNullException.ThrowIfNull(fileHandle); + 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); + + /// + /// Sets the date and time, in coordinated universal time (UTC), that the specified file or directory was last written to. + /// + /// + /// A to the file or directory for which to set the last write date and time information. + /// + /// + /// A containing the value to set for the last write date and time of . + /// This value is expressed in UTC time. + /// + /// + /// is . + /// + /// + /// specifies a value outside the range of dates, times, or both permitted for this operation. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// An I/O error occurred while performing the operation. + /// + public static void SetLastWriteTimeUtc(SafeFileHandle fileHandle, DateTime lastWriteTimeUtc) + { + ArgumentNullException.ThrowIfNull(fileHandle); + FileSystem.SetLastWriteTime(fileHandle, GetUtcDateTimeOffset(lastWriteTimeUtc)); + } public static DateTime GetLastWriteTime(string path) => FileSystem.GetLastWriteTime(Path.GetFullPath(path)).LocalDateTime; + /// + /// Returns the last write date and time of the specified file or directory. + /// + /// + /// A to the file or directory for which to obtain last write date and time information. + /// + /// + /// A structure set to the last write date and time for the specified file or + /// directory. This value is expressed in local time. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static DateTime GetLastWriteTime(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return FileSystem.GetLastWriteTime(fileHandle).LocalDateTime; + } + public static DateTime GetLastWriteTimeUtc(string path) => FileSystem.GetLastWriteTime(Path.GetFullPath(path)).UtcDateTime; + /// + /// Returns the last write date and time, in coordinated universal time (UTC), of the specified file or directory. + /// + /// + /// A to the file or directory for which to obtain last write date and time information. + /// + /// + /// A structure set to the last write date and time for the specified file or + /// directory. This value is expressed in UTC time. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static DateTime GetLastWriteTimeUtc(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return FileSystem.GetLastWriteTime(fileHandle).UtcDateTime; + } + public static FileAttributes GetAttributes(string path) => FileSystem.GetAttributes(Path.GetFullPath(path)); + /// + /// Gets the specified of the file or directory associated to + /// + /// + /// A to the file or directory for which the attributes are to be retrieved. + /// + /// + /// The of the file or directory. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + public static FileAttributes GetAttributes(SafeFileHandle fileHandle) + { + ArgumentNullException.ThrowIfNull(fileHandle); + return FileSystem.GetAttributes(fileHandle); + } + public static void SetAttributes(string path, FileAttributes fileAttributes) => FileSystem.SetAttributes(Path.GetFullPath(path), fileAttributes); + /// + /// Sets the specified of the file or directory associated to . + /// + /// + /// A to the file or directory for which should be set. + /// + /// + /// A bitwise combination of the enumeration values. + /// + /// + /// is . + /// + /// + /// The caller does not have the required permission. + /// + /// + /// It is not possible to change the compression status of a object + /// using the method. + /// + public static void SetAttributes(SafeFileHandle fileHandle, FileAttributes fileAttributes) + { + ArgumentNullException.ThrowIfNull(fileHandle); + FileSystem.SetAttributes(fileHandle, fileAttributes); + } + /// Gets the of the file on the path. /// The path to the file. /// The of the file on the path. 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 5535ea1063f40..ea01090fc5d06 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,13 +2,24 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; +using System.Diagnostics; +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) { + // Either `handle` or `path` must not be null + Debug.Assert(handle is not null || path is not null); + // 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 // setattrlist()", so we fall back to the method used on other unix @@ -19,7 +30,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 +38,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,8 +46,9 @@ internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory } } - private static unsafe Interop.Error SetCreationTimeCore(string path, long seconds, long nanoseconds) + private static unsafe Interop.Error SetCreationTimeCore(SafeFileHandle? handle, string? path, long seconds, long nanoseconds) { + Debug.Assert(handle is not null || path is not null); Interop.Sys.TimeSpec timeSpec = default; timeSpec.TvSec = seconds; @@ -46,15 +58,16 @@ private static unsafe Interop.Error SetCreationTimeCore(string path, long second attrList.bitmapCount = Interop.libc.AttrList.ATTR_BIT_MAP_COUNT; attrList.commonAttr = Interop.libc.AttrList.ATTR_CMN_CRTIME; - Interop.Error error = - Interop.libc.setattrlist(path, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), new CULong(Interop.libc.FSOPT_NOFOLLOW)) == 0 ? + int result = handle is not null + ? Interop.libc.fsetattrlist(handle, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), new CULong(Interop.libc.FSOPT_NOFOLLOW)) + : Interop.libc.setattrlist(path!, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), new CULong(Interop.libc.FSOPT_NOFOLLOW)); + + return result == 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 7f7ba95dc3964..f30b3ead243f9 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 static Interop.Error SetCreationTimeCore(string path, long seconds, long nanoseconds) => + private static 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 0621f3f82cf6e..5474baac8a29f 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 @@ -184,8 +184,15 @@ 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) + => GetAttributes(handle, handle.Path, Path.GetFileName(handle.Path), continueOnError); + + private FileAttributes GetAttributes(SafeFileHandle? handle, ReadOnlySpan path, ReadOnlySpan fileName, bool continueOnError = false) { - EnsureCachesInitialized(path, continueOnError); + Debug.Assert(handle is not null || path.Length > 0); + EnsureCachesInitialized(handle, path, continueOnError); if (!EntryExists) return (FileAttributes)(-1); @@ -208,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, handle.Path, 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. @@ -224,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) + bool hidden = (attributes & FileAttributes.Hidden) != 0; + if (hidden ^ HasHiddenFlag) { - // 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) - { - // 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); } } @@ -261,7 +273,9 @@ internal void SetAttributes(string path, FileAttributes attributes, bool asDirec // Change the permissions on the file if (newMode != oldMode) { - 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(); @@ -274,8 +288,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, handle.Path, 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)); @@ -292,8 +312,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)); @@ -302,11 +328,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, handle.Path, 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)); @@ -315,14 +353,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, handle.Path, time, asDirectory); + + internal void SetLastWriteTime(SafeFileHandle? handle, string? path, DateTimeOffset time, bool asDirectory) + => SetAccessOrWriteTime(handle, path, time, isAccessTime: false, asDirectory); private static 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: @@ -338,7 +382,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); @@ -370,7 +414,10 @@ 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 @@ -387,7 +434,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); 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 24e00768edf93..dd28e27822605 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 @@ -600,9 +600,15 @@ 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 UnixFileMode GetUnixFileMode(string fullPath) { UnixFileMode mode = default(FileStatus).GetUnixFileMode(fullPath); @@ -625,21 +631,39 @@ public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) 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 73362512175f3..6a4286f301fb5 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 { @@ -107,25 +108,17 @@ 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); + public static FileAttributes GetAttributes(string fullPath) => + (FileAttributes)GetAttributeData(fullPath, returnErrorOnNotFound: true).dwFileAttributes; - return (FileAttributes)data.dwFileAttributes; - } + public static FileAttributes GetAttributes(SafeFileHandle fileHandle) => + (FileAttributes)GetAttributeData(fileHandle).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); + public static DateTimeOffset GetCreationTime(string fullPath) => + GetAttributeData(fullPath).ftCreationTime.ToDateTimeOffset(); - return data.ftCreationTime.ToDateTimeOffset(); - } + public static DateTimeOffset GetCreationTime(SafeFileHandle fileHandle) => + GetAttributeData(fileHandle).ftCreationTime.ToDateTimeOffset(); public static FileSystemInfo GetFileSystemInfo(string fullPath, bool asDirectory) { @@ -134,24 +127,57 @@ public static FileSystemInfo GetFileSystemInfo(string fullPath, bool asDirectory (FileSystemInfo)new FileInfo(fullPath, null); } - public static DateTimeOffset GetLastAccessTime(string fullPath) + public static DateTimeOffset GetLastAccessTime(string fullPath) => + GetAttributeData(fullPath).ftLastAccessTime.ToDateTimeOffset(); + + public static DateTimeOffset GetLastAccessTime(SafeFileHandle fileHandle) => + GetAttributeData(fileHandle).ftLastAccessTime.ToDateTimeOffset(); + + public static DateTimeOffset GetLastWriteTime(string fullPath) => + GetAttributeData(fullPath).ftLastWriteTime.ToDateTimeOffset(); + + public static DateTimeOffset GetLastWriteTime(SafeFileHandle fileHandle) => + GetAttributeData(fileHandle).ftLastWriteTime.ToDateTimeOffset(); + + internal static Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA GetAttributeData(string fullPath, bool returnErrorOnNotFound = false) { 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 data.ftLastAccessTime.ToDateTimeOffset(); + int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound); + return errorCode != Interop.Errors.ERROR_SUCCESS + ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath) + : data; } - public static DateTimeOffset GetLastWriteTime(string fullPath) + internal static Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA GetAttributeData(SafeFileHandle fileHandle) { 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(fileHandle, ref data); + return errorCode != Interop.Errors.ERROR_SUCCESS + ? throw Win32Marshal.GetExceptionForWin32Error(errorCode, fileHandle.Path) + : data; + } - return data.ftLastWriteTime.ToDateTimeOffset(); + private 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(); + } + + PopulateAttributeData(ref data, fileInformationData); + return Interop.Errors.ERROR_SUCCESS; + } + + private static void PopulateAttributeData(ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, in Interop.Kernel32.BY_HANDLE_FILE_INFORMATION fileInformationData) + { + data.dwFileAttributes = (int)fileInformationData.dwFileAttributes; + data.ftCreationTime = fileInformationData.ftCreationTime; + data.ftLastAccessTime = fileInformationData.ftLastAccessTime; + data.ftLastWriteTime = fileInformationData.ftLastWriteTime; + data.nFileSizeHigh = fileInformationData.nFileSizeHigh; + data.nFileSizeLow = fileInformationData.nFileSizeLow; } private static void MoveDirectory(string sourceFullPath, string destFullPath, bool isCaseSensitiveRename) @@ -412,17 +438,36 @@ 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 + }; + + if (!Interop.Kernel32.SetFileInformationByHandle( + fileHandle, + Interop.Kernel32.FileBasicInfo, + &basicInfo, + (uint)sizeof(Interop.Kernel32.FILE_BASIC_INFO))) + { + 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, @@ -431,33 +476,52 @@ 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, fullPath, creationTime, lastAccessTime, lastWriteTime, changeTime, fileAttributes); + } + + private static unsafe void SetFileTime( + SafeFileHandle fileHandle, + string? fullPath = null, + 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(fullPath ?? fileHandle.Path); } } public static void SetCreationTime(string fullPath, DateTimeOffset time, bool asDirectory) => SetFileTime(fullPath, asDirectory, creationTime: time.ToFileTime()); + public static void SetCreationTime(SafeFileHandle fileHandle, DateTimeOffset time) + => SetFileTime(fileHandle, creationTime: 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.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs index a1113a1fd10e8..30242825dbf8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs @@ -75,7 +75,12 @@ public void Refresh() _fileStatus.RefreshCaches(FullPath); } - internal static void ThrowNotFound(string path) + internal static void ThrowNotFound(ReadOnlySpan path) + { + ThrowNotFound(path.Length == 0 ? default : path.ToString()); + } + + internal static void ThrowNotFound(string? path) { // Windows distinguishes between whether the directory or the file isn't found, // and throws a different exception in these cases. We attempt to approximate that @@ -84,8 +89,7 @@ internal static void ThrowNotFound(string path) // worst case in such a race condition (which could occur if the file system is // being manipulated concurrently with these checks) is that we throw a // FileNotFoundException instead of DirectoryNotFoundException. - - bool directoryError = !Directory.Exists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path))); + bool directoryError = path is not null && !FileSystem.DirectoryExists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path.AsSpan()))); throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), path, directoryError); } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index b88ace77e31e0..9ba025211e800 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9512,12 +9512,19 @@ public static void Delete(string path) { } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] public static void Encrypt(string path) { } public static bool Exists([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? path) { throw null; } + public static System.IO.FileAttributes GetAttributes(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.IO.FileAttributes GetAttributes(string path) { throw null; } + public static System.DateTime GetCreationTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.DateTime GetCreationTime(string path) { throw null; } + public static System.DateTime GetCreationTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.DateTime GetCreationTimeUtc(string path) { throw null; } + public static System.DateTime GetLastAccessTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.DateTime GetLastAccessTime(string path) { throw null; } + public static System.DateTime GetLastAccessTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.DateTime GetLastAccessTimeUtc(string path) { throw null; } + public static System.DateTime GetLastWriteTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.DateTime GetLastWriteTime(string path) { throw null; } + public static System.DateTime GetLastWriteTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } public static System.DateTime GetLastWriteTimeUtc(string path) { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.IO.UnixFileMode GetUnixFileMode(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } @@ -9550,12 +9557,19 @@ public static void Move(string sourceFileName, string destFileName, bool overwri public static void Replace(string sourceFileName, string destinationFileName, string? destinationBackupFileName) { } public static void Replace(string sourceFileName, string destinationFileName, string? destinationBackupFileName, bool ignoreMetadataErrors) { } public static System.IO.FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget) { throw null; } + public static void SetAttributes(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.IO.FileAttributes fileAttributes) { } public static void SetAttributes(string path, System.IO.FileAttributes fileAttributes) { } + public static void SetCreationTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.DateTime creationTime) { } public static void SetCreationTime(string path, System.DateTime creationTime) { } + public static void SetCreationTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.DateTime creationTimeUtc) { } public static void SetCreationTimeUtc(string path, System.DateTime creationTimeUtc) { } + public static void SetLastAccessTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.DateTime lastAccessTime) { } public static void SetLastAccessTime(string path, System.DateTime lastAccessTime) { } + public static void SetLastAccessTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.DateTime lastAccessTimeUtc) { } public static void SetLastAccessTimeUtc(string path, System.DateTime lastAccessTimeUtc) { } + public static void SetLastWriteTime(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.DateTime lastWriteTime) { } public static void SetLastWriteTime(string path, System.DateTime lastWriteTime) { } + public static void SetLastWriteTimeUtc(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.DateTime lastWriteTimeUtc) { } public static void SetLastWriteTimeUtc(string path, System.DateTime lastWriteTimeUtc) { } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static void SetUnixFileMode(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.IO.UnixFileMode mode) { } diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index a71f20515dfd3..a6e406b4a54d2 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -112,6 +112,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_LockFileRegion) DllImportEntry(SystemNative_LChflags) DllImportEntry(SystemNative_LChflagsCanSetHiddenFlag) + DllImportEntry(SystemNative_FChflags) DllImportEntry(SystemNative_CanGetHiddenFlag) DllImportEntry(SystemNative_ReadProcessStatusInfo) DllImportEntry(SystemNative_Log) @@ -242,6 +243,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_io.c b/src/native/libs/System.Native/pal_io.c index a721025fe688f..b31163a01e80b 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -1672,6 +1672,19 @@ int32_t SystemNative_LChflags(const char* path, uint32_t flags) #endif } +int32_t SystemNative_FChflags(intptr_t fd, uint32_t flags) +{ +#if HAVE_LCHFLAGS + int32_t result; + while ((result = fchflags(ToFileDescriptor(fd), flags)) < 0 && errno == EINTR); + return result; +#else + (void)fd, (void)flags; + errno = ENOTSUP; + return -1; +#endif +} + int32_t SystemNative_LChflagsCanSetHiddenFlag(void) { #if HAVE_LCHFLAGS diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 30dc534635c4f..ec3049f5b78ec 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -758,6 +758,13 @@ PALEXPORT int32_t SystemNative_LockFileRegion(intptr_t fd, int64_t offset, int64 */ PALEXPORT int32_t SystemNative_LChflags(const char* path, uint32_t flags); +/** +* Changes the file flags of the file "fd". +* +* Returns 0 for success, -1 for failure. Sets errno for failure. +*/ +PALEXPORT int32_t SystemNative_FChflags(intptr_t fd, uint32_t flags); + /** * Determines if the current platform supports setting UF_HIDDEN (0x8000) flag * diff --git a/src/native/libs/System.Native/pal_time.c b/src/native/libs/System.Native/pal_time.c index 4f9b5326cb73c..cffe378a83cba 100644 --- a/src/native/libs/System.Native/pal_time.c +++ b/src/native/libs/System.Native/pal_time.c @@ -53,6 +53,33 @@ int32_t SystemNative_UTimensat(const char* path, TimeSpec* times) return result; } +int32_t SystemNative_FUTimens(intptr_t fd, TimeSpec* times) +{ + int32_t result; + +#if HAVE_FUTIMENS + 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))); +#else + // Fallback on unsupported platforms (e.g. iOS, tvOS, watchOS) + // to futimes (lower precision) + struct timeval updatedTimes[2]; + updatedTimes[0].tv_sec = (long)times[0].tv_sec; + updatedTimes[0].tv_usec = (int)times[0].tv_nsec / 1000; + updatedTimes[1].tv_sec = (long)times[1].tv_sec; + updatedTimes[1].tv_usec = (int)times[1].tv_nsec / 1000; + + while (CheckInterrupted(result = futimes(ToFileDescriptor(fd), updatedTimes))); +#endif + + 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 12fe500133418..c7ef77da1e5bd 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. */