From b2eec67532a30c4d78a08c856402a433b705b55e Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 1 Feb 2022 09:36:31 +0100 Subject: [PATCH] Move SafeHandle implementation to FileStatus. --- .../src/Resources/Strings.resx | 3 + .../src/System/IO/FileStatus.SetTimes.OSX.cs | 22 +- .../IO/FileStatus.SetTimes.OtherUnix.cs | 10 +- .../src/System/IO/FileStatus.Unix.cs | 136 ++++++++--- .../src/System/IO/FileSystem.Unix.cs | 223 +----------------- .../src/System/ThrowHelper.cs | 6 - .../System.Runtime/ref/System.Runtime.cs | 14 ++ src/native/libs/System.Native/entrypoints.c | 1 + src/native/libs/System.Native/pal_time.c | 15 ++ src/native/libs/System.Native/pal_time.h | 7 + 10 files changed, 178 insertions(+), 259 deletions(-) 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/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 f7dcf9210a941d..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 @@ -554,256 +554,49 @@ public static FileAttributes GetAttributes(string fullPath) } public static FileAttributes GetAttributes(SafeFileHandle fileHandle) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus); - if (result != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } - - FileAttributes attributes = default; - - if (IsModeReadOnly(fileStatus)) - attributes |= FileAttributes.ReadOnly; - - if ((fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK) - attributes |= FileAttributes.ReparsePoint; - - if ((fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR) - attributes |= FileAttributes.Directory; - - if (fileHandle.Path != null && IsNameHidden(fileHandle.Path) || (fileStatus.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == (uint)Interop.Sys.UserFlags.UF_HIDDEN) - attributes |= FileAttributes.Hidden; - - return attributes != default ? attributes : FileAttributes.Normal; - } - - private static bool IsNameHidden(ReadOnlySpan fileName) => fileName.Length > 0 && fileName[0] == '.'; - - private static bool IsModeReadOnly(Interop.Sys.FileStatus status) - { - var mode = (Interop.Sys.Permissions)(status.Mode & (int)Interop.Sys.Permissions.Mask); - - bool isUserReadOnly = (mode & Interop.Sys.Permissions.S_IRUSR) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWUSR) == 0; // but not write permission - bool isGroupReadOnly = (mode & Interop.Sys.Permissions.S_IRGRP) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWGRP) == 0; // but not write permission - bool isOtherReadOnly = (mode & Interop.Sys.Permissions.S_IROTH) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWOTH) == 0; // but not write permission - - // If they are all the same, no need to check user/group. - if ((isUserReadOnly == isGroupReadOnly) && (isGroupReadOnly == isOtherReadOnly)) - { - return isUserReadOnly; - } - - if (status.Uid == Interop.Sys.GetEUid()) - { - // User owns the file. - return isUserReadOnly; - } - - // System files often have the same permissions for group and other (umask 022). - if (isGroupReadOnly == isUserReadOnly) - { - return isGroupReadOnly; - } - - if (Interop.Sys.IsMemberOfGroup(status.Gid)) - { - // User belongs to group that owns the file. - return isGroupReadOnly; - } - else - { - // Other permissions. - return isOtherReadOnly; - } - } + => 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) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus output); - if (result != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } - - // Validate that only flags from the attribute are being provided. This is an - // approximation for the validation done by the Win32 function. - const FileAttributes allValidFlags = - FileAttributes.Archive | FileAttributes.Compressed | FileAttributes.Device | - FileAttributes.Directory | FileAttributes.Encrypted | FileAttributes.Hidden | - FileAttributes.IntegrityStream | FileAttributes.Normal | FileAttributes.NoScrubData | - FileAttributes.NotContentIndexed | FileAttributes.Offline | FileAttributes.ReadOnly | - FileAttributes.ReparsePoint | FileAttributes.SparseFile | FileAttributes.System | - FileAttributes.Temporary; - if ((attributes & ~allValidFlags) != 0) - { - // Using constant string for argument to match historical throw - throw new ArgumentException(SR.Arg_InvalidFileAttrs, "Attributes"); - } - - if (Interop.Sys.CanSetHiddenFlag) - { - if ((attributes & FileAttributes.Hidden) != 0 && (output.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == 0) - { - Interop.Sys.FChflags(fileHandle, (output.UserFlags | (uint)Interop.Sys.UserFlags.UF_HIDDEN)); - } - } - - // The only thing we can reasonably change is whether the file object is readonly by changing permissions. - - int newMode = output.Mode; - if ((attributes & FileAttributes.ReadOnly) != 0) - { - // Take away all write permissions from user/group/everyone - newMode &= ~(int)(Interop.Sys.Permissions.S_IWUSR | Interop.Sys.Permissions.S_IWGRP | Interop.Sys.Permissions.S_IWOTH); - } - else if ((newMode & (int)Interop.Sys.Permissions.S_IRUSR) != 0) - { - // Give write permission to the owner if the owner has read permission - newMode |= (int)Interop.Sys.Permissions.S_IWUSR; - } - - // Change the permissions on the file - if (newMode != output.Mode) - { - int chModResult = Interop.Sys.FChMod(fileHandle, newMode); - if (chModResult != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(chModResult), fileHandle.Path); - } - } - } + => default(FileStatus).SetAttributes(fileHandle, attributes, asDirectory: false); public static DateTimeOffset GetCreationTime(string fullPath) => default(FileStatus).GetCreationTime(fullPath).UtcDateTime; public static DateTimeOffset GetCreationTime(SafeFileHandle fileHandle) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus output); - if (result == 0) - { - return DateTimeOffset.FromUnixTimeSeconds(output.CTime); - } - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } + => 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) - { - ThrowHelper.ThrowForMissingPath_SafeFileHandle(fileHandle.Path); - SetCreationTime(fileHandle.Path!, time, false); - } + => default(FileStatus).SetCreationTime(fileHandle, time, asDirectory: false); public static DateTimeOffset GetLastAccessTime(string fullPath) => default(FileStatus).GetLastAccessTime(fullPath).UtcDateTime; public static DateTimeOffset GetLastAccessTime(SafeFileHandle fileHandle) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus output); - if (result == 0) - { - return DateTimeOffset.FromUnixTimeSeconds(output.ATime); - } - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } + => 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) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus); - if (result != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } - - // we use futimens() to set the accessTime and writeTime - Interop.Sys.TimeSpec* buf = stackalloc Interop.Sys.TimeSpec[2]; - - long seconds = time.ToUnixTimeSeconds(); - long nanoseconds = UnixTimeSecondsToNanoseconds(time, seconds); - -#if TARGET_BROWSER - buf[0].TvSec = seconds; - buf[0].TvNsec = nanoseconds; - buf[1].TvSec = seconds; - buf[1].TvNsec = nanoseconds; -#else - buf[0].TvSec = seconds; - buf[0].TvNsec = nanoseconds; - buf[1].TvSec = fileStatus.MTime; - buf[1].TvNsec = fileStatus.MTimeNsec; -#endif - int timensResult = Interop.Sys.FUTimens(fileHandle, buf); - if (timensResult != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } - } - private static long UnixTimeSecondsToNanoseconds(DateTimeOffset time, long seconds) - { - const long TicksPerMillisecond = 10000; - const long TicksPerSecond = TicksPerMillisecond * 1000; - return (time.UtcDateTime.Ticks - DateTimeOffset.UnixEpoch.Ticks - seconds * TicksPerSecond) * 100; - } + => default(FileStatus).SetLastAccessTime(fileHandle, time, asDirectory: false); public static DateTimeOffset GetLastWriteTime(string fullPath) => default(FileStatus).GetLastWriteTime(fullPath).UtcDateTime; public static DateTimeOffset GetLastWriteTime(SafeFileHandle fileHandle) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus output); - if (result == 0) - { - return DateTimeOffset.FromUnixTimeSeconds(output.MTime); - } - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } + => 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) - { - int result = Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus); - if (result != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } - - // we use futimens() to set the accessTime and writeTime - Interop.Sys.TimeSpec* buf = stackalloc Interop.Sys.TimeSpec[2]; - - long seconds = time.ToUnixTimeSeconds(); - long nanoseconds = UnixTimeSecondsToNanoseconds(time, seconds); - -#if TARGET_BROWSER - buf[0].TvSec = seconds; - buf[0].TvNsec = nanoseconds; - buf[1].TvSec = seconds; - buf[1].TvNsec = nanoseconds; -#else - buf[0].TvSec = fileStatus.ATime; - buf[0].TvNsec = fileStatus.ATimeNsec; - buf[1].TvSec = seconds; - buf[1].TvNsec = nanoseconds; -#endif - - int timensResult = Interop.Sys.FUTimens(fileHandle, buf); - if (timensResult != 0) - { - throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(result), fileHandle.Path); - } - } + => default(FileStatus).SetLastWriteTime(fileHandle, time, asDirectory: false); public static string[] GetLogicalDrives() { diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index f39cca86fd96f8..781cd38aaadb30 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -530,12 +530,6 @@ internal static void ArgumentOutOfRangeException_Enum_Value() throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_Enum); } - internal static void ThrowForMissingPath_SafeFileHandle(string? path) - { - if (path is not null) return; - throw new IOException(SR.IO_SafeFileHandlePathNull); - } - private static Exception GetArraySegmentCtorValidationFailedException(Array? array, int offset, int count) { if (array == null) 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. */