xref: /reactos/drivers/filesystems/btrfs/boot.c (revision 803b5e13)
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 #ifndef _MSC_VER
34 NTSTATUS RtlUnicodeStringPrintf(PUNICODE_STRING DestinationString, const WCHAR* pszFormat, ...); // not in mingw
35 #endif
36 
37 // Not in any headers? Windbg knows about it though.
38 #define DOE_START_PENDING 0x10
39 
40 // Just as much as we need - the version in mingw is truncated still further
41 typedef struct {
42     CSHORT Type;
43     USHORT Size;
44     PDEVICE_OBJECT DeviceObject;
45     ULONG PowerFlags;
46     void* Dope;
47     ULONG ExtensionFlags;
48 } DEVOBJ_EXTENSION2;
49 
50 static bool get_system_root_partition(uint32_t* disk_num, uint32_t* partition_num) {
51     NTSTATUS Status;
52     HANDLE h;
53     UNICODE_STRING us, target;
54     OBJECT_ATTRIBUTES objatt;
55     WCHAR* s;
56     ULONG retlen = 0, left;
57 
58     static const WCHAR system_root[] = L"\\SystemRoot";
59     static const WCHAR arc_prefix[] = L"\\ArcName\\multi(0)disk(0)rdisk(";
60     static const WCHAR arc_middle[] = L")partition(";
61 
62     us.Buffer = (WCHAR*)system_root;
63     us.Length = us.MaximumLength = sizeof(system_root) - sizeof(WCHAR);
64 
65     InitializeObjectAttributes(&objatt, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
66 
67     Status = ZwOpenSymbolicLinkObject(&h, GENERIC_READ, &objatt);
68     if (!NT_SUCCESS(Status)) {
69         ERR("ZwOpenSymbolicLinkObject returned %08x\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 %08x\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 %08x\n", Status);
99         NtClose(h);
100         ExFreePool(target.Buffer);
101         return false;
102     }
103 
104     NtClose(h);
105 
106     TRACE("system root is %.*S\n", target.Length / sizeof(WCHAR), target.Buffer);
107 
108     if (target.Length <= sizeof(arc_prefix) - sizeof(WCHAR) ||
109         RtlCompareMemory(target.Buffer, arc_prefix, sizeof(arc_prefix) - sizeof(WCHAR)) != sizeof(arc_prefix) - sizeof(WCHAR)) {
110         ExFreePool(target.Buffer);
111         return false;
112     }
113 
114     s = &target.Buffer[(sizeof(arc_prefix) / sizeof(WCHAR)) - 1];
115     left = ((target.Length - sizeof(arc_prefix)) / sizeof(WCHAR)) + 1;
116 
117     if (left == 0 || s[0] < '0' || s[0] > '9') {
118         ExFreePool(target.Buffer);
119         return false;
120     }
121 
122     *disk_num = 0;
123 
124     while (left > 0 && s[0] >= '0' && s[0] <= '9') {
125         *disk_num *= 10;
126         *disk_num += s[0] - '0';
127         s++;
128         left--;
129     }
130 
131     if (left <= (sizeof(arc_middle) / sizeof(WCHAR)) - 1 ||
132         RtlCompareMemory(s, arc_middle, sizeof(arc_middle) - sizeof(WCHAR)) != sizeof(arc_middle) - sizeof(WCHAR)) {
133         ExFreePool(target.Buffer);
134         return false;
135     }
136 
137     s = &s[(sizeof(arc_middle) / sizeof(WCHAR)) - 1];
138     left -= (sizeof(arc_middle) / sizeof(WCHAR)) - 1;
139 
140     if (left == 0 || s[0] < '0' || s[0] > '9') {
141         ExFreePool(target.Buffer);
142         return false;
143     }
144 
145     *partition_num = 0;
146 
147     while (left > 0 && s[0] >= '0' && s[0] <= '9') {
148         *partition_num *= 10;
149         *partition_num += s[0] - '0';
150         s++;
151         left--;
152     }
153 
154     ExFreePool(target.Buffer);
155 
156     return true;
157 }
158 
159 static void change_symlink(uint32_t disk_num, uint32_t partition_num, BTRFS_UUID* uuid) {
160     NTSTATUS Status;
161     UNICODE_STRING us, us2;
162     WCHAR symlink[60], target[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) + 36], *w;
163 #ifdef __REACTOS__
164     unsigned int i;
165 #endif
166 
167     us.Buffer = symlink;
168     us.Length = 0;
169     us.MaximumLength = sizeof(symlink);
170 
171     Status = RtlUnicodeStringPrintf(&us, L"\\Device\\Harddisk%u\\Partition%u", disk_num, partition_num);
172     if (!NT_SUCCESS(Status)) {
173         ERR("RtlUnicodeStringPrintf returned %08x\n", Status);
174         return;
175     }
176 
177     Status = IoDeleteSymbolicLink(&us);
178     if (!NT_SUCCESS(Status))
179         ERR("IoDeleteSymbolicLink returned %08x\n", Status);
180 
181     RtlCopyMemory(target, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR));
182 
183     w = &target[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1];
184 
185 #ifndef __REACTOS__
186     for (unsigned int i = 0; i < 16; i++) {
187 #else
188     for (i = 0; i < 16; i++) {
189 #endif
190         *w = hex_digit(uuid->uuid[i] >> 4); w++;
191         *w = hex_digit(uuid->uuid[i] & 0xf); w++;
192 
193         if (i == 3 || i == 5 || i == 7 || i == 9) {
194             *w = L'-';
195             w++;
196         }
197     }
198 
199     *w = L'}';
200 
201     us2.Buffer = target;
202     us2.Length = us2.MaximumLength = sizeof(target);
203 
204     Status = IoCreateSymbolicLink(&us, &us2);
205     if (!NT_SUCCESS(Status))
206         ERR("IoCreateSymbolicLink returned %08x\n", Status);
207 }
208 
209 static void mountmgr_notification(BTRFS_UUID* uuid) {
210     UNICODE_STRING mmdevpath;
211     NTSTATUS Status;
212     PFILE_OBJECT FileObject;
213     PDEVICE_OBJECT mountmgr;
214     ULONG mmtnlen;
215     MOUNTMGR_TARGET_NAME* mmtn;
216     WCHAR* w;
217 #ifdef __REACTOS__
218     unsigned int i;
219 #endif
220 
221     RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
222     Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
223     if (!NT_SUCCESS(Status)) {
224         ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
225         return;
226     }
227 
228     mmtnlen = offsetof(MOUNTMGR_TARGET_NAME, DeviceName[0]) + sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR));
229 
230     mmtn = ExAllocatePoolWithTag(NonPagedPool, mmtnlen, ALLOC_TAG);
231     if (!mmtn) {
232         ERR("out of memory\n");
233         return;
234     }
235 
236     mmtn->DeviceNameLength = sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR));
237 
238     RtlCopyMemory(mmtn->DeviceName, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR));
239 
240     w = &mmtn->DeviceName[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1];
241 
242 #ifndef __REACTOS__
243     for (unsigned int i = 0; i < 16; i++) {
244 #else
245     for (i = 0; i < 16; i++) {
246 #endif
247         *w = hex_digit(uuid->uuid[i] >> 4); w++;
248         *w = hex_digit(uuid->uuid[i] & 0xf); w++;
249 
250         if (i == 3 || i == 5 || i == 7 || i == 9) {
251             *w = L'-';
252             w++;
253         }
254     }
255 
256     *w = L'}';
257 
258     Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION, mmtn, mmtnlen, NULL, 0, false, NULL);
259     if (!NT_SUCCESS(Status)) {
260         ERR("IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION returned %08x\n", Status);
261         ExFreePool(mmtn);
262         return;
263     }
264 
265     ExFreePool(mmtn);
266 }
267 
268 /* If booting from Btrfs, Windows will pass the device object for the raw partition to
269  * mount_vol - which is no good to us, as we only use the \Device\Btrfs{} devices we
270  * create so that RAID works correctly.
271  * At the time check_system_root gets called, \SystemRoot is a symlink to the ARC device,
272  * e.g. \ArcName\multi(0)disk(0)rdisk(0)partition(1)\Windows. We can't change the symlink,
273  * as it gets clobbered by IopReassignSystemRoot shortly afterwards, and we can't touch
274  * the \ArcName symlinks as they haven't been created yet. Instead, we need to change the
275  * symlink \Device\HarddiskX\PartitionY, which is what the ArcName symlink will shortly
276  * point to.
277  */
278 void __stdcall check_system_root(PDRIVER_OBJECT DriverObject, PVOID Context, ULONG Count) {
279     uint32_t disk_num, partition_num;
280     LIST_ENTRY* le;
281     bool done = false;
282     PDEVICE_OBJECT pdo_to_add = NULL;
283 
284     TRACE("(%p, %p, %u)\n", DriverObject, Context, Count);
285 
286     // wait for any PNP notifications in progress to finish
287     ExAcquireResourceExclusiveLite(&boot_lock, TRUE);
288     ExReleaseResourceLite(&boot_lock);
289 
290     if (!get_system_root_partition(&disk_num, &partition_num))
291         return;
292 
293     TRACE("system boot partition is disk %u, partition %u\n", disk_num, partition_num);
294 
295     ExAcquireResourceSharedLite(&pdo_list_lock, true);
296 
297     le = pdo_list.Flink;
298     while (le != &pdo_list) {
299         LIST_ENTRY* le2;
300         pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
301 
302         ExAcquireResourceSharedLite(&pdode->child_lock, true);
303 
304         le2 = pdode->children.Flink;
305 
306         while (le2 != &pdode->children) {
307             volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
308 
309             if (vc->disk_num == disk_num && vc->part_num == partition_num) {
310                 change_symlink(disk_num, partition_num, &pdode->uuid);
311                 done = true;
312 
313                 if (!pdode->vde)
314                     pdo_to_add = pdode->pdo;
315 
316                 break;
317             }
318 
319             le2 = le2->Flink;
320         }
321 
322         if (done) {
323             le2 = pdode->children.Flink;
324 
325             while (le2 != &pdode->children) {
326                 volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
327 
328                 /* On Windows 7 we need to clear the DO_SYSTEM_BOOT_PARTITION flag of
329                  * all of our underlying partition objects - otherwise IopMountVolume
330                  * will bugcheck with UNMOUNTABLE_BOOT_VOLUME when it tries and fails
331                  * to mount one. */
332                 if (vc->devobj) {
333                     PDEVICE_OBJECT dev = vc->devobj;
334 
335                     ObReferenceObject(dev);
336 
337                     while (dev) {
338                         PDEVICE_OBJECT dev2 = IoGetLowerDeviceObject(dev);
339 
340                         dev->Flags &= ~DO_SYSTEM_BOOT_PARTITION;
341 
342                         ObDereferenceObject(dev);
343 
344                         dev = dev2;
345                     }
346                 }
347 
348                 le2 = le2->Flink;
349             }
350 
351             ExReleaseResourceLite(&pdode->child_lock);
352 
353             break;
354         }
355 
356         ExReleaseResourceLite(&pdode->child_lock);
357 
358         le = le->Flink;
359     }
360 
361     ExReleaseResourceLite(&pdo_list_lock);
362 
363     // If our FS depends on volumes that aren't there when we do our IoRegisterPlugPlayNotification calls
364     // in DriverEntry, bus_query_device_relations won't get called until it's too late. We need to do our
365     // own call to AddDevice here as a result. We need to clear the DOE_START_PENDING bits, or NtOpenFile
366     // will return STATUS_NO_SUCH_DEVICE.
367     if (pdo_to_add) {
368         pdo_device_extension* pdode = pdo_to_add->DeviceExtension;
369 
370         AddDevice(drvobj, pdo_to_add);
371 
372         // To stop Windows sneakily setting DOE_START_PENDING
373         pdode->dont_report = true;
374 
375         if (pdo_to_add->DeviceObjectExtension) {
376             ((DEVOBJ_EXTENSION2*)pdo_to_add->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING;
377 
378             if (pdode && pdode->vde && pdode->vde->device)
379                 ((DEVOBJ_EXTENSION2*)pdode->vde->device->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING;
380         }
381 
382         mountmgr_notification(&pdode->uuid);
383     }
384 }
385