diff --git a/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs index a283b01a0b7e94..a8b3619fb51cf7 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs @@ -56,15 +56,18 @@ public void SafeFileHandle_IsAsync_ReturnsCorrectInformation(FileOptions options } } - // Unix doesn't directly support DeleteOnClose - // For FileStream created out of path, we mimic it by closing the handle first - // and then unlinking the path - // Since SafeFileHandle does not always have the path and we can't find path for given file descriptor on Unix - // this test runs only on Windows - [PlatformSpecific(TestPlatforms.Windows)] [Theory] [InlineData(FileOptions.DeleteOnClose)] [InlineData(FileOptions.DeleteOnClose | FileOptions.Asynchronous)] - public override void DeleteOnClose_FileDeletedAfterClose(FileOptions options) => base.DeleteOnClose_FileDeletedAfterClose(options); + public void DeleteOnClose_FileDeletedAfterSafeHandleDispose(FileOptions options) + { + string path = GetTestFilePath(); + Assert.False(File.Exists(path)); + using (SafeFileHandle sfh = File.OpenHandle(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, options)) + { + Assert.True(File.Exists(path)); + } + Assert.False(File.Exists(path)); + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs index cfe0c62d6c30f1..13cacf29bf6526 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Tests; +using Microsoft.Win32.SafeHandles; using Xunit; namespace System.IO.Tests @@ -35,14 +36,29 @@ public void NameNormalizesPath() } [Fact] - public void NameReturnsUnknownForHandle() + public void ConstructFileStreamFromHandle_NameMatchesOriginal() { - using (new ThreadCultureChange(CultureInfo.InvariantCulture)) - using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite)) - using (FileStream fsh = new FileStream(fs.SafeFileHandle, FileAccess.ReadWrite)) - { - Assert.Equal("[Unknown]", fsh.Name); - } + string path = GetTestFilePath(); + using var _ = new ThreadCultureChange(CultureInfo.InvariantCulture); + + using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite); + Assert.Equal(path, fs.Name); + + using FileStream fsh = new FileStream(fs.SafeFileHandle, FileAccess.ReadWrite); + Assert.Equal(path, fsh.Name); + } + + [Fact] + public void ConstructFileStreamFromHandleClone_NameReturnsUnknown() + { + string path = GetTestFilePath(); + using var _ = new ThreadCultureChange(CultureInfo.InvariantCulture); + + using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite); + Assert.Equal(path, fs.Name); + + using FileStream fsh = new FileStream(new SafeFileHandle(fs.SafeFileHandle.DangerousGetHandle(), ownsHandle: false), FileAccess.ReadWrite); + Assert.Equal("[Unknown]", fsh.Name); } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs index 61efd825f6e48e..c35e5f37874038 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs @@ -1,6 +1,7 @@ // 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 @@ -79,7 +80,7 @@ public void ValidFileOptions_Encrypted(FileOptions option) [Theory] [InlineData(FileOptions.DeleteOnClose)] [InlineData(FileOptions.DeleteOnClose | FileOptions.Asynchronous)] - public virtual void DeleteOnClose_FileDeletedAfterClose(FileOptions options) + public void DeleteOnClose_FileDeletedAfterClose(FileOptions options) { string path = GetTestFilePath(); Assert.False(File.Exists(path)); @@ -89,5 +90,37 @@ public virtual void DeleteOnClose_FileDeletedAfterClose(FileOptions options) } Assert.False(File.Exists(path)); } + + [Theory] + [InlineData(FileOptions.DeleteOnClose)] + [InlineData(FileOptions.DeleteOnClose | FileOptions.Asynchronous)] + public void DeleteOnClose_FileDeletedAfterSafeHandleRelease(FileOptions options) + { + string path = GetTestFilePath(); + Assert.False(File.Exists(path)); + + using (FileStream fs = CreateFileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 0x1000, options)) + { + Assert.True(File.Exists(path)); + + bool added = false; + try + { + fs.SafeFileHandle.DangerousAddRef(ref added); + + fs.Dispose(); + Assert.True(File.Exists(path)); + } + finally + { + if (added) + { + fs.SafeFileHandle.DangerousRelease(); + } + + Assert.False(File.Exists(path)); + } + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs index 9666c4ef1e5947..cd4e18e7486dbe 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs @@ -44,7 +44,7 @@ internal sealed unsafe class OverlappedValueTaskSource : IValueTaskSource, internal static readonly IOCompletionCallback s_ioCallback = IOCallback; internal readonly PreAllocatedOverlapped _preallocatedOverlapped; - private readonly SafeFileHandle _fileHandle; + internal readonly SafeFileHandle _fileHandle; internal MemoryHandle _memoryHandle; internal ManualResetValueTaskSourceCore _source; // mutable struct; do not make this readonly private NativeOverlapped* _overlapped; diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index a7102ac1d3b7c3..c510d4d9b4a65f 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -12,6 +12,7 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid { // not using bool? as it's not thread safe private volatile NullableBool _canSeek = NullableBool.Undefined; + private bool _deleteOnClose; public SafeFileHandle() : this(ownsHandle: true) { @@ -36,6 +37,7 @@ private static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int { Debug.Assert(path != null); SafeFileHandle handle = Interop.Sys.Open(path, flags, mode); + handle._path = path; if (handle.IsInvalid) { @@ -51,7 +53,7 @@ private static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int bool isDirectory = (error.Error == Interop.Error.ENOENT) && ((flags & Interop.Sys.OpenFlags.O_CREAT) != 0 - || !DirectoryExists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path!))!)); + || !DirectoryExists(System.IO.Path.GetDirectoryName(System.IO.Path.TrimEndingDirectorySeparator(path!))!)); Interop.CheckIo( error.Error, @@ -117,6 +119,17 @@ protected override bool ReleaseHandle() // problem trying to unlock it.) Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors + // If DeleteOnClose was requested when constructed, delete the file now. + // (Unix doesn't directly support DeleteOnClose, so we mimic it here.) + if (_deleteOnClose) + { + // Since we still have the file open, this will end up deleting + // it (assuming we're the only link to it) once it's closed, but the + // name will be removed immediately. + Debug.Assert(_path is not null); + Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist + } + // Close the descriptor. Although close is documented to potentially fail with EINTR, we never want // to retry, as the descriptor could actually have been closed, been subsequently reassigned, and // be in use elsewhere in the process. Instead, we simply check whether the call was successful. @@ -234,6 +247,7 @@ private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mo private void Init(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) { IsAsync = (options & FileOptions.Asynchronous) != 0; + _deleteOnClose = (options & FileOptions.DeleteOnClose) != 0; // Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive // lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory, diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs index a9265207f691f2..651b49be0226d4 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs @@ -103,6 +103,7 @@ private static unsafe SafeFileHandle CreateFile(string fullPath, FileMode mode, throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } + fileHandle._path = fullPath; fileHandle._fileOptions = options; return fileHandle; } diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs index 6f15fb45ee9385..4745be0fa01553 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs @@ -7,9 +7,13 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid { + private string? _path; + public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) { SetHandle(preexistingHandle); } + + internal string? Path => _path; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs index 8bf4539c46f5d5..bc3a04f2f5d971 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs @@ -17,10 +17,10 @@ public static partial class RandomAccess // that get stackalloced in the Linux kernel. private const int IovStackThreshold = 8; - internal static long GetFileLength(SafeFileHandle handle, string? path) + internal static long GetFileLength(SafeFileHandle handle) { int result = Interop.Sys.FStat(handle, out Interop.Sys.FileStatus status); - FileStreamHelpers.CheckFileCall(result, path); + FileStreamHelpers.CheckFileCall(result, handle.Path); return status.Size; } @@ -29,7 +29,7 @@ internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span buffer fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer)) { int result = Interop.Sys.PRead(handle, bufPtr, buffer.Length, fileOffset); - FileStreamHelpers.CheckFileCall(result, path: null); + FileStreamHelpers.CheckFileCall(result, handle.Path); return result; } } @@ -64,7 +64,7 @@ internal static unsafe long ReadScatterAtOffset(SafeFileHandle handle, IReadOnly } } - return FileStreamHelpers.CheckFileCall(result, path: null); + return FileStreamHelpers.CheckFileCall(result, handle.Path); } private static ValueTask ReadAtOffsetAsync(SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) @@ -79,7 +79,7 @@ internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs index 139f929f1fcf46..20dcbdeac8e77f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs @@ -16,23 +16,23 @@ public static partial class RandomAccess { private static readonly IOCompletionCallback s_callback = AllocateCallback(); - internal static unsafe long GetFileLength(SafeFileHandle handle, string? path) + internal static unsafe long GetFileLength(SafeFileHandle handle) { Interop.Kernel32.FILE_STANDARD_INFO info; if (!Interop.Kernel32.GetFileInformationByHandleEx(handle, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO))) { - throw Win32Marshal.GetExceptionForLastWin32Error(path); + throw Win32Marshal.GetExceptionForLastWin32Error(handle.Path); } return info.EndOfFile; } - internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span buffer, long fileOffset, string? path = null) + internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span buffer, long fileOffset) { if (handle.IsAsync) { - return ReadSyncUsingAsyncHandle(handle, buffer, fileOffset, path); + return ReadSyncUsingAsyncHandle(handle, buffer, fileOffset); } NativeOverlapped overlapped = GetNativeOverlappedForSyncHandle(handle, fileOffset); @@ -55,12 +55,12 @@ internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span buffer // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. return 0; default: - throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path); } } } - private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span buffer, long fileOffset, string? path) + private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span buffer, long fileOffset) { handle.EnsureThreadPoolBindingInitialized(); @@ -105,7 +105,7 @@ private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span buffer, long fileOffset, string? path = null) + internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { if (handle.IsAsync) { - return WriteSyncUsingAsyncHandle(handle, buffer, fileOffset, path); + return WriteSyncUsingAsyncHandle(handle, buffer, fileOffset); } NativeOverlapped overlapped = GetNativeOverlappedForSyncHandle(handle, fileOffset); @@ -141,12 +141,12 @@ internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset, string? path) + private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { handle.EnsureThreadPoolBindingInitialized(); @@ -193,7 +193,7 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO throw new IOException(SR.IO_FileTooLong); default: - throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path); } } } @@ -469,7 +469,7 @@ private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, default: // Error. Callback will not be called. vts.Dispose(); - return ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + return ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path)); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs index 2426fb3807a2e8..4d768cad53edae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs @@ -23,7 +23,7 @@ public static long GetLength(SafeFileHandle handle) { ValidateInput(handle, fileOffset: 0); - return GetFileLength(handle, path: null); + return GetFileLength(handle); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs index 7ff0e00919109e..9329c0fa60b13c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs @@ -95,7 +95,7 @@ private Exception HandleIOError(long positionBefore, int errorCode) _filePosition = positionBefore; } - return SafeFileHandle.OverlappedValueTaskSource.GetIOError(errorCode, _path); + return SafeFileHandle.OverlappedValueTaskSource.GetIOError(errorCode, _fileHandle.Path); } public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; // no buffering = nothing to flush @@ -131,7 +131,7 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc try { await FileStreamHelpers - .AsyncModeCopyToAsync(_fileHandle, _path, CanSeek, _filePosition, destination, bufferSize, cancellationToken) + .AsyncModeCopyToAsync(_fileHandle, CanSeek, _filePosition, destination, bufferSize, cancellationToken) .ConfigureAwait(false); } finally diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index 087c51f6f2dc07..17c03b7f931863 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -60,15 +60,15 @@ private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, internal static FileStreamStrategy EnableBufferingIfNeeded(WindowsFileStreamStrategy strategy, int bufferSize) => bufferSize > 1 ? new BufferedFileStreamStrategy(strategy, bufferSize) : strategy; - internal static void FlushToDisk(SafeFileHandle handle, string? path) + internal static void FlushToDisk(SafeFileHandle handle) { if (!Interop.Kernel32.FlushFileBuffers(handle)) { - throw Win32Marshal.GetExceptionForLastWin32Error(path); + throw Win32Marshal.GetExceptionForLastWin32Error(handle.Path); } } - internal static long Seek(SafeFileHandle handle, string? path, long offset, SeekOrigin origin, bool closeInvalidHandle = false) + internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin, bool closeInvalidHandle = false) { Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin >= SeekOrigin.Begin && origin <= SeekOrigin.End"); @@ -76,11 +76,11 @@ internal static long Seek(SafeFileHandle handle, string? path, long offset, Seek { if (closeInvalidHandle) { - throw Win32Marshal.GetExceptionForWin32Error(GetLastWin32ErrorAndDisposeHandleIfInvalid(handle), path); + throw Win32Marshal.GetExceptionForWin32Error(GetLastWin32ErrorAndDisposeHandleIfInvalid(handle), handle.Path); } else { - throw Win32Marshal.GetExceptionForLastWin32Error(path); + throw Win32Marshal.GetExceptionForLastWin32Error(handle.Path); } } @@ -116,7 +116,7 @@ internal static int GetLastWin32ErrorAndDisposeHandleIfInvalid(SafeFileHandle ha return errorCode; } - internal static void Lock(SafeFileHandle handle, string? path, long position, long length) + internal static void Lock(SafeFileHandle handle, long position, long length) { int positionLow = unchecked((int)(position)); int positionHigh = unchecked((int)(position >> 32)); @@ -125,11 +125,11 @@ internal static void Lock(SafeFileHandle handle, string? path, long position, lo if (!Interop.Kernel32.LockFile(handle, positionLow, positionHigh, lengthLow, lengthHigh)) { - throw Win32Marshal.GetExceptionForLastWin32Error(path); + throw Win32Marshal.GetExceptionForLastWin32Error(handle.Path); } } - internal static void Unlock(SafeFileHandle handle, string? path, long position, long length) + internal static void Unlock(SafeFileHandle handle, long position, long length) { int positionLow = unchecked((int)(position)); int positionHigh = unchecked((int)(position >> 32)); @@ -138,7 +138,7 @@ internal static void Unlock(SafeFileHandle handle, string? path, long position, if (!Interop.Kernel32.UnlockFile(handle, positionLow, positionHigh, lengthLow, lengthHigh)) { - throw Win32Marshal.GetExceptionForLastWin32Error(path); + throw Win32Marshal.GetExceptionForLastWin32Error(handle.Path); } } @@ -168,7 +168,7 @@ internal static void ValidateFileTypeForNonExtendedPaths(SafeFileHandle handle, } } - internal static unsafe void SetFileLength(SafeFileHandle handle, string? path, long length) + internal static unsafe void SetFileLength(SafeFileHandle handle, long length) { var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO { @@ -184,7 +184,7 @@ internal static unsafe void SetFileLength(SafeFileHandle handle, string? path, l int errorCode = Marshal.GetLastPInvokeError(); if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_FileLengthTooBig); - throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path); } } @@ -215,7 +215,7 @@ internal static unsafe int ReadFileNative(SafeFileHandle handle, Span byte } } - internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? path, bool canSeek, long filePosition, Stream destination, int bufferSize, CancellationToken cancellationToken) + internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, bool canSeek, long filePosition, Stream destination, int bufferSize, CancellationToken cancellationToken) { // For efficiency, we avoid creating a new task and associated state for each asynchronous read. // Instead, we create a single reusable awaitable object that will be triggered when an await completes @@ -310,7 +310,7 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? p break; default: // Everything else is an error (and there won't be a callback). - throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path); } } @@ -327,7 +327,7 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? p case Interop.Errors.ERROR_OPERATION_ABORTED: // canceled throw new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true)); default: // error - throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode, path); + throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode, handle.Path); } // Successful operation. If we got zero bytes, we're done: exit the read/write loop. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs index 5ee047869c9a68..6c225186a3b4b0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs @@ -66,7 +66,7 @@ public override long Length get { // Get the length of the file as reported by the OS - long length = RandomAccess.GetFileLength(_fileHandle, _path); + long length = RandomAccess.GetFileLength(_fileHandle); // But we may have buffered some data to be written that puts our length // beyond what the OS is aware of. Update accordingly. @@ -98,16 +98,6 @@ protected override void Dispose(bool disposing) // e.g. if this stream is wrapping a pipe and the pipe is now broken. } - // If DeleteOnClose was requested when constructed, delete the file now. - // (Unix doesn't directly support DeleteOnClose, so we mimic it here.) - if (_path != null && (_options & FileOptions.DeleteOnClose) != 0) - { - // Since we still have the file open, this will end up deleting - // it (assuming we're the only link to it) once it's closed, but the - // name will be removed immediately. - Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist - } - // Closing the file handle can fail, e.g. due to out of disk space // Throw these errors as exceptions when disposing if (_fileHandle != null && !_fileHandle.IsClosed && disposing) @@ -118,7 +108,7 @@ protected override void Dispose(bool disposing) if (SafeFileHandle.t_lastCloseErrorInfo != null) { - throw Interop.GetExceptionForIoErrno(SafeFileHandle.t_lastCloseErrorInfo.GetValueOrDefault(), _path, isDirectory: false); + throw Interop.GetExceptionForIoErrno(SafeFileHandle.t_lastCloseErrorInfo.GetValueOrDefault(), _fileHandle.Path, isDirectory: false); } } } @@ -166,7 +156,7 @@ private void FlushOSBuffer() // doesn't support synchronization. In such cases there's nothing to flush. break; default: - throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false); + throw Interop.GetExceptionForIoErrno(errorInfo, _fileHandle.Path, isDirectory: false); } } } @@ -614,14 +604,14 @@ private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, Debug.Assert(fileHandle.CanSeek); Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End); - long pos = FileStreamHelpers.CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin), _path); // SeekOrigin values are the same as Interop.libc.SeekWhence values + long pos = FileStreamHelpers.CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin), fileHandle.Path); // SeekOrigin values are the same as Interop.libc.SeekWhence values _filePosition = pos; return pos; } private int CheckFileCall(int result, bool ignoreNotSupported = false) { - FileStreamHelpers.CheckFileCall(result, _path, ignoreNotSupported); + FileStreamHelpers.CheckFileCall(result, _fileHandle?.Path, ignoreNotSupported); return result; } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs index addfebcadaccba..3218baa33e7738 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs @@ -99,7 +99,7 @@ public unsafe override long Length { get { - long len = RandomAccess.GetFileLength(_fileHandle, _path); + long len = RandomAccess.GetFileLength(_fileHandle); // If we're writing near the end of the file, we must include our // internal buffer in our Length calculation. Don't flush because @@ -179,7 +179,7 @@ public override async ValueTask DisposeAsync() } } - private void FlushOSBuffer() => FileStreamHelpers.FlushToDisk(_fileHandle, _path); + private void FlushOSBuffer() => FileStreamHelpers.FlushToDisk(_fileHandle); // Returns a task that flushes the internal write buffer private Task FlushWriteAsync(CancellationToken cancellationToken) @@ -266,7 +266,7 @@ private unsafe void SetLengthCore(long value) Debug.Assert(value >= 0, "value >= 0"); VerifyOSHandlePosition(); - FileStreamHelpers.SetFileLength(_fileHandle, _path, value); + FileStreamHelpers.SetFileLength(_fileHandle, value); if (_filePosition > value) { @@ -374,7 +374,7 @@ private unsafe int ReadNative(Span buffer) if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(_fileHandle)); - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, _fileHandle.Path); } } Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken."); @@ -473,7 +473,7 @@ private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, { Debug.Assert(fileHandle.CanSeek, "fileHandle.CanSeek"); - return _filePosition = FileStreamHelpers.Seek(fileHandle, _path, offset, origin, closeInvalidHandle); + return _filePosition = FileStreamHelpers.Seek(fileHandle, offset, origin, closeInvalidHandle); } partial void OnBufferAllocated() @@ -578,7 +578,7 @@ private unsafe void WriteCore(ReadOnlySpan source) // to a handle opened asynchronously. if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) throw new IOException(SR.IO_FileTooLongOrHandleNotSync); - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, _fileHandle.Path); } } Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken."); @@ -776,7 +776,7 @@ private unsafe Task ReadNativeAsync(Memory destination, int numBuffer } else { - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, _fileHandle.Path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING @@ -978,7 +978,7 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella } else { - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, _fileHandle.Path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING @@ -1100,7 +1100,7 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc try { await FileStreamHelpers - .AsyncModeCopyToAsync(_fileHandle, _path, canSeek, _filePosition, destination, bufferSize, cancellationToken) + .AsyncModeCopyToAsync(_fileHandle, canSeek, _filePosition, destination, bufferSize, cancellationToken) .ConfigureAwait(false); } finally @@ -1113,8 +1113,8 @@ await FileStreamHelpers } } - internal override void Lock(long position, long length) => FileStreamHelpers.Lock(_fileHandle, _path, position, length); + internal override void Lock(long position, long length) => FileStreamHelpers.Lock(_fileHandle, position, length); - internal override void Unlock(long position, long length) => FileStreamHelpers.Unlock(_fileHandle, _path, position, length); + internal override void Unlock(long position, long length) => FileStreamHelpers.Unlock(_fileHandle, position, length); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs index 9a88f5a65d4894..f01791ba9639e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs @@ -19,9 +19,6 @@ internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy /// Whether the file is opened for reading, writing, or both. private readonly FileAccess _access; - /// The path to the opened file. - private readonly string? _path; - /// The next available byte to be read from the _buffer. private int _readPos; @@ -82,7 +79,6 @@ internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess acc { string fullPath = Path.GetFullPath(path); - _path = fullPath; _access = access; _bufferLength = bufferSize; @@ -293,7 +289,7 @@ internal override SafeFileHandle SafeFileHandle } } - internal override string Name => _path ?? SR.IO_UnknownFileName; + internal override string Name => _fileHandle.Path ?? SR.IO_UnknownFileName; internal override bool IsAsync => _useAsyncIO; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs index dda1c621119757..f03c07fd4d0a09 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs @@ -11,7 +11,6 @@ namespace System.IO.Strategies internal abstract class WindowsFileStreamStrategy : FileStreamStrategy { protected readonly SafeFileHandle _fileHandle; // only ever null if ctor throws - protected readonly string? _path; // The path to the opened file. private readonly FileAccess _access; // What file was opened for. private readonly FileShare _share; @@ -32,7 +31,7 @@ internal WindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, Fil { // given strategy was created out of existing handle, so we have to perform // a syscall to get the current handle offset - _filePosition = FileStreamHelpers.Seek(handle, _path, 0, SeekOrigin.Current); + _filePosition = FileStreamHelpers.Seek(handle, 0, SeekOrigin.Current); } else { @@ -46,7 +45,6 @@ internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access { string fullPath = Path.GetFullPath(path); - _path = fullPath; _access = access; _share = share; @@ -80,12 +78,12 @@ public unsafe sealed override long Length { if (_share > FileShare.Read || _exposedHandle) { - return RandomAccess.GetFileLength(_fileHandle, _path); + return RandomAccess.GetFileLength(_fileHandle); } if (_length < 0) { - _length = RandomAccess.GetFileLength(_fileHandle, _path); + _length = RandomAccess.GetFileLength(_fileHandle); } return _length; @@ -115,7 +113,7 @@ public override long Position set => _filePosition = value; } - internal sealed override string Name => _path ?? SR.IO_UnknownFileName; + internal sealed override string Name => _fileHandle.Path ?? SR.IO_UnknownFileName; internal sealed override bool IsClosed => _fileHandle.IsClosed; @@ -130,7 +128,7 @@ internal sealed override SafeFileHandle SafeFileHandle { // Update the file offset before exposing it since it's possible that // in memory position is out-of-sync with the actual file position. - FileStreamHelpers.Seek(_fileHandle, _path, _filePosition, SeekOrigin.Begin); + FileStreamHelpers.Seek(_fileHandle, _filePosition, SeekOrigin.Begin); } _exposedHandle = true; @@ -179,7 +177,7 @@ internal sealed override void Flush(bool flushToDisk) { if (flushToDisk && CanWrite) { - FileStreamHelpers.FlushToDisk(_fileHandle, _path); + FileStreamHelpers.FlushToDisk(_fileHandle); } } @@ -218,9 +216,9 @@ public sealed override long Seek(long offset, SeekOrigin origin) return pos; } - internal sealed override void Lock(long position, long length) => FileStreamHelpers.Lock(_fileHandle, _path, position, length); + internal sealed override void Lock(long position, long length) => FileStreamHelpers.Lock(_fileHandle, position, length); - internal sealed override void Unlock(long position, long length) => FileStreamHelpers.Unlock(_fileHandle, _path, position, length); + internal sealed override void Unlock(long position, long length) => FileStreamHelpers.Unlock(_fileHandle, position, length); private void Init(FileMode mode, string originalPath) { @@ -249,7 +247,7 @@ protected unsafe void SetLengthCore(long value) { Debug.Assert(value >= 0, "value >= 0"); - FileStreamHelpers.SetFileLength(_fileHandle, _path, value); + FileStreamHelpers.SetFileLength(_fileHandle, value); _length = value; if (_filePosition > value) @@ -273,7 +271,7 @@ private unsafe int ReadSpan(Span destination) ThrowHelper.ThrowNotSupportedException_UnreadableStream(); } - int r = RandomAccess.ReadAtOffset(_fileHandle, destination, _filePosition, _path); + int r = RandomAccess.ReadAtOffset(_fileHandle, destination, _filePosition); Debug.Assert(r >= 0, $"RandomAccess.ReadAtOffset returned {r}."); _filePosition += r; @@ -296,7 +294,7 @@ private unsafe void WriteSpan(ReadOnlySpan source) ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } - int r = RandomAccess.WriteAtOffset(_fileHandle, source, _filePosition, _path); + int r = RandomAccess.WriteAtOffset(_fileHandle, source, _filePosition); Debug.Assert(r >= 0, $"RandomAccess.WriteAtOffset returned {r}."); _filePosition += r;