xref: /reactos/drivers/filesystems/btrfs/search.c (revision 5cadc268)
1 /* Copyright (c) Mark Harmstone 2016-17
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 #include <ntddk.h>
21 #include <ntifs.h>
22 #include <mountmgr.h>
23 #include <windef.h>
24 #include <ntddstor.h>
25 #include <ntdddisk.h>
26 #include <ntddvol.h>
27 
28 #include <initguid.h>
29 #include <wdmguid.h>
30 #include <ioevent.h>
31 
32 extern ERESOURCE pdo_list_lock;
33 extern LIST_ENTRY pdo_list;
34 extern UNICODE_STRING registry_path;
35 extern KEVENT mountmgr_thread_event;
36 extern HANDLE mountmgr_thread_handle;
37 extern bool shutting_down;
38 extern PDEVICE_OBJECT busobj;
39 extern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;
40 extern ERESOURCE boot_lock;
41 extern PDRIVER_OBJECT drvobj;
42 
43 typedef void (*pnp_callback)(PUNICODE_STRING devpath);
44 
45 #ifndef __REACTOS__
46 // not in mingw yet
47 #ifndef _MSC_VER
48 DEFINE_GUID(GUID_IO_VOLUME_FVE_STATUS_CHANGE, 0x062998b2, 0xee1f, 0x4b6a, 0xb8, 0x57, 0xe7, 0x6c, 0xbb, 0xe9, 0xa6, 0xda);
49 #endif
50 #endif // __REACTOS__
51 
52 extern PDEVICE_OBJECT master_devobj;
53 
54 typedef struct {
55     LIST_ENTRY list_entry;
56     PDEVICE_OBJECT devobj;
57     void* notification_entry;
58     UNICODE_STRING devpath;
59     WCHAR buf[1];
60 } fve_data;
61 
62 static LIST_ENTRY fve_data_list = { &fve_data_list, &fve_data_list };
63 KSPIN_LOCK fve_data_lock;
64 
65 static bool fs_ignored(BTRFS_UUID* uuid) {
66     UNICODE_STRING path, ignoreus;
67     NTSTATUS Status;
68     OBJECT_ATTRIBUTES oa;
69     KEY_VALUE_FULL_INFORMATION* kvfi;
70     ULONG dispos, retlen, kvfilen, i, j;
71     HANDLE h;
72     bool ret = false;
73 
74     path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));
75 
76     path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);
77     if (!path.Buffer) {
78         ERR("out of memory\n");
79         return false;
80     }
81 
82     RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);
83 
84     i = registry_path.Length / sizeof(WCHAR);
85 
86     path.Buffer[i] = '\\';
87     i++;
88 
89     for (j = 0; j < 16; j++) {
90         path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);
91         path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);
92 
93         i += 2;
94 
95         if (j == 3 || j == 5 || j == 7 || j == 9) {
96             path.Buffer[i] = '-';
97             i++;
98         }
99     }
100 
101     InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
102 
103     Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);
104 
105     if (!NT_SUCCESS(Status)) {
106         TRACE("ZwCreateKey returned %08lx\n", Status);
107         ExFreePool(path.Buffer);
108         return false;
109     }
110 
111     RtlInitUnicodeString(&ignoreus, L"Ignore");
112 
113     kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR));
114     kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
115     if (!kvfi) {
116         ERR("out of memory\n");
117         ZwClose(h);
118         ExFreePool(path.Buffer);
119         return false;
120     }
121 
122     Status = ZwQueryValueKey(h, &ignoreus, KeyValueFullInformation, kvfi, kvfilen, &retlen);
123     if (NT_SUCCESS(Status)) {
124         if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) {
125             uint32_t* pr = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset);
126 
127             ret = *pr;
128         }
129     }
130 
131     ZwClose(h);
132     ExFreePool(kvfi);
133     ExFreePool(path.Buffer);
134 
135     return ret;
136 }
137 
138 typedef struct {
139     PIO_WORKITEM work_item;
140     PFILE_OBJECT fileobj;
141     PDEVICE_OBJECT devobj;
142     UNICODE_STRING devpath;
143     WCHAR buf[1];
144 } fve_callback_context;
145 
146 _Function_class_(IO_WORKITEM_ROUTINE)
147 static void __stdcall fve_callback(PDEVICE_OBJECT DeviceObject, PVOID con) {
148     fve_callback_context* ctx = con;
149 
150     UNUSED(DeviceObject);
151 
152     if (volume_arrival(&ctx->devpath, true)) {
153         KIRQL irql;
154         LIST_ENTRY* le;
155         fve_data* d = NULL;
156 
157         // volume no longer locked - unregister notification
158 
159         KeAcquireSpinLock(&fve_data_lock, &irql);
160 
161         le = fve_data_list.Flink;
162         while (le != &fve_data_list) {
163             fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry);
164 
165             if (d2->devobj == ctx->devobj) {
166                 RemoveEntryList(&d2->list_entry);
167                 d = d2;
168                 break;
169             }
170 
171             le = le->Flink;
172         }
173 
174         KeReleaseSpinLock(&fve_data_lock, irql);
175 
176         if (d) {
177             IoUnregisterPlugPlayNotification(d->notification_entry);
178             ExFreePool(d);
179         }
180     }
181 
182     IoFreeWorkItem(ctx->work_item);
183     ExFreePool(ctx);
184 }
185 
186 static NTSTATUS __stdcall event_notification(PVOID NotificationStructure, PVOID Context) {
187     TARGET_DEVICE_REMOVAL_NOTIFICATION* tdrn = NotificationStructure;
188     PDEVICE_OBJECT devobj = Context;
189     PIO_WORKITEM work_item;
190     fve_callback_context* ctx;
191     LIST_ENTRY* le;
192     KIRQL irql;
193 
194     if (RtlCompareMemory(&tdrn->Event, &GUID_IO_VOLUME_FVE_STATUS_CHANGE, sizeof(GUID)) != sizeof(GUID))
195         return STATUS_SUCCESS;
196 
197     /* The FVE event has trailing data, presumably telling us whether the volume has
198      * been unlocked or whatever, but unfortunately it's undocumented! */
199 
200     work_item = IoAllocateWorkItem(master_devobj);
201     if (!work_item) {
202         ERR("out of memory\n");
203         return STATUS_SUCCESS;
204     }
205 
206     KeAcquireSpinLock(&fve_data_lock, &irql);
207 
208     le = fve_data_list.Flink;
209     while (le != &fve_data_list) {
210         fve_data* d = CONTAINING_RECORD(le, fve_data, list_entry);
211 
212         if (d->devobj == devobj) {
213             ctx = ExAllocatePoolWithTag(NonPagedPool, offsetof(fve_callback_context, buf) + d->devpath.Length,
214                                         ALLOC_TAG);
215 
216             if (!ctx) {
217                 KeReleaseSpinLock(&fve_data_lock, irql);
218                 ERR("out of memory\n");
219                 IoFreeWorkItem(work_item);
220                 return STATUS_SUCCESS;
221             }
222 
223             RtlCopyMemory(ctx->buf, d->devpath.Buffer, d->devpath.Length);
224             ctx->devpath.Length = ctx->devpath.MaximumLength = d->devpath.Length;
225 
226             KeReleaseSpinLock(&fve_data_lock, irql);
227 
228             ctx->devpath.Buffer = ctx->buf;
229 
230             ctx->fileobj = tdrn->FileObject;
231             ctx->devobj = devobj;
232             ctx->work_item = work_item;
233 
234             IoQueueWorkItem(work_item, fve_callback, DelayedWorkQueue, ctx);
235 
236             return STATUS_SUCCESS;
237         }
238 
239         le = le->Flink;
240     }
241 
242     KeReleaseSpinLock(&fve_data_lock, irql);
243 
244     IoFreeWorkItem(work_item);
245 
246     return STATUS_SUCCESS;
247 }
248 
249 static void register_fve_callback(PDEVICE_OBJECT devobj, PFILE_OBJECT fileobj,
250                                   PUNICODE_STRING devpath) {
251     NTSTATUS Status;
252     KIRQL irql;
253     LIST_ENTRY* le;
254 
255     fve_data* d = ExAllocatePoolWithTag(NonPagedPool, offsetof(fve_data, buf) + devpath->Length, ALLOC_TAG);
256     if (!d) {
257         ERR("out of memory\n");
258         return;
259     }
260 
261     d->devpath.Buffer = d->buf;
262     d->devpath.Length = d->devpath.MaximumLength = devpath->Length;
263     RtlCopyMemory(d->devpath.Buffer, devpath->Buffer, devpath->Length);
264 
265     KeAcquireSpinLock(&fve_data_lock, &irql);
266 
267     le = fve_data_list.Flink;
268     while (le != &fve_data_list) {
269         fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry);
270 
271         if (d2->devobj == devobj) {
272             KeReleaseSpinLock(&fve_data_lock, irql);
273             ExFreePool(d);
274             return;
275         }
276 
277         le = le->Flink;
278     }
279 
280     KeReleaseSpinLock(&fve_data_lock, irql);
281 
282     Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, fileobj, drvobj, event_notification,
283                                             devobj, &d->notification_entry);
284     if (!NT_SUCCESS(Status)) {
285         ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status);
286         return;
287     }
288 
289     KeAcquireSpinLock(&fve_data_lock, &irql);
290 
291     le = fve_data_list.Flink;
292     while (le != &fve_data_list) {
293         fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry);
294 
295         if (d2->devobj == devobj) {
296             KeReleaseSpinLock(&fve_data_lock, irql);
297             IoUnregisterPlugPlayNotification(d->notification_entry);
298             ExFreePool(d);
299             return;
300         }
301 
302         le = le->Flink;
303     }
304 
305     d->devobj = devobj;
306     InsertTailList(&fve_data_list, &d->list_entry);
307 
308     KeReleaseSpinLock(&fve_data_lock, irql);
309 }
310 
311 static bool test_vol(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject,
312                      PUNICODE_STRING devpath, DWORD disk_num, DWORD part_num, uint64_t length,
313                      bool fve_callback) {
314     NTSTATUS Status;
315     ULONG toread;
316     uint8_t* data = NULL;
317     uint32_t sector_size;
318     bool ret = true;
319 
320     TRACE("%.*S\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);
321 
322     sector_size = DeviceObject->SectorSize;
323 
324     if (sector_size == 0) {
325         DISK_GEOMETRY geometry;
326         IO_STATUS_BLOCK iosb;
327 
328         Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,
329                            &geometry, sizeof(DISK_GEOMETRY), true, &iosb);
330 
331         if (!NT_SUCCESS(Status)) {
332             ERR("%.*S had a sector size of 0, and IOCTL_DISK_GET_DRIVE_GEOMETRY returned %08lx\n",
333                 (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer, Status);
334             goto deref;
335         }
336 
337         if (iosb.Information < sizeof(DISK_GEOMETRY)) {
338             ERR("%.*S: IOCTL_DISK_GET_DRIVE_GEOMETRY returned %Iu bytes, expected %Iu\n",
339                 (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer, iosb.Information, sizeof(DISK_GEOMETRY));
340         }
341 
342         sector_size = geometry.BytesPerSector;
343 
344         if (sector_size == 0) {
345             ERR("%.*S had a sector size of 0\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);
346             goto deref;
347         }
348     }
349 
350     toread = (ULONG)sector_align(sizeof(superblock), sector_size);
351     data = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);
352     if (!data) {
353         ERR("out of memory\n");
354         goto deref;
355     }
356 
357     Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[0], toread, data, true);
358 
359     if (NT_SUCCESS(Status) && ((superblock*)data)->magic == BTRFS_MAGIC) {
360         superblock* sb = (superblock*)data;
361 
362         if (check_superblock_checksum(sb)) {
363             TRACE("volume found\n");
364 
365             if (length >= superblock_addrs[1] + toread) {
366                 ULONG i = 1;
367 
368                 superblock* sb2 = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);
369                 if (!sb2) {
370                     ERR("out of memory\n");
371                     goto deref;
372                 }
373 
374                 while (superblock_addrs[i] > 0 && length >= superblock_addrs[i] + toread) {
375                     Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[i], toread, (PUCHAR)sb2, true);
376 
377                     if (NT_SUCCESS(Status) && sb2->magic == BTRFS_MAGIC) {
378                         if (check_superblock_checksum(sb2) && sb2->generation > sb->generation)
379                             RtlCopyMemory(sb, sb2, toread);
380                     }
381 
382                     i++;
383                 }
384 
385                 ExFreePool(sb2);
386             }
387 
388             if (!fs_ignored(&sb->uuid)) {
389                 DeviceObject->Flags &= ~DO_VERIFY_VOLUME;
390                 add_volume_device(sb, devpath, length, disk_num, part_num);
391             }
392         }
393     } else if (Status == STATUS_FVE_LOCKED_VOLUME) {
394         if (fve_callback)
395             ret = false;
396         else
397             register_fve_callback(DeviceObject, FileObject, devpath);
398     }
399 
400 deref:
401     if (data)
402         ExFreePool(data);
403 
404     return ret;
405 }
406 
407 NTSTATUS remove_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) {
408     NTSTATUS Status;
409     MOUNTMGR_MOUNT_POINT* mmp;
410     ULONG mmpsize;
411     MOUNTMGR_MOUNT_POINTS mmps1, *mmps2;
412 
413     TRACE("removing drive letter\n");
414 
415     mmpsize = sizeof(MOUNTMGR_MOUNT_POINT) + devpath->Length;
416 
417     mmp = ExAllocatePoolWithTag(PagedPool, mmpsize, ALLOC_TAG);
418     if (!mmp) {
419         ERR("out of memory\n");
420         return STATUS_INSUFFICIENT_RESOURCES;
421     }
422 
423     RtlZeroMemory(mmp, mmpsize);
424 
425     mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);
426     mmp->DeviceNameLength = devpath->Length;
427     RtlCopyMemory(&mmp[1], devpath->Buffer, devpath->Length);
428 
429     Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, &mmps1, sizeof(MOUNTMGR_MOUNT_POINTS), false, NULL);
430 
431     if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {
432         ERR("IOCTL_MOUNTMGR_DELETE_POINTS 1 returned %08lx\n", Status);
433         ExFreePool(mmp);
434         return Status;
435     }
436 
437     if (Status != STATUS_BUFFER_OVERFLOW || mmps1.Size == 0) {
438         ExFreePool(mmp);
439         return STATUS_NOT_FOUND;
440     }
441 
442     mmps2 = ExAllocatePoolWithTag(PagedPool, mmps1.Size, ALLOC_TAG);
443     if (!mmps2) {
444         ERR("out of memory\n");
445         ExFreePool(mmp);
446         return STATUS_INSUFFICIENT_RESOURCES;
447     }
448 
449     Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, mmps2, mmps1.Size, false, NULL);
450 
451     if (!NT_SUCCESS(Status))
452         ERR("IOCTL_MOUNTMGR_DELETE_POINTS 2 returned %08lx\n", Status);
453 
454     ExFreePool(mmps2);
455     ExFreePool(mmp);
456 
457     return Status;
458 }
459 
460 void disk_arrival(PUNICODE_STRING devpath) {
461     PFILE_OBJECT fileobj;
462     PDEVICE_OBJECT devobj;
463     NTSTATUS Status;
464     STORAGE_DEVICE_NUMBER sdn;
465     ULONG dlisize;
466     DRIVE_LAYOUT_INFORMATION_EX* dli = NULL;
467     IO_STATUS_BLOCK iosb;
468     GET_LENGTH_INFORMATION gli;
469 
470     ExAcquireResourceSharedLite(&boot_lock, TRUE);
471 
472     Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj);
473     if (!NT_SUCCESS(Status)) {
474         ExReleaseResourceLite(&boot_lock);
475         ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
476         return;
477     }
478 
479     dlisize = 0;
480 
481     do {
482         dlisize += 1024;
483 
484         if (dli)
485             ExFreePool(dli);
486 
487         dli = ExAllocatePoolWithTag(PagedPool, dlisize, ALLOC_TAG);
488         if (!dli) {
489             ERR("out of memory\n");
490             goto end;
491         }
492 
493         Status = dev_ioctl(devobj, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,
494                            dli, dlisize, true, &iosb);
495     } while (Status == STATUS_BUFFER_TOO_SMALL);
496 
497     // only consider disk as a potential filesystem if it has no partitions
498     if (NT_SUCCESS(Status) && dli->PartitionCount > 0) {
499         ExFreePool(dli);
500         goto end;
501     }
502 
503     ExFreePool(dli);
504 
505     Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,
506                         &gli, sizeof(gli), true, NULL);
507 
508     if (!NT_SUCCESS(Status)) {
509         ERR("error reading length information: %08lx\n", Status);
510         goto end;
511     }
512 
513     Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
514                        &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);
515     if (!NT_SUCCESS(Status)) {
516         TRACE("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\n", Status);
517         sdn.DeviceNumber = 0xffffffff;
518         sdn.PartitionNumber = 0;
519     } else
520         TRACE("DeviceType = %lu, DeviceNumber = %lu, PartitionNumber = %lu\n", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber);
521 
522     test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber,
523              gli.Length.QuadPart, false);
524 
525 end:
526     ObDereferenceObject(fileobj);
527 
528     ExReleaseResourceLite(&boot_lock);
529 }
530 
531 void remove_volume_child(_Inout_ _Requires_exclusive_lock_held_(_Curr_->child_lock) _Releases_exclusive_lock_(_Curr_->child_lock) _In_ volume_device_extension* vde,
532                          _In_ volume_child* vc, _In_ bool skip_dev) {
533     NTSTATUS Status;
534     pdo_device_extension* pdode = vde->pdode;
535     device_extension* Vcb = vde->mounted_device ? vde->mounted_device->DeviceExtension : NULL;
536 
537     if (vc->notification_entry) {
538         if (fIoUnregisterPlugPlayNotificationEx)
539             fIoUnregisterPlugPlayNotificationEx(vc->notification_entry);
540         else
541             IoUnregisterPlugPlayNotification(vc->notification_entry);
542     }
543 
544     if (vde->mounted_device && (!Vcb || !Vcb->options.allow_degraded)) {
545         Status = pnp_surprise_removal(vde->mounted_device, NULL);
546         if (!NT_SUCCESS(Status))
547             ERR("pnp_surprise_removal returned %08lx\n", Status);
548     }
549 
550     if (!Vcb || !Vcb->options.allow_degraded) {
551         Status = IoSetDeviceInterfaceState(&vde->bus_name, false);
552         if (!NT_SUCCESS(Status))
553             WARN("IoSetDeviceInterfaceState returned %08lx\n", Status);
554     }
555 
556     if (pdode->children_loaded > 0) {
557         UNICODE_STRING mmdevpath;
558         PFILE_OBJECT FileObject;
559         PDEVICE_OBJECT mountmgr;
560         LIST_ENTRY* le;
561 
562         if (!Vcb || !Vcb->options.allow_degraded) {
563             RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
564             Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
565             if (!NT_SUCCESS(Status))
566                 ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
567             else {
568                 le = pdode->children.Flink;
569 
570                 while (le != &pdode->children) {
571                     volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);
572 
573                     if (vc2->had_drive_letter) { // re-add entry to mountmgr
574                         MOUNTDEV_NAME mdn;
575 
576                         Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);
577                         if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
578                             ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status);
579                         else {
580                             MOUNTDEV_NAME* mdn2;
581                             ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;
582 
583                             mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG);
584                             if (!mdn2)
585                                 ERR("out of memory\n");
586                             else {
587                                 Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);
588                                 if (!NT_SUCCESS(Status))
589                                     ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status);
590                                 else {
591                                     UNICODE_STRING name;
592 
593                                     name.Buffer = mdn2->Name;
594                                     name.Length = name.MaximumLength = mdn2->NameLength;
595 
596                                     Status = mountmgr_add_drive_letter(mountmgr, &name);
597                                     if (!NT_SUCCESS(Status))
598                                         WARN("mountmgr_add_drive_letter returned %08lx\n", Status);
599                                 }
600 
601                                 ExFreePool(mdn2);
602                             }
603                         }
604                     }
605 
606                     le = le->Flink;
607                 }
608 
609                 ObDereferenceObject(FileObject);
610             }
611         } else if (!skip_dev) {
612             ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);
613 
614             le = Vcb->devices.Flink;
615             while (le != &Vcb->devices) {
616                 device* dev = CONTAINING_RECORD(le, device, list_entry);
617 
618                 if (dev->devobj == vc->devobj) {
619                     dev->devobj = NULL; // mark as missing
620                     break;
621                 }
622 
623                 le = le->Flink;
624             }
625 
626             ExReleaseResourceLite(&Vcb->tree_lock);
627         }
628 
629         if (vde->device->Characteristics & FILE_REMOVABLE_MEDIA) {
630             vde->device->Characteristics &= ~FILE_REMOVABLE_MEDIA;
631 
632             le = pdode->children.Flink;
633             while (le != &pdode->children) {
634                 volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);
635 
636                 if (vc2 != vc && vc2->devobj->Characteristics & FILE_REMOVABLE_MEDIA) {
637                     vde->device->Characteristics |= FILE_REMOVABLE_MEDIA;
638                     break;
639                 }
640 
641                 le = le->Flink;
642             }
643         }
644     }
645 
646     ObDereferenceObject(vc->fileobj);
647     ExFreePool(vc->pnp_name.Buffer);
648     RemoveEntryList(&vc->list_entry);
649     ExFreePool(vc);
650 
651     pdode->children_loaded--;
652 
653     if (pdode->children_loaded == 0) { // remove volume device
654         bool remove = false;
655 
656         RemoveEntryList(&pdode->list_entry);
657 
658         vde->removing = true;
659 
660         Status = IoSetDeviceInterfaceState(&vde->bus_name, false);
661         if (!NT_SUCCESS(Status))
662             WARN("IoSetDeviceInterfaceState returned %08lx\n", Status);
663 
664         if (vde->pdo->AttachedDevice)
665             IoDetachDevice(vde->pdo);
666 
667         if (vde->open_count == 0)
668             remove = true;
669 
670         ExReleaseResourceLite(&pdode->child_lock);
671 
672         if (!no_pnp) {
673             bus_device_extension* bde = busobj->DeviceExtension;
674 
675             IoInvalidateDeviceRelations(bde->buspdo, BusRelations);
676         }
677 
678         if (remove) {
679             if (vde->name.Buffer)
680                 ExFreePool(vde->name.Buffer);
681 
682             if (Vcb)
683                 Vcb->vde = NULL;
684 
685             ExDeleteResourceLite(&pdode->child_lock);
686 
687             IoDeleteDevice(vde->device);
688         }
689     } else
690         ExReleaseResourceLite(&pdode->child_lock);
691 }
692 
693 bool volume_arrival(PUNICODE_STRING devpath, bool fve_callback) {
694     STORAGE_DEVICE_NUMBER sdn;
695     PFILE_OBJECT fileobj;
696     PDEVICE_OBJECT devobj;
697     GET_LENGTH_INFORMATION gli;
698     NTSTATUS Status;
699     bool ret = true;
700 
701     TRACE("%.*S\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);
702 
703     ExAcquireResourceSharedLite(&boot_lock, TRUE);
704 
705     Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj);
706     if (!NT_SUCCESS(Status)) {
707         ExReleaseResourceLite(&boot_lock);
708         ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
709         return false;
710     }
711 
712     // make sure we're not processing devices we've created ourselves
713 
714     if (devobj->DriverObject == drvobj)
715         goto end;
716 
717     Status = dev_ioctl(devobj, IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, true, NULL);
718     if (!NT_SUCCESS(Status))
719         TRACE("IOCTL_VOLUME_ONLINE returned %08lx\n", Status);
720 
721     Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL);
722     if (!NT_SUCCESS(Status)) {
723         ERR("IOCTL_DISK_GET_LENGTH_INFO returned %08lx\n", Status);
724         goto end;
725     }
726 
727     Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
728                        &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);
729     if (!NT_SUCCESS(Status)) {
730         TRACE("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\n", Status);
731         sdn.DeviceNumber = 0xffffffff;
732         sdn.PartitionNumber = 0;
733     } else
734         TRACE("DeviceType = %lu, DeviceNumber = %lu, PartitionNumber = %lu\n", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber);
735 
736     // If we've just added a partition to a whole-disk filesystem, unmount it
737     if (sdn.DeviceNumber != 0xffffffff && sdn.PartitionNumber != 0) {
738         LIST_ENTRY* le;
739 
740         ExAcquireResourceExclusiveLite(&pdo_list_lock, true);
741 
742         le = pdo_list.Flink;
743         while (le != &pdo_list) {
744             pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
745             LIST_ENTRY* le2;
746             bool changed = false;
747 
748             if (pdode->vde) {
749                 ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
750 
751                 le2 = pdode->children.Flink;
752                 while (le2 != &pdode->children) {
753                     volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
754                     LIST_ENTRY* le3 = le2->Flink;
755 
756                     if (vc->disk_num == sdn.DeviceNumber && vc->part_num == 0) {
757                         TRACE("removing device\n");
758 
759                         remove_volume_child(pdode->vde, vc, false);
760                         changed = true;
761 
762                         break;
763                     }
764 
765                     le2 = le3;
766                 }
767 
768                 if (!changed)
769                     ExReleaseResourceLite(&pdode->child_lock);
770                 else
771                     break;
772             }
773 
774             le = le->Flink;
775         }
776 
777         ExReleaseResourceLite(&pdo_list_lock);
778     }
779 
780     ret = test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber,
781                    gli.Length.QuadPart, fve_callback);
782 
783 end:
784     ObDereferenceObject(fileobj);
785 
786     ExReleaseResourceLite(&boot_lock);
787 
788     return ret;
789 }
790 
791 static void volume_arrival2(PUNICODE_STRING devpath) {
792     volume_arrival(devpath, false);
793 }
794 
795 void volume_removal(PUNICODE_STRING devpath) {
796     LIST_ENTRY* le;
797     UNICODE_STRING devpath2;
798 
799     TRACE("%.*S\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);
800 
801     devpath2 = *devpath;
802 
803     if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\' && (devpath->Buffer[1] == '\\' || devpath->Buffer[1] == '?') &&
804         devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\') {
805         devpath2.Buffer = &devpath2.Buffer[3];
806         devpath2.Length -= 3 * sizeof(WCHAR);
807         devpath2.MaximumLength -= 3 * sizeof(WCHAR);
808     }
809 
810     ExAcquireResourceExclusiveLite(&pdo_list_lock, true);
811 
812     le = pdo_list.Flink;
813     while (le != &pdo_list) {
814         pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
815         LIST_ENTRY* le2;
816         bool changed = false;
817 
818         if (pdode->vde) {
819             ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
820 
821             le2 = pdode->children.Flink;
822             while (le2 != &pdode->children) {
823                 volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
824                 LIST_ENTRY* le3 = le2->Flink;
825 
826                 if (vc->pnp_name.Length == devpath2.Length && RtlCompareMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length) == devpath2.Length) {
827                     TRACE("removing device\n");
828 
829                     if (!vc->boot_volume) {
830                         remove_volume_child(pdode->vde, vc, false);
831                         changed = true;
832                     }
833 
834                     break;
835                 }
836 
837                 le2 = le3;
838             }
839 
840             if (!changed)
841                 ExReleaseResourceLite(&pdode->child_lock);
842             else
843                 break;
844         }
845 
846         le = le->Flink;
847     }
848 
849     ExReleaseResourceLite(&pdo_list_lock);
850 }
851 
852 typedef struct {
853     UNICODE_STRING name;
854     pnp_callback func;
855     PIO_WORKITEM work_item;
856 } pnp_callback_context;
857 
858 _Function_class_(IO_WORKITEM_ROUTINE)
859 static void __stdcall do_pnp_callback(PDEVICE_OBJECT DeviceObject, PVOID con) {
860     pnp_callback_context* context = con;
861 
862     UNUSED(DeviceObject);
863 
864     context->func(&context->name);
865 
866     if (context->name.Buffer)
867         ExFreePool(context->name.Buffer);
868 
869     IoFreeWorkItem(context->work_item);
870 
871     ExFreePool(context);
872 }
873 
874 static void enqueue_pnp_callback(PUNICODE_STRING name, pnp_callback func) {
875     PIO_WORKITEM work_item;
876     pnp_callback_context* context;
877 
878     work_item = IoAllocateWorkItem(master_devobj);
879     if (!work_item) {
880         ERR("out of memory\n");
881         return;
882     }
883 
884     context = ExAllocatePoolWithTag(PagedPool, sizeof(pnp_callback_context), ALLOC_TAG);
885 
886     if (!context) {
887         ERR("out of memory\n");
888         IoFreeWorkItem(work_item);
889         return;
890     }
891 
892     if (name->Length > 0) {
893         context->name.Buffer = ExAllocatePoolWithTag(PagedPool, name->Length, ALLOC_TAG);
894         if (!context->name.Buffer) {
895             ERR("out of memory\n");
896             ExFreePool(context);
897             IoFreeWorkItem(work_item);
898             return;
899         }
900 
901         RtlCopyMemory(context->name.Buffer, name->Buffer, name->Length);
902         context->name.Length = context->name.MaximumLength = name->Length;
903     } else {
904         context->name.Length = context->name.MaximumLength = 0;
905         context->name.Buffer = NULL;
906     }
907 
908     context->func = func;
909     context->work_item = work_item;
910 
911     IoQueueWorkItem(work_item, do_pnp_callback, DelayedWorkQueue, context);
912 }
913 
914 _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)
915 NTSTATUS __stdcall volume_notification(PVOID NotificationStructure, PVOID Context) {
916     DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure;
917 
918     UNUSED(Context);
919 
920     if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
921         enqueue_pnp_callback(dicn->SymbolicLinkName, volume_arrival2);
922     else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))
923         enqueue_pnp_callback(dicn->SymbolicLinkName, volume_removal);
924 
925     return STATUS_SUCCESS;
926 }
927 
928 _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)
929 NTSTATUS __stdcall pnp_notification(PVOID NotificationStructure, PVOID Context) {
930     DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure;
931 
932     UNUSED(Context);
933 
934     if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
935         enqueue_pnp_callback(dicn->SymbolicLinkName, disk_arrival);
936     else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))
937         enqueue_pnp_callback(dicn->SymbolicLinkName, volume_removal);
938 
939     return STATUS_SUCCESS;
940 }
941 
942 static void mountmgr_process_drive(PDEVICE_OBJECT mountmgr, PUNICODE_STRING device_name) {
943     NTSTATUS Status;
944     LIST_ENTRY* le;
945     bool need_remove = false;
946     volume_child* vc2 = NULL;
947 
948     ExAcquireResourceSharedLite(&pdo_list_lock, true);
949 
950     le = pdo_list.Flink;
951     while (le != &pdo_list) {
952         pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
953         LIST_ENTRY* le2;
954 
955         ExAcquireResourceSharedLite(&pdode->child_lock, true);
956 
957         le2 = pdode->children.Flink;
958 
959         while (le2 != &pdode->children) {
960             volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
961 
962             if (vc->devobj) {
963                 MOUNTDEV_NAME mdn;
964 
965                 Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);
966                 if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
967                     ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status);
968                 else {
969                     MOUNTDEV_NAME* mdn2;
970                     ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;
971 
972                     mdn2 = ExAllocatePoolWithTag(NonPagedPool, mdnsize, ALLOC_TAG);
973                     if (!mdn2)
974                         ERR("out of memory\n");
975                     else {
976                         Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);
977                         if (!NT_SUCCESS(Status))
978                             ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status);
979                         else {
980                             if (mdn2->NameLength == device_name->Length && RtlCompareMemory(mdn2->Name, device_name->Buffer, device_name->Length) == device_name->Length) {
981                                 vc2 = vc;
982                                 need_remove = true;
983                                 break;
984                             }
985                         }
986 
987                         ExFreePool(mdn2);
988                     }
989                 }
990             }
991 
992             le2 = le2->Flink;
993         }
994 
995         ExReleaseResourceLite(&pdode->child_lock);
996 
997         if (need_remove)
998             break;
999 
1000         le = le->Flink;
1001     }
1002 
1003     ExReleaseResourceLite(&pdo_list_lock);
1004 
1005     if (need_remove) {
1006         Status = remove_drive_letter(mountmgr, device_name);
1007         if (!NT_SUCCESS(Status))
1008             ERR("remove_drive_letter returned %08lx\n", Status);
1009         else
1010             vc2->had_drive_letter = true;
1011     }
1012 }
1013 
1014 static void mountmgr_updated(PDEVICE_OBJECT mountmgr, MOUNTMGR_MOUNT_POINTS* mmps) {
1015     ULONG i;
1016 
1017     static const WCHAR pref[] = L"\\DosDevices\\";
1018 
1019     for (i = 0; i < mmps->NumberOfMountPoints; i++) {
1020         UNICODE_STRING symlink, device_name;
1021 
1022         if (mmps->MountPoints[i].SymbolicLinkNameOffset != 0) {
1023             symlink.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].SymbolicLinkNameOffset);
1024             symlink.Length = symlink.MaximumLength = mmps->MountPoints[i].SymbolicLinkNameLength;
1025         } else {
1026             symlink.Buffer = NULL;
1027             symlink.Length = symlink.MaximumLength = 0;
1028         }
1029 
1030         if (mmps->MountPoints[i].DeviceNameOffset != 0) {
1031             device_name.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].DeviceNameOffset);
1032             device_name.Length = device_name.MaximumLength = mmps->MountPoints[i].DeviceNameLength;
1033         } else {
1034             device_name.Buffer = NULL;
1035             device_name.Length = device_name.MaximumLength = 0;
1036         }
1037 
1038         if (symlink.Length > sizeof(pref) - sizeof(WCHAR) &&
1039             RtlCompareMemory(symlink.Buffer, pref, sizeof(pref) - sizeof(WCHAR)) == sizeof(pref) - sizeof(WCHAR))
1040             mountmgr_process_drive(mountmgr, &device_name);
1041     }
1042 }
1043 
1044 _Function_class_(KSTART_ROUTINE)
1045 void __stdcall mountmgr_thread(_In_ void* context) {
1046     UNICODE_STRING mmdevpath;
1047     NTSTATUS Status;
1048     PFILE_OBJECT FileObject;
1049     PDEVICE_OBJECT mountmgr;
1050     MOUNTMGR_CHANGE_NOTIFY_INFO mcni;
1051 
1052     UNUSED(context);
1053 
1054     RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
1055     Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
1056     if (!NT_SUCCESS(Status)) {
1057         ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
1058         return;
1059     }
1060 
1061     mcni.EpicNumber = 0;
1062 
1063     while (true) {
1064         PIRP Irp;
1065         MOUNTMGR_MOUNT_POINT mmp;
1066         MOUNTMGR_MOUNT_POINTS mmps;
1067         IO_STATUS_BLOCK iosb;
1068 
1069         KeClearEvent(&mountmgr_thread_event);
1070 
1071         Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_CHANGE_NOTIFY, mountmgr, &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO),
1072                                             &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO), false, &mountmgr_thread_event, &iosb);
1073 
1074         if (!Irp) {
1075             ERR("out of memory\n");
1076             break;
1077         }
1078 
1079         Status = IoCallDriver(mountmgr, Irp);
1080 
1081         if (Status == STATUS_PENDING) {
1082             KeWaitForSingleObject(&mountmgr_thread_event, Executive, KernelMode, false, NULL);
1083             Status = iosb.Status;
1084         }
1085 
1086         if (shutting_down)
1087             break;
1088 
1089         if (!NT_SUCCESS(Status)) {
1090             ERR("IOCTL_MOUNTMGR_CHANGE_NOTIFY returned %08lx\n", Status);
1091             break;
1092         }
1093 
1094         TRACE("mountmgr changed\n");
1095 
1096         RtlZeroMemory(&mmp, sizeof(MOUNTMGR_MOUNT_POINT));
1097 
1098         Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), &mmps, sizeof(MOUNTMGR_MOUNT_POINTS),
1099                            false, NULL);
1100 
1101         if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
1102             ERR("IOCTL_MOUNTMGR_QUERY_POINTS 1 returned %08lx\n", Status);
1103         else if (mmps.Size > 0) {
1104             MOUNTMGR_MOUNT_POINTS* mmps2;
1105 
1106             mmps2 = ExAllocatePoolWithTag(NonPagedPool, mmps.Size, ALLOC_TAG);
1107             if (!mmps2) {
1108                 ERR("out of memory\n");
1109                 break;
1110             }
1111 
1112             Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), mmps2, mmps.Size,
1113                                false, NULL);
1114             if (!NT_SUCCESS(Status))
1115                 ERR("IOCTL_MOUNTMGR_QUERY_POINTS returned %08lx\n", Status);
1116             else
1117                 mountmgr_updated(mountmgr, mmps2);
1118 
1119             ExFreePool(mmps2);
1120         }
1121     }
1122 
1123     ObDereferenceObject(FileObject);
1124 
1125     mountmgr_thread_handle = NULL;
1126 
1127     PsTerminateSystemThread(STATUS_SUCCESS);
1128 }
1129