醉说周易 發表於 2026-3-10 01:40:00

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

<p>最近在处理Win32磁盘管理<code>.NET 磁盘管理-技术方案选型 - 唐宋元明清2188 - 博客园-</code>获取本地磁盘信息时,遇到一个比较隐蔽的问题。</p>
<p><span style="color: rgba(255, 0, 0, 1)">磁盘对象获取异常,DEVICEIOCONTROL.IOCTL_STORAGE_GET_DEVICE_NUMBER FAILED, 函数不正确。(0X00000001)</span></p>
<p>当机器上出现动态卷、跨区扩展卷这类特殊卷时,GetDiskNumberByVolumeName 中执行 DeviceIoControl 会直接报错:</p>
<ul>
<li>
<p><span style="color: rgba(0, 0, 0, 1)">Win32异常码:1</span></p>
</li>
<li>
<p>Win32错误信息:函数不正确</p>
</li>
</ul>
<p>表面上看像是权限问题,或者句柄打开方式不对</p>
<h2>一、问题现象</h2>
<p>当前逻辑中,代码会先枚举系统卷,再通过卷句柄去反查磁盘号。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span>         <span style="color: rgba(0, 0, 255, 1)">private</span> OperateResult&lt;<span style="color: rgba(0, 0, 255, 1)">uint</span>?&gt; GetDiskNumberByVolumeName(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> volumeName)
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)">      {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>             <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开卷设备 volumeName: \\?\Volume{GUID}\</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span>             <span style="color: rgba(0, 0, 255, 1)">string</span> volumePathForDevice = volumeName.TrimEnd(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">\\</span><span style="color: rgba(128, 0, 0, 1)">'</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> \\?\Volume{GUID}</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>             IntPtr hVolume =<span style="color: rgba(0, 0, 0, 1)"> CreateFile(
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">                volumePathForDevice,
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>               <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只需要 IOCTL,不读写</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span>               FILE_SHARE_READ |<span style="color: rgba(0, 0, 0, 1)"> FILE_SHARE_WRITE,
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)">                IntPtr.Zero,
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)">                OPEN_EXISTING,
</span><span style="color: rgba(0, 128, 128, 1)">11</span>               <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">                IntPtr.Zero);
</span><span style="color: rgba(0, 128, 128, 1)">13</span>             IntPtr outBuf =<span style="color: rgba(0, 0, 0, 1)"> IntPtr.Zero;
</span><span style="color: rgba(0, 128, 128, 1)">14</span>             <span style="color: rgba(0, 0, 255, 1)">try</span>
<span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)">            {
</span><span style="color: rgba(0, 128, 128, 1)">16</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不存在这个物理盘(或者无权限),忽略此异常</span>
<span style="color: rgba(0, 128, 128, 1)">17</span>               <span style="color: rgba(0, 0, 255, 1)">if</span> (hVolume ==<span style="color: rgba(0, 0, 0, 1)"> INVALID_HANDLE_VALUE)
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">                {
</span><span style="color: rgba(0, 128, 128, 1)">19</span>                     <span style="color: rgba(0, 0, 255, 1)">return</span> OperateResult&lt;<span style="color: rgba(0, 0, 255, 1)">uint</span>?&gt;<span style="color: rgba(0, 0, 0, 1)">.ToSuccess();
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">21</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取 STORAGE_DEVICE_NUMBER</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>               <span style="color: rgba(0, 0, 255, 1)">uint</span> size = (<span style="color: rgba(0, 0, 255, 1)">uint</span>)Marshal.SizeOf&lt;STORAGE_DEVICE_NUMBER&gt;<span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)">23</span>               outBuf = Marshal.AllocHGlobal((<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">)size);
</span><span style="color: rgba(0, 128, 128, 1)">24</span>               <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">DeviceIoControl(
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 0, 1)">                        hVolume,
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)">                        IOCTL_STORAGE_GET_DEVICE_NUMBER,
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)">                        IntPtr.Zero,
</span><span style="color: rgba(0, 128, 128, 1)">28</span>                         <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)">                        outBuf,
</span><span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 0, 1)">                        size,
</span><span style="color: rgba(0, 128, 128, 1)">31</span>                         <span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)"> _,
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">                        IntPtr.Zero))
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)">                {
</span><span style="color: rgba(0, 128, 128, 1)">34</span>                     <span style="color: rgba(0, 0, 255, 1)">return</span> OperateResult&lt;<span style="color: rgba(0, 0, 255, 1)">uint</span>?&gt;.ToWin32Error(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">DeviceIoControl.IOCTL_STORAGE_GET_DEVICE_NUMBER failed</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, Marshal.GetLastWin32Error());
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">36</span>               STORAGE_DEVICE_NUMBER devNum = Marshal.PtrToStructure&lt;STORAGE_DEVICE_NUMBER&gt;<span style="color: rgba(0, 0, 0, 1)">(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)">37</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> DeviceType 为 FILE_DEVICE_DISK(0x07) 一般表示物理磁盘</span>
<span style="color: rgba(0, 128, 128, 1)">38</span>               <span style="color: rgba(0, 0, 255, 1)">var</span> diskNumber =<span style="color: rgba(0, 0, 0, 1)"> devNum.DeviceNumber;
</span><span style="color: rgba(0, 128, 128, 1)">39</span>               <span style="color: rgba(0, 0, 255, 1)">return</span> OperateResult&lt;<span style="color: rgba(0, 0, 255, 1)">uint</span>?&gt;<span style="color: rgba(0, 0, 0, 1)">.ToSuccess(diskNumber);
</span><span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">41</span>             <span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
</span><span style="color: rgba(0, 128, 128, 1)">42</span> <span style="color: rgba(0, 0, 0, 1)">            {
</span><span style="color: rgba(0, 128, 128, 1)">43</span>               <span style="color: rgba(0, 0, 255, 1)">return</span> OperateResult&lt;<span style="color: rgba(0, 0, 255, 1)">uint</span>?&gt;<span style="color: rgba(0, 0, 0, 1)">.ToError(e);
</span><span style="color: rgba(0, 128, 128, 1)">44</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">45</span>             <span style="color: rgba(0, 0, 255, 1)">finally</span>
<span style="color: rgba(0, 128, 128, 1)">46</span> <span style="color: rgba(0, 0, 0, 1)">            {
</span><span style="color: rgba(0, 128, 128, 1)">47</span> <span style="color: rgba(0, 0, 0, 1)">                Marshal.FreeHGlobal(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)">48</span> <span style="color: rgba(0, 0, 0, 1)">                CloseInPtr(hVolume);
</span><span style="color: rgba(0, 128, 128, 1)">49</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">50</span>         }</pre>
</div>
<p>核心调用点大致如下:</p>
<ul>
<li>枚举卷:<code>FindFirstVolumeW</code>&nbsp;/&nbsp;<code>FindNextVolumeW</code></li>
<li>打开卷句柄:<code>CreateFile("\\?\Volume{GUID}")</code></li>
<li>查询设备号:<code>IOCTL_STORAGE_GET_DEVICE_NUMBER</code></li>
</ul>
<p>在普通基础磁盘、普通分区场景下,这套逻辑是正常的。</p>
<p>但只要本地存在动态磁盘卷、跨区卷、条带卷或镜像卷,如下图:</p>
<p><img src="https://img2024.cnblogs.com/blog/685541/202603/685541-20260310012048236-1253932560.png" alt="image" loading="lazy"></p>
<p>就可能在&nbsp;<code>IOCTL_STORAGE_GET_DEVICE_NUMBER</code>&nbsp;这里失败,并返回&nbsp;<code>ERROR_INVALID_FUNCTION(1)</code>。</p>
<h2>二、根因分析</h2>
<p><code>IOCTL_STORAGE_GET_DEVICE_NUMBER</code>&nbsp;更适合“一个卷能明确映射到一个底层设备号”的场景。</p>
<p>而动态卷、跨区卷这类卷,本质上已经不是简单的“一个卷对应一个物理盘分区”模型。它们可能:</p>
<ul>
<li>一个卷对应多个磁盘 extent</li>
<li>一个卷跨越多个物理磁盘</li>
<li>卷设备背后由卷管理器做了抽象</li>
</ul>
<p>这时再去对卷句柄直接调用&nbsp;<code>IOCTL_STORAGE_GET_DEVICE_NUMBER</code>,驱动栈可能根本不支持,于是直接返回&nbsp;<code>ERROR_INVALID_FUNCTION</code>。</p>
<p>也就是说,不是调用方式写错了,而是<strong>调用的接口选错了。</strong>即:<strong>当前调用的 IOCTL 并不适用于这类卷</strong></p>
<p><strong>1. 原接口的局限</strong></p>
<p>这个 IOCTL 返回的是&nbsp;<code>STORAGE_DEVICE_NUMBER</code>,核心是:</p>
<ul>
<li><code>DeviceType</code></li>
<li><code>DeviceNumber</code></li>
<li><code>PartitionNumber</code></li>
</ul>
<p>它适合基础磁盘、普通分区、单一设备映射场景。</p>
<p><strong>2. 特殊卷真正需要的能力</strong></p>
<p>对于动态卷、跨区卷,正确的问题不是“这个卷对应哪个磁盘号”,而是“这个卷分布在哪些物理磁盘 extent 上”。</p>
<p>因此正确接口应改为:</p>
<ul>
<li><code>IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS</code></li>
</ul>
<p>这个 IOCTL 返回:</p>
<ul>
<li><code>VOLUME_DISK_EXTENTS</code></li>
<li>内部包含多个&nbsp;<code>DISK_EXTENT</code></li>
</ul>
<p>可以获取该卷分布在哪些磁盘上,以及每段 extent 的磁盘号、偏移和长度。</p>
<hr>
<h2>三、解决方案</h2>
<p>这类问题有三种解决方向</p>
<p>方案一:不支持动态/扩展卷</p>
<p>普通卷走&nbsp;<code>IOCTL_STORAGE_GET_DEVICE_NUMBER查询即可,不兼容动态卷</code></p>
<p>方案二:兼容动态卷,返回扩展卷真实结构</p>
<p>当出现&nbsp;<code>ERROR_INVALID_FUNCTION(1)</code>&nbsp;时,自动改走&nbsp;<code>IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS</code></p>
<p>返回的是一卷多盘的结果</p>
<p>方案三:按返回结果做兼容</p>
<ol>
<li>
<p><strong>没有拿到 extent</strong>:跳过该卷</p>
</li>
<li><strong>只映射到一个磁盘</strong>:继续按原模型处理</li>
<li><strong>映射到多个磁盘</strong>:说明是跨盘卷,当前&nbsp;<code>LocalDisk</code>&nbsp;/&nbsp;<code>DiskVolumePath</code>&nbsp;仍是一卷一盘模型,不强行归属,直接跳过,避免语义错误</li>
</ol>
<p>我们先看看Powershell是如何处理的:</p>
<p><img src="https://img2024.cnblogs.com/blog/685541/202603/685541-20260310014817125-1116681247.png" alt="image" loading="lazy"></p>
<p>Powershell,Volume列表返回了真实列表,但磁盘列表只返回了一个盘符C所在磁盘</p>
<p>再看看diskpart:</p>
<p><img src="https://img2024.cnblogs.com/blog/685541/202603/685541-20260310012724030-782572185.png" alt="image" loading="lazy"></p>
<p>diskpart返回数据更合理</p>
<p>所以我也决定采用方案三的兼容方法,返兼容数据</p>
<ul>
<li>普通基础磁盘卷:继续正常识别</li>
<li>动态卷但只落在单磁盘上的场景:可以通过&nbsp;<code>VOLUME_DISK_EXTENTS</code>&nbsp;正常识别</li>
<li>跨区卷/多磁盘卷:不再导致&nbsp;<code>GetDisks()</code>&nbsp;整体失败</li>
<li>卷枚举逻辑不会因为“跳过卷”而卡死</li>
</ul>
<p>也就是说,原来是一个特殊卷拖垮全部磁盘查询,现在变成了特殊卷按能力降级处理,普通磁盘查询保持可用。</p>
<p>代码修改如下,补充VolumeExtents:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span>      private OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)"> GetDiskNumberByVolumeName(string volumeName)
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">       {
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 0, 1)">         // 打开卷设备 volumeName: \\?\Volume{GUID}\
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(0, 0, 0, 1)">         string volumePathForDevice = volumeName.TrimEnd('\\'); // \\?\Volume{GUID}
</span><span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 0, 0, 1)">         IntPtr hVolume = CreateFile(
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(0, 0, 0, 1)">               volumePathForDevice,
</span><span style="color: rgba(0, 128, 128, 1)">7</span> <span style="color: rgba(0, 0, 0, 1)">               0, // 只需要 IOCTL,不读写
</span><span style="color: rgba(0, 128, 128, 1)">8</span> <span style="color: rgba(0, 0, 0, 1)">               FILE_SHARE_READ | FILE_SHARE_WRITE,
</span><span style="color: rgba(0, 128, 128, 1)">9</span> <span style="color: rgba(0, 0, 0, 1)">               IntPtr.Zero,
</span><span style="color: rgba(0, 128, 128, 1)"> 10</span> <span style="color: rgba(0, 0, 0, 1)">               OPEN_EXISTING,
</span><span style="color: rgba(0, 128, 128, 1)"> 11</span> <span style="color: rgba(0, 0, 0, 1)">               0,
</span><span style="color: rgba(0, 128, 128, 1)"> 12</span> <span style="color: rgba(0, 0, 0, 1)">               IntPtr.Zero);
</span><span style="color: rgba(0, 128, 128, 1)"> 13</span> <span style="color: rgba(0, 0, 0, 1)">         IntPtr outBuf = IntPtr.Zero;
</span><span style="color: rgba(0, 128, 128, 1)"> 14</span> <span style="color: rgba(0, 0, 0, 1)">         try
</span><span style="color: rgba(0, 128, 128, 1)"> 15</span> <span style="color: rgba(0, 0, 0, 1)">         {
</span><span style="color: rgba(0, 128, 128, 1)"> 16</span> <span style="color: rgba(0, 0, 0, 1)">               // 不存在这个物理盘(或者无权限),忽略此异常
</span><span style="color: rgba(0, 128, 128, 1)"> 17</span> <span style="color: rgba(0, 0, 0, 1)">               if (hVolume == INVALID_HANDLE_VALUE)
</span><span style="color: rgba(0, 128, 128, 1)"> 18</span> <span style="color: rgba(0, 0, 0, 1)">               {
</span><span style="color: rgba(0, 128, 128, 1)"> 19</span>                  return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToSuccess();
</span><span style="color: rgba(0, 128, 128, 1)"> 20</span> <span style="color: rgba(0, 0, 0, 1)">               }
</span><span style="color: rgba(0, 128, 128, 1)"> 21</span> <span style="color: rgba(0, 0, 0, 1)">               // 取 STORAGE_DEVICE_NUMBER
</span><span style="color: rgba(0, 128, 128, 1)"> 22</span>                uint size = (uint)Marshal.SizeOf<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">STORAGE_DEVICE_NUMBER</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 23</span> <span style="color: rgba(0, 0, 0, 1)">               outBuf = Marshal.AllocHGlobal((int)size);
</span><span style="color: rgba(0, 128, 128, 1)"> 24</span> <span style="color: rgba(0, 0, 0, 1)">               if (!DeviceIoControl(
</span><span style="color: rgba(0, 128, 128, 1)"> 25</span> <span style="color: rgba(0, 0, 0, 1)">                     hVolume,
</span><span style="color: rgba(0, 128, 128, 1)"> 26</span> <span style="color: rgba(0, 0, 0, 1)">                     IOCTL_STORAGE_GET_DEVICE_NUMBER,
</span><span style="color: rgba(0, 128, 128, 1)"> 27</span> <span style="color: rgba(0, 0, 0, 1)">                     IntPtr.Zero,
</span><span style="color: rgba(0, 128, 128, 1)"> 28</span> <span style="color: rgba(0, 0, 0, 1)">                     0,
</span><span style="color: rgba(0, 128, 128, 1)"> 29</span> <span style="color: rgba(0, 0, 0, 1)">                     outBuf,
</span><span style="color: rgba(0, 128, 128, 1)"> 30</span> <span style="color: rgba(0, 0, 0, 1)">                     size,
</span><span style="color: rgba(0, 128, 128, 1)"> 31</span> <span style="color: rgba(0, 0, 0, 1)">                     out _,
</span><span style="color: rgba(0, 128, 128, 1)"> 32</span> <span style="color: rgba(0, 0, 0, 1)">                     IntPtr.Zero))
</span><span style="color: rgba(0, 128, 128, 1)"> 33</span> <span style="color: rgba(0, 0, 0, 1)">               {
</span><span style="color: rgba(0, 128, 128, 1)"> 34</span> <span style="color: rgba(0, 0, 0, 1)">                   int err = Marshal.GetLastWin32Error();
</span><span style="color: rgba(0, 128, 128, 1)"> 35</span> <span style="color: rgba(0, 0, 0, 1)">                   if (err == ERROR_INVALID_FUNCTION)
</span><span style="color: rgba(0, 128, 128, 1)"> 36</span> <span style="color: rgba(0, 0, 0, 1)">                   {
</span><span style="color: rgba(0, 128, 128, 1)"> 37</span> <span style="color: rgba(0, 0, 0, 1)">                     var getDiskNumbersResult = GetDiskNumbersByVolumeExtents(volumeName);
</span><span style="color: rgba(0, 128, 128, 1)"> 38</span> <span style="color: rgba(0, 0, 0, 1)">                     if (!getDiskNumbersResult.Success)
</span><span style="color: rgba(0, 128, 128, 1)"> 39</span> <span style="color: rgba(0, 0, 0, 1)">                     {
</span><span style="color: rgba(0, 128, 128, 1)"> 40</span>                            return getDiskNumbersResult.ToResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 41</span> <span style="color: rgba(0, 0, 0, 1)">                     }
</span><span style="color: rgba(0, 128, 128, 1)"> 42</span>
<span style="color: rgba(0, 128, 128, 1)"> 43</span>                        var diskNumbers = getDiskNumbersResult.Data ?? new List<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 44</span> <span style="color: rgba(0, 0, 0, 1)">                     if (diskNumbers.Count == 0)
</span><span style="color: rgba(0, 128, 128, 1)"> 45</span> <span style="color: rgba(0, 0, 0, 1)">                     {
</span><span style="color: rgba(0, 128, 128, 1)"> 46</span>                            return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToSuccess();
</span><span style="color: rgba(0, 128, 128, 1)"> 47</span> <span style="color: rgba(0, 0, 0, 1)">                     }
</span><span style="color: rgba(0, 128, 128, 1)"> 48</span> <span style="color: rgba(0, 0, 0, 1)">                     if (diskNumbers.Count == 1)
</span><span style="color: rgba(0, 128, 128, 1)"> 49</span> <span style="color: rgba(0, 0, 0, 1)">                     {
</span><span style="color: rgba(0, 128, 128, 1)"> 50</span>                            return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToSuccess(diskNumbers);
</span><span style="color: rgba(0, 128, 128, 1)"> 51</span> <span style="color: rgba(0, 0, 0, 1)">                     }
</span><span style="color: rgba(0, 128, 128, 1)"> 52</span>
<span style="color: rgba(0, 128, 128, 1)"> 53</span>                        return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToSuccess();
</span><span style="color: rgba(0, 128, 128, 1)"> 54</span> <span style="color: rgba(0, 0, 0, 1)">                   }
</span><span style="color: rgba(0, 128, 128, 1)"> 55</span>                  return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToWin32Error("DeviceIoControl.IOCTL_STORAGE_GET_DEVICE_NUMBER failed", err);
</span><span style="color: rgba(0, 128, 128, 1)"> 56</span> <span style="color: rgba(0, 0, 0, 1)">               }
</span><span style="color: rgba(0, 128, 128, 1)"> 57</span>                STORAGE_DEVICE_NUMBER devNum = Marshal.PtrToStructure<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">STORAGE_DEVICE_NUMBER</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)"> 58</span> <span style="color: rgba(0, 0, 0, 1)">               // DeviceType 为 FILE_DEVICE_DISK(0x07) 一般表示物理磁盘
</span><span style="color: rgba(0, 128, 128, 1)"> 59</span> <span style="color: rgba(0, 0, 0, 1)">               var diskNumber = devNum.DeviceNumber;
</span><span style="color: rgba(0, 128, 128, 1)"> 60</span>                return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToSuccess(diskNumber);
</span><span style="color: rgba(0, 128, 128, 1)"> 61</span> <span style="color: rgba(0, 0, 0, 1)">         }
</span><span style="color: rgba(0, 128, 128, 1)"> 62</span> <span style="color: rgba(0, 0, 0, 1)">         catch (Exception e)
</span><span style="color: rgba(0, 128, 128, 1)"> 63</span> <span style="color: rgba(0, 0, 0, 1)">         {
</span><span style="color: rgba(0, 128, 128, 1)"> 64</span>                return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(255, 0, 0, 1)">?</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">.ToError(e);
</span><span style="color: rgba(0, 128, 128, 1)"> 65</span> <span style="color: rgba(0, 0, 0, 1)">         }
</span><span style="color: rgba(0, 128, 128, 1)"> 66</span> <span style="color: rgba(0, 0, 0, 1)">         finally
</span><span style="color: rgba(0, 128, 128, 1)"> 67</span> <span style="color: rgba(0, 0, 0, 1)">         {
</span><span style="color: rgba(0, 128, 128, 1)"> 68</span> <span style="color: rgba(0, 0, 0, 1)">               Marshal.FreeHGlobal(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)"> 69</span> <span style="color: rgba(0, 0, 0, 1)">               CloseInPtr(hVolume);
</span><span style="color: rgba(0, 128, 128, 1)"> 70</span> <span style="color: rgba(0, 0, 0, 1)">         }
</span><span style="color: rgba(0, 128, 128, 1)"> 71</span> <span style="color: rgba(0, 0, 0, 1)">       }
</span><span style="color: rgba(0, 128, 128, 1)"> 72</span>
<span style="color: rgba(0, 128, 128, 1)"> 73</span>      private OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">List</span><span style="color: rgba(255, 0, 0, 1)">&lt;uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">&gt; GetDiskNumbersByVolumeExtents(string volumeName)
</span><span style="color: rgba(0, 128, 128, 1)"> 74</span> <span style="color: rgba(0, 0, 0, 1)">       {
</span><span style="color: rgba(0, 128, 128, 1)"> 75</span> <span style="color: rgba(0, 0, 0, 1)">         string volumePathForDevice = volumeName.TrimEnd('\\');
</span><span style="color: rgba(0, 128, 128, 1)"> 76</span> <span style="color: rgba(0, 0, 0, 1)">         IntPtr hVolume = CreateFile(
</span><span style="color: rgba(0, 128, 128, 1)"> 77</span> <span style="color: rgba(0, 0, 0, 1)">               volumePathForDevice,
</span><span style="color: rgba(0, 128, 128, 1)"> 78</span> <span style="color: rgba(0, 0, 0, 1)">               0,
</span><span style="color: rgba(0, 128, 128, 1)"> 79</span> <span style="color: rgba(0, 0, 0, 1)">               FILE_SHARE_READ | FILE_SHARE_WRITE,
</span><span style="color: rgba(0, 128, 128, 1)"> 80</span> <span style="color: rgba(0, 0, 0, 1)">               IntPtr.Zero,
</span><span style="color: rgba(0, 128, 128, 1)"> 81</span> <span style="color: rgba(0, 0, 0, 1)">               OPEN_EXISTING,
</span><span style="color: rgba(0, 128, 128, 1)"> 82</span> <span style="color: rgba(0, 0, 0, 1)">               0,
</span><span style="color: rgba(0, 128, 128, 1)"> 83</span> <span style="color: rgba(0, 0, 0, 1)">               IntPtr.Zero);
</span><span style="color: rgba(0, 128, 128, 1)"> 84</span> <span style="color: rgba(0, 0, 0, 1)">         IntPtr outBuf = IntPtr.Zero;
</span><span style="color: rgba(0, 128, 128, 1)"> 85</span> <span style="color: rgba(0, 0, 0, 1)">         try
</span><span style="color: rgba(0, 128, 128, 1)"> 86</span> <span style="color: rgba(0, 0, 0, 1)">         {
</span><span style="color: rgba(0, 128, 128, 1)"> 87</span> <span style="color: rgba(0, 0, 0, 1)">               if (hVolume == INVALID_HANDLE_VALUE)
</span><span style="color: rgba(0, 128, 128, 1)"> 88</span> <span style="color: rgba(0, 0, 0, 1)">               {
</span><span style="color: rgba(0, 128, 128, 1)"> 89</span>                  return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">List</span><span style="color: rgba(255, 0, 0, 1)">&lt;uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>&gt;.ToSuccess(new List<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">());
</span><span style="color: rgba(0, 128, 128, 1)"> 90</span> <span style="color: rgba(0, 0, 0, 1)">               }
</span><span style="color: rgba(0, 128, 128, 1)"> 91</span>
<span style="color: rgba(0, 128, 128, 1)"> 92</span>                int extentSize = Marshal.SizeOf<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">DISK_EXTENT</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 93</span>                int firstExtentOffset = Marshal.OffsetOf<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">VOLUME_DISK_EXTENTS</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">(nameof(VOLUME_DISK_EXTENTS.Extents)).ToInt32();
</span><span style="color: rgba(0, 128, 128, 1)"> 94</span> <span style="color: rgba(0, 0, 0, 1)">               uint allocSize = (uint)(firstExtentOffset + extentSize * 4);
</span><span style="color: rgba(0, 128, 128, 1)"> 95</span>
<span style="color: rgba(0, 128, 128, 1)"> 96</span> <span style="color: rgba(0, 0, 0, 1)">               while (true)
</span><span style="color: rgba(0, 128, 128, 1)"> 97</span> <span style="color: rgba(0, 0, 0, 1)">               {
</span><span style="color: rgba(0, 128, 128, 1)"> 98</span> <span style="color: rgba(0, 0, 0, 1)">                   outBuf = Marshal.AllocHGlobal((int)allocSize);
</span><span style="color: rgba(0, 128, 128, 1)"> 99</span> <span style="color: rgba(0, 0, 0, 1)">                   if (DeviceIoControl(
</span><span style="color: rgba(0, 128, 128, 1)">100</span> <span style="color: rgba(0, 0, 0, 1)">                           hVolume,
</span><span style="color: rgba(0, 128, 128, 1)">101</span> <span style="color: rgba(0, 0, 0, 1)">                           IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
</span><span style="color: rgba(0, 128, 128, 1)">102</span> <span style="color: rgba(0, 0, 0, 1)">                           IntPtr.Zero,
</span><span style="color: rgba(0, 128, 128, 1)">103</span> <span style="color: rgba(0, 0, 0, 1)">                           0,
</span><span style="color: rgba(0, 128, 128, 1)">104</span> <span style="color: rgba(0, 0, 0, 1)">                           outBuf,
</span><span style="color: rgba(0, 128, 128, 1)">105</span> <span style="color: rgba(0, 0, 0, 1)">                           allocSize,
</span><span style="color: rgba(0, 128, 128, 1)">106</span> <span style="color: rgba(0, 0, 0, 1)">                           out uint bytesReturned,
</span><span style="color: rgba(0, 128, 128, 1)">107</span> <span style="color: rgba(0, 0, 0, 1)">                           IntPtr.Zero))
</span><span style="color: rgba(0, 128, 128, 1)">108</span> <span style="color: rgba(0, 0, 0, 1)">                   {
</span><span style="color: rgba(0, 128, 128, 1)">109</span> <span style="color: rgba(0, 0, 0, 1)">                     int extentCount = Marshal.ReadInt32(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)">110</span>                        var diskNumbers = new List<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">(extentCount);
</span><span style="color: rgba(0, 128, 128, 1)">111</span> <span style="color: rgba(0, 0, 0, 1)">                     IntPtr pCurrent = IntPtr.Add(outBuf, firstExtentOffset);
</span><span style="color: rgba(0, 128, 128, 1)">112</span>                        for (int i = 0; i <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)"> extentCount</span><span style="color: rgba(255, 0, 0, 1)">; i++)
</span><span style="color: rgba(0, 128, 128, 1)">113</span> <span style="color: rgba(255, 0, 0, 1)">                     {
</span><span style="color: rgba(0, 128, 128, 1)">114</span> <span style="color: rgba(255, 0, 0, 1)">                           var extent </span><span style="color: rgba(0, 0, 255, 1)">= Marshal.PtrToStructure&lt;DISK_EXTENT</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">(pCurrent);
</span><span style="color: rgba(0, 128, 128, 1)">115</span> <span style="color: rgba(0, 0, 0, 1)">                           uint diskNumber = unchecked((uint)extent.DiskNumber);
</span><span style="color: rgba(0, 128, 128, 1)">116</span> <span style="color: rgba(0, 0, 0, 1)">                           if (!diskNumbers.Contains(diskNumber))
</span><span style="color: rgba(0, 128, 128, 1)">117</span> <span style="color: rgba(0, 0, 0, 1)">                           {
</span><span style="color: rgba(0, 128, 128, 1)">118</span> <span style="color: rgba(0, 0, 0, 1)">                               diskNumbers.Add(diskNumber);
</span><span style="color: rgba(0, 128, 128, 1)">119</span> <span style="color: rgba(0, 0, 0, 1)">                           }
</span><span style="color: rgba(0, 128, 128, 1)">120</span> <span style="color: rgba(0, 0, 0, 1)">                           pCurrent = IntPtr.Add(pCurrent, extentSize);
</span><span style="color: rgba(0, 128, 128, 1)">121</span> <span style="color: rgba(0, 0, 0, 1)">                     }
</span><span style="color: rgba(0, 128, 128, 1)">122</span>                        return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">List</span><span style="color: rgba(255, 0, 0, 1)">&lt;uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">&gt;.ToSuccess(diskNumbers);
</span><span style="color: rgba(0, 128, 128, 1)">123</span> <span style="color: rgba(0, 0, 0, 1)">                   }
</span><span style="color: rgba(0, 128, 128, 1)">124</span>
<span style="color: rgba(0, 128, 128, 1)">125</span> <span style="color: rgba(0, 0, 0, 1)">                   int err = Marshal.GetLastWin32Error();
</span><span style="color: rgba(0, 128, 128, 1)">126</span> <span style="color: rgba(0, 0, 0, 1)">                   Marshal.FreeHGlobal(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)">127</span> <span style="color: rgba(0, 0, 0, 1)">                   outBuf = IntPtr.Zero;
</span><span style="color: rgba(0, 128, 128, 1)">128</span> <span style="color: rgba(0, 0, 0, 1)">                   if (err != ERROR_MORE_DATA &amp;&amp; err != ERROR_INSUFFICIENT_BUFFER &amp;&amp; err != ERROR_BUFFER_OVERFLOW)
</span><span style="color: rgba(0, 128, 128, 1)">129</span> <span style="color: rgba(0, 0, 0, 1)">                   {
</span><span style="color: rgba(0, 128, 128, 1)">130</span>                        return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">List</span><span style="color: rgba(255, 0, 0, 1)">&lt;uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">&gt;.ToWin32Error("DeviceIoControl.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS failed", err);
</span><span style="color: rgba(0, 128, 128, 1)">131</span> <span style="color: rgba(0, 0, 0, 1)">                   }
</span><span style="color: rgba(0, 128, 128, 1)">132</span>
<span style="color: rgba(0, 128, 128, 1)">133</span> <span style="color: rgba(0, 0, 0, 1)">                   uint nextSize = bytesReturned &gt; allocSize ? bytesReturned : allocSize * 2;
</span><span style="color: rgba(0, 128, 128, 1)">134</span> <span style="color: rgba(0, 0, 0, 1)">                   allocSize = nextSize;
</span><span style="color: rgba(0, 128, 128, 1)">135</span> <span style="color: rgba(0, 0, 0, 1)">               }
</span><span style="color: rgba(0, 128, 128, 1)">136</span> <span style="color: rgba(0, 0, 0, 1)">         }
</span><span style="color: rgba(0, 128, 128, 1)">137</span> <span style="color: rgba(0, 0, 0, 1)">         catch (Exception e)
</span><span style="color: rgba(0, 128, 128, 1)">138</span> <span style="color: rgba(0, 0, 0, 1)">         {
</span><span style="color: rgba(0, 128, 128, 1)">139</span>                return OperateResult<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">List</span><span style="color: rgba(255, 0, 0, 1)">&lt;uint</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="color: rgba(0, 0, 0, 1)">&gt;.ToError(e);
</span><span style="color: rgba(0, 128, 128, 1)">140</span> <span style="color: rgba(0, 0, 0, 1)">         }
</span><span style="color: rgba(0, 128, 128, 1)">141</span> <span style="color: rgba(0, 0, 0, 1)">         finally
</span><span style="color: rgba(0, 128, 128, 1)">142</span> <span style="color: rgba(0, 0, 0, 1)">         {
</span><span style="color: rgba(0, 128, 128, 1)">143</span> <span style="color: rgba(0, 0, 0, 1)">               Marshal.FreeHGlobal(outBuf);
</span><span style="color: rgba(0, 128, 128, 1)">144</span> <span style="color: rgba(0, 0, 0, 1)">               CloseInPtr(hVolume);
</span><span style="color: rgba(0, 128, 128, 1)">145</span> <span style="color: rgba(0, 0, 0, 1)">         }
</span><span style="color: rgba(0, 128, 128, 1)">146</span>      }</pre>
</div>
<p>三块磁盘查询结果:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Number: 0
DeviceName:WDC WD30EZRZ-00Z5HB0
SerialNumber: WD-WCC4N3TUDSUY
IsOnline: True
ReadOnly: False
BusType: Sata
IsInitialized: True
PartitionStyle: GPT
PartitionCount: 3
MountPaths: E:\
FileSystemType: NTFS
Tag: 杂烩
DiskSize: 2861588 M
DiskAllocateSize: 0 M
DiskUsedSize: 38354 M
------------------------------------------------------------
Number: 1
DeviceName:Samsung SSD 870 EVO 1TB
SerialNumber: S627NF0R903848J
IsOnline: True
ReadOnly: False
BusType: Sata
IsInitialized: True
PartitionStyle: GPT
PartitionCount: 3
MountPaths: D:\
FileSystemType: NTFS
Tag: 代码
DiskSize: 953869 M
DiskAllocateSize: 0 M
DiskUsedSize: 248179 M
------------------------------------------------------------
Number: 2
DeviceName:WDS500G3X0C-00SJG0
SerialNumber: E823_8FA6_BF53_0001_001B_448B_46D9_46A7.
IsOnline: True
ReadOnly: False
BusType: Nvme
IsInitialized: True
PartitionStyle: GPT
PartitionCount: 2
MountPaths: C:\
FileSystemType: NTFS
Tag: Win11_SYSTEM
DiskSize: 476940 M
DiskAllocateSize: 476739 M
DiskUsedSize: 334920 M
------------------------------------------------------------</span></pre>
</div>
<p>为什么没有直接做成完整支持动态卷?</p>
<p>因为大部分场景都建立在“一卷对应一盘”的前提上。</p>
<p>但动态卷、跨区卷天然可能是一卷多盘。如果硬塞进当前模型,会引出卷标归属、挂载路径展示、容量统计重复、修改挂载点和扩容能力边界等一系列问题。上层业务处理会变的更复杂</p>
<h2>四、结论</h2>
<p>这次问题的本质,不是代码写错,而是<strong>对卷类型的抽象过于理想化</strong>。</p>
<p>原来的逻辑默认一个卷一定能映射到一个磁盘号,但动态卷、跨区卷打破了这个前提。</p>
<p>最终结论是:</p>
<ul>
<li>普通卷:<code>IOCTL_STORAGE_GET_DEVICE_NUMBER</code></li>
<li>特殊卷:<code>IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS</code></li>
</ul>
<p>并且在现有单盘模型下,应对多磁盘卷做降级跳过,不要让特殊卷拖垮整体查询流程。</p>
<p>这次修复虽然不大,但本质上是把“错误的单一映射假设”改成了“按卷类型分流处理”,稳定性会好很多</p>

</div>
<div id="MySignature" role="contentinfo">
    <div>作者:唐宋元明清2188</div>
<div>出处:http://www.cnblogs.com/kybs0/</div>
<div>让学习成为习惯,假设明天就有重大机遇等着你,你准备好了么</div>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。 </div><br><br>
来源:https://www.cnblogs.com/kybs0/p/19694592
頁: [1]
查看完整版本: .NET Win32磁盘动态卷触发“函数不正确”问题排查