找回密码
 立即注册
首页 业界区 业界 .NET Win32磁盘动态卷/跨区卷触发“函数不正确”问题排 ...

.NET Win32磁盘动态卷/跨区卷触发“函数不正确”问题排查

终秀敏 3 小时前
最近在处理Win32磁盘管理.NET 磁盘管理-技术方案选型 - 唐宋元明清2188 - 博客园-获取本地磁盘信息时,遇到一个比较隐蔽的问题。
磁盘对象获取异常,DEVICEIOCONTROL.IOCTL_STORAGE_GET_DEVICE_NUMBER FAILED, 函数不正确。(0X00000001)
当机器上出现动态卷、跨区扩展卷这类特殊卷时,GetDiskNumberByVolumeName 中执行 DeviceIoControl 会直接报错:

  • Win32异常码:1
  • Win32错误信息:函数不正确
表面上看像是权限问题,或者句柄打开方式不对
一、问题现象

当前逻辑中,代码会先枚举系统卷,再通过卷句柄去反查磁盘号。
  1. 1         private OperateResult<uint?> GetDiskNumberByVolumeName(string volumeName)
  2. 2         {
  3. 3             // 打开卷设备 volumeName: \\?\Volume{GUID}\
  4. 4             string volumePathForDevice = volumeName.TrimEnd('\\'); // \\?\Volume{GUID}
  5. 5             IntPtr hVolume = CreateFile(
  6. 6                 volumePathForDevice,
  7. 7                 0, // 只需要 IOCTL,不读写
  8. 8                 FILE_SHARE_READ | FILE_SHARE_WRITE,
  9. 9                 IntPtr.Zero,
  10. 10                 OPEN_EXISTING,
  11. 11                 0,
  12. 12                 IntPtr.Zero);
  13. 13             IntPtr outBuf = IntPtr.Zero;
  14. 14             try
  15. 15             {
  16. 16                 // 不存在这个物理盘(或者无权限),忽略此异常
  17. 17                 if (hVolume == INVALID_HANDLE_VALUE)
  18. 18                 {
  19. 19                     return OperateResult<uint?>.ToSuccess();
  20. 20                 }
  21. 21                 // 取 STORAGE_DEVICE_NUMBER
  22. 22                 uint size = (uint)Marshal.SizeOf<STORAGE_DEVICE_NUMBER>();
  23. 23                 outBuf = Marshal.AllocHGlobal((int)size);
  24. 24                 if (!DeviceIoControl(
  25. 25                         hVolume,
  26. 26                         IOCTL_STORAGE_GET_DEVICE_NUMBER,
  27. 27                         IntPtr.Zero,
  28. 28                         0,
  29. 29                         outBuf,
  30. 30                         size,
  31. 31                         out _,
  32. 32                         IntPtr.Zero))
  33. 33                 {
  34. 34                     return OperateResult<uint?>.ToWin32Error("DeviceIoControl.IOCTL_STORAGE_GET_DEVICE_NUMBER failed", Marshal.GetLastWin32Error());
  35. 35                 }
  36. 36                 STORAGE_DEVICE_NUMBER devNum = Marshal.PtrToStructure<STORAGE_DEVICE_NUMBER>(outBuf);
  37. 37                 // DeviceType 为 FILE_DEVICE_DISK(0x07) 一般表示物理磁盘
  38. 38                 var diskNumber = devNum.DeviceNumber;
  39. 39                 return OperateResult<uint?>.ToSuccess(diskNumber);
  40. 40             }
  41. 41             catch (Exception e)
  42. 42             {
  43. 43                 return OperateResult<uint?>.ToError(e);
  44. 44             }
  45. 45             finally
  46. 46             {
  47. 47                 Marshal.FreeHGlobal(outBuf);
  48. 48                 CloseInPtr(hVolume);
  49. 49             }
  50. 50         }
复制代码
核心调用点大致如下:

  • 枚举卷:FindFirstVolumeW / FindNextVolumeW
  • 打开卷句柄:CreateFile("\\?\Volume{GUID}")
  • 查询设备号:IOCTL_STORAGE_GET_DEVICE_NUMBER
在普通基础磁盘、普通分区场景下,这套逻辑是正常的。
但只要本地存在动态磁盘卷、跨区卷、条带卷或镜像卷,如下图:
1.png

就可能在 IOCTL_STORAGE_GET_DEVICE_NUMBER 这里失败,并返回 ERROR_INVALID_FUNCTION(1)。
二、根因分析

IOCTL_STORAGE_GET_DEVICE_NUMBER 更适合“一个卷能明确映射到一个底层设备号”的场景。
而动态卷、跨区卷这类卷,本质上已经不是简单的“一个卷对应一个物理盘分区”模型。它们可能:

  • 一个卷对应多个磁盘 extent
  • 一个卷跨越多个物理磁盘
  • 卷设备背后由卷管理器做了抽象
这时再去对卷句柄直接调用 IOCTL_STORAGE_GET_DEVICE_NUMBER,驱动栈可能根本不支持,于是直接返回 ERROR_INVALID_FUNCTION。
也就是说,不是调用方式写错了,而是调用的接口选错了。即:当前调用的 IOCTL 并不适用于这类卷
1. 原接口的局限
这个 IOCTL 返回的是 STORAGE_DEVICE_NUMBER,核心是:

  • DeviceType
  • DeviceNumber
  • PartitionNumber
它适合基础磁盘、普通分区、单一设备映射场景。
2. 特殊卷真正需要的能力
对于动态卷、跨区卷,正确的问题不是“这个卷对应哪个磁盘号”,而是“这个卷分布在哪些物理磁盘 extent 上”。
因此正确接口应改为:

  • IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
这个 IOCTL 返回:

  • VOLUME_DISK_EXTENTS
  • 内部包含多个 DISK_EXTENT
可以获取该卷分布在哪些磁盘上,以及每段 extent 的磁盘号、偏移和长度。
三、解决方案

这类问题有三种解决方向
方案一:不支持动态/扩展卷
普通卷走 IOCTL_STORAGE_GET_DEVICE_NUMBER查询即可,不兼容动态卷
方案二:兼容动态卷,返回扩展卷真实结构
当出现 ERROR_INVALID_FUNCTION(1) 时,自动改走 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
返回的是一卷多盘的结果
方案三:按返回结果做兼容

  • 没有拿到 extent:跳过该卷
  • 只映射到一个磁盘:继续按原模型处理
  • 映射到多个磁盘:说明是跨盘卷,当前 LocalDisk / DiskVolumePath 仍是一卷一盘模型,不强行归属,直接跳过,避免语义错误
我们先看看Powershell是如何处理的:
2.png

Powershell,Volume列表返回了真实列表,但磁盘列表只返回了一个盘符C所在磁盘
再看看diskpart:
3.png

diskpart返回数据更合理
所以我也决定采用方案三的兼容方法,返兼容数据

  • 普通基础磁盘卷:继续正常识别
  • 动态卷但只落在单磁盘上的场景:可以通过 VOLUME_DISK_EXTENTS 正常识别
  • 跨区卷/多磁盘卷:不再导致 GetDisks() 整体失败
  • 卷枚举逻辑不会因为“跳过卷”而卡死
也就是说,原来是一个特殊卷拖垮全部磁盘查询,现在变成了特殊卷按能力降级处理,普通磁盘查询保持可用。
三块磁盘查询结果:
  1. Number: 0
  2. DeviceName:  WDC WD30EZRZ-00Z5HB0
  3. SerialNumber: WD-WCC4N3TUDSUY
  4. IsOnline: True
  5. ReadOnly: False
  6. BusType: Sata
  7. IsInitialized: True
  8. PartitionStyle: GPT
  9. PartitionCount: 3
  10. MountPaths: E:\
  11. FileSystemType: NTFS
  12. Tag: 杂烩
  13. DiskSize: 2861588 M
  14. DiskAllocateSize: 0 M
  15. DiskUsedSize: 38354 M
  16. ------------------------------------------------------------
  17. Number: 1
  18. DeviceName:  Samsung SSD 870 EVO 1TB
  19. SerialNumber: S627NF0R903848J
  20. IsOnline: True
  21. ReadOnly: False
  22. BusType: Sata
  23. IsInitialized: True
  24. PartitionStyle: GPT
  25. PartitionCount: 3
  26. MountPaths: D:\
  27. FileSystemType: NTFS
  28. Tag: 代码
  29. DiskSize: 953869 M
  30. DiskAllocateSize: 0 M
  31. DiskUsedSize: 248179 M
  32. ------------------------------------------------------------
  33. Number: 2
  34. DeviceName:  WDS500G3X0C-00SJG0
  35. SerialNumber: E823_8FA6_BF53_0001_001B_448B_46D9_46A7.
  36. IsOnline: True
  37. ReadOnly: False
  38. BusType: Nvme
  39. IsInitialized: True
  40. PartitionStyle: GPT
  41. PartitionCount: 2
  42. MountPaths: C:\
  43. FileSystemType: NTFS
  44. Tag: Win11_SYSTEM
  45. DiskSize: 476940 M
  46. DiskAllocateSize: 476739 M
  47. DiskUsedSize: 334920 M
  48. ------------------------------------------------------------
复制代码
为什么没有直接做成完整支持动态卷?
因为大部分场景都建立在“一卷对应一盘”的前提上。
但动态卷、跨区卷天然可能是一卷多盘。如果硬塞进当前模型,会引出卷标归属、挂载路径展示、容量统计重复、修改挂载点和扩容能力边界等一系列问题。上层业务处理会变的更复杂
四、结论

这次问题的本质,不是代码写错,而是对卷类型的抽象过于理想化
原来的逻辑默认一个卷一定能映射到一个磁盘号,但动态卷、跨区卷打破了这个前提。
最终结论是:

  • 普通卷:IOCTL_STORAGE_GET_DEVICE_NUMBER
  • 特殊卷:IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
并且在现有单盘模型下,应对多磁盘卷做降级跳过,不要让特殊卷拖垮整体查询流程。
这次修复虽然不大,但本质上是把“错误的单一映射假设”改成了“按卷类型分流处理”,稳定性会好很多
出处:http://www.cnblogs.com/kybs0/让学习成为习惯,假设明天就有重大机遇等着你,你准备好了么本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册