master
cpp 177 lines 6.21 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 disk.cpp
8
9 Abstract:
10
11 This file contains disk functions implementations.
12
13 --*/
14
15 #include "precomp.h"
16 #include <diskguid.h>
17
18 #include "disk.hpp"
19 #include "wslutil.h"
20
21 wil::unique_hfile wsl::windows::common::disk::OpenDevice(_In_ LPCWSTR Name, _In_ DWORD Access, size_t TimeoutMs)
22 {
23 auto openDevice = [&]() {
24 wil::unique_hfile handle{
25 CreateFileW(Name, Access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, 0, nullptr)};
26
27 THROW_LAST_ERROR_IF(!handle);
28
29 return handle;
30 };
31
32 // E_ACCESSDENIED is returned if the disk is in use, which can happen when the disk has just been detached and is being
33 // attached to the host again. Retry for 5 seconds before failing.
34
35 return wsl::shared::retry::RetryWithTimeout<wil::unique_hfile>(
36 openDevice, c_diskOperationRetry, std::chrono::milliseconds(TimeoutMs), []() {
37 const auto error = wil::ResultFromCaughtException();
38 return error == E_ACCESSDENIED || error == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION);
39 });
40 }
41
42 bool wsl::windows::common::disk::IsDiskOnline(_In_ HANDLE Disk)
43 {
44 GET_DISK_ATTRIBUTES attributes = {0};
45 Ioctl(Disk, IOCTL_DISK_GET_DISK_ATTRIBUTES, nullptr, 0, &attributes, sizeof(attributes));
46
47 return !WI_IsFlagSet(attributes.Attributes, DISK_ATTRIBUTE_OFFLINE);
48 }
49
50 void wsl::windows::common::disk::Ioctl(
51 _In_ HANDLE Disk, _In_ DWORD Code, _In_opt_ LPVOID InData, _In_ DWORD InDataSize, _Out_opt_ LPVOID OutData, _In_ DWORD OutDataSize)
52 {
53 DWORD bytesReturned = 0;
54 THROW_LAST_ERROR_IF(!DeviceIoControl(Disk, Code, InData, InDataSize, OutData, OutDataSize, &bytesReturned, nullptr));
55 }
56
57 void wsl::windows::common::disk::LockVolume(_In_ HANDLE Disk)
58 {
59 Ioctl(Disk, FSCTL_LOCK_VOLUME);
60 }
61
62 void wsl::windows::common::disk::SetOnline(_In_ HANDLE Disk, _In_ bool Online, _In_ size_t TimeoutMs)
63 {
64 // Lock and unmount all the volumes contained in the disk.
65 // This is done to make sure than the disk is not in use before setting
66 // the offline attribute (which doesn't fail if the disk is in use)
67
68 if (!Online)
69 {
70 const auto volumes = ListDiskVolumes(Disk);
71
72 // Lock all the volumes first so that we we're confident
73 // that FSCTL_DISMOUNT_VOLUME won't fail
74 // There's no need to unlock the volumes here as this is done when the handle is closed
75
76 for (const auto& e : volumes)
77 {
78 try
79 {
80 wsl::shared::retry::RetryWithTimeout<void>(
81 std::bind(&LockVolume, e.second.get()), c_diskOperationRetry, std::chrono::milliseconds(TimeoutMs), []() {
82 return wil::ResultFromCaughtException() == E_ACCESSDENIED;
83 });
84 }
85 catch (...)
86 {
87 // FSCTL_LOCK_VOLUME returns access denied if the disk is in use.
88 // Let's make the error a bit better for the user
89
90 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_DRIVE_LOCKED), wil::ResultFromCaughtException() == E_ACCESSDENIED);
91 throw;
92 }
93 }
94
95 for (const auto& e : volumes)
96 {
97 Ioctl(e.second.get(), FSCTL_DISMOUNT_VOLUME);
98 }
99 }
100
101 SET_DISK_ATTRIBUTES attributes = {0};
102 attributes.Version = sizeof(attributes);
103 attributes.AttributesMask = DISK_ATTRIBUTE_OFFLINE;
104 attributes.Attributes = Online ? 0 : DISK_ATTRIBUTE_OFFLINE;
105 Ioctl(Disk, IOCTL_DISK_SET_DISK_ATTRIBUTES, &attributes, sizeof(attributes));
106 }
107
108 DWORD
109 wsl::windows::common::disk::GetDiskNumber(_In_ HANDLE Disk)
110 {
111 STORAGE_DEVICE_NUMBER DiskNumber;
112 Ioctl(Disk, IOCTL_STORAGE_GET_DEVICE_NUMBER, nullptr, 0, &DiskNumber, sizeof(DiskNumber));
113
114 return DiskNumber.DeviceNumber;
115 }
116
117 std::map<std::wstring, wil::unique_hfile> wsl::windows::common::disk::ListDiskVolumes(_In_ HANDLE Disk)
118 {
119 ValidateDiskVolumesAreReady(Disk);
120
121 size_t partitionCount = 16;
122 std::vector<char> buffer;
123
124 for (;;)
125 {
126 buffer.resize(offsetof(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry) + partitionCount * sizeof(PARTITION_INFORMATION_EX));
127
128 if (!DeviceIoControl(Disk, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nullptr, 0, buffer.data(), gsl::narrow_cast<DWORD>(buffer.size()), nullptr, nullptr))
129 {
130 THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER);
131 THROW_IF_FAILED(SizeTMult(partitionCount, 2, &partitionCount));
132 }
133 else
134 {
135 break;
136 }
137 }
138
139 const auto* partitions = reinterpret_cast<PDRIVE_LAYOUT_INFORMATION_EX>(buffer.data());
140 std::vector<DWORD> partitionNumbers;
141 for (const auto& partition : gsl::make_span(partitions->PartitionEntry, partitions->PartitionCount))
142 {
143 if (partition.PartitionStyle == PARTITION_STYLE_MBR && partition.Mbr.PartitionType != PARTITION_ENTRY_UNUSED &&
144 partition.Mbr.PartitionType != PARTITION_SPACES && partition.Mbr.PartitionType != PARTITION_EXTENDED &&
145 partition.Mbr.PartitionType != PARTITION_XINT13_EXTENDED)
146 {
147 partitionNumbers.emplace_back(partition.PartitionNumber);
148 }
149 else if (
150 partition.PartitionStyle == PARTITION_STYLE_GPT && partition.Gpt.PartitionType != PARTITION_ENTRY_UNUSED_GUID &&
151 partition.Gpt.PartitionType != PARTITION_SPACES_GUID)
152 {
153 partitionNumbers.emplace_back(partition.PartitionNumber);
154 }
155
156 // If the partition scheme is neither MBR nor GPT, then it's RAW, which means
157 // that Windows doesn't recognize it.
158 }
159
160 auto diskNumber = GetDiskNumber(Disk);
161
162 auto transform = [diskNumber](DWORD partitionNumber) {
163 auto path = std::format(L"\\\\?\\Harddisk{}Partition{}", diskNumber, partitionNumber);
164 return std::make_pair(std::move(path), OpenDevice(path.c_str(), GENERIC_ALL));
165 };
166
167 std::map<std::wstring, wil::unique_hfile> output;
168 std::transform(partitionNumbers.begin(), partitionNumbers.end(), std::inserter(output, output.begin()), transform);
169
170 return output;
171 }
172
173 void wsl::windows::common::disk::ValidateDiskVolumesAreReady(_In_ HANDLE Disk)
174 {
175 // Will throw if the disk is not ready
176 Ioctl(Disk, IOCTL_DISK_ARE_VOLUMES_READY);
177 }