xref: /reactos/drivers/filesystems/btrfs/boot.c (revision c982533e)
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