1 /* Copyright (c) Mark Harmstone 2019
2 *
3 * This file is part of WinBtrfs.
4 *
5 * WinBtrfs is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public Licence as published by
7 * the Free Software Foundation, either version 3 of the Licence, or
8 * (at your option) any later version.
9 *
10 * WinBtrfs is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public Licence for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public Licence
16 * along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */
17
18 #include "btrfs_drv.h"
19
20 #ifndef __REACTOS__
21 #ifdef _MSC_VER
22 #include <ntstrsafe.h>
23 #endif
24 #else
25 #include <ntstrsafe.h>
26 #endif
27
28 extern ERESOURCE pdo_list_lock;
29 extern LIST_ENTRY pdo_list;
30 extern ERESOURCE boot_lock;
31 extern PDRIVER_OBJECT drvobj;
32
33 BTRFS_UUID boot_uuid; // initialized to 0
34 uint64_t boot_subvol = 0;
35
36 // Not in any headers? Windbg knows about it though.
37 #define DOE_START_PENDING 0x10
38
39 // Just as much as we need - the version in mingw is truncated still further
40 typedef struct {
41 CSHORT Type;
42 USHORT Size;
43 PDEVICE_OBJECT DeviceObject;
44 ULONG PowerFlags;
45 void* Dope;
46 ULONG ExtensionFlags;
47 } DEVOBJ_EXTENSION2;
48
get_system_root()49 static bool get_system_root() {
50 NTSTATUS Status;
51 HANDLE h;
52 UNICODE_STRING us, target;
53 OBJECT_ATTRIBUTES objatt;
54 ULONG retlen = 0;
55 bool second_time = false;
56
57 static const WCHAR system_root[] = L"\\SystemRoot";
58 static const WCHAR boot_device[] = L"\\Device\\BootDevice";
59 static const WCHAR arc_btrfs_prefix[] = L"\\ArcName\\btrfs(";
60
61 us.Buffer = (WCHAR*)system_root;
62 us.Length = us.MaximumLength = sizeof(system_root) - sizeof(WCHAR);
63
64 InitializeObjectAttributes(&objatt, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
65
66 while (true) {
67 Status = ZwOpenSymbolicLinkObject(&h, GENERIC_READ, &objatt);
68 if (!NT_SUCCESS(Status)) {
69 ERR("ZwOpenSymbolicLinkObject returned %08lx\n", Status);
70 return false;
71 }
72
73 target.Length = target.MaximumLength = 0;
74
75 Status = ZwQuerySymbolicLinkObject(h, &target, &retlen);
76 if (Status != STATUS_BUFFER_TOO_SMALL) {
77 ERR("ZwQuerySymbolicLinkObject returned %08lx\n", Status);
78 NtClose(h);
79 return false;
80 }
81
82 if (retlen == 0) {
83 NtClose(h);
84 return false;
85 }
86
87 target.Buffer = ExAllocatePoolWithTag(NonPagedPool, retlen, ALLOC_TAG);
88 if (!target.Buffer) {
89 ERR("out of memory\n");
90 NtClose(h);
91 return false;
92 }
93
94 target.Length = target.MaximumLength = (USHORT)retlen;
95
96 Status = ZwQuerySymbolicLinkObject(h, &target, NULL);
97 if (!NT_SUCCESS(Status)) {
98 ERR("ZwQuerySymbolicLinkObject returned %08lx\n", Status);
99 NtClose(h);
100 ExFreePool(target.Buffer);
101 return false;
102 }
103
104 NtClose(h);
105
106 if (second_time) {
107 TRACE("boot device is %.*S\n", (int)(target.Length / sizeof(WCHAR)), target.Buffer);
108 } else {
109 TRACE("system root is %.*S\n", (int)(target.Length / sizeof(WCHAR)), target.Buffer);
110 }
111
112 if (!second_time && target.Length >= sizeof(boot_device) - sizeof(WCHAR) &&
113 RtlCompareMemory(target.Buffer, boot_device, sizeof(boot_device) - sizeof(WCHAR)) == sizeof(boot_device) - sizeof(WCHAR)) {
114 ExFreePool(target.Buffer);
115
116 us.Buffer = (WCHAR*)boot_device;
117 us.Length = us.MaximumLength = sizeof(boot_device) - sizeof(WCHAR);
118
119 second_time = true;
120 } else
121 break;
122 }
123
124 if (target.Length >= sizeof(arc_btrfs_prefix) - sizeof(WCHAR) &&
125 RtlCompareMemory(target.Buffer, arc_btrfs_prefix, sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) == sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) {
126 WCHAR* s = &target.Buffer[(sizeof(arc_btrfs_prefix) / sizeof(WCHAR)) - 1];
127
128 for (unsigned int i = 0; i < 16; i++) {
129 if (*s >= '0' && *s <= '9')
130 boot_uuid.uuid[i] = (*s - '0') << 4;
131 else if (*s >= 'a' && *s <= 'f')
132 boot_uuid.uuid[i] = (*s - 'a' + 0xa) << 4;
133 else if (*s >= 'A' && *s <= 'F')
134 boot_uuid.uuid[i] = (*s - 'A' + 0xa) << 4;
135 else {
136 ExFreePool(target.Buffer);
137 return false;
138 }
139
140 s++;
141
142 if (*s >= '0' && *s <= '9')
143 boot_uuid.uuid[i] |= *s - '0';
144 else if (*s >= 'a' && *s <= 'f')
145 boot_uuid.uuid[i] |= *s - 'a' + 0xa;
146 else if (*s >= 'A' && *s <= 'F')
147 boot_uuid.uuid[i] |= *s - 'A' + 0xa;
148 else {
149 ExFreePool(target.Buffer);
150 return false;
151 }
152
153 s++;
154
155 if (i == 3 || i == 5 || i == 7 || i == 9) {
156 if (*s != '-') {
157 ExFreePool(target.Buffer);
158 return false;
159 }
160
161 s++;
162 }
163 }
164
165 if (*s != ')') {
166 ExFreePool(target.Buffer);
167 return false;
168 }
169
170 ExFreePool(target.Buffer);
171
172 return true;
173 }
174
175 ExFreePool(target.Buffer);
176
177 return false;
178 }
179
mountmgr_notification(BTRFS_UUID * uuid)180 static void mountmgr_notification(BTRFS_UUID* uuid) {
181 UNICODE_STRING mmdevpath;
182 NTSTATUS Status;
183 PFILE_OBJECT FileObject;
184 PDEVICE_OBJECT mountmgr;
185 ULONG mmtnlen;
186 MOUNTMGR_TARGET_NAME* mmtn;
187 WCHAR* w;
188
189 RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
190 Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
191 if (!NT_SUCCESS(Status)) {
192 ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
193 return;
194 }
195
196 mmtnlen = offsetof(MOUNTMGR_TARGET_NAME, DeviceName[0]) + sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR));
197
198 mmtn = ExAllocatePoolWithTag(NonPagedPool, mmtnlen, ALLOC_TAG);
199 if (!mmtn) {
200 ERR("out of memory\n");
201 return;
202 }
203
204 mmtn->DeviceNameLength = sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR));
205
206 RtlCopyMemory(mmtn->DeviceName, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR));
207
208 w = &mmtn->DeviceName[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1];
209
210 for (unsigned int i = 0; i < 16; i++) {
211 *w = hex_digit(uuid->uuid[i] >> 4); w++;
212 *w = hex_digit(uuid->uuid[i] & 0xf); w++;
213
214 if (i == 3 || i == 5 || i == 7 || i == 9) {
215 *w = L'-';
216 w++;
217 }
218 }
219
220 *w = L'}';
221
222 Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION, mmtn, mmtnlen, NULL, 0, false, NULL);
223 if (!NT_SUCCESS(Status)) {
224 ERR("IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION returned %08lx\n", Status);
225 ExFreePool(mmtn);
226 return;
227 }
228
229 ExFreePool(mmtn);
230 }
231
check_boot_options()232 static void check_boot_options() {
233 NTSTATUS Status;
234 WCHAR* s;
235
236 static const WCHAR pathw[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control";
237 static const WCHAR namew[] = L"SystemStartOptions";
238 static const WCHAR subvol[] = L"SUBVOL=";
239
240 _SEH2_TRY {
241 HANDLE control;
242 OBJECT_ATTRIBUTES oa;
243 UNICODE_STRING path;
244 ULONG kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR));
245 KEY_VALUE_FULL_INFORMATION* kvfi;
246 UNICODE_STRING name;
247 WCHAR* options;
248
249 path.Buffer = (WCHAR*)pathw;
250 path.Length = path.MaximumLength = sizeof(pathw) - sizeof(WCHAR);
251
252 InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
253
254 Status = ZwOpenKey(&control, KEY_QUERY_VALUE, &oa);
255 if (!NT_SUCCESS(Status)) {
256 ERR("ZwOpenKey returned %08lx\n", Status);
257 return;
258 }
259
260 // FIXME - don't fail if value too long (can we query for the length?)
261
262 kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
263 if (!kvfi) {
264 ERR("out of memory\n");
265 NtClose(control);
266 return;
267 }
268
269 name.Buffer = (WCHAR*)namew;
270 name.Length = name.MaximumLength = sizeof(namew) - sizeof(WCHAR);
271
272 Status = ZwQueryValueKey(control, &name, KeyValueFullInformation, kvfi,
273 kvfilen, &kvfilen);
274 if (!NT_SUCCESS(Status)) {
275 ERR("ZwQueryValueKey returned %08lx\n", Status);
276 NtClose(control);
277 return;
278 }
279
280 NtClose(control);
281
282 options = (WCHAR*)((uint8_t*)kvfi + kvfi->DataOffset);
283 options[kvfi->DataLength / sizeof(WCHAR)] = 0; // FIXME - make sure buffer long enough to allow this
284
285 s = wcsstr(options, subvol);
286
287 if (!s)
288 return;
289
290 s += (sizeof(subvol) / sizeof(WCHAR)) - 1;
291
292 boot_subvol = 0;
293
294 while (true) {
295 if (*s >= '0' && *s <= '9') {
296 boot_subvol <<= 4;
297 boot_subvol |= *s - '0';
298 } else if (*s >= 'a' && *s <= 'f') {
299 boot_subvol <<= 4;
300 boot_subvol |= *s - 'a' + 0xa;
301 } else if (*s >= 'A' && *s <= 'F') {
302 boot_subvol <<= 4;
303 boot_subvol |= *s - 'A' + 0xa;
304 } else
305 break;
306
307 s++;
308 }
309 } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
310 return;
311 } _SEH2_END;
312
313 if (boot_subvol != 0) {
314 TRACE("passed subvol %I64x in boot options\n", boot_subvol);
315 }
316 }
317
boot_add_device(DEVICE_OBJECT * pdo)318 void boot_add_device(DEVICE_OBJECT* pdo) {
319 pdo_device_extension* pdode = pdo->DeviceExtension;
320
321 AddDevice(drvobj, pdo);
322
323 // To stop Windows sneakily setting DOE_START_PENDING
324 pdode->dont_report = true;
325
326 if (pdo->DeviceObjectExtension) {
327 ((DEVOBJ_EXTENSION2*)pdo->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING;
328
329 if (pdode && pdode->vde && pdode->vde->device)
330 ((DEVOBJ_EXTENSION2*)pdode->vde->device->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING;
331 }
332
333 mountmgr_notification(&pdode->uuid);
334 }
335
check_system_root()336 void check_system_root() {
337 LIST_ENTRY* le;
338 PDEVICE_OBJECT pdo_to_add = NULL;
339
340 TRACE("()\n");
341
342 // wait for any PNP notifications in progress to finish
343 ExAcquireResourceExclusiveLite(&boot_lock, TRUE);
344 ExReleaseResourceLite(&boot_lock);
345
346 if (!get_system_root())
347 return;
348
349 ExAcquireResourceSharedLite(&pdo_list_lock, true);
350
351 le = pdo_list.Flink;
352 while (le != &pdo_list) {
353 pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
354
355 if (RtlCompareMemory(&pdode->uuid, &boot_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
356 if (!pdode->vde)
357 pdo_to_add = pdode->pdo;
358 else if (pdode->vde->device && !(pdode->vde->device->Flags & DO_SYSTEM_BOOT_PARTITION)) { // AddDevice has beaten us to it
359 NTSTATUS Status;
360
361 pdode->vde->device->Flags |= DO_SYSTEM_BOOT_PARTITION;
362 pdode->pdo->Flags |= DO_SYSTEM_BOOT_PARTITION;
363
364 Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, false);
365 if (!NT_SUCCESS(Status))
366 ERR("IoSetDeviceInterfaceState returned %08lx\n", Status);
367
368 Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, true);
369 if (!NT_SUCCESS(Status))
370 ERR("IoSetDeviceInterfaceState returned %08lx\n", Status);
371 }
372
373 break;
374 }
375
376 le = le->Flink;
377 }
378
379 ExReleaseResourceLite(&pdo_list_lock);
380
381 check_boot_options();
382
383 // If our FS depends on volumes that aren't there when we do our IoRegisterPlugPlayNotification calls
384 // in DriverEntry, bus_query_device_relations won't get called until it's too late. We need to do our
385 // own call to AddDevice here as a result. We need to clear the DOE_START_PENDING bits, or NtOpenFile
386 // will return STATUS_NO_SUCH_DEVICE.
387 if (pdo_to_add)
388 boot_add_device(pdo_to_add);
389 }
390