xref: /reactos/drivers/filesystems/btrfs/search.c (revision 41203c9b)
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 
27 #include <initguid.h>
28 #include <wdmguid.h>
29 
30 extern ERESOURCE pdo_list_lock;
31 extern LIST_ENTRY pdo_list;
32 extern UNICODE_STRING registry_path;
33 extern KEVENT mountmgr_thread_event;
34 extern HANDLE mountmgr_thread_handle;
35 extern bool shutting_down;
36 extern PDEVICE_OBJECT busobj;
37 extern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;
38 
39 typedef void (*pnp_callback)(PDRIVER_OBJECT DriverObject, PUNICODE_STRING devpath);
40 
41 extern PDEVICE_OBJECT master_devobj;
42 
43 static bool fs_ignored(BTRFS_UUID* uuid) {
44     UNICODE_STRING path, ignoreus;
45     NTSTATUS Status;
46     OBJECT_ATTRIBUTES oa;
47     KEY_VALUE_FULL_INFORMATION* kvfi;
48     ULONG dispos, retlen, kvfilen, i, j;
49     HANDLE h;
50     bool ret = false;
51 
52     path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));
53 
54     path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);
55     if (!path.Buffer) {
56         ERR("out of memory\n");
57         return false;
58     }
59 
60     RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);
61 
62     i = registry_path.Length / sizeof(WCHAR);
63 
64     path.Buffer[i] = '\\';
65     i++;
66 
67     for (j = 0; j < 16; j++) {
68         path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);
69         path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);
70 
71         i += 2;
72 
73         if (j == 3 || j == 5 || j == 7 || j == 9) {
74             path.Buffer[i] = '-';
75             i++;
76         }
77     }
78 
79     InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
80 
81     Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);
82 
83     if (!NT_SUCCESS(Status)) {
84         TRACE("ZwCreateKey returned %08x\n", Status);
85         ExFreePool(path.Buffer);
86         return false;
87     }
88 
89     RtlInitUnicodeString(&ignoreus, L"Ignore");
90 
91     kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR));
92     kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
93     if (!kvfi) {
94         ERR("out of memory\n");
95         ZwClose(h);
96         ExFreePool(path.Buffer);
97         return false;
98     }
99 
100     Status = ZwQueryValueKey(h, &ignoreus, KeyValueFullInformation, kvfi, kvfilen, &retlen);
101     if (NT_SUCCESS(Status)) {
102         if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) {
103             uint32_t* pr = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset);
104 
105             ret = *pr;
106         }
107     }
108 
109     ZwClose(h);
110     ExFreePool(kvfi);
111     ExFreePool(path.Buffer);
112 
113     return ret;
114 }
115 
116 static void test_vol(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject,
117                      PUNICODE_STRING devpath, DWORD disk_num, DWORD part_num, uint64_t length) {
118     NTSTATUS Status;
119     ULONG toread;
120     uint8_t* data = NULL;
121     uint32_t sector_size;
122 
123     TRACE("%.*S\n", devpath->Length / sizeof(WCHAR), devpath->Buffer);
124 
125     sector_size = DeviceObject->SectorSize;
126 
127     if (sector_size == 0) {
128         DISK_GEOMETRY geometry;
129         IO_STATUS_BLOCK iosb;
130 
131         Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,
132                            &geometry, sizeof(DISK_GEOMETRY), true, &iosb);
133 
134         if (!NT_SUCCESS(Status)) {
135             ERR("%.*S had a sector size of 0, and IOCTL_DISK_GET_DRIVE_GEOMETRY returned %08x\n",
136                 devpath->Length / sizeof(WCHAR), devpath->Buffer, Status);
137             goto deref;
138         }
139 
140         if (iosb.Information < sizeof(DISK_GEOMETRY)) {
141             ERR("%.*S: IOCTL_DISK_GET_DRIVE_GEOMETRY returned %u bytes, expected %u\n",
142                 devpath->Length / sizeof(WCHAR), devpath->Buffer, iosb.Information, sizeof(DISK_GEOMETRY));
143         }
144 
145         sector_size = geometry.BytesPerSector;
146 
147         if (sector_size == 0) {
148             ERR("%.*S had a sector size of 0\n", devpath->Length / sizeof(WCHAR), devpath->Buffer);
149             goto deref;
150         }
151     }
152 
153     toread = (ULONG)sector_align(sizeof(superblock), sector_size);
154     data = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);
155     if (!data) {
156         ERR("out of memory\n");
157         goto deref;
158     }
159 
160     Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[0], toread, data, true);
161 
162     if (NT_SUCCESS(Status) && ((superblock*)data)->magic == BTRFS_MAGIC) {
163         superblock* sb = (superblock*)data;
164         uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
165 
166         if (crc32 != *((uint32_t*)sb->checksum))
167             ERR("checksum error on superblock\n");
168         else {
169             TRACE("volume found\n");
170 
171             if (length >= superblock_addrs[1] + toread) {
172                 ULONG i = 1;
173 
174                 superblock* sb2 = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);
175                 if (!sb2) {
176                     ERR("out of memory\n");
177                     goto deref;
178                 }
179 
180                 while (superblock_addrs[i] > 0 && length >= superblock_addrs[i] + toread) {
181                     Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[i], toread, (PUCHAR)sb2, true);
182 
183                     if (NT_SUCCESS(Status) && sb2->magic == BTRFS_MAGIC) {
184                         crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb2->uuid, (ULONG)sizeof(superblock) - sizeof(sb2->checksum));
185 
186                         if (crc32 == *((uint32_t*)sb2->checksum) && sb2->generation > sb->generation)
187                             RtlCopyMemory(sb, sb2, toread);
188                     }
189 
190                     i++;
191                 }
192 
193                 ExFreePool(sb2);
194             }
195 
196             if (!fs_ignored(&sb->uuid)) {
197                 DeviceObject->Flags &= ~DO_VERIFY_VOLUME;
198                 add_volume_device(sb, devpath, length, disk_num, part_num);
199             }
200         }
201     }
202 
203 deref:
204     if (data)
205         ExFreePool(data);
206 }
207 
208 NTSTATUS remove_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) {
209     NTSTATUS Status;
210     MOUNTMGR_MOUNT_POINT* mmp;
211     ULONG mmpsize;
212     MOUNTMGR_MOUNT_POINTS mmps1, *mmps2;
213 
214     TRACE("removing drive letter\n");
215 
216     mmpsize = sizeof(MOUNTMGR_MOUNT_POINT) + devpath->Length;
217 
218     mmp = ExAllocatePoolWithTag(PagedPool, mmpsize, ALLOC_TAG);
219     if (!mmp) {
220         ERR("out of memory\n");
221         return STATUS_INSUFFICIENT_RESOURCES;
222     }
223 
224     RtlZeroMemory(mmp, mmpsize);
225 
226     mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);
227     mmp->DeviceNameLength = devpath->Length;
228     RtlCopyMemory(&mmp[1], devpath->Buffer, devpath->Length);
229 
230     Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, &mmps1, sizeof(MOUNTMGR_MOUNT_POINTS), false, NULL);
231 
232     if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {
233         ERR("IOCTL_MOUNTMGR_DELETE_POINTS 1 returned %08x\n", Status);
234         ExFreePool(mmp);
235         return Status;
236     }
237 
238     if (Status != STATUS_BUFFER_OVERFLOW || mmps1.Size == 0) {
239         ExFreePool(mmp);
240         return STATUS_NOT_FOUND;
241     }
242 
243     mmps2 = ExAllocatePoolWithTag(PagedPool, mmps1.Size, ALLOC_TAG);
244     if (!mmps2) {
245         ERR("out of memory\n");
246         ExFreePool(mmp);
247         return STATUS_INSUFFICIENT_RESOURCES;
248     }
249 
250     Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, mmps2, mmps1.Size, false, NULL);
251 
252     if (!NT_SUCCESS(Status))
253         ERR("IOCTL_MOUNTMGR_DELETE_POINTS 2 returned %08x\n", Status);
254 
255     ExFreePool(mmps2);
256     ExFreePool(mmp);
257 
258     return Status;
259 }
260 
261 void disk_arrival(PDRIVER_OBJECT DriverObject, PUNICODE_STRING devpath) {
262     PFILE_OBJECT fileobj;
263     PDEVICE_OBJECT devobj;
264     NTSTATUS Status;
265     STORAGE_DEVICE_NUMBER sdn;
266     ULONG dlisize;
267     DRIVE_LAYOUT_INFORMATION_EX* dli = NULL;
268     IO_STATUS_BLOCK iosb;
269     GET_LENGTH_INFORMATION gli;
270 
271     UNUSED(DriverObject);
272 
273     Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj);
274     if (!NT_SUCCESS(Status)) {
275         ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
276         return;
277     }
278 
279     dlisize = 0;
280 
281     do {
282         dlisize += 1024;
283 
284         if (dli)
285             ExFreePool(dli);
286 
287         dli = ExAllocatePoolWithTag(PagedPool, dlisize, ALLOC_TAG);
288         if (!dli) {
289             ERR("out of memory\n");
290             goto end;
291         }
292 
293         Status = dev_ioctl(devobj, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,
294                            dli, dlisize, true, &iosb);
295     } while (Status == STATUS_BUFFER_TOO_SMALL);
296 
297     // only consider disk as a potential filesystem if it has no partitions
298     if (NT_SUCCESS(Status) && dli->PartitionCount > 0) {
299         ExFreePool(dli);
300         goto end;
301     }
302 
303     ExFreePool(dli);
304 
305     Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,
306                         &gli, sizeof(gli), true, NULL);
307 
308     if (!NT_SUCCESS(Status)) {
309         ERR("error reading length information: %08x\n", Status);
310         goto end;
311     }
312 
313     Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
314                        &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);
315     if (!NT_SUCCESS(Status)) {
316         TRACE("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08x\n", Status);
317         sdn.DeviceNumber = 0xffffffff;
318         sdn.PartitionNumber = 0;
319     } else
320         TRACE("DeviceType = %u, DeviceNumber = %u, PartitionNumber = %u\n", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber);
321 
322     test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber, gli.Length.QuadPart);
323 
324 end:
325     ObDereferenceObject(fileobj);
326 }
327 
328 void remove_volume_child(_Inout_ _Requires_exclusive_lock_held_(_Curr_->child_lock) _Releases_exclusive_lock_(_Curr_->child_lock) _In_ volume_device_extension* vde,
329                          _In_ volume_child* vc, _In_ bool skip_dev) {
330     NTSTATUS Status;
331     pdo_device_extension* pdode = vde->pdode;
332     device_extension* Vcb = vde->mounted_device ? vde->mounted_device->DeviceExtension : NULL;
333 
334     if (vc->notification_entry) {
335         if (fIoUnregisterPlugPlayNotificationEx)
336             fIoUnregisterPlugPlayNotificationEx(vc->notification_entry);
337         else
338             IoUnregisterPlugPlayNotification(vc->notification_entry);
339     }
340 
341     if (vde->mounted_device && (!Vcb || !Vcb->options.allow_degraded)) {
342         Status = pnp_surprise_removal(vde->mounted_device, NULL);
343         if (!NT_SUCCESS(Status))
344             ERR("pnp_surprise_removal returned %08x\n", Status);
345     }
346 
347     if (!Vcb || !Vcb->options.allow_degraded) {
348         Status = IoSetDeviceInterfaceState(&vde->bus_name, false);
349         if (!NT_SUCCESS(Status))
350             WARN("IoSetDeviceInterfaceState returned %08x\n", Status);
351     }
352 
353     if (pdode->children_loaded > 0) {
354         UNICODE_STRING mmdevpath;
355         PFILE_OBJECT FileObject;
356         PDEVICE_OBJECT mountmgr;
357         LIST_ENTRY* le;
358 
359         if (!Vcb || !Vcb->options.allow_degraded) {
360             RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
361             Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
362             if (!NT_SUCCESS(Status))
363                 ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
364             else {
365                 le = pdode->children.Flink;
366 
367                 while (le != &pdode->children) {
368                     volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);
369 
370                     if (vc2->had_drive_letter) { // re-add entry to mountmgr
371                         MOUNTDEV_NAME mdn;
372 
373                         Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);
374                         if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
375                             ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08x\n", Status);
376                         else {
377                             MOUNTDEV_NAME* mdn2;
378                             ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;
379 
380                             mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG);
381                             if (!mdn2)
382                                 ERR("out of memory\n");
383                             else {
384                                 Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);
385                                 if (!NT_SUCCESS(Status))
386                                     ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08x\n", Status);
387                                 else {
388                                     UNICODE_STRING name;
389 
390                                     name.Buffer = mdn2->Name;
391                                     name.Length = name.MaximumLength = mdn2->NameLength;
392 
393                                     Status = mountmgr_add_drive_letter(mountmgr, &name);
394                                     if (!NT_SUCCESS(Status))
395                                         WARN("mountmgr_add_drive_letter returned %08x\n", Status);
396                                 }
397 
398                                 ExFreePool(mdn2);
399                             }
400                         }
401                     }
402 
403                     le = le->Flink;
404                 }
405 
406                 ObDereferenceObject(FileObject);
407             }
408         } else if (!skip_dev) {
409             ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);
410 
411             le = Vcb->devices.Flink;
412             while (le != &Vcb->devices) {
413                 device* dev = CONTAINING_RECORD(le, device, list_entry);
414 
415                 if (dev->devobj == vc->devobj) {
416                     dev->devobj = NULL; // mark as missing
417                     break;
418                 }
419 
420                 le = le->Flink;
421             }
422 
423             ExReleaseResourceLite(&Vcb->tree_lock);
424         }
425 
426         if (vde->device->Characteristics & FILE_REMOVABLE_MEDIA) {
427             vde->device->Characteristics &= ~FILE_REMOVABLE_MEDIA;
428 
429             le = pdode->children.Flink;
430             while (le != &pdode->children) {
431                 volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);
432 
433                 if (vc2 != vc && vc2->devobj->Characteristics & FILE_REMOVABLE_MEDIA) {
434                     vde->device->Characteristics |= FILE_REMOVABLE_MEDIA;
435                     break;
436                 }
437 
438                 le = le->Flink;
439             }
440         }
441     }
442 
443     ObDereferenceObject(vc->fileobj);
444     ExFreePool(vc->pnp_name.Buffer);
445     RemoveEntryList(&vc->list_entry);
446     ExFreePool(vc);
447 
448     pdode->children_loaded--;
449 
450     if (pdode->children_loaded == 0) { // remove volume device
451         bool remove = false;
452 
453         RemoveEntryList(&pdode->list_entry);
454 
455         vde->removing = true;
456 
457         Status = IoSetDeviceInterfaceState(&vde->bus_name, false);
458         if (!NT_SUCCESS(Status))
459             WARN("IoSetDeviceInterfaceState returned %08x\n", Status);
460 
461         if (vde->pdo->AttachedDevice)
462             IoDetachDevice(vde->pdo);
463 
464         if (vde->open_count == 0)
465             remove = true;
466 
467         ExReleaseResourceLite(&pdode->child_lock);
468 
469         if (!no_pnp) {
470             bus_device_extension* bde = busobj->DeviceExtension;
471 
472             IoInvalidateDeviceRelations(bde->buspdo, BusRelations);
473         }
474 
475         if (remove) {
476             if (vde->name.Buffer)
477                 ExFreePool(vde->name.Buffer);
478 
479             if (Vcb)
480                 Vcb->vde = NULL;
481 
482             ExDeleteResourceLite(&pdode->child_lock);
483 
484             IoDeleteDevice(vde->device);
485         }
486     } else
487         ExReleaseResourceLite(&pdode->child_lock);
488 }
489 
490 void volume_arrival(PDRIVER_OBJECT DriverObject, PUNICODE_STRING devpath) {
491     STORAGE_DEVICE_NUMBER sdn;
492     PFILE_OBJECT fileobj;
493     PDEVICE_OBJECT devobj;
494     GET_LENGTH_INFORMATION gli;
495     NTSTATUS Status;
496 
497     TRACE("%.*S\n", devpath->Length / sizeof(WCHAR), devpath->Buffer);
498 
499     Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj);
500     if (!NT_SUCCESS(Status)) {
501         ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
502         return;
503     }
504 
505     // make sure we're not processing devices we've created ourselves
506 
507     if (devobj->DriverObject == DriverObject)
508         goto end;
509 
510     Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL);
511     if (!NT_SUCCESS(Status)) {
512         ERR("IOCTL_DISK_GET_LENGTH_INFO returned %08x\n", Status);
513         goto end;
514     }
515 
516     Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
517                        &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);
518     if (!NT_SUCCESS(Status)) {
519         TRACE("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08x\n", Status);
520         sdn.DeviceNumber = 0xffffffff;
521         sdn.PartitionNumber = 0;
522     } else
523         TRACE("DeviceType = %u, DeviceNumber = %u, PartitionNumber = %u\n", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber);
524 
525     // If we've just added a partition to a whole-disk filesystem, unmount it
526     if (sdn.DeviceNumber != 0xffffffff && sdn.PartitionNumber != 0) {
527         LIST_ENTRY* le;
528 
529         ExAcquireResourceExclusiveLite(&pdo_list_lock, true);
530 
531         le = pdo_list.Flink;
532         while (le != &pdo_list) {
533             pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
534             LIST_ENTRY* le2;
535             bool changed = false;
536 
537             if (pdode->vde) {
538                 ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
539 
540                 le2 = pdode->children.Flink;
541                 while (le2 != &pdode->children) {
542                     volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
543                     LIST_ENTRY* le3 = le2->Flink;
544 
545                     if (vc->disk_num == sdn.DeviceNumber && vc->part_num == 0) {
546                         TRACE("removing device\n");
547 
548                         remove_volume_child(pdode->vde, vc, false);
549                         changed = true;
550 
551                         break;
552                     }
553 
554                     le2 = le3;
555                 }
556 
557                 if (!changed)
558                     ExReleaseResourceLite(&pdode->child_lock);
559                 else
560                     break;
561             }
562 
563             le = le->Flink;
564         }
565 
566         ExReleaseResourceLite(&pdo_list_lock);
567     }
568 
569     test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber, gli.Length.QuadPart);
570 
571 end:
572     ObDereferenceObject(fileobj);
573 }
574 
575 void volume_removal(PDRIVER_OBJECT DriverObject, PUNICODE_STRING devpath) {
576     LIST_ENTRY* le;
577     UNICODE_STRING devpath2;
578 
579     TRACE("%.*S\n", devpath->Length / sizeof(WCHAR), devpath->Buffer);
580 
581     UNUSED(DriverObject);
582 
583     devpath2 = *devpath;
584 
585     if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\' && (devpath->Buffer[1] == '\\' || devpath->Buffer[1] == '?') &&
586         devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\') {
587         devpath2.Buffer = &devpath2.Buffer[3];
588         devpath2.Length -= 3 * sizeof(WCHAR);
589         devpath2.MaximumLength -= 3 * sizeof(WCHAR);
590     }
591 
592     ExAcquireResourceExclusiveLite(&pdo_list_lock, true);
593 
594     le = pdo_list.Flink;
595     while (le != &pdo_list) {
596         pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
597         LIST_ENTRY* le2;
598         bool changed = false;
599 
600         if (pdode->vde) {
601             ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
602 
603             le2 = pdode->children.Flink;
604             while (le2 != &pdode->children) {
605                 volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
606                 LIST_ENTRY* le3 = le2->Flink;
607 
608                 if (vc->pnp_name.Length == devpath2.Length && RtlCompareMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length) == devpath2.Length) {
609                     TRACE("removing device\n");
610 
611                     remove_volume_child(pdode->vde, vc, false);
612                     changed = true;
613 
614                     break;
615                 }
616 
617                 le2 = le3;
618             }
619 
620             if (!changed)
621                 ExReleaseResourceLite(&pdode->child_lock);
622             else
623                 break;
624         }
625 
626         le = le->Flink;
627     }
628 
629     ExReleaseResourceLite(&pdo_list_lock);
630 }
631 
632 typedef struct {
633     PDRIVER_OBJECT DriverObject;
634     UNICODE_STRING name;
635     pnp_callback func;
636     PIO_WORKITEM work_item;
637 } pnp_callback_context;
638 
639 _Function_class_(IO_WORKITEM_ROUTINE)
640 static void __stdcall do_pnp_callback(PDEVICE_OBJECT DeviceObject, PVOID con) {
641     pnp_callback_context* context = con;
642 
643     UNUSED(DeviceObject);
644 
645     context->func(context->DriverObject, &context->name);
646 
647     if (context->name.Buffer)
648         ExFreePool(context->name.Buffer);
649 
650     IoFreeWorkItem(context->work_item);
651 }
652 
653 static void enqueue_pnp_callback(PDRIVER_OBJECT DriverObject, PUNICODE_STRING name, pnp_callback func) {
654     PIO_WORKITEM work_item;
655     pnp_callback_context* context;
656 
657     work_item = IoAllocateWorkItem(master_devobj);
658 
659     context = ExAllocatePoolWithTag(PagedPool, sizeof(pnp_callback_context), ALLOC_TAG);
660 
661     if (!context) {
662         ERR("out of memory\n");
663         IoFreeWorkItem(work_item);
664         return;
665     }
666 
667     context->DriverObject = DriverObject;
668 
669     if (name->Length > 0) {
670         context->name.Buffer = ExAllocatePoolWithTag(PagedPool, name->Length, ALLOC_TAG);
671         if (!context->name.Buffer) {
672             ERR("out of memory\n");
673             ExFreePool(context);
674             IoFreeWorkItem(work_item);
675             return;
676         }
677 
678         RtlCopyMemory(context->name.Buffer, name->Buffer, name->Length);
679         context->name.Length = context->name.MaximumLength = name->Length;
680     } else {
681         context->name.Length = context->name.MaximumLength = 0;
682         context->name.Buffer = NULL;
683     }
684 
685     context->func = func;
686     context->work_item = work_item;
687 
688     IoQueueWorkItem(work_item, do_pnp_callback, DelayedWorkQueue, context);
689 }
690 
691 _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)
692 NTSTATUS __stdcall volume_notification(PVOID NotificationStructure, PVOID Context) {
693     DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure;
694     PDRIVER_OBJECT DriverObject = (PDRIVER_OBJECT)Context;
695 
696     if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
697         enqueue_pnp_callback(DriverObject, dicn->SymbolicLinkName, volume_arrival);
698     else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))
699         enqueue_pnp_callback(DriverObject, dicn->SymbolicLinkName, volume_removal);
700 
701     return STATUS_SUCCESS;
702 }
703 
704 _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)
705 NTSTATUS __stdcall pnp_notification(PVOID NotificationStructure, PVOID Context) {
706     DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure;
707     PDRIVER_OBJECT DriverObject = (PDRIVER_OBJECT)Context;
708 
709     if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
710         enqueue_pnp_callback(DriverObject, dicn->SymbolicLinkName, disk_arrival);
711     else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))
712         enqueue_pnp_callback(DriverObject, dicn->SymbolicLinkName, volume_removal);
713 
714     return STATUS_SUCCESS;
715 }
716 
717 static void mountmgr_process_drive(PDEVICE_OBJECT mountmgr, PUNICODE_STRING device_name) {
718     NTSTATUS Status;
719     LIST_ENTRY* le;
720     bool need_remove = false;
721     volume_child* vc2 = NULL;
722 
723     ExAcquireResourceSharedLite(&pdo_list_lock, true);
724 
725     le = pdo_list.Flink;
726     while (le != &pdo_list) {
727         pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
728         LIST_ENTRY* le2;
729 
730         ExAcquireResourceSharedLite(&pdode->child_lock, true);
731 
732         le2 = pdode->children.Flink;
733 
734         while (le2 != &pdode->children) {
735             volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
736 
737             if (vc->devobj) {
738                 MOUNTDEV_NAME mdn;
739 
740                 Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);
741                 if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
742                     ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08x\n", Status);
743                 else {
744                     MOUNTDEV_NAME* mdn2;
745                     ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;
746 
747                     mdn2 = ExAllocatePoolWithTag(NonPagedPool, mdnsize, ALLOC_TAG);
748                     if (!mdn2)
749                         ERR("out of memory\n");
750                     else {
751                         Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);
752                         if (!NT_SUCCESS(Status))
753                             ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08x\n", Status);
754                         else {
755                             if (mdn2->NameLength == device_name->Length && RtlCompareMemory(mdn2->Name, device_name->Buffer, device_name->Length) == device_name->Length) {
756                                 vc2 = vc;
757                                 need_remove = true;
758                                 break;
759                             }
760                         }
761 
762                         ExFreePool(mdn2);
763                     }
764                 }
765             }
766 
767             le2 = le2->Flink;
768         }
769 
770         ExReleaseResourceLite(&pdode->child_lock);
771 
772         if (need_remove)
773             break;
774 
775         le = le->Flink;
776     }
777 
778     ExReleaseResourceLite(&pdo_list_lock);
779 
780     if (need_remove) {
781         Status = remove_drive_letter(mountmgr, device_name);
782         if (!NT_SUCCESS(Status))
783             ERR("remove_drive_letter returned %08x\n", Status);
784         else
785             vc2->had_drive_letter = true;
786     }
787 }
788 
789 static void mountmgr_updated(PDEVICE_OBJECT mountmgr, MOUNTMGR_MOUNT_POINTS* mmps) {
790     ULONG i;
791 
792     static const WCHAR pref[] = L"\\DosDevices\\";
793 
794     for (i = 0; i < mmps->NumberOfMountPoints; i++) {
795         UNICODE_STRING symlink, device_name;
796 
797         if (mmps->MountPoints[i].SymbolicLinkNameOffset != 0) {
798             symlink.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].SymbolicLinkNameOffset);
799             symlink.Length = symlink.MaximumLength = mmps->MountPoints[i].SymbolicLinkNameLength;
800         } else {
801             symlink.Buffer = NULL;
802             symlink.Length = symlink.MaximumLength = 0;
803         }
804 
805         if (mmps->MountPoints[i].DeviceNameOffset != 0) {
806             device_name.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].DeviceNameOffset);
807             device_name.Length = device_name.MaximumLength = mmps->MountPoints[i].DeviceNameLength;
808         } else {
809             device_name.Buffer = NULL;
810             device_name.Length = device_name.MaximumLength = 0;
811         }
812 
813         if (symlink.Length > sizeof(pref) - sizeof(WCHAR) &&
814             RtlCompareMemory(symlink.Buffer, pref, sizeof(pref) - sizeof(WCHAR)) == sizeof(pref) - sizeof(WCHAR))
815             mountmgr_process_drive(mountmgr, &device_name);
816     }
817 }
818 
819 _Function_class_(KSTART_ROUTINE)
820 void __stdcall mountmgr_thread(_In_ void* context) {
821     UNICODE_STRING mmdevpath;
822     NTSTATUS Status;
823     PFILE_OBJECT FileObject;
824     PDEVICE_OBJECT mountmgr;
825     MOUNTMGR_CHANGE_NOTIFY_INFO mcni;
826 
827     UNUSED(context);
828 
829     RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
830     Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
831     if (!NT_SUCCESS(Status)) {
832         ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
833         return;
834     }
835 
836     mcni.EpicNumber = 0;
837 
838     while (true) {
839         PIRP Irp;
840         MOUNTMGR_MOUNT_POINT mmp;
841         MOUNTMGR_MOUNT_POINTS mmps;
842         IO_STATUS_BLOCK iosb;
843 
844         KeClearEvent(&mountmgr_thread_event);
845 
846         Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_CHANGE_NOTIFY, mountmgr, &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO),
847                                             &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO), false, &mountmgr_thread_event, &iosb);
848 
849         if (!Irp) {
850             ERR("out of memory\n");
851             break;
852         }
853 
854         Status = IoCallDriver(mountmgr, Irp);
855 
856         if (Status == STATUS_PENDING) {
857             KeWaitForSingleObject(&mountmgr_thread_event, Executive, KernelMode, false, NULL);
858             Status = iosb.Status;
859         }
860 
861         if (shutting_down)
862             break;
863 
864         if (!NT_SUCCESS(Status)) {
865             ERR("IOCTL_MOUNTMGR_CHANGE_NOTIFY returned %08x\n", Status);
866             break;
867         }
868 
869         TRACE("mountmgr changed\n");
870 
871         RtlZeroMemory(&mmp, sizeof(MOUNTMGR_MOUNT_POINT));
872 
873         Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), &mmps, sizeof(MOUNTMGR_MOUNT_POINTS),
874                            false, NULL);
875 
876         if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
877             ERR("IOCTL_MOUNTMGR_QUERY_POINTS 1 returned %08x\n", Status);
878         else if (mmps.Size > 0) {
879             MOUNTMGR_MOUNT_POINTS* mmps2;
880 
881             mmps2 = ExAllocatePoolWithTag(NonPagedPool, mmps.Size, ALLOC_TAG);
882             if (!mmps2) {
883                 ERR("out of memory\n");
884                 break;
885             }
886 
887             Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINTS), mmps2, mmps.Size,
888                                false, NULL);
889             if (!NT_SUCCESS(Status))
890                 ERR("IOCTL_MOUNTMGR_QUERY_POINTS returned %08x\n", Status);
891             else
892                 mountmgr_updated(mountmgr, mmps2);
893 
894             ExFreePool(mmps2);
895         }
896     }
897 
898     ObDereferenceObject(FileObject);
899 
900     mountmgr_thread_handle = NULL;
901 
902     PsTerminateSystemThread(STATUS_SUCCESS);
903 }
904