From 87a44c3d91dcf230dfdcbf6ee1b9465f5bb5f583 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 12 Nov 2021 16:13:09 +0100 Subject: [PATCH] System.IO files cleanup (#61413) * Environment.SystemPageSize returns cached value * we are no longer shipping MS.IO.Redist, so we can use Array.MaxLength directly * we are no longer shipping MS.IO.Redist, there is no need for File to be partial * we are no longer shipping MS.IO.Redist, there is no need for FileInfo to be partial * there is no need for .Win32.cs and .Windows.cs file anymore --- .../System.Private.CoreLib.Shared.projitems | 3 - .../src/System/IO/File.cs | 224 ++++++++++++++++- .../src/System/IO/File.netcoreapp.cs | 233 ------------------ .../src/System/IO/FileInfo.cs | 10 +- .../src/System/IO/FileInfo.netcoreapp.cs | 14 -- .../src/System/IO/FileSystem.Win32.cs | 56 ----- .../src/System/IO/FileSystem.Windows.cs | 49 +++- .../src/System/IO/RandomAccess.Windows.cs | 5 +- 8 files changed, 272 insertions(+), 322 deletions(-) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/File.netcoreapp.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.netcoreapp.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Win32.cs 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 31613fdb4a950..2d07a361eefba 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 @@ -414,11 +414,9 @@ - - @@ -1840,7 +1838,6 @@ - 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 e63935c5f0c80..29dff0c4d009f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -16,10 +16,9 @@ namespace System.IO { // Class for creating FileStream objects, and some basic file management // routines such as Delete, etc. - public static partial class File + public static class File { - // Don't use Array.MaxLength. MS.IO.Redist targets .NET Framework. - private const int MaxByteArrayLength = 0x7FFFFFC7; + private const int ChunkSize = 8192; private static Encoding? s_UTF8NoBOM; // UTF-8 without BOM and with error detection. Same as the default encoding for StreamWriter. @@ -121,6 +120,12 @@ public static bool Exists([NotNullWhen(true)] string? path) return false; } + /// + /// Initializes a new instance of the class with the specified path, creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size. + /// + /// for information about exceptions. + public static FileStream Open(string path, FileStreamOptions options) => new FileStream(path, options); + public static FileStream Open(string path, FileMode mode) => Open(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.None); @@ -130,6 +135,44 @@ public static FileStream Open(string path, FileMode mode, FileAccess access) public static FileStream Open(string path, FileMode mode, FileAccess access, FileShare share) => new FileStream(path, mode, access, share); + /// + /// Initializes a new instance of the class with the specified path, creation mode, read/write and sharing permission, the access other SafeFileHandles can have to the same file, additional file options and the allocation size. + /// + /// A relative or absolute path for the file that the current instance will encapsulate. + /// One of the enumeration values that determines how to open or create the file. The default value is + /// A bitwise combination of the enumeration values that determines how the file can be accessed. The default value is + /// A bitwise combination of the enumeration values that determines how the file will be shared by processes. The default value is . + /// The initial allocation size in bytes for the file. A positive value is effective only when a regular file is being created, overwritten, or replaced. + /// Negative values are not allowed. In other cases (including the default 0 value), it's ignored. + /// An object that describes optional parameters to use. + /// is . + /// is an empty string (""), contains only white space, or contains one or more invalid characters. + /// -or- + /// refers to a non-file device, such as CON:, COM1:, LPT1:, etc. in an NTFS environment. + /// refers to a non-file device, such as CON:, COM1:, LPT1:, etc. in a non-NTFS environment. + /// is negative. + /// -or- + /// , , or contain an invalid value. + /// The file cannot be found, such as when is or , and the file specified by does not exist. The file must already exist in these modes. + /// An I/O error, such as specifying when the file specified by already exists, occurred. + /// -or- + /// The disk was full (when was provided and was pointing to a regular file). + /// -or- + /// The file was too large (when was provided and was pointing to a regular file). + /// The caller does not have the required permission. + /// The specified path is invalid, such as being on an unmapped drive. + /// The requested is not permitted by the operating system for the specified , such as when is or and the file or directory is set for read-only access. + /// -or- + /// is specified for , but file encryption is not supported on the current platform. + /// The specified path, file name, or both exceed the system-defined maximum length. + public static SafeFileHandle OpenHandle(string path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read, + FileShare share = FileShare.Read, FileOptions options = FileOptions.None, long preallocationSize = 0) + { + Strategies.FileStreamHelpers.ValidateArguments(path, mode, access, share, bufferSize: 0, options, preallocationSize); + + return SafeFileHandle.Open(Path.GetFullPath(path), mode, access, share, options, preallocationSize); + } + // File and Directory UTC APIs treat a DateTimeKind.Unspecified as UTC whereas // ToUniversalTime treats this as local. internal static DateTimeOffset GetUtcDateTimeOffset(DateTime dateTime) @@ -537,9 +580,9 @@ private static async Task InternalReadAllBytesUnknownLengthAsync(FileStr if (bytesRead == rentedArray.Length) { uint newLength = (uint)rentedArray.Length * 2; - if (newLength > MaxByteArrayLength) + if (newLength > Array.MaxLength) { - newLength = (uint)Math.Max(MaxByteArrayLength, rentedArray.Length + 1); + newLength = (uint)Math.Max(Array.MaxLength, rentedArray.Length + 1); } byte[] tmp = ArrayPool.Shared.Rent((int)newLength); @@ -731,5 +774,176 @@ private static void Validate(string path, Encoding encoding) if (path.Length == 0) throw new ArgumentException(SR.Argument_EmptyPath, nameof(path)); } + + private static byte[] ReadAllBytesUnknownLength(FileStream fs) + { + byte[]? rentedArray = null; + Span buffer = stackalloc byte[512]; + try + { + int bytesRead = 0; + while (true) + { + if (bytesRead == buffer.Length) + { + uint newLength = (uint)buffer.Length * 2; + if (newLength > Array.MaxLength) + { + newLength = (uint)Math.Max(Array.MaxLength, buffer.Length + 1); + } + + byte[] tmp = ArrayPool.Shared.Rent((int)newLength); + buffer.CopyTo(tmp); + byte[]? oldRentedArray = rentedArray; + buffer = rentedArray = tmp; + if (oldRentedArray != null) + { + ArrayPool.Shared.Return(oldRentedArray); + } + } + + Debug.Assert(bytesRead < buffer.Length); + int n = fs.Read(buffer.Slice(bytesRead)); + if (n == 0) + { + return buffer.Slice(0, bytesRead).ToArray(); + } + bytesRead += n; + } + } + finally + { + if (rentedArray != null) + { + ArrayPool.Shared.Return(rentedArray); + } + } + } + + private static void WriteToFile(string path, FileMode mode, string? contents, Encoding encoding) + { + ReadOnlySpan preamble = encoding.GetPreamble(); + int preambleSize = preamble.Length; + + using SafeFileHandle fileHandle = OpenHandle(path, mode, FileAccess.Write, FileShare.Read, FileOptions.None, GetPreallocationSize(mode, contents, encoding, preambleSize)); + long fileOffset = mode == FileMode.Append && fileHandle.CanSeek ? RandomAccess.GetLength(fileHandle) : 0; + + if (string.IsNullOrEmpty(contents)) + { + if (preambleSize > 0 // even if the content is empty, we want to store the preamble + && fileOffset == 0) // if we're appending to a file that already has data, don't write the preamble. + { + RandomAccess.WriteAtOffset(fileHandle, preamble, fileOffset); + } + return; + } + + int bytesNeeded = preambleSize + encoding.GetMaxByteCount(Math.Min(contents.Length, ChunkSize)); + byte[]? rentedBytes = null; + Span bytes = bytesNeeded <= 1024 ? stackalloc byte[1024] : (rentedBytes = ArrayPool.Shared.Rent(bytesNeeded)); + + try + { + if (fileOffset == 0) + { + preamble.CopyTo(bytes); + } + else + { + preambleSize = 0; // don't append preamble to a non-empty file + } + + Encoder encoder = encoding.GetEncoder(); + ReadOnlySpan remaining = contents; + while (!remaining.IsEmpty) + { + ReadOnlySpan toEncode = remaining.Slice(0, Math.Min(remaining.Length, ChunkSize)); + remaining = remaining.Slice(toEncode.Length); + int encoded = encoder.GetBytes(toEncode, bytes.Slice(preambleSize), flush: remaining.IsEmpty); + Span toStore = bytes.Slice(0, preambleSize + encoded); + + RandomAccess.WriteAtOffset(fileHandle, toStore, fileOffset); + + fileOffset += toStore.Length; + preambleSize = 0; + } + } + finally + { + if (rentedBytes is not null) + { + ArrayPool.Shared.Return(rentedBytes); + } + } + } + + private static async Task WriteToFileAsync(string path, FileMode mode, string? contents, Encoding encoding, CancellationToken cancellationToken) + { + ReadOnlyMemory preamble = encoding.GetPreamble(); + int preambleSize = preamble.Length; + + using SafeFileHandle fileHandle = OpenHandle(path, mode, FileAccess.Write, FileShare.Read, FileOptions.Asynchronous, GetPreallocationSize(mode, contents, encoding, preambleSize)); + long fileOffset = mode == FileMode.Append && fileHandle.CanSeek ? RandomAccess.GetLength(fileHandle) : 0; + + if (string.IsNullOrEmpty(contents)) + { + if (preambleSize > 0 // even if the content is empty, we want to store the preamble + && fileOffset == 0) // if we're appending to a file that already has data, don't write the preamble. + { + await RandomAccess.WriteAtOffsetAsync(fileHandle, preamble, fileOffset, cancellationToken).ConfigureAwait(false); + } + return; + } + + byte[] bytes = ArrayPool.Shared.Rent(preambleSize + encoding.GetMaxByteCount(Math.Min(contents.Length, ChunkSize))); + + try + { + if (fileOffset == 0) + { + preamble.CopyTo(bytes); + } + else + { + preambleSize = 0; // don't append preamble to a non-empty file + } + + Encoder encoder = encoding.GetEncoder(); + ReadOnlyMemory remaining = contents.AsMemory(); + while (!remaining.IsEmpty) + { + ReadOnlyMemory toEncode = remaining.Slice(0, Math.Min(remaining.Length, ChunkSize)); + remaining = remaining.Slice(toEncode.Length); + int encoded = encoder.GetBytes(toEncode.Span, bytes.AsSpan(preambleSize), flush: remaining.IsEmpty); + ReadOnlyMemory toStore = new ReadOnlyMemory(bytes, 0, preambleSize + encoded); + + await RandomAccess.WriteAtOffsetAsync(fileHandle, toStore, fileOffset, cancellationToken).ConfigureAwait(false); + + fileOffset += toStore.Length; + preambleSize = 0; + } + } + finally + { + ArrayPool.Shared.Return(bytes); + } + } + + private static long GetPreallocationSize(FileMode mode, string? contents, Encoding encoding, int preambleSize) + { + // for a single write operation, setting preallocationSize has no perf benefit, as it requires an additional sys-call + if (contents is null || contents.Length < ChunkSize) + { + return 0; + } + + // preallocationSize is ignored for Append mode, there is no need to spend cycles on GetByteCount + if (mode == FileMode.Append) + { + return 0; + } + + return preambleSize + encoding.GetByteCount(contents); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.netcoreapp.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.netcoreapp.cs deleted file mode 100644 index 1e627dae59732..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.netcoreapp.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Diagnostics; -using System.IO.Strategies; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; - -namespace System.IO -{ - public static partial class File - { - private const int ChunkSize = 8192; - - /// - /// Initializes a new instance of the class with the specified path, creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size. - /// - /// for information about exceptions. - public static FileStream Open(string path, FileStreamOptions options) => new FileStream(path, options); - - /// - /// Initializes a new instance of the class with the specified path, creation mode, read/write and sharing permission, the access other SafeFileHandles can have to the same file, additional file options and the allocation size. - /// - /// A relative or absolute path for the file that the current instance will encapsulate. - /// One of the enumeration values that determines how to open or create the file. The default value is - /// A bitwise combination of the enumeration values that determines how the file can be accessed. The default value is - /// A bitwise combination of the enumeration values that determines how the file will be shared by processes. The default value is . - /// The initial allocation size in bytes for the file. A positive value is effective only when a regular file is being created, overwritten, or replaced. - /// Negative values are not allowed. In other cases (including the default 0 value), it's ignored. - /// An object that describes optional parameters to use. - /// is . - /// is an empty string (""), contains only white space, or contains one or more invalid characters. - /// -or- - /// refers to a non-file device, such as CON:, COM1:, LPT1:, etc. in an NTFS environment. - /// refers to a non-file device, such as CON:, COM1:, LPT1:, etc. in a non-NTFS environment. - /// is negative. - /// -or- - /// , , or contain an invalid value. - /// The file cannot be found, such as when is or , and the file specified by does not exist. The file must already exist in these modes. - /// An I/O error, such as specifying when the file specified by already exists, occurred. - /// -or- - /// The disk was full (when was provided and was pointing to a regular file). - /// -or- - /// The file was too large (when was provided and was pointing to a regular file). - /// The caller does not have the required permission. - /// The specified path is invalid, such as being on an unmapped drive. - /// The requested is not permitted by the operating system for the specified , such as when is or and the file or directory is set for read-only access. - /// -or- - /// is specified for , but file encryption is not supported on the current platform. - /// The specified path, file name, or both exceed the system-defined maximum length. - public static SafeFileHandle OpenHandle(string path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read, - FileShare share = FileShare.Read, FileOptions options = FileOptions.None, long preallocationSize = 0) - { - FileStreamHelpers.ValidateArguments(path, mode, access, share, bufferSize: 0, options, preallocationSize); - - return SafeFileHandle.Open(Path.GetFullPath(path), mode, access, share, options, preallocationSize); - } - - private static byte[] ReadAllBytesUnknownLength(FileStream fs) - { - byte[]? rentedArray = null; - Span buffer = stackalloc byte[512]; - try - { - int bytesRead = 0; - while (true) - { - if (bytesRead == buffer.Length) - { - uint newLength = (uint)buffer.Length * 2; - if (newLength > Array.MaxLength) - { - newLength = (uint)Math.Max(Array.MaxLength, buffer.Length + 1); - } - - byte[] tmp = ArrayPool.Shared.Rent((int)newLength); - buffer.CopyTo(tmp); - byte[]? oldRentedArray = rentedArray; - buffer = rentedArray = tmp; - if (oldRentedArray != null) - { - ArrayPool.Shared.Return(oldRentedArray); - } - } - - Debug.Assert(bytesRead < buffer.Length); - int n = fs.Read(buffer.Slice(bytesRead)); - if (n == 0) - { - return buffer.Slice(0, bytesRead).ToArray(); - } - bytesRead += n; - } - } - finally - { - if (rentedArray != null) - { - ArrayPool.Shared.Return(rentedArray); - } - } - } - - private static void WriteToFile(string path, FileMode mode, string? contents, Encoding encoding) - { - ReadOnlySpan preamble = encoding.GetPreamble(); - int preambleSize = preamble.Length; - - using SafeFileHandle fileHandle = OpenHandle(path, mode, FileAccess.Write, FileShare.Read, FileOptions.None, GetPreallocationSize(mode, contents, encoding, preambleSize)); - long fileOffset = mode == FileMode.Append && fileHandle.CanSeek ? RandomAccess.GetLength(fileHandle) : 0; - - if (string.IsNullOrEmpty(contents)) - { - if (preambleSize > 0 // even if the content is empty, we want to store the preamble - && fileOffset == 0) // if we're appending to a file that already has data, don't write the preamble. - { - RandomAccess.WriteAtOffset(fileHandle, preamble, fileOffset); - } - return; - } - - int bytesNeeded = preambleSize + encoding.GetMaxByteCount(Math.Min(contents.Length, ChunkSize)); - byte[]? rentedBytes = null; - Span bytes = bytesNeeded <= 1024 ? stackalloc byte[1024] : (rentedBytes = ArrayPool.Shared.Rent(bytesNeeded)); - - try - { - if (fileOffset == 0) - { - preamble.CopyTo(bytes); - } - else - { - preambleSize = 0; // don't append preamble to a non-empty file - } - - Encoder encoder = encoding.GetEncoder(); - ReadOnlySpan remaining = contents; - while (!remaining.IsEmpty) - { - ReadOnlySpan toEncode = remaining.Slice(0, Math.Min(remaining.Length, ChunkSize)); - remaining = remaining.Slice(toEncode.Length); - int encoded = encoder.GetBytes(toEncode, bytes.Slice(preambleSize), flush: remaining.IsEmpty); - Span toStore = bytes.Slice(0, preambleSize + encoded); - - RandomAccess.WriteAtOffset(fileHandle, toStore, fileOffset); - - fileOffset += toStore.Length; - preambleSize = 0; - } - } - finally - { - if (rentedBytes is not null) - { - ArrayPool.Shared.Return(rentedBytes); - } - } - } - - private static async Task WriteToFileAsync(string path, FileMode mode, string? contents, Encoding encoding, CancellationToken cancellationToken) - { - ReadOnlyMemory preamble = encoding.GetPreamble(); - int preambleSize = preamble.Length; - - using SafeFileHandle fileHandle = OpenHandle(path, mode, FileAccess.Write, FileShare.Read, FileOptions.Asynchronous, GetPreallocationSize(mode, contents, encoding, preambleSize)); - long fileOffset = mode == FileMode.Append && fileHandle.CanSeek ? RandomAccess.GetLength(fileHandle) : 0; - - if (string.IsNullOrEmpty(contents)) - { - if (preambleSize > 0 // even if the content is empty, we want to store the preamble - && fileOffset == 0) // if we're appending to a file that already has data, don't write the preamble. - { - await RandomAccess.WriteAtOffsetAsync(fileHandle, preamble, fileOffset, cancellationToken).ConfigureAwait(false); - } - return; - } - - byte[] bytes = ArrayPool.Shared.Rent(preambleSize + encoding.GetMaxByteCount(Math.Min(contents.Length, ChunkSize))); - - try - { - if (fileOffset == 0) - { - preamble.CopyTo(bytes); - } - else - { - preambleSize = 0; // don't append preamble to a non-empty file - } - - Encoder encoder = encoding.GetEncoder(); - ReadOnlyMemory remaining = contents.AsMemory(); - while (!remaining.IsEmpty) - { - ReadOnlyMemory toEncode = remaining.Slice(0, Math.Min(remaining.Length, ChunkSize)); - remaining = remaining.Slice(toEncode.Length); - int encoded = encoder.GetBytes(toEncode.Span, bytes.AsSpan(preambleSize), flush: remaining.IsEmpty); - ReadOnlyMemory toStore = new ReadOnlyMemory(bytes, 0, preambleSize + encoded); - - await RandomAccess.WriteAtOffsetAsync(fileHandle, toStore, fileOffset, cancellationToken).ConfigureAwait(false); - - fileOffset += toStore.Length; - preambleSize = 0; - } - } - finally - { - ArrayPool.Shared.Return(bytes); - } - } - - private static long GetPreallocationSize(FileMode mode, string? contents, Encoding encoding, int preambleSize) - { - // for a single write operation, setting preallocationSize has no perf benefit, as it requires an additional sys-call - if (contents is null || contents.Length < ChunkSize) - { - return 0; - } - - // preallocationSize is ignored for Append mode, there is no need to spend cycles on GetByteCount - if (mode == FileMode.Append) - { - return 0; - } - - return preambleSize + encoding.GetByteCount(contents); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs index 4f42c95ff454f..2ce33d996d61e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs @@ -1,9 +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 System; using System.Diagnostics; -using System.IO; using System.Text; using System.Runtime.Versioning; @@ -11,7 +9,7 @@ namespace System.IO { // Class for creating FileStream objects, and some basic file management // routines such as Delete, etc. - public sealed partial class FileInfo : FileSystemInfo + public sealed class FileInfo : FileSystemInfo { private FileInfo() { } @@ -72,6 +70,12 @@ public bool IsReadOnly } } + /// + /// Initializes a new instance of the class with the specified creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size. + /// + /// for information about exceptions. + public FileStream Open(FileStreamOptions options) => File.Open(NormalizedPath, options); + public StreamReader OpenText() => new StreamReader(NormalizedPath, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.netcoreapp.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.netcoreapp.cs deleted file mode 100644 index 4c6c5f343e24f..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.netcoreapp.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.IO -{ - public sealed partial class FileInfo : FileSystemInfo - { - /// - /// Initializes a new instance of the class with the specified creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size. - /// - /// for information about exceptions. - public FileStream Open(FileStreamOptions options) => File.Open(NormalizedPath, options); - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Win32.cs deleted file mode 100644 index a9893cd1736c5..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Win32.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -namespace System.IO -{ - internal static partial class FileSystem - { - public static void Encrypt(string path) - { - string fullPath = Path.GetFullPath(path); - - if (!Interop.Advapi32.EncryptFile(fullPath)) - { - ThrowExceptionEncryptDecryptFail(fullPath); - } - } - - public static void Decrypt(string path) - { - string fullPath = Path.GetFullPath(path); - - if (!Interop.Advapi32.DecryptFile(fullPath)) - { - ThrowExceptionEncryptDecryptFail(fullPath); - } - } - - private static unsafe void ThrowExceptionEncryptDecryptFail(string fullPath) - { - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) - { - // Check to see if the file system support the Encrypted File System (EFS) - string name = DriveInfoInternal.NormalizeDriveName(Path.GetPathRoot(fullPath)!); - - using (DisableMediaInsertionPrompt.Create()) - { - if (!Interop.Kernel32.GetVolumeInformation(name, null, 0, null, null, out int fileSystemFlags, null, 0)) - { - errorCode = Marshal.GetLastWin32Error(); - throw Win32Marshal.GetExceptionForWin32Error(errorCode, name); - } - - if ((fileSystemFlags & Interop.Kernel32.FILE_SUPPORTS_ENCRYPTION) == 0) - { - throw new NotSupportedException(SR.PlatformNotSupported_FileEncryption); - } - } - } - throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); - } - } -} 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 b94d7eeb74faf..56ca40921ae27 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 @@ -2,18 +2,59 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Win32.SafeHandles; -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; -using System.IO; -using System.Text; using System.Buffers; namespace System.IO { internal static partial class FileSystem { + public static void Encrypt(string path) + { + string fullPath = Path.GetFullPath(path); + + if (!Interop.Advapi32.EncryptFile(fullPath)) + { + ThrowExceptionEncryptDecryptFail(fullPath); + } + } + + public static void Decrypt(string path) + { + string fullPath = Path.GetFullPath(path); + + if (!Interop.Advapi32.DecryptFile(fullPath)) + { + ThrowExceptionEncryptDecryptFail(fullPath); + } + } + + private static unsafe void ThrowExceptionEncryptDecryptFail(string fullPath) + { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) + { + // Check to see if the file system support the Encrypted File System (EFS) + string name = DriveInfoInternal.NormalizeDriveName(Path.GetPathRoot(fullPath)!); + + using (DisableMediaInsertionPrompt.Create()) + { + if (!Interop.Kernel32.GetVolumeInformation(name, null, 0, null, null, out int fileSystemFlags, null, 0)) + { + errorCode = Marshal.GetLastWin32Error(); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, name); + } + + if ((fileSystemFlags & Interop.Kernel32.FILE_SUPPORTS_ENCRYPTION) == 0) + { + throw new NotSupportedException(SR.PlatformNotSupported_FileEncryption); + } + } + } + throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); + } + public static void CopyFile(string sourceFullPath, string destFullPath, bool overwrite) { int errorCode = Interop.Kernel32.CopyFile(sourceFullPath, destFullPath, !overwrite); 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 6cbb6770004c4..348b0bf4f15a5 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 @@ -18,9 +18,6 @@ public static partial class RandomAccess { private static readonly IOCompletionCallback s_callback = AllocateCallback(); - // TODO: Use SystemPageSize directly when #57442 is fixed. - private static readonly int s_cachedPageSize = Environment.SystemPageSize; - internal static unsafe long GetFileLength(SafeFileHandle handle) { Interop.Kernel32.FILE_STANDARD_INFO info; @@ -441,7 +438,7 @@ private static unsafe bool TryPrepareScatterGatherBuffers(IReadOnly THandler handler, [NotNullWhen(true)] out MemoryHandle[]? handlesToDispose, out IntPtr segmentsPtr, out int totalBytes) where THandler : struct, IMemoryHandler { - int pageSize = s_cachedPageSize; + int pageSize = Environment.SystemPageSize; Debug.Assert(BitOperations.IsPow2(pageSize), "Page size is not a power of two."); // We take advantage of the fact that the page size is // a power of two to avoid an expensive modulo operation.