xref: /reactos/drivers/filesystems/btrfs/volume.c (revision 09dde2cf)
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 #include <mountdev.h>
20 #include <ntddvol.h>
21 #include <ntddstor.h>
22 #include <ntdddisk.h>
23 #include <wdmguid.h>
24 
25 #define IOCTL_VOLUME_IS_DYNAMIC     CTL_CODE(IOCTL_VOLUME_BASE, 18, METHOD_BUFFERED, FILE_ANY_ACCESS)
26 #define IOCTL_VOLUME_POST_ONLINE    CTL_CODE(IOCTL_VOLUME_BASE, 25, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
27 
28 extern PDRIVER_OBJECT drvobj;
29 extern PDEVICE_OBJECT master_devobj;
30 extern PDEVICE_OBJECT busobj;
31 extern ERESOURCE pdo_list_lock;
32 extern LIST_ENTRY pdo_list;
33 extern UNICODE_STRING registry_path;
34 extern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;
35 
36 NTSTATUS vol_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
37     volume_device_extension* vde = DeviceObject->DeviceExtension;
38 
39     TRACE("(%p, %p)\n", DeviceObject, Irp);
40 
41     if (vde->removing)
42         return STATUS_DEVICE_NOT_READY;
43 
44     Irp->IoStatus.Information = FILE_OPENED;
45     InterlockedIncrement(&vde->open_count);
46 
47     return STATUS_SUCCESS;
48 }
49 
50 void free_vol(volume_device_extension* vde) {
51     PDEVICE_OBJECT pdo;
52 
53     vde->dead = true;
54 
55     if (vde->mounted_device) {
56         device_extension* Vcb = vde->mounted_device->DeviceExtension;
57 
58         Vcb->vde = NULL;
59     }
60 
61     if (vde->name.Buffer)
62         ExFreePool(vde->name.Buffer);
63 
64     ExDeleteResourceLite(&vde->pdode->child_lock);
65 
66     if (vde->pdo->AttachedDevice)
67         IoDetachDevice(vde->pdo);
68 
69     while (!IsListEmpty(&vde->pdode->children)) {
70         volume_child* vc = CONTAINING_RECORD(RemoveHeadList(&vde->pdode->children), volume_child, list_entry);
71 
72         if (vc->notification_entry) {
73             if (fIoUnregisterPlugPlayNotificationEx)
74                 fIoUnregisterPlugPlayNotificationEx(vc->notification_entry);
75             else
76                 IoUnregisterPlugPlayNotification(vc->notification_entry);
77         }
78 
79         if (vc->pnp_name.Buffer)
80             ExFreePool(vc->pnp_name.Buffer);
81 
82         ExFreePool(vc);
83     }
84 
85     if (no_pnp)
86         ExFreePool(vde->pdode);
87 
88     pdo = vde->pdo;
89     IoDeleteDevice(vde->device);
90 
91     if (!no_pnp)
92         IoDeleteDevice(pdo);
93 }
94 
95 NTSTATUS vol_close(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
96     volume_device_extension* vde = DeviceObject->DeviceExtension;
97     pdo_device_extension* pdode = vde->pdode;
98 
99     TRACE("(%p, %p)\n", DeviceObject, Irp);
100 
101     Irp->IoStatus.Information = 0;
102 
103     if (vde->dead)
104         return STATUS_SUCCESS;
105 
106     ExAcquireResourceExclusiveLite(&pdo_list_lock, true);
107 
108     if (vde->dead) {
109         ExReleaseResourceLite(&pdo_list_lock);
110         return STATUS_SUCCESS;
111     }
112 
113     ExAcquireResourceSharedLite(&pdode->child_lock, true);
114 
115     if (InterlockedDecrement(&vde->open_count) == 0 && vde->removing) {
116         ExReleaseResourceLite(&pdode->child_lock);
117 
118         free_vol(vde);
119     } else
120         ExReleaseResourceLite(&pdode->child_lock);
121 
122     ExReleaseResourceLite(&pdo_list_lock);
123 
124     return STATUS_SUCCESS;
125 }
126 
127 typedef struct {
128     IO_STATUS_BLOCK iosb;
129     KEVENT Event;
130 } vol_read_context;
131 
132 _Function_class_(IO_COMPLETION_ROUTINE)
133 static NTSTATUS __stdcall vol_read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
134     vol_read_context* context = conptr;
135 
136     UNUSED(DeviceObject);
137 
138     context->iosb = Irp->IoStatus;
139     KeSetEvent(&context->Event, 0, false);
140 
141     return STATUS_MORE_PROCESSING_REQUIRED;
142 }
143 
144 NTSTATUS vol_read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
145     volume_device_extension* vde = DeviceObject->DeviceExtension;
146     pdo_device_extension* pdode = vde->pdode;
147     volume_child* vc;
148     NTSTATUS Status;
149     PIRP Irp2;
150     vol_read_context context;
151     PIO_STACK_LOCATION IrpSp, IrpSp2;
152 
153     TRACE("(%p, %p)\n", DeviceObject, Irp);
154 
155     ExAcquireResourceSharedLite(&pdode->child_lock, true);
156 
157     if (IsListEmpty(&pdode->children)) {
158         ExReleaseResourceLite(&pdode->child_lock);
159         Status = STATUS_INVALID_DEVICE_REQUEST;
160         goto end;
161     }
162 
163     vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);
164 
165     // We can't use IoSkipCurrentIrpStackLocation as the device isn't in our stack
166 
167     Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);
168 
169     if (!Irp2) {
170         ERR("IoAllocateIrp failed\n");
171         ExReleaseResourceLite(&pdode->child_lock);
172         Status = STATUS_INSUFFICIENT_RESOURCES;
173         goto end;
174     }
175 
176     IrpSp = IoGetCurrentIrpStackLocation(Irp);
177     IrpSp2 = IoGetNextIrpStackLocation(Irp2);
178 
179     IrpSp2->MajorFunction = IRP_MJ_READ;
180     IrpSp2->FileObject = vc->fileobj;
181 
182     if (vc->devobj->Flags & DO_BUFFERED_IO) {
183         Irp2->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, IrpSp->Parameters.Read.Length, ALLOC_TAG);
184         if (!Irp2->AssociatedIrp.SystemBuffer) {
185             ERR("out of memory\n");
186             ExReleaseResourceLite(&pdode->child_lock);
187             Status = STATUS_INSUFFICIENT_RESOURCES;
188             goto end;
189         }
190 
191         Irp2->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;
192 
193         Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
194     } else if (vc->devobj->Flags & DO_DIRECT_IO)
195         Irp2->MdlAddress = Irp->MdlAddress;
196     else
197         Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
198 
199     IrpSp2->Parameters.Read.Length = IrpSp->Parameters.Read.Length;
200     IrpSp2->Parameters.Read.ByteOffset.QuadPart = IrpSp->Parameters.Read.ByteOffset.QuadPart;
201 
202     KeInitializeEvent(&context.Event, NotificationEvent, false);
203     Irp2->UserIosb = &context.iosb;
204 
205     IoSetCompletionRoutine(Irp2, vol_read_completion, &context, true, true, true);
206 
207     Status = IoCallDriver(vc->devobj, Irp2);
208 
209     if (Status == STATUS_PENDING) {
210         KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
211         Status = context.iosb.Status;
212     }
213 
214     ExReleaseResourceLite(&pdode->child_lock);
215 
216     Irp->IoStatus.Information = context.iosb.Information;
217 
218 end:
219     Irp->IoStatus.Status = Status;
220     IoCompleteRequest(Irp, IO_NO_INCREMENT);
221 
222     return Status;
223 }
224 
225 NTSTATUS vol_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
226     volume_device_extension* vde = DeviceObject->DeviceExtension;
227     pdo_device_extension* pdode = vde->pdode;
228     volume_child* vc;
229     NTSTATUS Status;
230     PIRP Irp2;
231     vol_read_context context;
232     PIO_STACK_LOCATION IrpSp, IrpSp2;
233 
234     TRACE("(%p, %p)\n", DeviceObject, Irp);
235 
236     ExAcquireResourceSharedLite(&pdode->child_lock, true);
237 
238     if (IsListEmpty(&pdode->children)) {
239         ExReleaseResourceLite(&pdode->child_lock);
240         Status = STATUS_INVALID_DEVICE_REQUEST;
241         goto end;
242     }
243 
244     vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);
245 
246     if (vc->list_entry.Flink != &pdode->children) { // more than once device
247         ExReleaseResourceLite(&pdode->child_lock);
248         Status = STATUS_ACCESS_DENIED;
249         goto end;
250     }
251 
252     // We can't use IoSkipCurrentIrpStackLocation as the device isn't in our stack
253 
254     Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);
255 
256     if (!Irp2) {
257         ERR("IoAllocateIrp failed\n");
258         ExReleaseResourceLite(&pdode->child_lock);
259         Status = STATUS_INSUFFICIENT_RESOURCES;
260         goto end;
261     }
262 
263     IrpSp = IoGetCurrentIrpStackLocation(Irp);
264     IrpSp2 = IoGetNextIrpStackLocation(Irp2);
265 
266     IrpSp2->MajorFunction = IRP_MJ_WRITE;
267     IrpSp2->FileObject = vc->fileobj;
268 
269     if (vc->devobj->Flags & DO_BUFFERED_IO) {
270         Irp2->AssociatedIrp.SystemBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
271 
272         Irp2->Flags |= IRP_BUFFERED_IO;
273 
274         Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
275     } else if (vc->devobj->Flags & DO_DIRECT_IO)
276         Irp2->MdlAddress = Irp->MdlAddress;
277     else
278         Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
279 
280     IrpSp2->Parameters.Write.Length = IrpSp->Parameters.Write.Length;
281     IrpSp2->Parameters.Write.ByteOffset.QuadPart = IrpSp->Parameters.Write.ByteOffset.QuadPart;
282 
283     KeInitializeEvent(&context.Event, NotificationEvent, false);
284     Irp2->UserIosb = &context.iosb;
285 
286     IoSetCompletionRoutine(Irp2, vol_read_completion, &context, true, true, true);
287 
288     Status = IoCallDriver(vc->devobj, Irp2);
289 
290     if (Status == STATUS_PENDING) {
291         KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
292         Status = context.iosb.Status;
293     }
294 
295     ExReleaseResourceLite(&pdode->child_lock);
296 
297     Irp->IoStatus.Information = context.iosb.Information;
298 
299 end:
300     Irp->IoStatus.Status = Status;
301     IoCompleteRequest(Irp, IO_NO_INCREMENT);
302 
303     return Status;
304 }
305 
306 static NTSTATUS vol_query_device_name(volume_device_extension* vde, PIRP Irp) {
307     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
308     PMOUNTDEV_NAME name;
309 
310     if (IrpSp->FileObject && IrpSp->FileObject->FsContext)
311         return STATUS_INVALID_PARAMETER;
312 
313     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_NAME)) {
314         Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
315         return STATUS_BUFFER_TOO_SMALL;
316     }
317 
318     name = Irp->AssociatedIrp.SystemBuffer;
319     name->NameLength = vde->name.Length;
320 
321     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(MOUNTDEV_NAME, Name[0]) + name->NameLength) {
322         Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
323         return STATUS_BUFFER_OVERFLOW;
324     }
325 
326     RtlCopyMemory(name->Name, vde->name.Buffer, vde->name.Length);
327 
328     Irp->IoStatus.Information = offsetof(MOUNTDEV_NAME, Name[0]) + name->NameLength;
329 
330     return STATUS_SUCCESS;
331 }
332 
333 static NTSTATUS vol_query_unique_id(volume_device_extension* vde, PIRP Irp) {
334     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
335     MOUNTDEV_UNIQUE_ID* mduid;
336     pdo_device_extension* pdode;
337 
338     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_UNIQUE_ID)) {
339         Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
340         return STATUS_BUFFER_TOO_SMALL;
341     }
342 
343     mduid = Irp->AssociatedIrp.SystemBuffer;
344     mduid->UniqueIdLength = sizeof(BTRFS_UUID);
345 
346     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(MOUNTDEV_UNIQUE_ID, UniqueId[0]) + mduid->UniqueIdLength) {
347         Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
348         return STATUS_BUFFER_OVERFLOW;
349     }
350 
351     if (!vde->pdo)
352         return STATUS_INVALID_PARAMETER;
353 
354     pdode = vde->pdode;
355 
356     RtlCopyMemory(mduid->UniqueId, &pdode->uuid, sizeof(BTRFS_UUID));
357 
358     Irp->IoStatus.Information = offsetof(MOUNTDEV_UNIQUE_ID, UniqueId[0]) + mduid->UniqueIdLength;
359 
360     return STATUS_SUCCESS;
361 }
362 
363 static NTSTATUS vol_is_dynamic(PIRP Irp) {
364     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
365     uint8_t* buf;
366 
367     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength == 0 || !Irp->AssociatedIrp.SystemBuffer)
368         return STATUS_INVALID_PARAMETER;
369 
370     buf = (uint8_t*)Irp->AssociatedIrp.SystemBuffer;
371 
372     *buf = 1;
373 
374     Irp->IoStatus.Information = 1;
375 
376     return STATUS_SUCCESS;
377 }
378 
379 static NTSTATUS vol_check_verify(volume_device_extension* vde) {
380     pdo_device_extension* pdode = vde->pdode;
381     NTSTATUS Status;
382     LIST_ENTRY* le;
383 
384     ExAcquireResourceSharedLite(&pdode->child_lock, true);
385 
386     le = pdode->children.Flink;
387     while (le != &pdode->children) {
388         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
389 
390         Status = dev_ioctl(vc->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, false, NULL);
391         if (!NT_SUCCESS(Status))
392             goto end;
393 
394         le = le->Flink;
395     }
396 
397     Status = STATUS_SUCCESS;
398 
399 end:
400     ExReleaseResourceLite(&pdode->child_lock);
401 
402     return Status;
403 }
404 
405 static NTSTATUS vol_get_disk_extents(volume_device_extension* vde, PIRP Irp) {
406     pdo_device_extension* pdode = vde->pdode;
407     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
408     LIST_ENTRY* le;
409     ULONG num_extents = 0, i, max_extents = 1;
410     NTSTATUS Status;
411     VOLUME_DISK_EXTENTS *ext, *ext3;
412 
413     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(VOLUME_DISK_EXTENTS))
414         return STATUS_BUFFER_TOO_SMALL;
415 
416     ExAcquireResourceSharedLite(&pdode->child_lock, true);
417 
418     le = pdode->children.Flink;
419     while (le != &pdode->children) {
420         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
421         VOLUME_DISK_EXTENTS ext2;
422 
423         Status = dev_ioctl(vc->devobj, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, &ext2, sizeof(VOLUME_DISK_EXTENTS), false, NULL);
424         if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {
425             ERR("IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS returned %08lx\n", Status);
426             goto end;
427         }
428 
429         num_extents += ext2.NumberOfDiskExtents;
430 
431         if (ext2.NumberOfDiskExtents > max_extents)
432             max_extents = ext2.NumberOfDiskExtents;
433 
434         le = le->Flink;
435     }
436 
437     ext = Irp->AssociatedIrp.SystemBuffer;
438 
439     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (num_extents * sizeof(DISK_EXTENT))) {
440         Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]);
441         ext->NumberOfDiskExtents = num_extents;
442         Status = STATUS_BUFFER_OVERFLOW;
443         goto end;
444     }
445 
446     ext3 = ExAllocatePoolWithTag(PagedPool, offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (max_extents * sizeof(DISK_EXTENT)), ALLOC_TAG);
447     if (!ext3) {
448         ERR("out of memory\n");
449         Status = STATUS_INSUFFICIENT_RESOURCES;
450         goto end;
451     }
452 
453     i = 0;
454     ext->NumberOfDiskExtents = 0;
455 
456     le = pdode->children.Flink;
457     while (le != &pdode->children) {
458         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
459 
460         Status = dev_ioctl(vc->devobj, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, ext3,
461                            (ULONG)offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (max_extents * sizeof(DISK_EXTENT)), false, NULL);
462         if (!NT_SUCCESS(Status)) {
463             ERR("IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS returned %08lx\n", Status);
464             ExFreePool(ext3);
465             goto end;
466         }
467 
468         if (i + ext3->NumberOfDiskExtents > num_extents) {
469             Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]);
470             ext->NumberOfDiskExtents = i + ext3->NumberOfDiskExtents;
471             Status = STATUS_BUFFER_OVERFLOW;
472             ExFreePool(ext3);
473             goto end;
474         }
475 
476         RtlCopyMemory(&ext->Extents[i], ext3->Extents, sizeof(DISK_EXTENT) * ext3->NumberOfDiskExtents);
477         i += ext3->NumberOfDiskExtents;
478 
479         le = le->Flink;
480     }
481 
482     ExFreePool(ext3);
483 
484     Status = STATUS_SUCCESS;
485 
486     ext->NumberOfDiskExtents = i;
487     Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (i * sizeof(DISK_EXTENT));
488 
489 end:
490     ExReleaseResourceLite(&pdode->child_lock);
491 
492     return Status;
493 }
494 
495 static NTSTATUS vol_is_writable(volume_device_extension* vde) {
496     pdo_device_extension* pdode = vde->pdode;
497     NTSTATUS Status;
498     LIST_ENTRY* le;
499     bool writable = false;
500 
501     ExAcquireResourceSharedLite(&pdode->child_lock, true);
502 
503     le = pdode->children.Flink;
504     while (le != &pdode->children) {
505         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
506 
507         Status = dev_ioctl(vc->devobj, IOCTL_DISK_IS_WRITABLE, NULL, 0, NULL, 0, true, NULL);
508 
509         if (NT_SUCCESS(Status)) {
510             writable = true;
511             break;
512         } else if (Status != STATUS_MEDIA_WRITE_PROTECTED)
513             goto end;
514 
515         le = le->Flink;
516     }
517 
518     Status = writable ? STATUS_SUCCESS : STATUS_MEDIA_WRITE_PROTECTED;
519 
520 end:
521 ExReleaseResourceLite(&pdode->child_lock);
522 
523     return STATUS_SUCCESS;
524 }
525 
526 static NTSTATUS vol_get_length(volume_device_extension* vde, PIRP Irp) {
527     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
528     pdo_device_extension* pdode = vde->pdode;
529     GET_LENGTH_INFORMATION* gli;
530     LIST_ENTRY* le;
531 
532     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(GET_LENGTH_INFORMATION))
533         return STATUS_BUFFER_TOO_SMALL;
534 
535     gli = (GET_LENGTH_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
536 
537     gli->Length.QuadPart = 0;
538 
539     ExAcquireResourceSharedLite(&pdode->child_lock, true);
540 
541     le = pdode->children.Flink;
542     while (le != &pdode->children) {
543         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
544 
545         gli->Length.QuadPart += vc->size;
546 
547         le = le->Flink;
548     }
549 
550     ExReleaseResourceLite(&pdode->child_lock);
551 
552     Irp->IoStatus.Information = sizeof(GET_LENGTH_INFORMATION);
553 
554     return STATUS_SUCCESS;
555 }
556 
557 static NTSTATUS vol_get_drive_geometry(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
558     volume_device_extension* vde = DeviceObject->DeviceExtension;
559     pdo_device_extension* pdode = vde->pdode;
560     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
561     DISK_GEOMETRY* geom;
562     uint64_t length;
563     LIST_ENTRY* le;
564 
565     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY))
566         return STATUS_BUFFER_TOO_SMALL;
567 
568     length = 0;
569 
570     ExAcquireResourceSharedLite(&pdode->child_lock, true);
571 
572     le = pdode->children.Flink;
573     while (le != &pdode->children) {
574         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
575 
576         length += vc->size;
577 
578         le = le->Flink;
579     }
580 
581     ExReleaseResourceLite(&pdode->child_lock);
582 
583     geom = (DISK_GEOMETRY*)Irp->AssociatedIrp.SystemBuffer;
584     geom->BytesPerSector = DeviceObject->SectorSize == 0 ? 0x200 : DeviceObject->SectorSize;
585     geom->SectorsPerTrack = 0x3f;
586     geom->TracksPerCylinder = 0xff;
587     geom->Cylinders.QuadPart = length / (UInt32x32To64(geom->TracksPerCylinder, geom->SectorsPerTrack) * geom->BytesPerSector);
588     geom->MediaType = DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA ? RemovableMedia : FixedMedia;
589 
590     Irp->IoStatus.Information = sizeof(DISK_GEOMETRY);
591 
592     return STATUS_SUCCESS;
593 }
594 
595 static NTSTATUS vol_get_gpt_attributes(PIRP Irp) {
596     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
597     VOLUME_GET_GPT_ATTRIBUTES_INFORMATION* vggai;
598 
599     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(VOLUME_GET_GPT_ATTRIBUTES_INFORMATION))
600         return STATUS_BUFFER_TOO_SMALL;
601 
602     vggai = (VOLUME_GET_GPT_ATTRIBUTES_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
603 
604     vggai->GptAttributes = 0;
605 
606     Irp->IoStatus.Information = sizeof(VOLUME_GET_GPT_ATTRIBUTES_INFORMATION);
607 
608     return STATUS_SUCCESS;
609 }
610 
611 static NTSTATUS vol_get_device_number(volume_device_extension* vde, PIRP Irp) {
612     pdo_device_extension* pdode = vde->pdode;
613     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
614     volume_child* vc;
615     STORAGE_DEVICE_NUMBER* sdn;
616 
617     // If only one device, return its disk number. This is needed for ejection to work.
618 
619     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_DEVICE_NUMBER))
620         return STATUS_BUFFER_TOO_SMALL;
621 
622     ExAcquireResourceSharedLite(&pdode->child_lock, true);
623 
624     if (IsListEmpty(&pdode->children) || pdode->num_children > 1) {
625         ExReleaseResourceLite(&pdode->child_lock);
626         return STATUS_INVALID_DEVICE_REQUEST;
627     }
628 
629     vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);
630 
631     if (vc->disk_num == 0xffffffff) {
632         ExReleaseResourceLite(&pdode->child_lock);
633         return STATUS_INVALID_DEVICE_REQUEST;
634     }
635 
636     sdn = (STORAGE_DEVICE_NUMBER*)Irp->AssociatedIrp.SystemBuffer;
637 
638     sdn->DeviceType = FILE_DEVICE_DISK;
639     sdn->DeviceNumber = vc->disk_num;
640     sdn->PartitionNumber = vc->part_num;
641 
642     ExReleaseResourceLite(&pdode->child_lock);
643 
644     Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);
645 
646     return STATUS_SUCCESS;
647 }
648 
649 _Function_class_(IO_COMPLETION_ROUTINE)
650 static NTSTATUS __stdcall vol_ioctl_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
651     KEVENT* event = conptr;
652 
653     UNUSED(DeviceObject);
654     UNUSED(Irp);
655 
656     KeSetEvent(event, 0, false);
657 
658     return STATUS_MORE_PROCESSING_REQUIRED;
659 }
660 
661 static NTSTATUS vol_ioctl_passthrough(volume_device_extension* vde, PIRP Irp) {
662     NTSTATUS Status;
663     volume_child* vc;
664     PIRP Irp2;
665     PIO_STACK_LOCATION IrpSp, IrpSp2;
666     KEVENT Event;
667     pdo_device_extension* pdode = vde->pdode;
668 
669     TRACE("(%p, %p)\n", vde, Irp);
670 
671     ExAcquireResourceSharedLite(&pdode->child_lock, true);
672 
673     if (IsListEmpty(&pdode->children)) {
674         ExReleaseResourceLite(&pdode->child_lock);
675         return STATUS_INVALID_DEVICE_REQUEST;
676     }
677 
678     vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);
679 
680     if (vc->list_entry.Flink != &pdode->children) { // more than one device
681         ExReleaseResourceLite(&pdode->child_lock);
682         return STATUS_INVALID_DEVICE_REQUEST;
683     }
684 
685     Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);
686 
687     if (!Irp2) {
688         ERR("IoAllocateIrp failed\n");
689         ExReleaseResourceLite(&pdode->child_lock);
690         return STATUS_INSUFFICIENT_RESOURCES;
691     }
692 
693     IrpSp = IoGetCurrentIrpStackLocation(Irp);
694     IrpSp2 = IoGetNextIrpStackLocation(Irp2);
695 
696     IrpSp2->MajorFunction = IrpSp->MajorFunction;
697     IrpSp2->MinorFunction = IrpSp->MinorFunction;
698     IrpSp2->FileObject = vc->fileobj;
699 
700     IrpSp2->Parameters.DeviceIoControl.OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
701     IrpSp2->Parameters.DeviceIoControl.InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
702     IrpSp2->Parameters.DeviceIoControl.IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
703     IrpSp2->Parameters.DeviceIoControl.Type3InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
704 
705     Irp2->AssociatedIrp.SystemBuffer = Irp->AssociatedIrp.SystemBuffer;
706     Irp2->MdlAddress = Irp->MdlAddress;
707     Irp2->UserBuffer = Irp->UserBuffer;
708     Irp2->Flags = Irp->Flags;
709 
710     KeInitializeEvent(&Event, NotificationEvent, false);
711 
712     IoSetCompletionRoutine(Irp2, vol_ioctl_completion, &Event, true, true, true);
713 
714     Status = IoCallDriver(vc->devobj, Irp2);
715 
716     if (Status == STATUS_PENDING) {
717         KeWaitForSingleObject(&Event, Executive, KernelMode, false, NULL);
718         Status = Irp2->IoStatus.Status;
719     }
720 
721     Irp->IoStatus.Status = Irp2->IoStatus.Status;
722     Irp->IoStatus.Information = Irp2->IoStatus.Information;
723 
724     ExReleaseResourceLite(&pdode->child_lock);
725 
726     IoFreeIrp(Irp2);
727 
728     return Status;
729 }
730 
731 static NTSTATUS vol_query_stable_guid(volume_device_extension* vde, PIRP Irp) {
732     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
733     MOUNTDEV_STABLE_GUID* mdsg;
734     pdo_device_extension* pdode;
735 
736     if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_STABLE_GUID)) {
737         Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID);
738         return STATUS_BUFFER_TOO_SMALL;
739     }
740 
741     mdsg = Irp->AssociatedIrp.SystemBuffer;
742 
743     if (!vde->pdo)
744         return STATUS_INVALID_PARAMETER;
745 
746     pdode = vde->pdode;
747 
748     RtlCopyMemory(&mdsg->StableGuid, &pdode->uuid, sizeof(BTRFS_UUID));
749 
750     Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID);
751 
752     return STATUS_SUCCESS;
753 }
754 
755 NTSTATUS vol_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
756     volume_device_extension* vde = DeviceObject->DeviceExtension;
757     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
758 
759     TRACE("(%p, %p)\n", DeviceObject, Irp);
760 
761     Irp->IoStatus.Information = 0;
762 
763     switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {
764         case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME:
765             return vol_query_device_name(vde, Irp);
766 
767         case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID:
768             return vol_query_unique_id(vde, Irp);
769 
770         case IOCTL_STORAGE_GET_DEVICE_NUMBER:
771             return vol_get_device_number(vde, Irp);
772 
773         case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME:
774             TRACE("unhandled control code IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME\n");
775             break;
776 
777         case IOCTL_MOUNTDEV_QUERY_STABLE_GUID:
778             return vol_query_stable_guid(vde, Irp);
779 
780         case IOCTL_MOUNTDEV_LINK_CREATED:
781             TRACE("unhandled control code IOCTL_MOUNTDEV_LINK_CREATED\n");
782             break;
783 
784         case IOCTL_VOLUME_GET_GPT_ATTRIBUTES:
785             return vol_get_gpt_attributes(Irp);
786 
787         case IOCTL_VOLUME_IS_DYNAMIC:
788             return vol_is_dynamic(Irp);
789 
790         case IOCTL_VOLUME_ONLINE:
791             Irp->IoStatus.Information = 0;
792             return STATUS_SUCCESS;
793 
794         case IOCTL_VOLUME_POST_ONLINE:
795             Irp->IoStatus.Information = 0;
796             return STATUS_SUCCESS;
797 
798         case IOCTL_DISK_GET_DRIVE_GEOMETRY:
799             return vol_get_drive_geometry(DeviceObject, Irp);
800 
801         case IOCTL_DISK_IS_WRITABLE:
802             return vol_is_writable(vde);
803 
804         case IOCTL_DISK_GET_LENGTH_INFO:
805             return vol_get_length(vde, Irp);
806 
807         case IOCTL_STORAGE_CHECK_VERIFY:
808         case IOCTL_DISK_CHECK_VERIFY:
809             return vol_check_verify(vde);
810 
811         case IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS:
812             return vol_get_disk_extents(vde, Irp);
813 
814         default: { // pass ioctl through if only one child device
815             NTSTATUS Status = vol_ioctl_passthrough(vde, Irp);
816 #ifdef _DEBUG
817             ULONG code = IrpSp->Parameters.DeviceIoControl.IoControlCode;
818 
819             if (NT_SUCCESS(Status))
820                 TRACE("passing through ioctl %lx (returning %08lx)\n", code, Status);
821             else
822                 WARN("passing through ioctl %lx (returning %08lx)\n", code, Status);
823 #endif
824 
825             return Status;
826         }
827     }
828 
829     return STATUS_INVALID_DEVICE_REQUEST;
830 }
831 
832 NTSTATUS mountmgr_add_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) {
833     NTSTATUS Status;
834     ULONG mmdltsize;
835     MOUNTMGR_DRIVE_LETTER_TARGET* mmdlt;
836     MOUNTMGR_DRIVE_LETTER_INFORMATION mmdli;
837 
838     mmdltsize = (ULONG)offsetof(MOUNTMGR_DRIVE_LETTER_TARGET, DeviceName[0]) + devpath->Length;
839 
840     mmdlt = ExAllocatePoolWithTag(NonPagedPool, mmdltsize, ALLOC_TAG);
841     if (!mmdlt) {
842         ERR("out of memory\n");
843         return STATUS_INSUFFICIENT_RESOURCES;
844     }
845 
846     mmdlt->DeviceNameLength = devpath->Length;
847     RtlCopyMemory(&mmdlt->DeviceName, devpath->Buffer, devpath->Length);
848     TRACE("mmdlt = %.*S\n", (int)(mmdlt->DeviceNameLength / sizeof(WCHAR)), mmdlt->DeviceName);
849 
850     Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER, mmdlt, mmdltsize, &mmdli, sizeof(MOUNTMGR_DRIVE_LETTER_INFORMATION), false, NULL);
851 
852     if (!NT_SUCCESS(Status))
853         ERR("IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER returned %08lx\n", Status);
854     else
855         TRACE("DriveLetterWasAssigned = %u, CurrentDriveLetter = %c\n", mmdli.DriveLetterWasAssigned, mmdli.CurrentDriveLetter);
856 
857     ExFreePool(mmdlt);
858 
859     return Status;
860 }
861 
862 _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)
863 NTSTATUS __stdcall pnp_removal(PVOID NotificationStructure, PVOID Context) {
864     TARGET_DEVICE_REMOVAL_NOTIFICATION* tdrn = (TARGET_DEVICE_REMOVAL_NOTIFICATION*)NotificationStructure;
865     pdo_device_extension* pdode = (pdo_device_extension*)Context;
866 
867     if (RtlCompareMemory(&tdrn->Event, &GUID_TARGET_DEVICE_QUERY_REMOVE, sizeof(GUID)) == sizeof(GUID)) {
868         TRACE("GUID_TARGET_DEVICE_QUERY_REMOVE\n");
869 
870         if (pdode->vde && pdode->vde->mounted_device)
871             pnp_query_remove_device(pdode->vde->mounted_device, NULL);
872     }
873 
874     return STATUS_SUCCESS;
875 }
876 
877 static bool allow_degraded_mount(BTRFS_UUID* uuid) {
878     HANDLE h;
879     NTSTATUS Status;
880     OBJECT_ATTRIBUTES oa;
881     UNICODE_STRING path, adus;
882     uint32_t degraded = mount_allow_degraded;
883     ULONG i, j, kvfilen, retlen;
884     KEY_VALUE_FULL_INFORMATION* kvfi;
885 
886     path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));
887     path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);
888 
889     if (!path.Buffer) {
890         ERR("out of memory\n");
891         return false;
892     }
893 
894     RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);
895     i = registry_path.Length / sizeof(WCHAR);
896 
897     path.Buffer[i] = '\\';
898     i++;
899 
900     for (j = 0; j < 16; j++) {
901         path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);
902         path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);
903 
904         i += 2;
905 
906         if (j == 3 || j == 5 || j == 7 || j == 9) {
907             path.Buffer[i] = '-';
908             i++;
909         }
910     }
911 
912     InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
913 
914     kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR));
915     kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
916     if (!kvfi) {
917         ERR("out of memory\n");
918         ExFreePool(path.Buffer);
919         return false;
920     }
921 
922     Status = ZwOpenKey(&h, KEY_QUERY_VALUE, &oa);
923     if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
924         goto end;
925     else if (!NT_SUCCESS(Status)) {
926         ERR("ZwOpenKey returned %08lx\n", Status);
927         goto end;
928     }
929 
930     adus.Buffer = L"AllowDegraded";
931     adus.Length = adus.MaximumLength = sizeof(adus.Buffer) - sizeof(WCHAR);
932 
933     if (NT_SUCCESS(ZwQueryValueKey(h, &adus, KeyValueFullInformation, kvfi, kvfilen, &retlen))) {
934         if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) {
935             uint32_t* val = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset);
936 
937             degraded = *val;
938         }
939     }
940 
941     ZwClose(h);
942 
943 end:
944     ExFreePool(kvfi);
945 
946     ExFreePool(path.Buffer);
947 
948     return degraded;
949 }
950 
951 typedef struct {
952     LIST_ENTRY list_entry;
953     UNICODE_STRING name;
954     NTSTATUS Status;
955     BTRFS_UUID uuid;
956 } drive_letter_removal;
957 
958 static void drive_letter_callback2(pdo_device_extension* pdode, PDEVICE_OBJECT mountmgr) {
959     LIST_ENTRY* le;
960     LIST_ENTRY dlrlist;
961 
962     InitializeListHead(&dlrlist);
963 
964     ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
965 
966     le = pdode->children.Flink;
967 
968     while (le != &pdode->children) {
969         drive_letter_removal* dlr;
970 
971         volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
972 
973         dlr = ExAllocatePoolWithTag(PagedPool, sizeof(drive_letter_removal), ALLOC_TAG);
974         if (!dlr) {
975             ERR("out of memory\n");
976 
977             while (!IsListEmpty(&dlrlist)) {
978                 dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry);
979 
980                 ExFreePool(dlr->name.Buffer);
981                 ExFreePool(dlr);
982             }
983 
984             ExReleaseResourceLite(&pdode->child_lock);
985             return;
986         }
987 
988         dlr->name.Length = dlr->name.MaximumLength = vc->pnp_name.Length + (3 * sizeof(WCHAR));
989         dlr->name.Buffer = ExAllocatePoolWithTag(PagedPool, dlr->name.Length, ALLOC_TAG);
990 
991         if (!dlr->name.Buffer) {
992             ERR("out of memory\n");
993 
994             ExFreePool(dlr);
995 
996             while (!IsListEmpty(&dlrlist)) {
997                 dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry);
998 
999                 ExFreePool(dlr->name.Buffer);
1000                 ExFreePool(dlr);
1001             }
1002 
1003             ExReleaseResourceLite(&pdode->child_lock);
1004             return;
1005         }
1006 
1007         RtlCopyMemory(dlr->name.Buffer, L"\\??", 3 * sizeof(WCHAR));
1008         RtlCopyMemory(&dlr->name.Buffer[3], vc->pnp_name.Buffer, vc->pnp_name.Length);
1009 
1010         dlr->uuid = vc->uuid;
1011 
1012         InsertTailList(&dlrlist, &dlr->list_entry);
1013 
1014         le = le->Flink;
1015     }
1016 
1017     ExReleaseResourceLite(&pdode->child_lock);
1018 
1019     le = dlrlist.Flink;
1020     while (le != &dlrlist) {
1021         drive_letter_removal* dlr = CONTAINING_RECORD(le, drive_letter_removal, list_entry);
1022 
1023         dlr->Status = remove_drive_letter(mountmgr, &dlr->name);
1024 
1025         if (!NT_SUCCESS(dlr->Status) && dlr->Status != STATUS_NOT_FOUND)
1026             WARN("remove_drive_letter returned %08lx\n", dlr->Status);
1027 
1028         le = le->Flink;
1029     }
1030 
1031     // set vc->had_drive_letter
1032 
1033     ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
1034 
1035     while (!IsListEmpty(&dlrlist)) {
1036         drive_letter_removal* dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry);
1037 
1038         le = pdode->children.Flink;
1039 
1040         while (le != &pdode->children) {
1041             volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
1042 
1043             if (RtlCompareMemory(&vc->uuid, &dlr->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
1044                 vc->had_drive_letter = NT_SUCCESS(dlr->Status);
1045                 break;
1046             }
1047 
1048             le = le->Flink;
1049         }
1050 
1051         ExFreePool(dlr->name.Buffer);
1052         ExFreePool(dlr);
1053     }
1054 
1055     ExReleaseResourceLite(&pdode->child_lock);
1056 }
1057 
1058 _Function_class_(IO_WORKITEM_ROUTINE)
1059 static void __stdcall drive_letter_callback(pdo_device_extension* pdode) {
1060     NTSTATUS Status;
1061     UNICODE_STRING mmdevpath;
1062     PDEVICE_OBJECT mountmgr;
1063     PFILE_OBJECT mountmgrfo;
1064 
1065     RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
1066     Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr);
1067     if (!NT_SUCCESS(Status)) {
1068         ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
1069         return;
1070     }
1071 
1072     drive_letter_callback2(pdode, mountmgr);
1073 
1074     ObDereferenceObject(mountmgrfo);
1075 }
1076 
1077 void add_volume_device(superblock* sb, PUNICODE_STRING devpath, uint64_t length, ULONG disk_num, ULONG part_num) {
1078     NTSTATUS Status;
1079     LIST_ENTRY* le;
1080     PDEVICE_OBJECT DeviceObject;
1081     volume_child* vc;
1082     PFILE_OBJECT FileObject;
1083     UNICODE_STRING devpath2;
1084     bool inserted = false, new_pdo = false;
1085     pdo_device_extension* pdode = NULL;
1086     PDEVICE_OBJECT pdo = NULL;
1087     bool process_drive_letters = false;
1088 
1089     if (devpath->Length == 0)
1090         return;
1091 
1092     ExAcquireResourceExclusiveLite(&pdo_list_lock, true);
1093 
1094     le = pdo_list.Flink;
1095     while (le != &pdo_list) {
1096         pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
1097 
1098         if (RtlCompareMemory(&pdode2->uuid, &sb->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
1099             pdode = pdode2;
1100             break;
1101         }
1102 
1103         le = le->Flink;
1104     }
1105 
1106     Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject);
1107     if (!NT_SUCCESS(Status)) {
1108         ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
1109         ExReleaseResourceLite(&pdo_list_lock);
1110         return;
1111     }
1112 
1113     if (!pdode) {
1114         if (no_pnp) {
1115             Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF, NULL, NULL, 0, &pdo);
1116 
1117             if (!NT_SUCCESS(Status)) {
1118                 ERR("IoReportDetectedDevice returned %08lx\n", Status);
1119                 ExReleaseResourceLite(&pdo_list_lock);
1120                 return;
1121             }
1122 
1123             pdode = ExAllocatePoolWithTag(NonPagedPool, sizeof(pdo_device_extension), ALLOC_TAG);
1124 
1125             if (!pdode) {
1126                 ERR("out of memory\n");
1127                 ExReleaseResourceLite(&pdo_list_lock);
1128                 return;
1129             }
1130         } else {
1131             Status = IoCreateDevice(drvobj, sizeof(pdo_device_extension), NULL, FILE_DEVICE_DISK,
1132                                     FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN, false, &pdo);
1133             if (!NT_SUCCESS(Status)) {
1134                 ERR("IoCreateDevice returned %08lx\n", Status);
1135                 ExReleaseResourceLite(&pdo_list_lock);
1136                 goto fail;
1137             }
1138 
1139             pdo->Flags |= DO_BUS_ENUMERATED_DEVICE;
1140 
1141             pdode = pdo->DeviceExtension;
1142         }
1143 
1144         RtlZeroMemory(pdode, sizeof(pdo_device_extension));
1145 
1146         pdode->type = VCB_TYPE_PDO;
1147         pdode->pdo = pdo;
1148         pdode->uuid = sb->uuid;
1149 
1150         ExInitializeResourceLite(&pdode->child_lock);
1151         InitializeListHead(&pdode->children);
1152         pdode->num_children = sb->num_devices;
1153         pdode->children_loaded = 0;
1154 
1155         pdo->Flags &= ~DO_DEVICE_INITIALIZING;
1156         pdo->SectorSize = (USHORT)sb->sector_size;
1157 
1158         ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
1159 
1160         new_pdo = true;
1161     } else {
1162         ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
1163         ExConvertExclusiveToSharedLite(&pdo_list_lock);
1164 
1165         le = pdode->children.Flink;
1166         while (le != &pdode->children) {
1167             volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);
1168 
1169             if (RtlCompareMemory(&vc2->uuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
1170                 // duplicate, ignore
1171                 ExReleaseResourceLite(&pdode->child_lock);
1172                 ExReleaseResourceLite(&pdo_list_lock);
1173                 goto fail;
1174             }
1175 
1176             le = le->Flink;
1177         }
1178     }
1179 
1180     vc = ExAllocatePoolWithTag(PagedPool, sizeof(volume_child), ALLOC_TAG);
1181     if (!vc) {
1182         ERR("out of memory\n");
1183 
1184         ExReleaseResourceLite(&pdode->child_lock);
1185         ExReleaseResourceLite(&pdo_list_lock);
1186 
1187         goto fail;
1188     }
1189 
1190     vc->uuid = sb->dev_item.device_uuid;
1191     vc->devid = sb->dev_item.dev_id;
1192     vc->generation = sb->generation;
1193     vc->notification_entry = NULL;
1194     vc->boot_volume = false;
1195 
1196     Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, FileObject,
1197                                             drvobj, pnp_removal, pdode, &vc->notification_entry);
1198     if (!NT_SUCCESS(Status))
1199         WARN("IoRegisterPlugPlayNotification returned %08lx\n", Status);
1200 
1201     vc->devobj = DeviceObject;
1202     vc->fileobj = FileObject;
1203 
1204     devpath2 = *devpath;
1205 
1206     // The PNP path sometimes begins \\?\ and sometimes \??\. We need to remove this prefix
1207     // so we can compare properly if the device is removed.
1208     if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\' && (devpath->Buffer[1] == '\\' || devpath->Buffer[1] == '?') &&
1209         devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\') {
1210         devpath2.Buffer = &devpath2.Buffer[3];
1211         devpath2.Length -= 3 * sizeof(WCHAR);
1212         devpath2.MaximumLength -= 3 * sizeof(WCHAR);
1213     }
1214 
1215     vc->pnp_name.Length = vc->pnp_name.MaximumLength = devpath2.Length;
1216     vc->pnp_name.Buffer = ExAllocatePoolWithTag(PagedPool, devpath2.Length, ALLOC_TAG);
1217 
1218     if (vc->pnp_name.Buffer)
1219         RtlCopyMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length);
1220     else {
1221         ERR("out of memory\n");
1222         vc->pnp_name.Length = vc->pnp_name.MaximumLength = 0;
1223     }
1224 
1225     vc->size = length;
1226     vc->seeding = sb->flags & BTRFS_SUPERBLOCK_FLAGS_SEEDING ? true : false;
1227     vc->disk_num = disk_num;
1228     vc->part_num = part_num;
1229     vc->had_drive_letter = false;
1230 
1231     le = pdode->children.Flink;
1232     while (le != &pdode->children) {
1233         volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);
1234 
1235         if (vc2->generation < vc->generation) {
1236             if (le == pdode->children.Flink)
1237                 pdode->num_children = sb->num_devices;
1238 
1239             InsertHeadList(vc2->list_entry.Blink, &vc->list_entry);
1240             inserted = true;
1241             break;
1242         }
1243 
1244         le = le->Flink;
1245     }
1246 
1247     if (!inserted)
1248         InsertTailList(&pdode->children, &vc->list_entry);
1249 
1250     pdode->children_loaded++;
1251 
1252     if (pdode->vde && pdode->vde->mounted_device) {
1253         device_extension* Vcb = pdode->vde->mounted_device->DeviceExtension;
1254 
1255         ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);
1256 
1257         le = Vcb->devices.Flink;
1258         while (le != &Vcb->devices) {
1259             device* dev = CONTAINING_RECORD(le, device, list_entry);
1260 
1261             if (!dev->devobj && RtlCompareMemory(&dev->devitem.device_uuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
1262                 dev->devobj = DeviceObject;
1263                 dev->disk_num = disk_num;
1264                 dev->part_num = part_num;
1265                 init_device(Vcb, dev, false);
1266                 break;
1267             }
1268 
1269             le = le->Flink;
1270         }
1271 
1272         ExReleaseResourceLite(&Vcb->tree_lock);
1273     }
1274 
1275     if (DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA) {
1276         pdode->removable = true;
1277 
1278         if (pdode->vde && pdode->vde->device)
1279             pdode->vde->device->Characteristics |= FILE_REMOVABLE_MEDIA;
1280     }
1281 
1282     if (pdode->num_children == pdode->children_loaded || (pdode->children_loaded == 1 && allow_degraded_mount(&sb->uuid))) {
1283         if ((!new_pdo || !no_pnp) && pdode->vde) {
1284             Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, true);
1285             if (!NT_SUCCESS(Status))
1286                 WARN("IoSetDeviceInterfaceState returned %08lx\n", Status);
1287         }
1288 
1289         process_drive_letters = true;
1290     }
1291 
1292     ExReleaseResourceLite(&pdode->child_lock);
1293 
1294     if (new_pdo)
1295         InsertTailList(&pdo_list, &pdode->list_entry);
1296 
1297     ExReleaseResourceLite(&pdo_list_lock);
1298 
1299     if (process_drive_letters)
1300         drive_letter_callback(pdode);
1301 
1302     if (new_pdo) {
1303         if (RtlCompareMemory(&sb->uuid, &boot_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID))
1304             boot_add_device(pdo);
1305         else if (no_pnp)
1306             AddDevice(drvobj, pdo);
1307         else {
1308             bus_device_extension* bde = busobj->DeviceExtension;
1309             IoInvalidateDeviceRelations(bde->buspdo, BusRelations);
1310         }
1311     }
1312 
1313     return;
1314 
1315 fail:
1316     ObDereferenceObject(FileObject);
1317 }
1318