未验证 提交 1ae7c5f8 编写于 作者: V Victor Irzak 提交者: GitHub

Handle FileStream.Length for devices (#73708)

Co-authored-by: NTheodore Tsirpanis <teo@tsirpanis.gr>
Co-authored-by: NAdam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: NJan Kotas <jkotas@microsoft.com>
上级 ae24786f
...@@ -12,14 +12,17 @@ internal static partial class Kernel32 ...@@ -12,14 +12,17 @@ internal static partial class Kernel32
// https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_get_reparse_point // https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_get_reparse_point
internal const int FSCTL_GET_REPARSE_POINT = 0x000900a8; internal const int FSCTL_GET_REPARSE_POINT = 0x000900a8;
// https://docs.microsoft.com/windows-hardware/drivers/ddi/ntddstor/ni-ntddstor-ioctl_storage_read_capacity
internal const int IOCTL_STORAGE_READ_CAPACITY = 0x002D5140;
[LibraryImport(Libraries.Kernel32, EntryPoint = "DeviceIoControl", SetLastError = true)] [LibraryImport(Libraries.Kernel32, EntryPoint = "DeviceIoControl", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool DeviceIoControl( internal static unsafe partial bool DeviceIoControl(
SafeHandle hDevice, SafeHandle hDevice,
uint dwIoControlCode, uint dwIoControlCode,
IntPtr lpInBuffer, void* lpInBuffer,
uint nInBufferSize, uint nInBufferSize,
byte[] lpOutBuffer, void* lpOutBuffer,
uint nOutBufferSize, uint nOutBufferSize,
out uint lpBytesReturned, out uint lpBytesReturned,
IntPtr lpOverlapped); IntPtr lpOverlapped);
......
// 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;
internal static partial class Interop
{
internal static partial class Kernel32
{
// https://docs.microsoft.com/en-us/windows/win32/devio/storage-read-capacity
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct STORAGE_READ_CAPACITY
{
internal uint Version;
internal uint Size;
internal uint BlockLength;
internal long NumberOfBlocks;
internal long DiskLength;
}
}
}
...@@ -36,5 +36,22 @@ public void ReturnsExactSizeForNonEmptyFiles(FileOptions options) ...@@ -36,5 +36,22 @@ public void ReturnsExactSizeForNonEmptyFiles(FileOptions options)
Assert.Equal(fileSize, RandomAccess.GetLength(handle)); Assert.Equal(fileSize, RandomAccess.GetLength(handle));
} }
} }
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindowsAndElevated))]
[MemberData(nameof(GetSyncAsyncOptions))]
public void ReturnsActualLengthForDevices(FileOptions options)
{
// both File.Exists and Path.Exists return false when "\\?\PhysicalDrive0" exists
// that is why we just try and swallow the exception when it occurs
try
{
using (SafeFileHandle handle = File.OpenHandle(@"\\?\PhysicalDrive0", FileMode.Open, options: options))
{
long length = RandomAccess.GetLength(handle);
Assert.True(length > 0);
}
}
catch (FileNotFoundException) { }
}
} }
} }
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Buffers;
namespace Microsoft.Win32.SafeHandles namespace Microsoft.Win32.SafeHandles
{ {
...@@ -286,12 +287,43 @@ unsafe long GetFileLengthCore() ...@@ -286,12 +287,43 @@ unsafe long GetFileLengthCore()
{ {
Interop.Kernel32.FILE_STANDARD_INFO info; Interop.Kernel32.FILE_STANDARD_INFO info;
if (!Interop.Kernel32.GetFileInformationByHandleEx(this, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO))) if (Interop.Kernel32.GetFileInformationByHandleEx(this, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO)))
{
return info.EndOfFile;
}
// In theory when GetFileInformationByHandleEx fails, then
// a) IsDevice can modify last error (not true today, but can be in the future),
// b) DeviceIoControl can succeed (last error set to ERROR_SUCCESS) but return fewer bytes than requested.
// The error is stored and in such cases exception for the first failure is going to be thrown.
int lastError = Marshal.GetLastWin32Error();
if (Path is null || !PathInternal.IsDevice(Path))
{
throw Win32Marshal.GetExceptionForWin32Error(lastError, Path);
}
Interop.Kernel32.STORAGE_READ_CAPACITY storageReadCapacity;
bool success = Interop.Kernel32.DeviceIoControl(
this,
dwIoControlCode: Interop.Kernel32.IOCTL_STORAGE_READ_CAPACITY,
lpInBuffer: null,
nInBufferSize: 0,
lpOutBuffer: &storageReadCapacity,
nOutBufferSize: (uint)sizeof(Interop.Kernel32.STORAGE_READ_CAPACITY),
out uint bytesReturned,
IntPtr.Zero);
if (!success)
{ {
throw Win32Marshal.GetExceptionForLastWin32Error(Path); throw Win32Marshal.GetExceptionForLastWin32Error(Path);
} }
else if (bytesReturned != sizeof(Interop.Kernel32.STORAGE_READ_CAPACITY))
{
throw Win32Marshal.GetExceptionForWin32Error(lastError, Path);
}
return info.EndOfFile; return storageReadCapacity.DiskLength;
} }
} }
} }
......
...@@ -1720,6 +1720,9 @@ ...@@ -1720,6 +1720,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs"> <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs</Link> <Link>Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs</Link>
</Compile> </Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.STORAGE_READ_CAPACITY.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.STORAGE_READ_CAPACITY.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"> <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs">
<Link>Common\Interop\Windows\Interop.UNICODE_STRING.cs</Link> <Link>Common\Interop\Windows\Interop.UNICODE_STRING.cs</Link>
</Compile> </Compile>
......
...@@ -574,15 +574,20 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i ...@@ -574,15 +574,20 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i
byte[] buffer = ArrayPool<byte>.Shared.Rent(Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE); byte[] buffer = ArrayPool<byte>.Shared.Rent(Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
try try
{ {
bool success = Interop.Kernel32.DeviceIoControl( bool success;
handle,
dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT, fixed (byte* pBuffer = buffer)
lpInBuffer: IntPtr.Zero, {
nInBufferSize: 0, success = Interop.Kernel32.DeviceIoControl(
lpOutBuffer: buffer, handle,
nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT,
out _, lpInBuffer: null,
IntPtr.Zero); nInBufferSize: 0,
lpOutBuffer: pBuffer,
nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
out _,
IntPtr.Zero);
}
if (!success) if (!success)
{ {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册