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 "xxhash.h"
20 #include "crc32c.h"
21 #include <ata.h>
22 #include <ntddscsi.h>
23 #include <ntddstor.h>
24 
25 #define MAX_CSUM_SIZE (4096 - sizeof(tree_header) - sizeof(leaf_node))
26 
27 // #define DEBUG_WRITE_LOOPS
28 
29 typedef struct {
30     KEVENT Event;
31     IO_STATUS_BLOCK iosb;
32 } write_context;
33 
34 typedef struct {
35     EXTENT_ITEM_TREE eit;
36     uint8_t type;
37     TREE_BLOCK_REF tbr;
38 } EXTENT_ITEM_TREE2;
39 
40 typedef struct {
41     EXTENT_ITEM ei;
42     uint8_t type;
43     TREE_BLOCK_REF tbr;
44 } EXTENT_ITEM_SKINNY_METADATA;
45 
46 static NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp);
47 static NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback);
48 
49 #ifndef _MSC_VER // not in mingw yet
50 #define DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED 0x80000000
51 #endif
52 
53 _Function_class_(IO_COMPLETION_ROUTINE)
54 static NTSTATUS __stdcall write_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
55     write_context* context = conptr;
56 
57     UNUSED(DeviceObject);
58 
59     context->iosb = Irp->IoStatus;
60     KeSetEvent(&context->Event, 0, false);
61 
62     return STATUS_MORE_PROCESSING_REQUIRED;
63 }
64 
65 NTSTATUS write_data_phys(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t address,
66                          _In_reads_bytes_(length) void* data, _In_ uint32_t length) {
67     NTSTATUS Status;
68     LARGE_INTEGER offset;
69     PIRP Irp;
70     PIO_STACK_LOCATION IrpSp;
71     write_context context;
72 
73     TRACE("(%p, %I64x, %p, %x)\n", device, address, data, length);
74 
75     RtlZeroMemory(&context, sizeof(write_context));
76 
77     KeInitializeEvent(&context.Event, NotificationEvent, false);
78 
79     offset.QuadPart = address;
80 
81     Irp = IoAllocateIrp(device->StackSize, false);
82 
83     if (!Irp) {
84         ERR("IoAllocateIrp failed\n");
85         return STATUS_INSUFFICIENT_RESOURCES;
86     }
87 
88     IrpSp = IoGetNextIrpStackLocation(Irp);
89     IrpSp->MajorFunction = IRP_MJ_WRITE;
90     IrpSp->FileObject = fileobj;
91 
92     if (device->Flags & DO_BUFFERED_IO) {
93         Irp->AssociatedIrp.SystemBuffer = data;
94 
95         Irp->Flags = IRP_BUFFERED_IO;
96     } else if (device->Flags & DO_DIRECT_IO) {
97         Irp->MdlAddress = IoAllocateMdl(data, length, false, false, NULL);
98         if (!Irp->MdlAddress) {
99             DbgPrint("IoAllocateMdl failed\n");
100             Status = STATUS_INSUFFICIENT_RESOURCES;
101             goto exit;
102         }
103 
104         Status = STATUS_SUCCESS;
105 
106         _SEH2_TRY {
107             MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoReadAccess);
108         } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
109             Status = _SEH2_GetExceptionCode();
110         } _SEH2_END;
111 
112         if (!NT_SUCCESS(Status)) {
113             ERR("MmProbeAndLockPages threw exception %08lx\n", Status);
114             IoFreeMdl(Irp->MdlAddress);
115             goto exit;
116         }
117     } else {
118         Irp->UserBuffer = data;
119     }
120 
121     IrpSp->Parameters.Write.Length = length;
122     IrpSp->Parameters.Write.ByteOffset = offset;
123 
124     Irp->UserIosb = &context.iosb;
125 
126     Irp->UserEvent = &context.Event;
127 
128     IoSetCompletionRoutine(Irp, write_completion, &context, true, true, true);
129 
130     Status = IoCallDriver(device, Irp);
131 
132     if (Status == STATUS_PENDING) {
133         KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
134         Status = context.iosb.Status;
135     }
136 
137     if (!NT_SUCCESS(Status)) {
138         ERR("IoCallDriver returned %08lx\n", Status);
139     }
140 
141     if (device->Flags & DO_DIRECT_IO) {
142         MmUnlockPages(Irp->MdlAddress);
143         IoFreeMdl(Irp->MdlAddress);
144     }
145 
146 exit:
147     IoFreeIrp(Irp);
148 
149     return Status;
150 }
151 
152 static void add_trim_entry(device* dev, uint64_t address, uint64_t size) {
153     space* s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
154     if (!s) {
155         ERR("out of memory\n");
156         return;
157     }
158 
159     s->address = address;
160     s->size = size;
161     dev->num_trim_entries++;
162 
163     InsertTailList(&dev->trim_list, &s->list_entry);
164 }
165 
166 static void clean_space_cache_chunk(device_extension* Vcb, chunk* c) {
167     ULONG type;
168 
169     if (Vcb->trim && !Vcb->options.no_trim) {
170         if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE)
171             type = BLOCK_FLAG_DUPLICATE;
172         else if (c->chunk_item->type & BLOCK_FLAG_RAID0)
173             type = BLOCK_FLAG_RAID0;
174         else if (c->chunk_item->type & BLOCK_FLAG_RAID1)
175             type = BLOCK_FLAG_DUPLICATE;
176         else if (c->chunk_item->type & BLOCK_FLAG_RAID10)
177             type = BLOCK_FLAG_RAID10;
178         else if (c->chunk_item->type & BLOCK_FLAG_RAID5)
179             type = BLOCK_FLAG_RAID5;
180         else if (c->chunk_item->type & BLOCK_FLAG_RAID6)
181             type = BLOCK_FLAG_RAID6;
182         else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3)
183             type = BLOCK_FLAG_DUPLICATE;
184         else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4)
185             type = BLOCK_FLAG_DUPLICATE;
186         else // SINGLE
187             type = BLOCK_FLAG_DUPLICATE;
188     }
189 
190     while (!IsListEmpty(&c->deleting)) {
191         space* s = CONTAINING_RECORD(c->deleting.Flink, space, list_entry);
192 
193         if (Vcb->trim && !Vcb->options.no_trim && (!Vcb->options.no_barrier || !(c->chunk_item->type & BLOCK_FLAG_METADATA))) {
194             CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
195 
196             if (type == BLOCK_FLAG_DUPLICATE) {
197                 uint16_t i;
198 
199                 for (i = 0; i < c->chunk_item->num_stripes; i++) {
200                     if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim)
201                         add_trim_entry(c->devices[i], s->address - c->offset + cis[i].offset, s->size);
202                 }
203             } else if (type == BLOCK_FLAG_RAID0) {
204                 uint64_t startoff, endoff;
205                 uint16_t startoffstripe, endoffstripe, i;
206 
207                 get_raid0_offset(s->address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe);
208                 get_raid0_offset(s->address - c->offset + s->size - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe);
209 
210                 for (i = 0; i < c->chunk_item->num_stripes; i++) {
211                     if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim) {
212                         uint64_t stripestart, stripeend;
213 
214                         if (startoffstripe > i)
215                             stripestart = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;
216                         else if (startoffstripe == i)
217                             stripestart = startoff;
218                         else
219                             stripestart = startoff - (startoff % c->chunk_item->stripe_length);
220 
221                         if (endoffstripe > i)
222                             stripeend = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;
223                         else if (endoffstripe == i)
224                             stripeend = endoff + 1;
225                         else
226                             stripeend = endoff - (endoff % c->chunk_item->stripe_length);
227 
228                         if (stripestart != stripeend)
229                             add_trim_entry(c->devices[i], stripestart + cis[i].offset, stripeend - stripestart);
230                     }
231                 }
232             } else if (type == BLOCK_FLAG_RAID10) {
233                 uint64_t startoff, endoff;
234                 uint16_t sub_stripes, startoffstripe, endoffstripe, i;
235 
236                 sub_stripes = max(1, c->chunk_item->sub_stripes);
237 
238                 get_raid0_offset(s->address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &startoff, &startoffstripe);
239                 get_raid0_offset(s->address - c->offset + s->size - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &endoff, &endoffstripe);
240 
241                 startoffstripe *= sub_stripes;
242                 endoffstripe *= sub_stripes;
243 
244                 for (i = 0; i < c->chunk_item->num_stripes; i += sub_stripes) {
245                     ULONG j;
246                     uint64_t stripestart, stripeend;
247 
248                     if (startoffstripe > i)
249                         stripestart = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;
250                     else if (startoffstripe == i)
251                         stripestart = startoff;
252                     else
253                         stripestart = startoff - (startoff % c->chunk_item->stripe_length);
254 
255                     if (endoffstripe > i)
256                         stripeend = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;
257                     else if (endoffstripe == i)
258                         stripeend = endoff + 1;
259                     else
260                         stripeend = endoff - (endoff % c->chunk_item->stripe_length);
261 
262                     if (stripestart != stripeend) {
263                         for (j = 0; j < sub_stripes; j++) {
264                             if (c->devices[i+j] && c->devices[i+j]->devobj && !c->devices[i+j]->readonly && c->devices[i+j]->trim)
265                                 add_trim_entry(c->devices[i+j], stripestart + cis[i+j].offset, stripeend - stripestart);
266                         }
267                     }
268                 }
269             }
270             // FIXME - RAID5(?), RAID6(?)
271         }
272 
273         RemoveEntryList(&s->list_entry);
274         ExFreePool(s);
275     }
276 }
277 
278 typedef struct {
279     DEVICE_MANAGE_DATA_SET_ATTRIBUTES* dmdsa;
280     ATA_PASS_THROUGH_EX apte;
281     PIRP Irp;
282     IO_STATUS_BLOCK iosb;
283 #ifdef DEBUG_TRIM_EMULATION
284     PMDL mdl;
285     void* buf;
286 #endif
287 } ioctl_context_stripe;
288 
289 typedef struct {
290     KEVENT Event;
291     LONG left;
292     ioctl_context_stripe* stripes;
293 } ioctl_context;
294 
295 _Function_class_(IO_COMPLETION_ROUTINE)
296 static NTSTATUS __stdcall ioctl_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
297     ioctl_context* context = (ioctl_context*)conptr;
298     LONG left2 = InterlockedDecrement(&context->left);
299 
300     UNUSED(DeviceObject);
301     UNUSED(Irp);
302 
303     if (left2 == 0)
304         KeSetEvent(&context->Event, 0, false);
305 
306     return STATUS_MORE_PROCESSING_REQUIRED;
307 }
308 
309 #ifdef DEBUG_TRIM_EMULATION
310 static void trim_emulation(device* dev) {
311     LIST_ENTRY* le;
312     ioctl_context context;
313     unsigned int i = 0, count = 0;
314 
315     le = dev->trim_list.Flink;
316     while (le != &dev->trim_list) {
317         count++;
318         le = le->Flink;
319     }
320 
321     context.left = count;
322 
323     KeInitializeEvent(&context.Event, NotificationEvent, false);
324 
325     context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG);
326     if (!context.stripes) {
327         ERR("out of memory\n");
328         return;
329     }
330 
331     RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left);
332 
333     i = 0;
334     le = dev->trim_list.Flink;
335     while (le != &dev->trim_list) {
336         ioctl_context_stripe* stripe = &context.stripes[i];
337         space* s = CONTAINING_RECORD(le, space, list_entry);
338 
339         WARN("(%I64x, %I64x)\n", s->address, s->size);
340 
341         stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false);
342 
343         if (!stripe->Irp) {
344             ERR("IoAllocateIrp failed\n");
345         } else {
346             PIO_STACK_LOCATION IrpSp = IoGetNextIrpStackLocation(stripe->Irp);
347             IrpSp->MajorFunction = IRP_MJ_WRITE;
348             IrpSp->FileObject = dev->fileobj;
349 
350             stripe->buf = ExAllocatePoolWithTag(NonPagedPool, (uint32_t)s->size, ALLOC_TAG);
351 
352             if (!stripe->buf) {
353                 ERR("out of memory\n");
354             } else {
355                 RtlZeroMemory(stripe->buf, (uint32_t)s->size); // FIXME - randomize instead?
356 
357                 stripe->mdl = IoAllocateMdl(stripe->buf, (uint32_t)s->size, false, false, NULL);
358 
359                 if (!stripe->mdl) {
360                     ERR("IoAllocateMdl failed\n");
361                 } else {
362                     MmBuildMdlForNonPagedPool(stripe->mdl);
363 
364                     stripe->Irp->MdlAddress = stripe->mdl;
365 
366                     IrpSp->Parameters.Write.ByteOffset.QuadPart = s->address;
367                     IrpSp->Parameters.Write.Length = s->size;
368 
369                     stripe->Irp->UserIosb = &stripe->iosb;
370 
371                     IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true);
372 
373                     IoCallDriver(dev->devobj, stripe->Irp);
374                 }
375             }
376         }
377 
378         i++;
379 
380         le = le->Flink;
381     }
382 
383     KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
384 
385     for (i = 0; i < count; i++) {
386         ioctl_context_stripe* stripe = &context.stripes[i];
387 
388         if (stripe->mdl)
389             IoFreeMdl(stripe->mdl);
390 
391         if (stripe->buf)
392             ExFreePool(stripe->buf);
393     }
394 
395     ExFreePool(context.stripes);
396 }
397 #endif
398 
399 static void clean_space_cache(device_extension* Vcb) {
400     LIST_ENTRY* le;
401     chunk* c;
402 #ifndef DEBUG_TRIM_EMULATION
403     ULONG num;
404 #endif
405 
406     TRACE("(%p)\n", Vcb);
407 
408     ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);
409 
410     le = Vcb->chunks.Flink;
411     while (le != &Vcb->chunks) {
412         c = CONTAINING_RECORD(le, chunk, list_entry);
413 
414         if (c->space_changed) {
415             acquire_chunk_lock(c, Vcb);
416 
417             if (c->space_changed)
418                 clean_space_cache_chunk(Vcb, c);
419 
420             c->space_changed = false;
421 
422             release_chunk_lock(c, Vcb);
423         }
424 
425         le = le->Flink;
426     }
427 
428     ExReleaseResourceLite(&Vcb->chunk_lock);
429 
430     if (Vcb->trim && !Vcb->options.no_trim) {
431 #ifndef DEBUG_TRIM_EMULATION
432         ioctl_context context;
433         ULONG total_num;
434 
435         context.left = 0;
436 
437         le = Vcb->devices.Flink;
438         while (le != &Vcb->devices) {
439             device* dev = CONTAINING_RECORD(le, device, list_entry);
440 
441             if (dev->devobj && !dev->readonly && dev->trim && dev->num_trim_entries > 0)
442                 context.left++;
443 
444             le = le->Flink;
445         }
446 
447         if (context.left == 0)
448             return;
449 
450         total_num = context.left;
451         num = 0;
452 
453         KeInitializeEvent(&context.Event, NotificationEvent, false);
454 
455         context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG);
456         if (!context.stripes) {
457             ERR("out of memory\n");
458             return;
459         }
460 
461         RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left);
462 #endif
463 
464         le = Vcb->devices.Flink;
465         while (le != &Vcb->devices) {
466             device* dev = CONTAINING_RECORD(le, device, list_entry);
467 
468             if (dev->devobj && !dev->readonly && dev->trim && dev->num_trim_entries > 0) {
469 #ifdef DEBUG_TRIM_EMULATION
470                 trim_emulation(dev);
471 #else
472                 LIST_ENTRY* le2;
473                 ioctl_context_stripe* stripe = &context.stripes[num];
474                 DEVICE_DATA_SET_RANGE* ranges;
475                 ULONG datalen = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)) + (dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE)), i;
476                 PIO_STACK_LOCATION IrpSp;
477 
478                 stripe->dmdsa = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);
479                 if (!stripe->dmdsa) {
480                     ERR("out of memory\n");
481                     goto nextdev;
482                 }
483 
484                 stripe->dmdsa->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES);
485                 stripe->dmdsa->Action = DeviceDsmAction_Trim;
486                 stripe->dmdsa->Flags = DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED;
487                 stripe->dmdsa->ParameterBlockOffset = 0;
488                 stripe->dmdsa->ParameterBlockLength = 0;
489                 stripe->dmdsa->DataSetRangesOffset = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t));
490                 stripe->dmdsa->DataSetRangesLength = dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE);
491 
492                 ranges = (DEVICE_DATA_SET_RANGE*)((uint8_t*)stripe->dmdsa + stripe->dmdsa->DataSetRangesOffset);
493 
494                 i = 0;
495 
496                 le2 = dev->trim_list.Flink;
497                 while (le2 != &dev->trim_list) {
498                     space* s = CONTAINING_RECORD(le2, space, list_entry);
499 
500                     ranges[i].StartingOffset = s->address;
501                     ranges[i].LengthInBytes = s->size;
502                     i++;
503 
504                     le2 = le2->Flink;
505                 }
506 
507                 stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false);
508 
509                 if (!stripe->Irp) {
510                     ERR("IoAllocateIrp failed\n");
511                     goto nextdev;
512                 }
513 
514                 IrpSp = IoGetNextIrpStackLocation(stripe->Irp);
515                 IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
516                 IrpSp->FileObject = dev->fileobj;
517 
518                 IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES;
519                 IrpSp->Parameters.DeviceIoControl.InputBufferLength = datalen;
520                 IrpSp->Parameters.DeviceIoControl.OutputBufferLength = 0;
521 
522                 stripe->Irp->AssociatedIrp.SystemBuffer = stripe->dmdsa;
523                 stripe->Irp->Flags |= IRP_BUFFERED_IO;
524                 stripe->Irp->UserBuffer = NULL;
525                 stripe->Irp->UserIosb = &stripe->iosb;
526 
527                 IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true);
528 
529                 IoCallDriver(dev->devobj, stripe->Irp);
530 
531 nextdev:
532 #endif
533                 while (!IsListEmpty(&dev->trim_list)) {
534                     space* s = CONTAINING_RECORD(RemoveHeadList(&dev->trim_list), space, list_entry);
535                     ExFreePool(s);
536                 }
537 
538                 dev->num_trim_entries = 0;
539 
540 #ifndef DEBUG_TRIM_EMULATION
541                 num++;
542 #endif
543             }
544 
545             le = le->Flink;
546         }
547 
548 #ifndef DEBUG_TRIM_EMULATION
549         KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
550 
551         for (num = 0; num < total_num; num++) {
552             if (context.stripes[num].dmdsa)
553                 ExFreePool(context.stripes[num].dmdsa);
554 
555             if (context.stripes[num].Irp)
556                 IoFreeIrp(context.stripes[num].Irp);
557         }
558 
559         ExFreePool(context.stripes);
560 #endif
561     }
562 }
563 
564 static bool trees_consistent(device_extension* Vcb) {
565     ULONG maxsize = Vcb->superblock.node_size - sizeof(tree_header);
566     LIST_ENTRY* le;
567 
568     le = Vcb->trees.Flink;
569     while (le != &Vcb->trees) {
570         tree* t = CONTAINING_RECORD(le, tree, list_entry);
571 
572         if (t->write) {
573             if (t->header.num_items == 0 && t->parent) {
574 #ifdef DEBUG_WRITE_LOOPS
575                 ERR("empty tree found, looping again\n");
576 #endif
577                 return false;
578             }
579 
580             if (t->size > maxsize) {
581 #ifdef DEBUG_WRITE_LOOPS
582                 ERR("overlarge tree found (%u > %u), looping again\n", t->size, maxsize);
583 #endif
584                 return false;
585             }
586 
587             if (!t->has_new_address) {
588 #ifdef DEBUG_WRITE_LOOPS
589                 ERR("tree found without new address, looping again\n");
590 #endif
591                 return false;
592             }
593         }
594 
595         le = le->Flink;
596     }
597 
598     return true;
599 }
600 
601 static NTSTATUS add_parents(device_extension* Vcb, PIRP Irp) {
602     ULONG level;
603     LIST_ENTRY* le;
604 
605     for (level = 0; level <= 255; level++) {
606         bool nothing_found = true;
607 
608         TRACE("level = %lu\n", level);
609 
610         le = Vcb->trees.Flink;
611         while (le != &Vcb->trees) {
612             tree* t = CONTAINING_RECORD(le, tree, list_entry);
613 
614             if (t->write && t->header.level == level) {
615                 TRACE("tree %p: root = %I64x, level = %x, parent = %p\n", t, t->header.tree_id, t->header.level, t->parent);
616 
617                 nothing_found = false;
618 
619                 if (t->parent) {
620                     if (!t->parent->write)
621                         TRACE("adding tree %p (level %x)\n", t->parent, t->header.level);
622 
623                     t->parent->write = true;
624                 } else if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) {
625                     KEY searchkey;
626                     traverse_ptr tp;
627                     NTSTATUS Status;
628 #ifdef __REACTOS__
629                     tree* t2;
630 #endif
631 
632                     searchkey.obj_id = t->root->id;
633                     searchkey.obj_type = TYPE_ROOT_ITEM;
634                     searchkey.offset = 0xffffffffffffffff;
635 
636                     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
637                     if (!NT_SUCCESS(Status)) {
638                         ERR("error - find_item returned %08lx\n", Status);
639                         return Status;
640                     }
641 
642                     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
643                         ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id);
644                         return STATUS_INTERNAL_ERROR;
645                     }
646 
647                     if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, delete and create new entry
648                         ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
649 
650                         if (!ri) {
651                             ERR("out of memory\n");
652                             return STATUS_INSUFFICIENT_RESOURCES;
653                         }
654 
655                         RtlCopyMemory(ri, &t->root->root_item, sizeof(ROOT_ITEM));
656 
657                         Status = delete_tree_item(Vcb, &tp);
658                         if (!NT_SUCCESS(Status)) {
659                             ERR("delete_tree_item returned %08lx\n", Status);
660                             ExFreePool(ri);
661                             return Status;
662                         }
663 
664                         Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp);
665                         if (!NT_SUCCESS(Status)) {
666                             ERR("insert_tree_item returned %08lx\n", Status);
667                             ExFreePool(ri);
668                             return Status;
669                         }
670                     }
671 
672 #ifndef __REACTOS__
673                     tree* t2 = tp.tree;
674 #else
675                     t2 = tp.tree;
676 #endif
677                     while (t2) {
678                         t2->write = true;
679 
680                         t2 = t2->parent;
681                     }
682                 }
683             }
684 
685             le = le->Flink;
686         }
687 
688         if (nothing_found)
689             break;
690     }
691 
692     return STATUS_SUCCESS;
693 }
694 
695 static void add_parents_to_cache(tree* t) {
696     while (t->parent) {
697         t = t->parent;
698         t->write = true;
699     }
700 }
701 
702 static bool insert_tree_extent_skinny(device_extension* Vcb, uint8_t level, uint64_t root_id, chunk* c, uint64_t address, PIRP Irp, LIST_ENTRY* rollback) {
703     NTSTATUS Status;
704     EXTENT_ITEM_SKINNY_METADATA* eism;
705     traverse_ptr insert_tp;
706 
707     eism = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_SKINNY_METADATA), ALLOC_TAG);
708     if (!eism) {
709         ERR("out of memory\n");
710         return false;
711     }
712 
713     eism->ei.refcount = 1;
714     eism->ei.generation = Vcb->superblock.generation;
715     eism->ei.flags = EXTENT_ITEM_TREE_BLOCK;
716     eism->type = TYPE_TREE_BLOCK_REF;
717     eism->tbr.offset = root_id;
718 
719     Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, Irp);
720     if (!NT_SUCCESS(Status)) {
721         ERR("insert_tree_item returned %08lx\n", Status);
722         ExFreePool(eism);
723         return false;
724     }
725 
726     acquire_chunk_lock(c, Vcb);
727 
728     space_list_subtract(c, false, address, Vcb->superblock.node_size, rollback);
729 
730     release_chunk_lock(c, Vcb);
731 
732     add_parents_to_cache(insert_tp.tree);
733 
734     return true;
735 }
736 
737 bool find_metadata_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t* address) {
738     LIST_ENTRY* le;
739     space* s;
740 
741     TRACE("(%p, %I64x, %p)\n", Vcb, c->offset, address);
742 
743     if (Vcb->superblock.node_size > c->chunk_item->size - c->used)
744         return false;
745 
746     if (!c->cache_loaded) {
747         NTSTATUS Status = load_cache_chunk(Vcb, c, NULL);
748 
749         if (!NT_SUCCESS(Status)) {
750             ERR("load_cache_chunk returned %08lx\n", Status);
751             return false;
752         }
753     }
754 
755     if (IsListEmpty(&c->space_size))
756         return false;
757 
758     if (!c->last_alloc_set) {
759         s = CONTAINING_RECORD(c->space.Blink, space, list_entry);
760 
761         c->last_alloc = s->address;
762         c->last_alloc_set = true;
763 
764         if (s->size >= Vcb->superblock.node_size) {
765             *address = s->address;
766             c->last_alloc += Vcb->superblock.node_size;
767             return true;
768         }
769     }
770 
771     le = c->space.Flink;
772     while (le != &c->space) {
773         s = CONTAINING_RECORD(le, space, list_entry);
774 
775         if (s->address <= c->last_alloc && s->address + s->size >= c->last_alloc + Vcb->superblock.node_size) {
776             *address = c->last_alloc;
777             c->last_alloc += Vcb->superblock.node_size;
778             return true;
779         }
780 
781         le = le->Flink;
782     }
783 
784     le = c->space_size.Flink;
785     while (le != &c->space_size) {
786         s = CONTAINING_RECORD(le, space, list_entry_size);
787 
788         if (s->size == Vcb->superblock.node_size) {
789             *address = s->address;
790             c->last_alloc = s->address + Vcb->superblock.node_size;
791             return true;
792         } else if (s->size < Vcb->superblock.node_size) {
793             if (le == c->space_size.Flink)
794                 return false;
795 
796             s = CONTAINING_RECORD(le->Blink, space, list_entry_size);
797 
798             *address = s->address;
799             c->last_alloc = s->address + Vcb->superblock.node_size;
800 
801             return true;
802         }
803 
804         le = le->Flink;
805     }
806 
807     s = CONTAINING_RECORD(c->space_size.Blink, space, list_entry_size);
808 
809     if (s->size > Vcb->superblock.node_size) {
810         *address = s->address;
811         c->last_alloc = s->address + Vcb->superblock.node_size;
812         return true;
813     }
814 
815     return false;
816 }
817 
818 static bool insert_tree_extent(device_extension* Vcb, uint8_t level, uint64_t root_id, chunk* c, uint64_t* new_address, PIRP Irp, LIST_ENTRY* rollback) {
819     NTSTATUS Status;
820     uint64_t address;
821     EXTENT_ITEM_TREE2* eit2;
822     traverse_ptr insert_tp;
823 
824     TRACE("(%p, %x, %I64x, %p, %p, %p, %p)\n", Vcb, level, root_id, c, new_address, Irp, rollback);
825 
826     if (!find_metadata_address_in_chunk(Vcb, c, &address))
827         return false;
828 
829     if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {
830         bool b = insert_tree_extent_skinny(Vcb, level, root_id, c, address, Irp, rollback);
831 
832         if (b)
833             *new_address = address;
834 
835         return b;
836     }
837 
838     eit2 = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_TREE2), ALLOC_TAG);
839     if (!eit2) {
840         ERR("out of memory\n");
841         return false;
842     }
843 
844     eit2->eit.extent_item.refcount = 1;
845     eit2->eit.extent_item.generation = Vcb->superblock.generation;
846     eit2->eit.extent_item.flags = EXTENT_ITEM_TREE_BLOCK;
847     eit2->eit.level = level;
848     eit2->type = TYPE_TREE_BLOCK_REF;
849     eit2->tbr.offset = root_id;
850 
851     Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, eit2, sizeof(EXTENT_ITEM_TREE2), &insert_tp, Irp);
852     if (!NT_SUCCESS(Status)) {
853         ERR("insert_tree_item returned %08lx\n", Status);
854         ExFreePool(eit2);
855         return false;
856     }
857 
858     acquire_chunk_lock(c, Vcb);
859 
860     space_list_subtract(c, false, address, Vcb->superblock.node_size, rollback);
861 
862     release_chunk_lock(c, Vcb);
863 
864     add_parents_to_cache(insert_tp.tree);
865 
866     *new_address = address;
867 
868     return true;
869 }
870 
871 NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {
872     NTSTATUS Status;
873     chunk *origchunk = NULL, *c;
874     LIST_ENTRY* le;
875     uint64_t flags, addr;
876 
877     if (t->root->id == BTRFS_ROOT_CHUNK)
878         flags = Vcb->system_flags;
879     else
880         flags = Vcb->metadata_flags;
881 
882     if (t->has_address) {
883         origchunk = get_chunk_from_address(Vcb, t->header.address);
884 
885         if (origchunk && !origchunk->readonly && !origchunk->reloc && origchunk->chunk_item->type == flags &&
886             insert_tree_extent(Vcb, t->header.level, t->root->id, origchunk, &addr, Irp, rollback)) {
887             t->new_address = addr;
888             t->has_new_address = true;
889             return STATUS_SUCCESS;
890         }
891     }
892 
893     ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);
894 
895     le = Vcb->chunks.Flink;
896     while (le != &Vcb->chunks) {
897         c = CONTAINING_RECORD(le, chunk, list_entry);
898 
899         if (!c->readonly && !c->reloc) {
900             acquire_chunk_lock(c, Vcb);
901 
902             if (c != origchunk && c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {
903                 if (insert_tree_extent(Vcb, t->header.level, t->root->id, c, &addr, Irp, rollback)) {
904                     release_chunk_lock(c, Vcb);
905                     ExReleaseResourceLite(&Vcb->chunk_lock);
906                     t->new_address = addr;
907                     t->has_new_address = true;
908                     return STATUS_SUCCESS;
909                 }
910             }
911 
912             release_chunk_lock(c, Vcb);
913         }
914 
915         le = le->Flink;
916     }
917 
918     // allocate new chunk if necessary
919 
920     Status = alloc_chunk(Vcb, flags, &c, false);
921 
922     if (!NT_SUCCESS(Status)) {
923         ERR("alloc_chunk returned %08lx\n", Status);
924         ExReleaseResourceLite(&Vcb->chunk_lock);
925         return Status;
926     }
927 
928     acquire_chunk_lock(c, Vcb);
929 
930     if ((c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {
931         if (insert_tree_extent(Vcb, t->header.level, t->root->id, c, &addr, Irp, rollback)) {
932             release_chunk_lock(c, Vcb);
933             ExReleaseResourceLite(&Vcb->chunk_lock);
934             t->new_address = addr;
935             t->has_new_address = true;
936             return STATUS_SUCCESS;
937         }
938     }
939 
940     release_chunk_lock(c, Vcb);
941 
942     ExReleaseResourceLite(&Vcb->chunk_lock);
943 
944     ERR("couldn't find any metadata chunks with %x bytes free\n", Vcb->superblock.node_size);
945 
946     return STATUS_DISK_FULL;
947 }
948 
949 static NTSTATUS reduce_tree_extent(device_extension* Vcb, uint64_t address, tree* t, uint64_t parent_root, uint8_t level, PIRP Irp, LIST_ENTRY* rollback) {
950     NTSTATUS Status;
951     uint64_t rc, root;
952 
953     TRACE("(%p, %I64x, %p)\n", Vcb, address, t);
954 
955     rc = get_extent_refcount(Vcb, address, Vcb->superblock.node_size, Irp);
956     if (rc == 0) {
957         ERR("error - refcount for extent %I64x was 0\n", address);
958         return STATUS_INTERNAL_ERROR;
959     }
960 
961     if (!t || t->parent)
962         root = parent_root;
963     else
964         root = t->header.tree_id;
965 
966     Status = decrease_extent_refcount_tree(Vcb, address, Vcb->superblock.node_size, root, level, Irp);
967     if (!NT_SUCCESS(Status)) {
968         ERR("decrease_extent_refcount_tree returned %08lx\n", Status);
969         return Status;
970     }
971 
972     if (rc == 1) {
973         chunk* c = get_chunk_from_address(Vcb, address);
974 
975         if (c) {
976             acquire_chunk_lock(c, Vcb);
977 
978             if (!c->cache_loaded) {
979                 Status = load_cache_chunk(Vcb, c, NULL);
980 
981                 if (!NT_SUCCESS(Status)) {
982                     ERR("load_cache_chunk returned %08lx\n", Status);
983                     release_chunk_lock(c, Vcb);
984                     return Status;
985                 }
986             }
987 
988             c->used -= Vcb->superblock.node_size;
989 
990             space_list_add(c, address, Vcb->superblock.node_size, rollback);
991 
992             release_chunk_lock(c, Vcb);
993         } else
994             ERR("could not find chunk for address %I64x\n", address);
995     }
996 
997     return STATUS_SUCCESS;
998 }
999 
1000 static NTSTATUS add_changed_extent_ref_edr(changed_extent* ce, EXTENT_DATA_REF* edr, bool old) {
1001     LIST_ENTRY *le2, *list;
1002     changed_extent_ref* cer;
1003 
1004     list = old ? &ce->old_refs : &ce->refs;
1005 
1006     le2 = list->Flink;
1007     while (le2 != list) {
1008         cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
1009 
1010         if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == edr->root && cer->edr.objid == edr->objid && cer->edr.offset == edr->offset) {
1011             cer->edr.count += edr->count;
1012             goto end;
1013         }
1014 
1015         le2 = le2->Flink;
1016     }
1017 
1018     cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);
1019     if (!cer) {
1020         ERR("out of memory\n");
1021         return STATUS_INSUFFICIENT_RESOURCES;
1022     }
1023 
1024     cer->type = TYPE_EXTENT_DATA_REF;
1025     RtlCopyMemory(&cer->edr, edr, sizeof(EXTENT_DATA_REF));
1026     InsertTailList(list, &cer->list_entry);
1027 
1028 end:
1029     if (old)
1030         ce->old_count += edr->count;
1031     else
1032         ce->count += edr->count;
1033 
1034     return STATUS_SUCCESS;
1035 }
1036 
1037 static NTSTATUS add_changed_extent_ref_sdr(changed_extent* ce, SHARED_DATA_REF* sdr, bool old) {
1038     LIST_ENTRY *le2, *list;
1039     changed_extent_ref* cer;
1040 
1041     list = old ? &ce->old_refs : &ce->refs;
1042 
1043     le2 = list->Flink;
1044     while (le2 != list) {
1045         cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
1046 
1047         if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr->offset) {
1048             cer->sdr.count += sdr->count;
1049             goto end;
1050         }
1051 
1052         le2 = le2->Flink;
1053     }
1054 
1055     cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);
1056     if (!cer) {
1057         ERR("out of memory\n");
1058         return STATUS_INSUFFICIENT_RESOURCES;
1059     }
1060 
1061     cer->type = TYPE_SHARED_DATA_REF;
1062     RtlCopyMemory(&cer->sdr, sdr, sizeof(SHARED_DATA_REF));
1063     InsertTailList(list, &cer->list_entry);
1064 
1065 end:
1066     if (old)
1067         ce->old_count += sdr->count;
1068     else
1069         ce->count += sdr->count;
1070 
1071     return STATUS_SUCCESS;
1072 }
1073 
1074 static bool shared_tree_is_unique(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {
1075     KEY searchkey;
1076     traverse_ptr tp;
1077     NTSTATUS Status;
1078 
1079     if (!t->updated_extents && t->has_address) {
1080         Status = update_tree_extents(Vcb, t, Irp, rollback);
1081         if (!NT_SUCCESS(Status)) {
1082             ERR("update_tree_extents returned %08lx\n", Status);
1083             return false;
1084         }
1085     }
1086 
1087     searchkey.obj_id = t->header.address;
1088     searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
1089     searchkey.offset = 0xffffffffffffffff;
1090 
1091     Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
1092     if (!NT_SUCCESS(Status)) {
1093         ERR("error - find_item returned %08lx\n", Status);
1094         return false;
1095     }
1096 
1097     if (tp.item->key.obj_id == t->header.address && (tp.item->key.obj_type == TYPE_METADATA_ITEM || tp.item->key.obj_type == TYPE_EXTENT_ITEM))
1098         return false;
1099     else
1100         return true;
1101 }
1102 
1103 static NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {
1104     NTSTATUS Status;
1105     uint64_t rc = get_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, Irp);
1106     uint64_t flags = get_extent_flags(Vcb, t->header.address, Irp);
1107 
1108     if (rc == 0) {
1109         ERR("refcount for extent %I64x was 0\n", t->header.address);
1110         return STATUS_INTERNAL_ERROR;
1111     }
1112 
1113     if (flags & EXTENT_ITEM_SHARED_BACKREFS || t->header.flags & HEADER_FLAG_SHARED_BACKREF || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {
1114         TREE_BLOCK_REF tbr;
1115         bool unique = rc > 1 ? false : (t->parent ? shared_tree_is_unique(Vcb, t->parent, Irp, rollback) : false);
1116 
1117         if (t->header.level == 0) {
1118             LIST_ENTRY* le;
1119 
1120             le = t->itemlist.Flink;
1121             while (le != &t->itemlist) {
1122                 tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
1123 
1124                 if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
1125                     EXTENT_DATA* ed = (EXTENT_DATA*)td->data;
1126 
1127                     if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
1128                         EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
1129 
1130                         if (ed2->size > 0) {
1131                             EXTENT_DATA_REF edr;
1132                             changed_extent* ce = NULL;
1133                             chunk* c = get_chunk_from_address(Vcb, ed2->address);
1134 
1135                             if (c) {
1136                                 LIST_ENTRY* le2;
1137 
1138                                 le2 = c->changed_extents.Flink;
1139                                 while (le2 != &c->changed_extents) {
1140                                     changed_extent* ce2 = CONTAINING_RECORD(le2, changed_extent, list_entry);
1141 
1142                                     if (ce2->address == ed2->address) {
1143                                         ce = ce2;
1144                                         break;
1145                                     }
1146 
1147                                     le2 = le2->Flink;
1148                                 }
1149                             }
1150 
1151                             edr.root = t->root->id;
1152                             edr.objid = td->key.obj_id;
1153                             edr.offset = td->key.offset - ed2->offset;
1154                             edr.count = 1;
1155 
1156                             if (ce) {
1157                                 Status = add_changed_extent_ref_edr(ce, &edr, true);
1158                                 if (!NT_SUCCESS(Status)) {
1159                                     ERR("add_changed_extent_ref_edr returned %08lx\n", Status);
1160                                     return Status;
1161                                 }
1162 
1163                                 Status = add_changed_extent_ref_edr(ce, &edr, false);
1164                                 if (!NT_SUCCESS(Status)) {
1165                                     ERR("add_changed_extent_ref_edr returned %08lx\n", Status);
1166                                     return Status;
1167                                 }
1168                             }
1169 
1170                             Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp);
1171                             if (!NT_SUCCESS(Status)) {
1172                                 ERR("increase_extent_refcount returned %08lx\n", Status);
1173                                 return Status;
1174                             }
1175 
1176                             if ((flags & EXTENT_ITEM_SHARED_BACKREFS && unique) || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {
1177                                 uint64_t sdrrc = find_extent_shared_data_refcount(Vcb, ed2->address, t->header.address, Irp);
1178 
1179                                 if (sdrrc > 0) {
1180                                     SHARED_DATA_REF sdr;
1181 
1182                                     sdr.offset = t->header.address;
1183                                     sdr.count = 1;
1184 
1185                                     Status = decrease_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0,
1186                                                                       t->header.address, ce ? ce->superseded : false, Irp);
1187                                     if (!NT_SUCCESS(Status)) {
1188                                         ERR("decrease_extent_refcount returned %08lx\n", Status);
1189                                         return Status;
1190                                     }
1191 
1192                                     if (ce) {
1193                                         LIST_ENTRY* le2;
1194 
1195                                         le2 = ce->refs.Flink;
1196                                         while (le2 != &ce->refs) {
1197                                             changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
1198 
1199                                             if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr.offset) {
1200                                                 ce->count--;
1201                                                 cer->sdr.count--;
1202                                                 break;
1203                                             }
1204 
1205                                             le2 = le2->Flink;
1206                                         }
1207 
1208                                         le2 = ce->old_refs.Flink;
1209                                         while (le2 != &ce->old_refs) {
1210                                             changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
1211 
1212                                             if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr.offset) {
1213                                                 ce->old_count--;
1214 
1215                                                 if (cer->sdr.count > 1)
1216                                                     cer->sdr.count--;
1217                                                 else {
1218                                                     RemoveEntryList(&cer->list_entry);
1219                                                     ExFreePool(cer);
1220                                                 }
1221 
1222                                                 break;
1223                                             }
1224 
1225                                             le2 = le2->Flink;
1226                                         }
1227                                     }
1228                                 }
1229                             }
1230 
1231                             // FIXME - clear shared flag if unique?
1232                         }
1233                     }
1234                 }
1235 
1236                 le = le->Flink;
1237             }
1238         } else {
1239             LIST_ENTRY* le;
1240 
1241             le = t->itemlist.Flink;
1242             while (le != &t->itemlist) {
1243                 tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
1244 
1245                 if (!td->inserted) {
1246                     tbr.offset = t->root->id;
1247 
1248                     Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF,
1249                                                       &tbr, &td->key, t->header.level - 1, Irp);
1250                     if (!NT_SUCCESS(Status)) {
1251                         ERR("increase_extent_refcount returned %08lx\n", Status);
1252                         return Status;
1253                     }
1254 
1255                     if (unique || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {
1256                         uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, td->treeholder.address, t->header.address, Irp);
1257 
1258                         if (sbrrc > 0) {
1259                             SHARED_BLOCK_REF sbr;
1260 
1261                             sbr.offset = t->header.address;
1262 
1263                             Status = decrease_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0,
1264                                                               t->header.address, false, Irp);
1265                             if (!NT_SUCCESS(Status)) {
1266                                 ERR("decrease_extent_refcount returned %08lx\n", Status);
1267                                 return Status;
1268                             }
1269                         }
1270                     }
1271 
1272                     // FIXME - clear shared flag if unique?
1273                 }
1274 
1275                 le = le->Flink;
1276             }
1277         }
1278 
1279         if (unique) {
1280             uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, t->header.address, t->parent->header.address, Irp);
1281 
1282             if (sbrrc == 1) {
1283                 SHARED_BLOCK_REF sbr;
1284 
1285                 sbr.offset = t->parent->header.address;
1286 
1287                 Status = decrease_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0,
1288                                                   t->parent->header.address, false, Irp);
1289                 if (!NT_SUCCESS(Status)) {
1290                     ERR("decrease_extent_refcount returned %08lx\n", Status);
1291                     return Status;
1292                 }
1293             }
1294         }
1295 
1296         if (t->parent)
1297             tbr.offset = t->parent->header.tree_id;
1298         else
1299             tbr.offset = t->header.tree_id;
1300 
1301         Status = increase_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr,
1302                                           t->parent ? &t->paritem->key : NULL, t->header.level, Irp);
1303         if (!NT_SUCCESS(Status)) {
1304             ERR("increase_extent_refcount returned %08lx\n", Status);
1305             return Status;
1306         }
1307 
1308         // FIXME - clear shared flag if unique?
1309 
1310         t->header.flags &= ~HEADER_FLAG_SHARED_BACKREF;
1311     }
1312 
1313     if (rc > 1 || t->header.tree_id == t->root->id) {
1314         Status = reduce_tree_extent(Vcb, t->header.address, t, t->parent ? t->parent->header.tree_id : t->header.tree_id, t->header.level, Irp, rollback);
1315 
1316         if (!NT_SUCCESS(Status)) {
1317             ERR("reduce_tree_extent returned %08lx\n", Status);
1318             return Status;
1319         }
1320     }
1321 
1322     t->has_address = false;
1323 
1324     if ((rc > 1 || t->header.tree_id != t->root->id) && !(flags & EXTENT_ITEM_SHARED_BACKREFS)) {
1325         if (t->header.tree_id == t->root->id) {
1326             flags |= EXTENT_ITEM_SHARED_BACKREFS;
1327             update_extent_flags(Vcb, t->header.address, flags, Irp);
1328         }
1329 
1330         if (t->header.level > 0) {
1331             LIST_ENTRY* le;
1332 
1333             le = t->itemlist.Flink;
1334             while (le != &t->itemlist) {
1335                 tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
1336 
1337                 if (!td->inserted) {
1338                     if (t->header.tree_id == t->root->id) {
1339                         SHARED_BLOCK_REF sbr;
1340 
1341                         sbr.offset = t->header.address;
1342 
1343                         Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, &td->key, t->header.level - 1, Irp);
1344                     } else {
1345                         TREE_BLOCK_REF tbr;
1346 
1347                         tbr.offset = t->root->id;
1348 
1349                         Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, &td->key, t->header.level - 1, Irp);
1350                     }
1351 
1352                     if (!NT_SUCCESS(Status)) {
1353                         ERR("increase_extent_refcount returned %08lx\n", Status);
1354                         return Status;
1355                     }
1356                 }
1357 
1358                 le = le->Flink;
1359             }
1360         } else {
1361             LIST_ENTRY* le;
1362 
1363             le = t->itemlist.Flink;
1364             while (le != &t->itemlist) {
1365                 tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
1366 
1367                 if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
1368                     EXTENT_DATA* ed = (EXTENT_DATA*)td->data;
1369 
1370                     if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
1371                         EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
1372 
1373                         if (ed2->size > 0) {
1374                             changed_extent* ce = NULL;
1375                             chunk* c = get_chunk_from_address(Vcb, ed2->address);
1376 
1377                             if (c) {
1378                                 LIST_ENTRY* le2;
1379 
1380                                 le2 = c->changed_extents.Flink;
1381                                 while (le2 != &c->changed_extents) {
1382                                     changed_extent* ce2 = CONTAINING_RECORD(le2, changed_extent, list_entry);
1383 
1384                                     if (ce2->address == ed2->address) {
1385                                         ce = ce2;
1386                                         break;
1387                                     }
1388 
1389                                     le2 = le2->Flink;
1390                                 }
1391                             }
1392 
1393                             if (t->header.tree_id == t->root->id) {
1394                                 SHARED_DATA_REF sdr;
1395 
1396                                 sdr.offset = t->header.address;
1397                                 sdr.count = 1;
1398 
1399                                 if (ce) {
1400                                     Status = add_changed_extent_ref_sdr(ce, &sdr, true);
1401                                     if (!NT_SUCCESS(Status)) {
1402                                         ERR("add_changed_extent_ref_edr returned %08lx\n", Status);
1403                                         return Status;
1404                                     }
1405 
1406                                     Status = add_changed_extent_ref_sdr(ce, &sdr, false);
1407                                     if (!NT_SUCCESS(Status)) {
1408                                         ERR("add_changed_extent_ref_edr returned %08lx\n", Status);
1409                                         return Status;
1410                                     }
1411                                 }
1412 
1413                                 Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, Irp);
1414                             } else {
1415                                 EXTENT_DATA_REF edr;
1416 
1417                                 edr.root = t->root->id;
1418                                 edr.objid = td->key.obj_id;
1419                                 edr.offset = td->key.offset - ed2->offset;
1420                                 edr.count = 1;
1421 
1422                                 if (ce) {
1423                                     Status = add_changed_extent_ref_edr(ce, &edr, true);
1424                                     if (!NT_SUCCESS(Status)) {
1425                                         ERR("add_changed_extent_ref_edr returned %08lx\n", Status);
1426                                         return Status;
1427                                     }
1428 
1429                                     Status = add_changed_extent_ref_edr(ce, &edr, false);
1430                                     if (!NT_SUCCESS(Status)) {
1431                                         ERR("add_changed_extent_ref_edr returned %08lx\n", Status);
1432                                         return Status;
1433                                     }
1434                                 }
1435 
1436                                 Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp);
1437                             }
1438 
1439                             if (!NT_SUCCESS(Status)) {
1440                                 ERR("increase_extent_refcount returned %08lx\n", Status);
1441                                 return Status;
1442                             }
1443                         }
1444                     }
1445                 }
1446 
1447                 le = le->Flink;
1448             }
1449         }
1450     }
1451 
1452     t->updated_extents = true;
1453     t->header.tree_id = t->root->id;
1454 
1455     return STATUS_SUCCESS;
1456 }
1457 
1458 static NTSTATUS allocate_tree_extents(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {
1459     LIST_ENTRY* le;
1460     NTSTATUS Status;
1461     bool changed = false;
1462     uint8_t max_level = 0, level;
1463 
1464     TRACE("(%p)\n", Vcb);
1465 
1466     le = Vcb->trees.Flink;
1467     while (le != &Vcb->trees) {
1468         tree* t = CONTAINING_RECORD(le, tree, list_entry);
1469 
1470         if (t->write && !t->has_new_address) {
1471             chunk* c;
1472 
1473             if (t->has_address) {
1474                 c = get_chunk_from_address(Vcb, t->header.address);
1475 
1476                 if (c) {
1477                     if (!c->cache_loaded) {
1478                         acquire_chunk_lock(c, Vcb);
1479 
1480                         if (!c->cache_loaded) {
1481                             Status = load_cache_chunk(Vcb, c, NULL);
1482 
1483                             if (!NT_SUCCESS(Status)) {
1484                                 ERR("load_cache_chunk returned %08lx\n", Status);
1485                                 release_chunk_lock(c, Vcb);
1486                                 return Status;
1487                             }
1488                         }
1489 
1490                         release_chunk_lock(c, Vcb);
1491                     }
1492                 }
1493             }
1494 
1495             Status = get_tree_new_address(Vcb, t, Irp, rollback);
1496             if (!NT_SUCCESS(Status)) {
1497                 ERR("get_tree_new_address returned %08lx\n", Status);
1498                 return Status;
1499             }
1500 
1501             TRACE("allocated extent %I64x\n", t->new_address);
1502 
1503             c = get_chunk_from_address(Vcb, t->new_address);
1504 
1505             if (c)
1506                 c->used += Vcb->superblock.node_size;
1507             else {
1508                 ERR("could not find chunk for address %I64x\n", t->new_address);
1509                 return STATUS_INTERNAL_ERROR;
1510             }
1511 
1512             changed = true;
1513 
1514             if (t->header.level > max_level)
1515                 max_level = t->header.level;
1516         }
1517 
1518         le = le->Flink;
1519     }
1520 
1521     if (!changed)
1522         return STATUS_SUCCESS;
1523 
1524     level = max_level;
1525     do {
1526         le = Vcb->trees.Flink;
1527         while (le != &Vcb->trees) {
1528             tree* t = CONTAINING_RECORD(le, tree, list_entry);
1529 
1530             if (t->write && !t->updated_extents && t->has_address && t->header.level == level) {
1531                 Status = update_tree_extents(Vcb, t, Irp, rollback);
1532                 if (!NT_SUCCESS(Status)) {
1533                     ERR("update_tree_extents returned %08lx\n", Status);
1534                     return Status;
1535                 }
1536             }
1537 
1538             le = le->Flink;
1539         }
1540 
1541         if (level == 0)
1542             break;
1543 
1544         level--;
1545     } while (true);
1546 
1547     return STATUS_SUCCESS;
1548 }
1549 
1550 static NTSTATUS update_root_root(device_extension* Vcb, bool no_cache, PIRP Irp, LIST_ENTRY* rollback) {
1551     LIST_ENTRY* le;
1552     NTSTATUS Status;
1553 
1554     TRACE("(%p)\n", Vcb);
1555 
1556     le = Vcb->trees.Flink;
1557     while (le != &Vcb->trees) {
1558         tree* t = CONTAINING_RECORD(le, tree, list_entry);
1559 
1560         if (t->write && !t->parent) {
1561             if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) {
1562                 KEY searchkey;
1563                 traverse_ptr tp;
1564 
1565                 searchkey.obj_id = t->root->id;
1566                 searchkey.obj_type = TYPE_ROOT_ITEM;
1567                 searchkey.offset = 0xffffffffffffffff;
1568 
1569                 Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
1570                 if (!NT_SUCCESS(Status)) {
1571                     ERR("error - find_item returned %08lx\n", Status);
1572                     return Status;
1573                 }
1574 
1575                 if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
1576                     ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id);
1577                     return STATUS_INTERNAL_ERROR;
1578                 }
1579 
1580                 TRACE("updating the address for root %I64x to %I64x\n", searchkey.obj_id, t->new_address);
1581 
1582                 t->root->root_item.block_number = t->new_address;
1583                 t->root->root_item.root_level = t->header.level;
1584                 t->root->root_item.generation = Vcb->superblock.generation;
1585                 t->root->root_item.generation2 = Vcb->superblock.generation;
1586 
1587                 // item is guaranteed to be at least sizeof(ROOT_ITEM), due to add_parents
1588 
1589                 RtlCopyMemory(tp.item->data, &t->root->root_item, sizeof(ROOT_ITEM));
1590             }
1591 
1592             t->root->treeholder.address = t->new_address;
1593             t->root->treeholder.generation = Vcb->superblock.generation;
1594         }
1595 
1596         le = le->Flink;
1597     }
1598 
1599     if (!no_cache && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)) {
1600         ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);
1601         Status = update_chunk_caches(Vcb, Irp, rollback);
1602         ExReleaseResourceLite(&Vcb->chunk_lock);
1603 
1604         if (!NT_SUCCESS(Status)) {
1605             ERR("update_chunk_caches returned %08lx\n", Status);
1606             return Status;
1607         }
1608     }
1609 
1610     return STATUS_SUCCESS;
1611 }
1612 
1613 NTSTATUS do_tree_writes(device_extension* Vcb, LIST_ENTRY* tree_writes, bool no_free) {
1614     chunk* c;
1615     LIST_ENTRY* le;
1616     tree_write* tw;
1617     NTSTATUS Status;
1618     ULONG i, num_bits;
1619     write_data_context* wtc;
1620     ULONG bit_num = 0;
1621     bool raid56 = false;
1622 
1623     // merge together runs
1624     c = NULL;
1625     le = tree_writes->Flink;
1626     while (le != tree_writes) {
1627         tw = CONTAINING_RECORD(le, tree_write, list_entry);
1628 
1629         if (!c || tw->address < c->offset || tw->address >= c->offset + c->chunk_item->size)
1630             c = get_chunk_from_address(Vcb, tw->address);
1631         else {
1632             tree_write* tw2 = CONTAINING_RECORD(le->Blink, tree_write, list_entry);
1633 
1634             if (tw->address == tw2->address + tw2->length) {
1635                 uint8_t* data = ExAllocatePoolWithTag(NonPagedPool, tw2->length + tw->length, ALLOC_TAG);
1636 
1637                 if (!data) {
1638                     ERR("out of memory\n");
1639                     return STATUS_INSUFFICIENT_RESOURCES;
1640                 }
1641 
1642                 RtlCopyMemory(data, tw2->data, tw2->length);
1643                 RtlCopyMemory(&data[tw2->length], tw->data, tw->length);
1644 
1645                 if (!no_free || tw2->allocated)
1646                     ExFreePool(tw2->data);
1647 
1648                 tw2->data = data;
1649                 tw2->length += tw->length;
1650                 tw2->allocated = true;
1651 
1652                 if (!no_free || tw->allocated)
1653                     ExFreePool(tw->data);
1654 
1655                 RemoveEntryList(&tw->list_entry);
1656                 ExFreePool(tw);
1657 
1658                 le = tw2->list_entry.Flink;
1659                 continue;
1660             }
1661         }
1662 
1663         tw->c = c;
1664 
1665         if (c->chunk_item->type & (BLOCK_FLAG_RAID5 | BLOCK_FLAG_RAID6))
1666             raid56 = true;
1667 
1668         le = le->Flink;
1669     }
1670 
1671     num_bits = 0;
1672 
1673     le = tree_writes->Flink;
1674     while (le != tree_writes) {
1675         tw = CONTAINING_RECORD(le, tree_write, list_entry);
1676 
1677         num_bits++;
1678 
1679         le = le->Flink;
1680     }
1681 
1682     wtc = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_data_context) * num_bits, ALLOC_TAG);
1683     if (!wtc) {
1684         ERR("out of memory\n");
1685         return STATUS_INSUFFICIENT_RESOURCES;
1686     }
1687 
1688     le = tree_writes->Flink;
1689 
1690     while (le != tree_writes) {
1691         tw = CONTAINING_RECORD(le, tree_write, list_entry);
1692 
1693         TRACE("address: %I64x, size: %x\n", tw->address, tw->length);
1694 
1695         KeInitializeEvent(&wtc[bit_num].Event, NotificationEvent, false);
1696         InitializeListHead(&wtc[bit_num].stripes);
1697         wtc[bit_num].need_wait = false;
1698         wtc[bit_num].stripes_left = 0;
1699         wtc[bit_num].parity1 = wtc[bit_num].parity2 = wtc[bit_num].scratch = NULL;
1700         wtc[bit_num].mdl = wtc[bit_num].parity1_mdl = wtc[bit_num].parity2_mdl = NULL;
1701 
1702         Status = write_data(Vcb, tw->address, tw->data, tw->length, &wtc[bit_num], NULL, NULL, false, 0, HighPagePriority);
1703         if (!NT_SUCCESS(Status)) {
1704             ERR("write_data returned %08lx\n", Status);
1705 
1706             for (i = 0; i < num_bits; i++) {
1707                 free_write_data_stripes(&wtc[i]);
1708             }
1709             ExFreePool(wtc);
1710 
1711             return Status;
1712         }
1713 
1714         bit_num++;
1715 
1716         le = le->Flink;
1717     }
1718 
1719     for (i = 0; i < num_bits; i++) {
1720         if (wtc[i].stripes.Flink != &wtc[i].stripes) {
1721             // launch writes and wait
1722             le = wtc[i].stripes.Flink;
1723             while (le != &wtc[i].stripes) {
1724                 write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);
1725 
1726                 if (stripe->status != WriteDataStatus_Ignore) {
1727                     wtc[i].need_wait = true;
1728                     IoCallDriver(stripe->device->devobj, stripe->Irp);
1729                 }
1730 
1731                 le = le->Flink;
1732             }
1733         }
1734     }
1735 
1736     for (i = 0; i < num_bits; i++) {
1737         if (wtc[i].need_wait)
1738             KeWaitForSingleObject(&wtc[i].Event, Executive, KernelMode, false, NULL);
1739     }
1740 
1741     for (i = 0; i < num_bits; i++) {
1742         le = wtc[i].stripes.Flink;
1743         while (le != &wtc[i].stripes) {
1744             write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);
1745 
1746             if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) {
1747                 Status = stripe->iosb.Status;
1748                 log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS);
1749                 break;
1750             }
1751 
1752             le = le->Flink;
1753         }
1754 
1755         free_write_data_stripes(&wtc[i]);
1756     }
1757 
1758     ExFreePool(wtc);
1759 
1760     if (raid56) {
1761         c = NULL;
1762 
1763         le = tree_writes->Flink;
1764         while (le != tree_writes) {
1765             tw = CONTAINING_RECORD(le, tree_write, list_entry);
1766 
1767             if (tw->c != c) {
1768                 c = tw->c;
1769 
1770                 ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true);
1771 
1772                 while (!IsListEmpty(&c->partial_stripes)) {
1773                     partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry);
1774 
1775                     Status = flush_partial_stripe(Vcb, c, ps);
1776 
1777                     if (ps->bmparr)
1778                         ExFreePool(ps->bmparr);
1779 
1780                     ExFreePool(ps);
1781 
1782                     if (!NT_SUCCESS(Status)) {
1783                         ERR("flush_partial_stripe returned %08lx\n", Status);
1784                         ExReleaseResourceLite(&c->partial_stripes_lock);
1785                         return Status;
1786                     }
1787                 }
1788 
1789                 ExReleaseResourceLite(&c->partial_stripes_lock);
1790             }
1791 
1792             le = le->Flink;
1793         }
1794     }
1795 
1796     return STATUS_SUCCESS;
1797 }
1798 
1799 void calc_tree_checksum(device_extension* Vcb, tree_header* th) {
1800     switch (Vcb->superblock.csum_type) {
1801         case CSUM_TYPE_CRC32C:
1802             *((uint32_t*)th) = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));
1803         break;
1804 
1805         case CSUM_TYPE_XXHASH:
1806             *((uint64_t*)th) = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0);
1807         break;
1808 
1809         case CSUM_TYPE_SHA256:
1810             calc_sha256((uint8_t*)th, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));
1811         break;
1812 
1813         case CSUM_TYPE_BLAKE2:
1814             blake2b((uint8_t*)th, BLAKE2_HASH_SIZE, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));
1815         break;
1816     }
1817 }
1818 
1819 static NTSTATUS write_trees(device_extension* Vcb, PIRP Irp) {
1820     ULONG level;
1821     uint8_t *data, *body;
1822     NTSTATUS Status;
1823     LIST_ENTRY* le;
1824     LIST_ENTRY tree_writes;
1825     tree_write* tw;
1826 
1827     TRACE("(%p)\n", Vcb);
1828 
1829     InitializeListHead(&tree_writes);
1830 
1831     for (level = 0; level <= 255; level++) {
1832         bool nothing_found = true;
1833 
1834         TRACE("level = %lu\n", level);
1835 
1836         le = Vcb->trees.Flink;
1837         while (le != &Vcb->trees) {
1838             tree* t = CONTAINING_RECORD(le, tree, list_entry);
1839 
1840             if (t->write && t->header.level == level) {
1841                 KEY firstitem, searchkey;
1842                 LIST_ENTRY* le2;
1843                 traverse_ptr tp;
1844 
1845                 if (!t->has_new_address) {
1846                     ERR("error - tried to write tree with no new address\n");
1847                     return STATUS_INTERNAL_ERROR;
1848                 }
1849 
1850                 le2 = t->itemlist.Flink;
1851                 while (le2 != &t->itemlist) {
1852                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
1853                     if (!td->ignore) {
1854                         firstitem = td->key;
1855                         break;
1856                     }
1857                     le2 = le2->Flink;
1858                 }
1859 
1860                 if (t->parent) {
1861                     t->paritem->key = firstitem;
1862                     t->paritem->treeholder.address = t->new_address;
1863                     t->paritem->treeholder.generation = Vcb->superblock.generation;
1864                 }
1865 
1866                 if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {
1867                     EXTENT_ITEM_TREE* eit;
1868 
1869                     searchkey.obj_id = t->new_address;
1870                     searchkey.obj_type = TYPE_EXTENT_ITEM;
1871                     searchkey.offset = Vcb->superblock.node_size;
1872 
1873                     Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
1874                     if (!NT_SUCCESS(Status)) {
1875                         ERR("error - find_item returned %08lx\n", Status);
1876                         return Status;
1877                     }
1878 
1879                     if (keycmp(searchkey, tp.item->key)) {
1880                         ERR("could not find %I64x,%x,%I64x in extent_root (found %I64x,%x,%I64x instead)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
1881                         return STATUS_INTERNAL_ERROR;
1882                     }
1883 
1884                     if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) {
1885                         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_TREE));
1886                         return STATUS_INTERNAL_ERROR;
1887                     }
1888 
1889                     eit = (EXTENT_ITEM_TREE*)tp.item->data;
1890                     eit->firstitem = firstitem;
1891                 }
1892 
1893                 nothing_found = false;
1894             }
1895 
1896             le = le->Flink;
1897         }
1898 
1899         if (nothing_found)
1900             break;
1901     }
1902 
1903     TRACE("allocated tree extents\n");
1904 
1905     le = Vcb->trees.Flink;
1906     while (le != &Vcb->trees) {
1907         tree* t = CONTAINING_RECORD(le, tree, list_entry);
1908         LIST_ENTRY* le2;
1909 #ifdef DEBUG_PARANOID
1910         uint32_t num_items = 0, size = 0;
1911         bool crash = false;
1912 #endif
1913 
1914         if (t->write) {
1915 #ifdef DEBUG_PARANOID
1916             bool first = true;
1917             KEY lastkey;
1918 
1919             le2 = t->itemlist.Flink;
1920             while (le2 != &t->itemlist) {
1921                 tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
1922                 if (!td->ignore) {
1923                     num_items++;
1924 
1925                     if (!first) {
1926                         if (keycmp(td->key, lastkey) == 0) {
1927                             ERR("(%I64x,%x,%I64x): duplicate key\n", td->key.obj_id, td->key.obj_type, td->key.offset);
1928                             crash = true;
1929                         } else if (keycmp(td->key, lastkey) == -1) {
1930                             ERR("(%I64x,%x,%I64x): key out of order\n", td->key.obj_id, td->key.obj_type, td->key.offset);
1931                             crash = true;
1932                         }
1933                     } else
1934                         first = false;
1935 
1936                     lastkey = td->key;
1937 
1938                     if (t->header.level == 0)
1939                         size += td->size;
1940                 }
1941                 le2 = le2->Flink;
1942             }
1943 
1944             if (t->header.level == 0)
1945                 size += num_items * sizeof(leaf_node);
1946             else
1947                 size += num_items * sizeof(internal_node);
1948 
1949             if (num_items != t->header.num_items) {
1950                 ERR("tree %I64x, level %x: num_items was %x, expected %x\n", t->root->id, t->header.level, num_items, t->header.num_items);
1951                 crash = true;
1952             }
1953 
1954             if (size != t->size) {
1955                 ERR("tree %I64x, level %x: size was %x, expected %x\n", t->root->id, t->header.level, size, t->size);
1956                 crash = true;
1957             }
1958 
1959             if (t->header.num_items == 0 && t->parent) {
1960                 ERR("tree %I64x, level %x: tried to write empty tree with parent\n", t->root->id, t->header.level);
1961                 crash = true;
1962             }
1963 
1964             if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) {
1965                 ERR("tree %I64x, level %x: tried to write overlarge tree (%x > %Ix)\n", t->root->id, t->header.level, t->size, Vcb->superblock.node_size - sizeof(tree_header));
1966                 crash = true;
1967             }
1968 
1969             if (crash) {
1970                 ERR("tree %p\n", t);
1971                 le2 = t->itemlist.Flink;
1972                 while (le2 != &t->itemlist) {
1973                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
1974                     if (!td->ignore) {
1975                         ERR("%I64x,%x,%I64x inserted=%u\n", td->key.obj_id, td->key.obj_type, td->key.offset, td->inserted);
1976                     }
1977                     le2 = le2->Flink;
1978                 }
1979                 int3;
1980             }
1981 #endif
1982             t->header.address = t->new_address;
1983             t->header.generation = Vcb->superblock.generation;
1984             t->header.tree_id = t->root->id;
1985             t->header.flags |= HEADER_FLAG_MIXED_BACKREF;
1986             t->header.fs_uuid = Vcb->superblock.metadata_uuid;
1987             t->has_address = true;
1988 
1989             data = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);
1990             if (!data) {
1991                 ERR("out of memory\n");
1992                 Status = STATUS_INSUFFICIENT_RESOURCES;
1993                 goto end;
1994             }
1995 
1996             body = data + sizeof(tree_header);
1997 
1998             RtlCopyMemory(data, &t->header, sizeof(tree_header));
1999             RtlZeroMemory(body, Vcb->superblock.node_size - sizeof(tree_header));
2000 
2001             if (t->header.level == 0) {
2002                 leaf_node* itemptr = (leaf_node*)body;
2003                 int i = 0;
2004                 uint8_t* dataptr = data + Vcb->superblock.node_size;
2005 
2006                 le2 = t->itemlist.Flink;
2007                 while (le2 != &t->itemlist) {
2008                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
2009                     if (!td->ignore) {
2010                         dataptr = dataptr - td->size;
2011 
2012                         itemptr[i].key = td->key;
2013                         itemptr[i].offset = (uint32_t)((uint8_t*)dataptr - (uint8_t*)body);
2014                         itemptr[i].size = td->size;
2015                         i++;
2016 
2017                         if (td->size > 0)
2018                             RtlCopyMemory(dataptr, td->data, td->size);
2019                     }
2020 
2021                     le2 = le2->Flink;
2022                 }
2023             } else {
2024                 internal_node* itemptr = (internal_node*)body;
2025                 int i = 0;
2026 
2027                 le2 = t->itemlist.Flink;
2028                 while (le2 != &t->itemlist) {
2029                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
2030                     if (!td->ignore) {
2031                         itemptr[i].key = td->key;
2032                         itemptr[i].address = td->treeholder.address;
2033                         itemptr[i].generation = td->treeholder.generation;
2034                         i++;
2035                     }
2036 
2037                     le2 = le2->Flink;
2038                 }
2039             }
2040 
2041             calc_tree_checksum(Vcb, (tree_header*)data);
2042 
2043             tw = ExAllocatePoolWithTag(PagedPool, sizeof(tree_write), ALLOC_TAG);
2044             if (!tw) {
2045                 ERR("out of memory\n");
2046                 ExFreePool(data);
2047                 Status = STATUS_INSUFFICIENT_RESOURCES;
2048                 goto end;
2049             }
2050 
2051             tw->address = t->new_address;
2052             tw->length = Vcb->superblock.node_size;
2053             tw->data = data;
2054             tw->allocated = false;
2055 
2056             if (IsListEmpty(&tree_writes))
2057                 InsertTailList(&tree_writes, &tw->list_entry);
2058             else {
2059                 bool inserted = false;
2060 
2061                 le2 = tree_writes.Flink;
2062                 while (le2 != &tree_writes) {
2063                     tree_write* tw2 = CONTAINING_RECORD(le2, tree_write, list_entry);
2064 
2065                     if (tw2->address > tw->address) {
2066                         InsertHeadList(le2->Blink, &tw->list_entry);
2067                         inserted = true;
2068                         break;
2069                     }
2070 
2071                     le2 = le2->Flink;
2072                 }
2073 
2074                 if (!inserted)
2075                     InsertTailList(&tree_writes, &tw->list_entry);
2076             }
2077         }
2078 
2079         le = le->Flink;
2080     }
2081 
2082     Status = do_tree_writes(Vcb, &tree_writes, false);
2083     if (!NT_SUCCESS(Status)) {
2084         ERR("do_tree_writes returned %08lx\n", Status);
2085         goto end;
2086     }
2087 
2088     Status = STATUS_SUCCESS;
2089 
2090 end:
2091     while (!IsListEmpty(&tree_writes)) {
2092         le = RemoveHeadList(&tree_writes);
2093         tw = CONTAINING_RECORD(le, tree_write, list_entry);
2094 
2095         if (tw->data)
2096             ExFreePool(tw->data);
2097 
2098         ExFreePool(tw);
2099     }
2100 
2101     return Status;
2102 }
2103 
2104 static void update_backup_superblock(device_extension* Vcb, superblock_backup* sb, PIRP Irp) {
2105     KEY searchkey;
2106     traverse_ptr tp;
2107 
2108     RtlZeroMemory(sb, sizeof(superblock_backup));
2109 
2110     sb->root_tree_addr = Vcb->superblock.root_tree_addr;
2111     sb->root_tree_generation = Vcb->superblock.generation;
2112     sb->root_level = Vcb->superblock.root_level;
2113 
2114     sb->chunk_tree_addr = Vcb->superblock.chunk_tree_addr;
2115     sb->chunk_tree_generation = Vcb->superblock.chunk_root_generation;
2116     sb->chunk_root_level = Vcb->superblock.chunk_root_level;
2117 
2118     searchkey.obj_id = BTRFS_ROOT_EXTENT;
2119     searchkey.obj_type = TYPE_ROOT_ITEM;
2120     searchkey.offset = 0xffffffffffffffff;
2121 
2122     if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {
2123         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
2124             ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
2125 
2126             sb->extent_tree_addr = ri->block_number;
2127             sb->extent_tree_generation = ri->generation;
2128             sb->extent_root_level = ri->root_level;
2129         }
2130     }
2131 
2132     searchkey.obj_id = BTRFS_ROOT_FSTREE;
2133 
2134     if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {
2135         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
2136             ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
2137 
2138             sb->fs_tree_addr = ri->block_number;
2139             sb->fs_tree_generation = ri->generation;
2140             sb->fs_root_level = ri->root_level;
2141         }
2142     }
2143 
2144     searchkey.obj_id = BTRFS_ROOT_DEVTREE;
2145 
2146     if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {
2147         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
2148             ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
2149 
2150             sb->dev_root_addr = ri->block_number;
2151             sb->dev_root_generation = ri->generation;
2152             sb->dev_root_level = ri->root_level;
2153         }
2154     }
2155 
2156     searchkey.obj_id = BTRFS_ROOT_CHECKSUM;
2157 
2158     if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {
2159         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
2160             ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
2161 
2162             sb->csum_root_addr = ri->block_number;
2163             sb->csum_root_generation = ri->generation;
2164             sb->csum_root_level = ri->root_level;
2165         }
2166     }
2167 
2168     sb->total_bytes = Vcb->superblock.total_bytes;
2169     sb->bytes_used = Vcb->superblock.bytes_used;
2170     sb->num_devices = Vcb->superblock.num_devices;
2171 }
2172 
2173 typedef struct {
2174     void* context;
2175     uint8_t* buf;
2176     PMDL mdl;
2177     device* device;
2178     NTSTATUS Status;
2179     PIRP Irp;
2180     LIST_ENTRY list_entry;
2181 } write_superblocks_stripe;
2182 
2183 typedef struct _write_superblocks_context {
2184     KEVENT Event;
2185     LIST_ENTRY stripes;
2186     LONG left;
2187 } write_superblocks_context;
2188 
2189 _Function_class_(IO_COMPLETION_ROUTINE)
2190 static NTSTATUS __stdcall write_superblock_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
2191     write_superblocks_stripe* stripe = conptr;
2192     write_superblocks_context* context = stripe->context;
2193 
2194     UNUSED(DeviceObject);
2195 
2196     stripe->Status = Irp->IoStatus.Status;
2197 
2198     if (InterlockedDecrement(&context->left) == 0)
2199         KeSetEvent(&context->Event, 0, false);
2200 
2201     return STATUS_MORE_PROCESSING_REQUIRED;
2202 }
2203 
2204 static void calc_superblock_checksum(superblock* sb) {
2205     switch (sb->csum_type) {
2206         case CSUM_TYPE_CRC32C:
2207             *(uint32_t*)sb = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
2208         break;
2209 
2210         case CSUM_TYPE_XXHASH:
2211             *(uint64_t*)sb = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0);
2212         break;
2213 
2214         case CSUM_TYPE_SHA256:
2215             calc_sha256((uint8_t*)sb, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));
2216         break;
2217 
2218         case CSUM_TYPE_BLAKE2:
2219             blake2b((uint8_t*)sb, BLAKE2_HASH_SIZE, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));
2220         break;
2221     }
2222 }
2223 
2224 static NTSTATUS write_superblock(device_extension* Vcb, device* device, write_superblocks_context* context) {
2225     unsigned int i = 0;
2226 
2227     // All the documentation says that the Linux driver only writes one superblock
2228     // if it thinks a disk is an SSD, but this doesn't seem to be the case!
2229 
2230     while (superblock_addrs[i] > 0 && device->devitem.num_bytes >= superblock_addrs[i] + sizeof(superblock)) {
2231         ULONG sblen = (ULONG)sector_align(sizeof(superblock), Vcb->superblock.sector_size);
2232         superblock* sb;
2233         write_superblocks_stripe* stripe;
2234         PIO_STACK_LOCATION IrpSp;
2235 
2236         sb = ExAllocatePoolWithTag(NonPagedPool, sblen, ALLOC_TAG);
2237         if (!sb) {
2238             ERR("out of memory\n");
2239             return STATUS_INSUFFICIENT_RESOURCES;
2240         }
2241 
2242         RtlCopyMemory(sb, &Vcb->superblock, sizeof(superblock));
2243 
2244         if (sblen > sizeof(superblock))
2245             RtlZeroMemory((uint8_t*)sb + sizeof(superblock), sblen - sizeof(superblock));
2246 
2247         RtlCopyMemory(&sb->dev_item, &device->devitem, sizeof(DEV_ITEM));
2248         sb->sb_phys_addr = superblock_addrs[i];
2249 
2250         calc_superblock_checksum(sb);
2251 
2252         stripe = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_superblocks_stripe), ALLOC_TAG);
2253         if (!stripe) {
2254             ERR("out of memory\n");
2255             ExFreePool(sb);
2256             return STATUS_INSUFFICIENT_RESOURCES;
2257         }
2258 
2259         stripe->buf = (uint8_t*)sb;
2260 
2261         stripe->Irp = IoAllocateIrp(device->devobj->StackSize, false);
2262         if (!stripe->Irp) {
2263             ERR("IoAllocateIrp failed\n");
2264             ExFreePool(stripe);
2265             ExFreePool(sb);
2266             return STATUS_INSUFFICIENT_RESOURCES;
2267         }
2268 
2269         IrpSp = IoGetNextIrpStackLocation(stripe->Irp);
2270         IrpSp->MajorFunction = IRP_MJ_WRITE;
2271         IrpSp->FileObject = device->fileobj;
2272 
2273         if (i == 0)
2274             IrpSp->Flags |= SL_WRITE_THROUGH;
2275 
2276         if (device->devobj->Flags & DO_BUFFERED_IO) {
2277             stripe->Irp->AssociatedIrp.SystemBuffer = sb;
2278             stripe->mdl = NULL;
2279 
2280             stripe->Irp->Flags = IRP_BUFFERED_IO;
2281         } else if (device->devobj->Flags & DO_DIRECT_IO) {
2282             stripe->mdl = IoAllocateMdl(sb, sblen, false, false, NULL);
2283             if (!stripe->mdl) {
2284                 ERR("IoAllocateMdl failed\n");
2285                 IoFreeIrp(stripe->Irp);
2286                 ExFreePool(stripe);
2287                 ExFreePool(sb);
2288                 return STATUS_INSUFFICIENT_RESOURCES;
2289             }
2290 
2291             stripe->Irp->MdlAddress = stripe->mdl;
2292 
2293             MmBuildMdlForNonPagedPool(stripe->mdl);
2294         } else {
2295             stripe->Irp->UserBuffer = sb;
2296             stripe->mdl = NULL;
2297         }
2298 
2299         IrpSp->Parameters.Write.Length = sblen;
2300         IrpSp->Parameters.Write.ByteOffset.QuadPart = superblock_addrs[i];
2301 
2302         IoSetCompletionRoutine(stripe->Irp, write_superblock_completion, stripe, true, true, true);
2303 
2304         stripe->context = context;
2305         stripe->device = device;
2306         InsertTailList(&context->stripes, &stripe->list_entry);
2307 
2308         context->left++;
2309 
2310         i++;
2311     }
2312 
2313     if (i == 0)
2314         ERR("no superblocks written!\n");
2315 
2316     return STATUS_SUCCESS;
2317 }
2318 
2319 static NTSTATUS write_superblocks(device_extension* Vcb, PIRP Irp) {
2320     uint64_t i;
2321     NTSTATUS Status;
2322     LIST_ENTRY* le;
2323     write_superblocks_context context;
2324 
2325     TRACE("(%p)\n", Vcb);
2326 
2327     le = Vcb->trees.Flink;
2328     while (le != &Vcb->trees) {
2329         tree* t = CONTAINING_RECORD(le, tree, list_entry);
2330 
2331         if (t->write && !t->parent) {
2332             if (t->root == Vcb->root_root) {
2333                 Vcb->superblock.root_tree_addr = t->new_address;
2334                 Vcb->superblock.root_level = t->header.level;
2335             } else if (t->root == Vcb->chunk_root) {
2336                 Vcb->superblock.chunk_tree_addr = t->new_address;
2337                 Vcb->superblock.chunk_root_generation = t->header.generation;
2338                 Vcb->superblock.chunk_root_level = t->header.level;
2339             }
2340         }
2341 
2342         le = le->Flink;
2343     }
2344 
2345     for (i = 0; i < BTRFS_NUM_BACKUP_ROOTS - 1; i++) {
2346         RtlCopyMemory(&Vcb->superblock.backup[i], &Vcb->superblock.backup[i+1], sizeof(superblock_backup));
2347     }
2348 
2349     update_backup_superblock(Vcb, &Vcb->superblock.backup[BTRFS_NUM_BACKUP_ROOTS - 1], Irp);
2350 
2351     KeInitializeEvent(&context.Event, NotificationEvent, false);
2352     InitializeListHead(&context.stripes);
2353     context.left = 0;
2354 
2355     le = Vcb->devices.Flink;
2356     while (le != &Vcb->devices) {
2357         device* dev = CONTAINING_RECORD(le, device, list_entry);
2358 
2359         if (dev->devobj && !dev->readonly) {
2360             Status = write_superblock(Vcb, dev, &context);
2361             if (!NT_SUCCESS(Status)) {
2362                 ERR("write_superblock returned %08lx\n", Status);
2363                 goto end;
2364             }
2365         }
2366 
2367         le = le->Flink;
2368     }
2369 
2370     if (IsListEmpty(&context.stripes)) {
2371         ERR("error - not writing any superblocks\n");
2372         Status = STATUS_INTERNAL_ERROR;
2373         goto end;
2374     }
2375 
2376     le = context.stripes.Flink;
2377     while (le != &context.stripes) {
2378         write_superblocks_stripe* stripe = CONTAINING_RECORD(le, write_superblocks_stripe, list_entry);
2379 
2380         IoCallDriver(stripe->device->devobj, stripe->Irp);
2381 
2382         le = le->Flink;
2383     }
2384 
2385     KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
2386 
2387     le = context.stripes.Flink;
2388     while (le != &context.stripes) {
2389         write_superblocks_stripe* stripe = CONTAINING_RECORD(le, write_superblocks_stripe, list_entry);
2390 
2391         if (!NT_SUCCESS(stripe->Status)) {
2392             ERR("device %I64x returned %08lx\n", stripe->device->devitem.dev_id, stripe->Status);
2393             log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS);
2394             Status = stripe->Status;
2395             goto end;
2396         }
2397 
2398         le = le->Flink;
2399     }
2400 
2401     Status = STATUS_SUCCESS;
2402 
2403 end:
2404     while (!IsListEmpty(&context.stripes)) {
2405         write_superblocks_stripe* stripe = CONTAINING_RECORD(RemoveHeadList(&context.stripes), write_superblocks_stripe, list_entry);
2406 
2407         if (stripe->mdl) {
2408             if (stripe->mdl->MdlFlags & MDL_PAGES_LOCKED)
2409                 MmUnlockPages(stripe->mdl);
2410 
2411             IoFreeMdl(stripe->mdl);
2412         }
2413 
2414         if (stripe->Irp)
2415             IoFreeIrp(stripe->Irp);
2416 
2417         if (stripe->buf)
2418             ExFreePool(stripe->buf);
2419 
2420         ExFreePool(stripe);
2421     }
2422 
2423     return Status;
2424 }
2425 
2426 static NTSTATUS flush_changed_extent(device_extension* Vcb, chunk* c, changed_extent* ce, PIRP Irp, LIST_ENTRY* rollback) {
2427     LIST_ENTRY *le, *le2;
2428     NTSTATUS Status;
2429     uint64_t old_size;
2430 
2431     if (ce->count == 0 && ce->old_count == 0) {
2432         while (!IsListEmpty(&ce->refs)) {
2433             changed_extent_ref* cer = CONTAINING_RECORD(RemoveHeadList(&ce->refs), changed_extent_ref, list_entry);
2434             ExFreePool(cer);
2435         }
2436 
2437         while (!IsListEmpty(&ce->old_refs)) {
2438             changed_extent_ref* cer = CONTAINING_RECORD(RemoveHeadList(&ce->old_refs), changed_extent_ref, list_entry);
2439             ExFreePool(cer);
2440         }
2441 
2442         goto end;
2443     }
2444 
2445     le = ce->refs.Flink;
2446     while (le != &ce->refs) {
2447         changed_extent_ref* cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);
2448         uint32_t old_count = 0;
2449 
2450         if (cer->type == TYPE_EXTENT_DATA_REF) {
2451             le2 = ce->old_refs.Flink;
2452             while (le2 != &ce->old_refs) {
2453                 changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
2454 
2455                 if (cer2->type == TYPE_EXTENT_DATA_REF && cer2->edr.root == cer->edr.root && cer2->edr.objid == cer->edr.objid && cer2->edr.offset == cer->edr.offset) {
2456                     old_count = cer2->edr.count;
2457                     break;
2458                 }
2459 
2460                 le2 = le2->Flink;
2461             }
2462 
2463             old_size = ce->old_count > 0 ? ce->old_size : ce->size;
2464 
2465             if (cer->edr.count > old_count) {
2466                 Status = increase_extent_refcount_data(Vcb, ce->address, old_size, cer->edr.root, cer->edr.objid, cer->edr.offset, cer->edr.count - old_count, Irp);
2467 
2468                 if (!NT_SUCCESS(Status)) {
2469                     ERR("increase_extent_refcount_data returned %08lx\n", Status);
2470                     return Status;
2471                 }
2472             }
2473         } else if (cer->type == TYPE_SHARED_DATA_REF) {
2474             le2 = ce->old_refs.Flink;
2475             while (le2 != &ce->old_refs) {
2476                 changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
2477 
2478                 if (cer2->type == TYPE_SHARED_DATA_REF && cer2->sdr.offset == cer->sdr.offset) {
2479                     RemoveEntryList(&cer2->list_entry);
2480                     ExFreePool(cer2);
2481                     break;
2482                 }
2483 
2484                 le2 = le2->Flink;
2485             }
2486         }
2487 
2488         le = le->Flink;
2489     }
2490 
2491     le = ce->refs.Flink;
2492     while (le != &ce->refs) {
2493         changed_extent_ref* cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);
2494         LIST_ENTRY* le3 = le->Flink;
2495         uint32_t old_count = 0;
2496 
2497         if (cer->type == TYPE_EXTENT_DATA_REF) {
2498             le2 = ce->old_refs.Flink;
2499             while (le2 != &ce->old_refs) {
2500                 changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);
2501 
2502                 if (cer2->type == TYPE_EXTENT_DATA_REF && cer2->edr.root == cer->edr.root && cer2->edr.objid == cer->edr.objid && cer2->edr.offset == cer->edr.offset) {
2503                     old_count = cer2->edr.count;
2504 
2505                     RemoveEntryList(&cer2->list_entry);
2506                     ExFreePool(cer2);
2507                     break;
2508                 }
2509 
2510                 le2 = le2->Flink;
2511             }
2512 
2513             old_size = ce->old_count > 0 ? ce->old_size : ce->size;
2514 
2515             if (cer->edr.count < old_count) {
2516                 Status = decrease_extent_refcount_data(Vcb, ce->address, old_size, cer->edr.root, cer->edr.objid, cer->edr.offset,
2517                                                        old_count - cer->edr.count, ce->superseded, Irp);
2518 
2519                 if (!NT_SUCCESS(Status)) {
2520                     ERR("decrease_extent_refcount_data returned %08lx\n", Status);
2521                     return Status;
2522                 }
2523             }
2524 
2525             if (ce->size != ce->old_size && ce->old_count > 0) {
2526                 KEY searchkey;
2527                 traverse_ptr tp;
2528                 void* data;
2529 
2530                 searchkey.obj_id = ce->address;
2531                 searchkey.obj_type = TYPE_EXTENT_ITEM;
2532                 searchkey.offset = ce->old_size;
2533 
2534                 Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
2535                 if (!NT_SUCCESS(Status)) {
2536                     ERR("error - find_item returned %08lx\n", Status);
2537                     return Status;
2538                 }
2539 
2540                 if (keycmp(searchkey, tp.item->key)) {
2541                     ERR("could not find (%I64x,%x,%I64x) in extent tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
2542                     return STATUS_INTERNAL_ERROR;
2543                 }
2544 
2545                 if (tp.item->size > 0) {
2546                     data = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
2547 
2548                     if (!data) {
2549                         ERR("out of memory\n");
2550                         return STATUS_INSUFFICIENT_RESOURCES;
2551                     }
2552 
2553                     RtlCopyMemory(data, tp.item->data, tp.item->size);
2554                 } else
2555                     data = NULL;
2556 
2557                 Status = insert_tree_item(Vcb, Vcb->extent_root, ce->address, TYPE_EXTENT_ITEM, ce->size, data, tp.item->size, NULL, Irp);
2558                 if (!NT_SUCCESS(Status)) {
2559                     ERR("insert_tree_item returned %08lx\n", Status);
2560                     if (data) ExFreePool(data);
2561                     return Status;
2562                 }
2563 
2564                 Status = delete_tree_item(Vcb, &tp);
2565                 if (!NT_SUCCESS(Status)) {
2566                     ERR("delete_tree_item returned %08lx\n", Status);
2567                     return Status;
2568                 }
2569             }
2570         }
2571 
2572         RemoveEntryList(&cer->list_entry);
2573         ExFreePool(cer);
2574 
2575         le = le3;
2576     }
2577 
2578 #ifdef DEBUG_PARANOID
2579     if (!IsListEmpty(&ce->old_refs))
2580         WARN("old_refs not empty\n");
2581 #endif
2582 
2583 end:
2584     if (ce->count == 0 && !ce->superseded) {
2585         c->used -= ce->size;
2586         space_list_add(c, ce->address, ce->size, rollback);
2587     }
2588 
2589     RemoveEntryList(&ce->list_entry);
2590     ExFreePool(ce);
2591 
2592     return STATUS_SUCCESS;
2593 }
2594 
2595 void add_checksum_entry(device_extension* Vcb, uint64_t address, ULONG length, void* csum, PIRP Irp) {
2596     KEY searchkey;
2597     traverse_ptr tp, next_tp;
2598     NTSTATUS Status;
2599     uint64_t startaddr, endaddr;
2600     ULONG len;
2601     RTL_BITMAP bmp;
2602     ULONG* bmparr;
2603     ULONG runlength, index;
2604 
2605     TRACE("(%p, %I64x, %lx, %p, %p)\n", Vcb, address, length, csum, Irp);
2606 
2607     searchkey.obj_id = EXTENT_CSUM_ID;
2608     searchkey.obj_type = TYPE_EXTENT_CSUM;
2609     searchkey.offset = address;
2610 
2611     // FIXME - create checksum_root if it doesn't exist at all
2612 
2613     Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);
2614     if (Status == STATUS_NOT_FOUND) { // tree is completely empty
2615         if (csum) { // not deleted
2616             ULONG length2 = length;
2617             uint64_t off = address;
2618             void* data = csum;
2619 
2620             do {
2621                 uint16_t il = (uint16_t)min(length2, MAX_CSUM_SIZE / Vcb->csum_size);
2622 
2623                 void* checksums = ExAllocatePoolWithTag(PagedPool, il * Vcb->csum_size, ALLOC_TAG);
2624                 if (!checksums) {
2625                     ERR("out of memory\n");
2626                     return;
2627                 }
2628 
2629                 RtlCopyMemory(checksums, data, il * Vcb->csum_size);
2630 
2631                 Status = insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, off, checksums,
2632                                           il * Vcb->csum_size, NULL, Irp);
2633                 if (!NT_SUCCESS(Status)) {
2634                     ERR("insert_tree_item returned %08lx\n", Status);
2635                     ExFreePool(checksums);
2636                     return;
2637                 }
2638 
2639                 length2 -= il;
2640 
2641                 if (length2 > 0) {
2642                     off += il * Vcb->superblock.sector_size;
2643                     data = (uint8_t*)data + (il * Vcb->csum_size);
2644                 }
2645             } while (length2 > 0);
2646         }
2647     } else if (!NT_SUCCESS(Status)) {
2648         ERR("find_item returned %08lx\n", Status);
2649         return;
2650     } else {
2651         uint32_t tplen;
2652         void* checksums;
2653 
2654         // FIXME - check entry is TYPE_EXTENT_CSUM?
2655 
2656         if (tp.item->key.offset < address && tp.item->key.offset + (tp.item->size * Vcb->superblock.sector_size / Vcb->csum_size) >= address)
2657             startaddr = tp.item->key.offset;
2658         else
2659             startaddr = address;
2660 
2661         searchkey.obj_id = EXTENT_CSUM_ID;
2662         searchkey.obj_type = TYPE_EXTENT_CSUM;
2663         searchkey.offset = address + (length * Vcb->superblock.sector_size);
2664 
2665         Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);
2666         if (!NT_SUCCESS(Status)) {
2667             ERR("find_item returned %08lx\n", Status);
2668             return;
2669         }
2670 
2671         tplen = tp.item->size / Vcb->csum_size;
2672 
2673         if (tp.item->key.offset + (tplen * Vcb->superblock.sector_size) >= address + (length * Vcb->superblock.sector_size))
2674             endaddr = tp.item->key.offset + (tplen * Vcb->superblock.sector_size);
2675         else
2676             endaddr = address + (length * Vcb->superblock.sector_size);
2677 
2678         TRACE("cs starts at %I64x (%lx sectors)\n", address, length);
2679         TRACE("startaddr = %I64x\n", startaddr);
2680         TRACE("endaddr = %I64x\n", endaddr);
2681 
2682         len = (ULONG)((endaddr - startaddr) / Vcb->superblock.sector_size);
2683 
2684         checksums = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * len, ALLOC_TAG);
2685         if (!checksums) {
2686             ERR("out of memory\n");
2687             return;
2688         }
2689 
2690         bmparr = ExAllocatePoolWithTag(PagedPool, sizeof(ULONG) * ((len/8)+1), ALLOC_TAG);
2691         if (!bmparr) {
2692             ERR("out of memory\n");
2693             ExFreePool(checksums);
2694             return;
2695         }
2696 
2697         RtlInitializeBitMap(&bmp, bmparr, len);
2698         RtlSetAllBits(&bmp);
2699 
2700         searchkey.obj_id = EXTENT_CSUM_ID;
2701         searchkey.obj_type = TYPE_EXTENT_CSUM;
2702         searchkey.offset = address;
2703 
2704         Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);
2705         if (!NT_SUCCESS(Status)) {
2706             ERR("find_item returned %08lx\n", Status);
2707             ExFreePool(checksums);
2708             ExFreePool(bmparr);
2709             return;
2710         }
2711 
2712         // set bit = free space, cleared bit = allocated sector
2713 
2714         while (tp.item->key.offset < endaddr) {
2715             if (tp.item->key.offset >= startaddr) {
2716                 if (tp.item->size > 0) {
2717                     ULONG itemlen = (ULONG)min((len - (tp.item->key.offset - startaddr) / Vcb->superblock.sector_size) * Vcb->csum_size, tp.item->size);
2718 
2719                     RtlCopyMemory((uint8_t*)checksums + ((tp.item->key.offset - startaddr) * Vcb->csum_size / Vcb->superblock.sector_size),
2720                                   tp.item->data, itemlen);
2721                     RtlClearBits(&bmp, (ULONG)((tp.item->key.offset - startaddr) / Vcb->superblock.sector_size), itemlen / Vcb->csum_size);
2722                 }
2723 
2724                 Status = delete_tree_item(Vcb, &tp);
2725                 if (!NT_SUCCESS(Status)) {
2726                     ERR("delete_tree_item returned %08lx\n", Status);
2727                     ExFreePool(checksums);
2728                     ExFreePool(bmparr);
2729                     return;
2730                 }
2731             }
2732 
2733             if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {
2734                 tp = next_tp;
2735             } else
2736                 break;
2737         }
2738 
2739         if (!csum) { // deleted
2740             RtlSetBits(&bmp, (ULONG)((address - startaddr) / Vcb->superblock.sector_size), length);
2741         } else {
2742             RtlCopyMemory((uint8_t*)checksums + ((address - startaddr) * Vcb->csum_size / Vcb->superblock.sector_size),
2743                           csum, length * Vcb->csum_size);
2744             RtlClearBits(&bmp, (ULONG)((address - startaddr) / Vcb->superblock.sector_size), length);
2745         }
2746 
2747         runlength = RtlFindFirstRunClear(&bmp, &index);
2748 
2749         while (runlength != 0) {
2750             if (index >= len)
2751                 break;
2752 
2753             if (index + runlength >= len) {
2754                 runlength = len - index;
2755 
2756                 if (runlength == 0)
2757                     break;
2758             }
2759 
2760             do {
2761                 uint16_t rl;
2762                 uint64_t off;
2763                 void* data;
2764 
2765                 if (runlength * Vcb->csum_size > MAX_CSUM_SIZE)
2766                     rl = (uint16_t)(MAX_CSUM_SIZE / Vcb->csum_size);
2767                 else
2768                     rl = (uint16_t)runlength;
2769 
2770                 data = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * rl, ALLOC_TAG);
2771                 if (!data) {
2772                     ERR("out of memory\n");
2773                     ExFreePool(bmparr);
2774                     ExFreePool(checksums);
2775                     return;
2776                 }
2777 
2778                 RtlCopyMemory(data, (uint8_t*)checksums + (Vcb->csum_size * index), Vcb->csum_size * rl);
2779 
2780                 off = startaddr + UInt32x32To64(index, Vcb->superblock.sector_size);
2781 
2782                 Status = insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, off, data, Vcb->csum_size * rl, NULL, Irp);
2783                 if (!NT_SUCCESS(Status)) {
2784                     ERR("insert_tree_item returned %08lx\n", Status);
2785                     ExFreePool(data);
2786                     ExFreePool(bmparr);
2787                     ExFreePool(checksums);
2788                     return;
2789                 }
2790 
2791                 runlength -= rl;
2792                 index += rl;
2793             } while (runlength > 0);
2794 
2795             runlength = RtlFindNextForwardRunClear(&bmp, index, &index);
2796         }
2797 
2798         ExFreePool(bmparr);
2799         ExFreePool(checksums);
2800     }
2801 }
2802 
2803 static NTSTATUS update_chunk_usage(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {
2804     LIST_ENTRY *le = Vcb->chunks.Flink, *le2;
2805     chunk* c;
2806     KEY searchkey;
2807     traverse_ptr tp;
2808     BLOCK_GROUP_ITEM* bgi;
2809     NTSTATUS Status;
2810 
2811     TRACE("(%p)\n", Vcb);
2812 
2813     ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);
2814 
2815     while (le != &Vcb->chunks) {
2816         c = CONTAINING_RECORD(le, chunk, list_entry);
2817 
2818         acquire_chunk_lock(c, Vcb);
2819 
2820         if (!c->cache_loaded && (!IsListEmpty(&c->changed_extents) || c->used != c->oldused)) {
2821             Status = load_cache_chunk(Vcb, c, NULL);
2822 
2823             if (!NT_SUCCESS(Status)) {
2824                 ERR("load_cache_chunk returned %08lx\n", Status);
2825                 release_chunk_lock(c, Vcb);
2826                 goto end;
2827             }
2828         }
2829 
2830         le2 = c->changed_extents.Flink;
2831         while (le2 != &c->changed_extents) {
2832             LIST_ENTRY* le3 = le2->Flink;
2833             changed_extent* ce = CONTAINING_RECORD(le2, changed_extent, list_entry);
2834 
2835             Status = flush_changed_extent(Vcb, c, ce, Irp, rollback);
2836             if (!NT_SUCCESS(Status)) {
2837                 ERR("flush_changed_extent returned %08lx\n", Status);
2838                 release_chunk_lock(c, Vcb);
2839                 goto end;
2840             }
2841 
2842             le2 = le3;
2843         }
2844 
2845         // This is usually done by update_chunks, but we have to check again in case any new chunks
2846         // have been allocated since.
2847         if (c->created) {
2848             Status = create_chunk(Vcb, c, Irp);
2849             if (!NT_SUCCESS(Status)) {
2850                 ERR("create_chunk returned %08lx\n", Status);
2851                 release_chunk_lock(c, Vcb);
2852                 goto end;
2853             }
2854         }
2855 
2856         if (c->old_cache) {
2857             if (c->old_cache->dirty) {
2858                 LIST_ENTRY batchlist;
2859 
2860                 InitializeListHead(&batchlist);
2861 
2862                 Status = flush_fcb(c->old_cache, false, &batchlist, Irp);
2863                 if (!NT_SUCCESS(Status)) {
2864                     ERR("flush_fcb returned %08lx\n", Status);
2865                     release_chunk_lock(c, Vcb);
2866                     clear_batch_list(Vcb, &batchlist);
2867                     goto end;
2868                 }
2869 
2870                 Status = commit_batch_list(Vcb, &batchlist, Irp);
2871                 if (!NT_SUCCESS(Status)) {
2872                     ERR("commit_batch_list returned %08lx\n", Status);
2873                     release_chunk_lock(c, Vcb);
2874                     goto end;
2875                 }
2876             }
2877 
2878             free_fcb(c->old_cache);
2879 
2880             if (c->old_cache->refcount == 0)
2881                 reap_fcb(c->old_cache);
2882 
2883             c->old_cache = NULL;
2884         }
2885 
2886         if (c->used != c->oldused) {
2887 #ifdef __REACTOS__
2888             uint64_t old_phys_used, phys_used;
2889 #endif
2890             searchkey.obj_id = c->offset;
2891             searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;
2892             searchkey.offset = c->chunk_item->size;
2893 
2894             Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
2895             if (!NT_SUCCESS(Status)) {
2896                 ERR("error - find_item returned %08lx\n", Status);
2897                 release_chunk_lock(c, Vcb);
2898                 goto end;
2899             }
2900 
2901             if (keycmp(searchkey, tp.item->key)) {
2902                 ERR("could not find (%I64x,%x,%I64x) in extent_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
2903                 Status = STATUS_INTERNAL_ERROR;
2904                 release_chunk_lock(c, Vcb);
2905                 goto end;
2906             }
2907 
2908             if (tp.item->size < sizeof(BLOCK_GROUP_ITEM)) {
2909                 ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(BLOCK_GROUP_ITEM));
2910                 Status = STATUS_INTERNAL_ERROR;
2911                 release_chunk_lock(c, Vcb);
2912                 goto end;
2913             }
2914 
2915             bgi = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
2916             if (!bgi) {
2917                 ERR("out of memory\n");
2918                 Status = STATUS_INSUFFICIENT_RESOURCES;
2919                 release_chunk_lock(c, Vcb);
2920                 goto end;
2921             }
2922 
2923             RtlCopyMemory(bgi, tp.item->data, tp.item->size);
2924             bgi->used = c->used;
2925 
2926 #ifdef DEBUG_PARANOID
2927             if (bgi->used & 0x8000000000000000) {
2928                 ERR("refusing to write BLOCK_GROUP_ITEM with negative usage value (%I64x)", bgi->used);
2929                 int3;
2930             }
2931 #endif
2932 
2933             TRACE("adjusting usage of chunk %I64x to %I64x\n", c->offset, c->used);
2934 
2935             Status = delete_tree_item(Vcb, &tp);
2936             if (!NT_SUCCESS(Status)) {
2937                 ERR("delete_tree_item returned %08lx\n", Status);
2938                 ExFreePool(bgi);
2939                 release_chunk_lock(c, Vcb);
2940                 goto end;
2941             }
2942 
2943             Status = insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, bgi, tp.item->size, NULL, Irp);
2944             if (!NT_SUCCESS(Status)) {
2945                 ERR("insert_tree_item returned %08lx\n", Status);
2946                 ExFreePool(bgi);
2947                 release_chunk_lock(c, Vcb);
2948                 goto end;
2949             }
2950 
2951 #ifndef __REACTOS__
2952             uint64_t old_phys_used = chunk_estimate_phys_size(Vcb, c, c->oldused);
2953             uint64_t phys_used = chunk_estimate_phys_size(Vcb, c, c->used);
2954 #else
2955             old_phys_used = chunk_estimate_phys_size(Vcb, c, c->oldused);
2956             phys_used = chunk_estimate_phys_size(Vcb, c, c->used);
2957 #endif
2958 
2959             if (Vcb->superblock.bytes_used + phys_used > old_phys_used)
2960                 Vcb->superblock.bytes_used += phys_used - old_phys_used;
2961             else
2962                 Vcb->superblock.bytes_used = 0;
2963 
2964             c->oldused = c->used;
2965         }
2966 
2967         release_chunk_lock(c, Vcb);
2968 
2969         le = le->Flink;
2970     }
2971 
2972     Status = STATUS_SUCCESS;
2973 
2974 end:
2975     ExReleaseResourceLite(&Vcb->chunk_lock);
2976 
2977     return Status;
2978 }
2979 
2980 static void get_first_item(tree* t, KEY* key) {
2981     LIST_ENTRY* le;
2982 
2983     le = t->itemlist.Flink;
2984     while (le != &t->itemlist) {
2985         tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
2986 
2987         *key = td->key;
2988         return;
2989     }
2990 }
2991 
2992 static NTSTATUS split_tree_at(device_extension* Vcb, tree* t, tree_data* newfirstitem, uint32_t numitems, uint32_t size) {
2993     tree *nt, *pt;
2994     tree_data* td;
2995     tree_data* oldlastitem;
2996 
2997     TRACE("splitting tree in %I64x at (%I64x,%x,%I64x)\n", t->root->id, newfirstitem->key.obj_id, newfirstitem->key.obj_type, newfirstitem->key.offset);
2998 
2999     nt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
3000     if (!nt) {
3001         ERR("out of memory\n");
3002         return STATUS_INSUFFICIENT_RESOURCES;
3003     }
3004 
3005     if (t->header.level > 0) {
3006         nt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);
3007         if (!nt->nonpaged) {
3008             ERR("out of memory\n");
3009             ExFreePool(nt);
3010             return STATUS_INSUFFICIENT_RESOURCES;
3011         }
3012 
3013         ExInitializeFastMutex(&nt->nonpaged->mutex);
3014     } else
3015         nt->nonpaged = NULL;
3016 
3017     RtlCopyMemory(&nt->header, &t->header, sizeof(tree_header));
3018     nt->header.address = 0;
3019     nt->header.generation = Vcb->superblock.generation;
3020     nt->header.num_items = t->header.num_items - numitems;
3021     nt->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN;
3022 
3023     nt->has_address = false;
3024     nt->Vcb = Vcb;
3025     nt->parent = t->parent;
3026 
3027 #ifdef DEBUG_PARANOID
3028     if (nt->parent && nt->parent->header.level <= nt->header.level) int3;
3029 #endif
3030 
3031     nt->root = t->root;
3032     nt->new_address = 0;
3033     nt->has_new_address = false;
3034     nt->updated_extents = false;
3035     nt->uniqueness_determined = true;
3036     nt->is_unique = true;
3037     nt->list_entry_hash.Flink = NULL;
3038     nt->buf = NULL;
3039     InitializeListHead(&nt->itemlist);
3040 
3041     oldlastitem = CONTAINING_RECORD(newfirstitem->list_entry.Blink, tree_data, list_entry);
3042 
3043     nt->itemlist.Flink = &newfirstitem->list_entry;
3044     nt->itemlist.Blink = t->itemlist.Blink;
3045     nt->itemlist.Flink->Blink = &nt->itemlist;
3046     nt->itemlist.Blink->Flink = &nt->itemlist;
3047 
3048     t->itemlist.Blink = &oldlastitem->list_entry;
3049     t->itemlist.Blink->Flink = &t->itemlist;
3050 
3051     nt->size = t->size - size;
3052     t->size = size;
3053     t->header.num_items = numitems;
3054     nt->write = true;
3055 
3056     InsertTailList(&Vcb->trees, &nt->list_entry);
3057 
3058     if (nt->header.level > 0) {
3059         LIST_ENTRY* le = nt->itemlist.Flink;
3060 
3061         while (le != &nt->itemlist) {
3062             tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
3063 
3064             if (td2->treeholder.tree) {
3065                 td2->treeholder.tree->parent = nt;
3066 #ifdef DEBUG_PARANOID
3067                 if (td2->treeholder.tree->parent && td2->treeholder.tree->parent->header.level <= td2->treeholder.tree->header.level) int3;
3068 #endif
3069             }
3070 
3071             le = le->Flink;
3072         }
3073     } else {
3074         LIST_ENTRY* le = nt->itemlist.Flink;
3075 
3076         while (le != &nt->itemlist) {
3077             tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
3078 
3079             if (!td2->inserted && td2->data) {
3080                 uint8_t* data = ExAllocatePoolWithTag(PagedPool, td2->size, ALLOC_TAG);
3081 
3082                 if (!data) {
3083                     ERR("out of memory\n");
3084                     return STATUS_INSUFFICIENT_RESOURCES;
3085                 }
3086 
3087                 RtlCopyMemory(data, td2->data, td2->size);
3088                 td2->data = data;
3089                 td2->inserted = true;
3090             }
3091 
3092             le = le->Flink;
3093         }
3094     }
3095 
3096     if (nt->parent) {
3097         td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);
3098         if (!td) {
3099             ERR("out of memory\n");
3100             return STATUS_INSUFFICIENT_RESOURCES;
3101         }
3102 
3103         td->key = newfirstitem->key;
3104 
3105         InsertHeadList(&t->paritem->list_entry, &td->list_entry);
3106 
3107         td->ignore = false;
3108         td->inserted = true;
3109         td->treeholder.tree = nt;
3110         nt->paritem = td;
3111 
3112         nt->parent->header.num_items++;
3113         nt->parent->size += sizeof(internal_node);
3114 
3115         goto end;
3116     }
3117 
3118     TRACE("adding new tree parent\n");
3119 
3120     if (nt->header.level == 255) {
3121         ERR("cannot add parent to tree at level 255\n");
3122         return STATUS_INTERNAL_ERROR;
3123     }
3124 
3125     pt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
3126     if (!pt) {
3127         ERR("out of memory\n");
3128         return STATUS_INSUFFICIENT_RESOURCES;
3129     }
3130 
3131     pt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);
3132     if (!pt->nonpaged) {
3133         ERR("out of memory\n");
3134         ExFreePool(pt);
3135         return STATUS_INSUFFICIENT_RESOURCES;
3136     }
3137 
3138     ExInitializeFastMutex(&pt->nonpaged->mutex);
3139 
3140     RtlCopyMemory(&pt->header, &nt->header, sizeof(tree_header));
3141     pt->header.address = 0;
3142     pt->header.num_items = 2;
3143     pt->header.level = nt->header.level + 1;
3144     pt->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN;
3145 
3146     pt->has_address = false;
3147     pt->Vcb = Vcb;
3148     pt->parent = NULL;
3149     pt->paritem = NULL;
3150     pt->root = t->root;
3151     pt->new_address = 0;
3152     pt->has_new_address = false;
3153     pt->updated_extents = false;
3154     pt->size = pt->header.num_items * sizeof(internal_node);
3155     pt->uniqueness_determined = true;
3156     pt->is_unique = true;
3157     pt->list_entry_hash.Flink = NULL;
3158     pt->buf = NULL;
3159     InitializeListHead(&pt->itemlist);
3160 
3161     InsertTailList(&Vcb->trees, &pt->list_entry);
3162 
3163     td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);
3164     if (!td) {
3165         ERR("out of memory\n");
3166         return STATUS_INSUFFICIENT_RESOURCES;
3167     }
3168 
3169     get_first_item(t, &td->key);
3170     td->ignore = false;
3171     td->inserted = false;
3172     td->treeholder.address = 0;
3173     td->treeholder.generation = Vcb->superblock.generation;
3174     td->treeholder.tree = t;
3175     InsertTailList(&pt->itemlist, &td->list_entry);
3176     t->paritem = td;
3177 
3178     td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);
3179     if (!td) {
3180         ERR("out of memory\n");
3181         return STATUS_INSUFFICIENT_RESOURCES;
3182     }
3183 
3184     td->key = newfirstitem->key;
3185     td->ignore = false;
3186     td->inserted = false;
3187     td->treeholder.address = 0;
3188     td->treeholder.generation = Vcb->superblock.generation;
3189     td->treeholder.tree = nt;
3190     InsertTailList(&pt->itemlist, &td->list_entry);
3191     nt->paritem = td;
3192 
3193     pt->write = true;
3194 
3195     t->root->treeholder.tree = pt;
3196 
3197     t->parent = pt;
3198     nt->parent = pt;
3199 
3200 #ifdef DEBUG_PARANOID
3201     if (t->parent && t->parent->header.level <= t->header.level) int3;
3202     if (nt->parent && nt->parent->header.level <= nt->header.level) int3;
3203 #endif
3204 
3205 end:
3206     t->root->root_item.bytes_used += Vcb->superblock.node_size;
3207 
3208     return STATUS_SUCCESS;
3209 }
3210 
3211 static NTSTATUS split_tree(device_extension* Vcb, tree* t) {
3212     LIST_ENTRY* le;
3213     uint32_t size, ds, numitems;
3214 
3215     size = 0;
3216     numitems = 0;
3217 
3218     // FIXME - naïve implementation: maximizes number of filled trees
3219 
3220     le = t->itemlist.Flink;
3221     while (le != &t->itemlist) {
3222         tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
3223 
3224         if (!td->ignore) {
3225             if (t->header.level == 0)
3226                 ds = sizeof(leaf_node) + td->size;
3227             else
3228                 ds = sizeof(internal_node);
3229 
3230             if (numitems == 0 && ds > Vcb->superblock.node_size - sizeof(tree_header)) {
3231                 ERR("(%I64x,%x,%I64x) in tree %I64x is too large (%x > %Ix)\n",
3232                     td->key.obj_id, td->key.obj_type, td->key.offset, t->root->id,
3233                     ds, Vcb->superblock.node_size - sizeof(tree_header));
3234                 return STATUS_INTERNAL_ERROR;
3235             }
3236 
3237             // FIXME - move back if previous item was deleted item with same key
3238             if (size + ds > Vcb->superblock.node_size - sizeof(tree_header))
3239                 return split_tree_at(Vcb, t, td, numitems, size);
3240 
3241             size += ds;
3242             numitems++;
3243         }
3244 
3245         le = le->Flink;
3246     }
3247 
3248     return STATUS_SUCCESS;
3249 }
3250 
3251 bool is_tree_unique(device_extension* Vcb, tree* t, PIRP Irp) {
3252     KEY searchkey;
3253     traverse_ptr tp;
3254     NTSTATUS Status;
3255     bool ret = false;
3256     EXTENT_ITEM* ei;
3257     uint8_t* type;
3258 
3259     if (t->uniqueness_determined)
3260         return t->is_unique;
3261 
3262     if (t->parent && !is_tree_unique(Vcb, t->parent, Irp))
3263         goto end;
3264 
3265     if (t->has_address) {
3266         searchkey.obj_id = t->header.address;
3267         searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
3268         searchkey.offset = 0xffffffffffffffff;
3269 
3270         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
3271         if (!NT_SUCCESS(Status)) {
3272             ERR("error - find_item returned %08lx\n", Status);
3273             goto end;
3274         }
3275 
3276         if (tp.item->key.obj_id != t->header.address || (tp.item->key.obj_type != TYPE_METADATA_ITEM && tp.item->key.obj_type != TYPE_EXTENT_ITEM))
3277             goto end;
3278 
3279         if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->size == sizeof(EXTENT_ITEM_V0))
3280             goto end;
3281 
3282         if (tp.item->size < sizeof(EXTENT_ITEM))
3283             goto end;
3284 
3285         ei = (EXTENT_ITEM*)tp.item->data;
3286 
3287         if (ei->refcount > 1)
3288             goto end;
3289 
3290         if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) {
3291             EXTENT_ITEM2* ei2;
3292 
3293             if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2))
3294                 goto end;
3295 
3296             ei2 = (EXTENT_ITEM2*)&ei[1];
3297             type = (uint8_t*)&ei2[1];
3298         } else
3299             type = (uint8_t*)&ei[1];
3300 
3301         if (type >= tp.item->data + tp.item->size || *type != TYPE_TREE_BLOCK_REF)
3302             goto end;
3303     }
3304 
3305     ret = true;
3306 
3307 end:
3308     t->is_unique = ret;
3309     t->uniqueness_determined = true;
3310 
3311     return ret;
3312 }
3313 
3314 static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, bool* done, bool* done_deletions, PIRP Irp, LIST_ENTRY* rollback) {
3315     LIST_ENTRY* le;
3316     tree_data* nextparitem = NULL;
3317     NTSTATUS Status;
3318     tree *next_tree, *par;
3319 
3320     *done = false;
3321 
3322     TRACE("trying to amalgamate tree in root %I64x, level %x (size %u)\n", t->root->id, t->header.level, t->size);
3323 
3324     // FIXME - doesn't capture everything, as it doesn't ascend
3325     le = t->paritem->list_entry.Flink;
3326     while (le != &t->parent->itemlist) {
3327         tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
3328 
3329         if (!td->ignore) {
3330             nextparitem = td;
3331             break;
3332         }
3333 
3334         le = le->Flink;
3335     }
3336 
3337     if (!nextparitem)
3338         return STATUS_SUCCESS;
3339 
3340     TRACE("nextparitem: key = %I64x,%x,%I64x\n", nextparitem->key.obj_id, nextparitem->key.obj_type, nextparitem->key.offset);
3341 
3342     if (!nextparitem->treeholder.tree) {
3343         Status = do_load_tree(Vcb, &nextparitem->treeholder, t->root, t->parent, nextparitem, NULL);
3344         if (!NT_SUCCESS(Status)) {
3345             ERR("do_load_tree returned %08lx\n", Status);
3346             return Status;
3347         }
3348     }
3349 
3350     if (!is_tree_unique(Vcb, nextparitem->treeholder.tree, Irp))
3351         return STATUS_SUCCESS;
3352 
3353     next_tree = nextparitem->treeholder.tree;
3354 
3355     if (!next_tree->updated_extents && next_tree->has_address) {
3356         Status = update_tree_extents(Vcb, next_tree, Irp, rollback);
3357         if (!NT_SUCCESS(Status)) {
3358             ERR("update_tree_extents returned %08lx\n", Status);
3359             return Status;
3360         }
3361     }
3362 
3363     if (t->size + next_tree->size <= Vcb->superblock.node_size - sizeof(tree_header)) {
3364         // merge two trees into one
3365 
3366         t->header.num_items += next_tree->header.num_items;
3367         t->size += next_tree->size;
3368 
3369         if (next_tree->header.level > 0) {
3370             le = next_tree->itemlist.Flink;
3371 
3372             while (le != &next_tree->itemlist) {
3373                 tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
3374 
3375                 if (td2->treeholder.tree) {
3376                     td2->treeholder.tree->parent = t;
3377 #ifdef DEBUG_PARANOID
3378                     if (td2->treeholder.tree->parent && td2->treeholder.tree->parent->header.level <= td2->treeholder.tree->header.level) int3;
3379 #endif
3380                 }
3381 
3382                 td2->inserted = true;
3383                 le = le->Flink;
3384             }
3385         } else {
3386             le = next_tree->itemlist.Flink;
3387 
3388             while (le != &next_tree->itemlist) {
3389                 tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
3390 
3391                 if (!td2->inserted && td2->data) {
3392                     uint8_t* data = ExAllocatePoolWithTag(PagedPool, td2->size, ALLOC_TAG);
3393 
3394                     if (!data) {
3395                         ERR("out of memory\n");
3396                         return STATUS_INSUFFICIENT_RESOURCES;
3397                     }
3398 
3399                     RtlCopyMemory(data, td2->data, td2->size);
3400                     td2->data = data;
3401                     td2->inserted = true;
3402                 }
3403 
3404                 le = le->Flink;
3405             }
3406         }
3407 
3408         t->itemlist.Blink->Flink = next_tree->itemlist.Flink;
3409         t->itemlist.Blink->Flink->Blink = t->itemlist.Blink;
3410         t->itemlist.Blink = next_tree->itemlist.Blink;
3411         t->itemlist.Blink->Flink = &t->itemlist;
3412 
3413         next_tree->itemlist.Flink = next_tree->itemlist.Blink = &next_tree->itemlist;
3414 
3415         next_tree->header.num_items = 0;
3416         next_tree->size = 0;
3417 
3418         if (next_tree->has_new_address) { // delete associated EXTENT_ITEM
3419             Status = reduce_tree_extent(Vcb, next_tree->new_address, next_tree, next_tree->parent->header.tree_id, next_tree->header.level, Irp, rollback);
3420 
3421             if (!NT_SUCCESS(Status)) {
3422                 ERR("reduce_tree_extent returned %08lx\n", Status);
3423                 return Status;
3424             }
3425         } else if (next_tree->has_address) {
3426             Status = reduce_tree_extent(Vcb, next_tree->header.address, next_tree, next_tree->parent->header.tree_id, next_tree->header.level, Irp, rollback);
3427 
3428             if (!NT_SUCCESS(Status)) {
3429                 ERR("reduce_tree_extent returned %08lx\n", Status);
3430                 return Status;
3431             }
3432         }
3433 
3434         if (!nextparitem->ignore) {
3435             nextparitem->ignore = true;
3436             next_tree->parent->header.num_items--;
3437             next_tree->parent->size -= sizeof(internal_node);
3438 
3439             *done_deletions = true;
3440         }
3441 
3442         par = next_tree->parent;
3443         while (par) {
3444             par->write = true;
3445             par = par->parent;
3446         }
3447 
3448         RemoveEntryList(&nextparitem->list_entry);
3449         ExFreePool(next_tree->paritem);
3450         next_tree->paritem = NULL;
3451 
3452         next_tree->root->root_item.bytes_used -= Vcb->superblock.node_size;
3453 
3454         free_tree(next_tree);
3455 
3456         *done = true;
3457     } else {
3458         // rebalance by moving items from second tree into first
3459         ULONG avg_size = (t->size + next_tree->size) / 2;
3460         KEY firstitem = {0, 0, 0};
3461         bool changed = false;
3462 
3463         TRACE("attempting rebalance\n");
3464 
3465         le = next_tree->itemlist.Flink;
3466         while (le != &next_tree->itemlist && t->size < avg_size && next_tree->header.num_items > 1) {
3467             tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
3468             ULONG size;
3469 
3470             if (!td->ignore) {
3471                 if (next_tree->header.level == 0)
3472                     size = sizeof(leaf_node) + td->size;
3473                 else
3474                     size = sizeof(internal_node);
3475             } else
3476                 size = 0;
3477 
3478             if (t->size + size < Vcb->superblock.node_size - sizeof(tree_header)) {
3479                 RemoveEntryList(&td->list_entry);
3480                 InsertTailList(&t->itemlist, &td->list_entry);
3481 
3482                 if (next_tree->header.level > 0 && td->treeholder.tree) {
3483                     td->treeholder.tree->parent = t;
3484 #ifdef DEBUG_PARANOID
3485                     if (td->treeholder.tree->parent && td->treeholder.tree->parent->header.level <= td->treeholder.tree->header.level) int3;
3486 #endif
3487                 } else if (next_tree->header.level == 0 && !td->inserted && td->size > 0) {
3488                     uint8_t* data = ExAllocatePoolWithTag(PagedPool, td->size, ALLOC_TAG);
3489 
3490                     if (!data) {
3491                         ERR("out of memory\n");
3492                         return STATUS_INSUFFICIENT_RESOURCES;
3493                     }
3494 
3495                     RtlCopyMemory(data, td->data, td->size);
3496                     td->data = data;
3497                 }
3498 
3499                 td->inserted = true;
3500 
3501                 if (!td->ignore) {
3502                     next_tree->size -= size;
3503                     t->size += size;
3504                     next_tree->header.num_items--;
3505                     t->header.num_items++;
3506                 }
3507 
3508                 changed = true;
3509             } else
3510                 break;
3511 
3512             le = next_tree->itemlist.Flink;
3513         }
3514 
3515         le = next_tree->itemlist.Flink;
3516         while (le != &next_tree->itemlist) {
3517             tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
3518 
3519             if (!td->ignore) {
3520                 firstitem = td->key;
3521                 break;
3522             }
3523 
3524             le = le->Flink;
3525         }
3526 
3527         // FIXME - once ascension is working, make this work with parent's parent, etc.
3528         if (next_tree->paritem)
3529             next_tree->paritem->key = firstitem;
3530 
3531         par = next_tree;
3532         while (par) {
3533             par->write = true;
3534             par = par->parent;
3535         }
3536 
3537         if (changed)
3538             *done = true;
3539     }
3540 
3541     return STATUS_SUCCESS;
3542 }
3543 
3544 static NTSTATUS update_extent_level(device_extension* Vcb, uint64_t address, tree* t, uint8_t level, PIRP Irp) {
3545     KEY searchkey;
3546     traverse_ptr tp;
3547     NTSTATUS Status;
3548 
3549     if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {
3550         searchkey.obj_id = address;
3551         searchkey.obj_type = TYPE_METADATA_ITEM;
3552         searchkey.offset = t->header.level;
3553 
3554         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
3555         if (!NT_SUCCESS(Status)) {
3556             ERR("error - find_item returned %08lx\n", Status);
3557             return Status;
3558         }
3559 
3560         if (!keycmp(tp.item->key, searchkey)) {
3561             EXTENT_ITEM_SKINNY_METADATA* eism;
3562 
3563             if (tp.item->size > 0) {
3564                 eism = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
3565 
3566                 if (!eism) {
3567                     ERR("out of memory\n");
3568                     return STATUS_INSUFFICIENT_RESOURCES;
3569                 }
3570 
3571                 RtlCopyMemory(eism, tp.item->data, tp.item->size);
3572             } else
3573                 eism = NULL;
3574 
3575             Status = delete_tree_item(Vcb, &tp);
3576             if (!NT_SUCCESS(Status)) {
3577                 ERR("delete_tree_item returned %08lx\n", Status);
3578                 if (eism) ExFreePool(eism);
3579                 return Status;
3580             }
3581 
3582             Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, tp.item->size, NULL, Irp);
3583             if (!NT_SUCCESS(Status)) {
3584                 ERR("insert_tree_item returned %08lx\n", Status);
3585                 if (eism) ExFreePool(eism);
3586                 return Status;
3587             }
3588 
3589             return STATUS_SUCCESS;
3590         }
3591     }
3592 
3593     searchkey.obj_id = address;
3594     searchkey.obj_type = TYPE_EXTENT_ITEM;
3595     searchkey.offset = 0xffffffffffffffff;
3596 
3597     Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
3598     if (!NT_SUCCESS(Status)) {
3599         ERR("error - find_item returned %08lx\n", Status);
3600         return Status;
3601     }
3602 
3603     if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
3604         EXTENT_ITEM_TREE* eit;
3605 
3606         if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) {
3607             ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_TREE));
3608             return STATUS_INTERNAL_ERROR;
3609         }
3610 
3611         eit = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
3612 
3613         if (!eit) {
3614             ERR("out of memory\n");
3615             return STATUS_INSUFFICIENT_RESOURCES;
3616         }
3617 
3618         RtlCopyMemory(eit, tp.item->data, tp.item->size);
3619 
3620         Status = delete_tree_item(Vcb, &tp);
3621         if (!NT_SUCCESS(Status)) {
3622             ERR("delete_tree_item returned %08lx\n", Status);
3623             ExFreePool(eit);
3624             return Status;
3625         }
3626 
3627         eit->level = level;
3628 
3629         Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, eit, tp.item->size, NULL, Irp);
3630         if (!NT_SUCCESS(Status)) {
3631             ERR("insert_tree_item returned %08lx\n", Status);
3632             ExFreePool(eit);
3633             return Status;
3634         }
3635 
3636         return STATUS_SUCCESS;
3637     }
3638 
3639     ERR("could not find EXTENT_ITEM for address %I64x\n", address);
3640 
3641     return STATUS_INTERNAL_ERROR;
3642 }
3643 
3644 static NTSTATUS update_tree_extents_recursive(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {
3645     NTSTATUS Status;
3646 
3647     if (t->parent && !t->parent->updated_extents && t->parent->has_address) {
3648         Status = update_tree_extents_recursive(Vcb, t->parent, Irp, rollback);
3649         if (!NT_SUCCESS(Status))
3650             return Status;
3651     }
3652 
3653     Status = update_tree_extents(Vcb, t, Irp, rollback);
3654     if (!NT_SUCCESS(Status)) {
3655         ERR("update_tree_extents returned %08lx\n", Status);
3656         return Status;
3657     }
3658 
3659     return STATUS_SUCCESS;
3660 }
3661 
3662 static NTSTATUS do_splits(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {
3663     ULONG level, max_level;
3664     uint32_t min_size;
3665     bool empty, done_deletions = false;
3666     NTSTATUS Status;
3667     tree* t;
3668 
3669     TRACE("(%p)\n", Vcb);
3670 
3671     max_level = 0;
3672 
3673     for (level = 0; level <= 255; level++) {
3674         LIST_ENTRY *le, *nextle;
3675 
3676         empty = true;
3677 
3678         TRACE("doing level %lu\n", level);
3679 
3680         le = Vcb->trees.Flink;
3681 
3682         while (le != &Vcb->trees) {
3683             t = CONTAINING_RECORD(le, tree, list_entry);
3684 
3685             nextle = le->Flink;
3686 
3687             if (t->write && t->header.level == level) {
3688                 empty = false;
3689 
3690                 if (t->header.num_items == 0) {
3691                     if (t->parent) {
3692                         done_deletions = true;
3693 
3694                         TRACE("deleting tree in root %I64x\n", t->root->id);
3695 
3696                         t->root->root_item.bytes_used -= Vcb->superblock.node_size;
3697 
3698                         if (t->has_new_address) { // delete associated EXTENT_ITEM
3699                             Status = reduce_tree_extent(Vcb, t->new_address, t, t->parent->header.tree_id, t->header.level, Irp, rollback);
3700 
3701                             if (!NT_SUCCESS(Status)) {
3702                                 ERR("reduce_tree_extent returned %08lx\n", Status);
3703                                 return Status;
3704                             }
3705 
3706                             t->has_new_address = false;
3707                         } else if (t->has_address) {
3708                             Status = reduce_tree_extent(Vcb,t->header.address, t, t->parent->header.tree_id, t->header.level, Irp, rollback);
3709 
3710                             if (!NT_SUCCESS(Status)) {
3711                                 ERR("reduce_tree_extent returned %08lx\n", Status);
3712                                 return Status;
3713                             }
3714 
3715                             t->has_address = false;
3716                         }
3717 
3718                         if (!t->paritem->ignore) {
3719                             t->paritem->ignore = true;
3720                             t->parent->header.num_items--;
3721                             t->parent->size -= sizeof(internal_node);
3722                         }
3723 
3724                         RemoveEntryList(&t->paritem->list_entry);
3725                         ExFreePool(t->paritem);
3726                         t->paritem = NULL;
3727 
3728                         free_tree(t);
3729                     } else if (t->header.level != 0) {
3730                         if (t->has_new_address) {
3731                             Status = update_extent_level(Vcb, t->new_address, t, 0, Irp);
3732 
3733                             if (!NT_SUCCESS(Status)) {
3734                                 ERR("update_extent_level returned %08lx\n", Status);
3735                                 return Status;
3736                             }
3737                         }
3738 
3739                         t->header.level = 0;
3740                     }
3741                 } else if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) {
3742                     TRACE("splitting overlarge tree (%x > %Ix)\n", t->size, Vcb->superblock.node_size - sizeof(tree_header));
3743 
3744                     if (!t->updated_extents && t->has_address) {
3745                         Status = update_tree_extents_recursive(Vcb, t, Irp, rollback);
3746                         if (!NT_SUCCESS(Status)) {
3747                             ERR("update_tree_extents_recursive returned %08lx\n", Status);
3748                             return Status;
3749                         }
3750                     }
3751 
3752                     Status = split_tree(Vcb, t);
3753 
3754                     if (!NT_SUCCESS(Status)) {
3755                         ERR("split_tree returned %08lx\n", Status);
3756                         return Status;
3757                     }
3758                 }
3759             }
3760 
3761             le = nextle;
3762         }
3763 
3764         if (!empty) {
3765             max_level = level;
3766         } else {
3767             TRACE("nothing found for level %lu\n", level);
3768             break;
3769         }
3770     }
3771 
3772     min_size = (Vcb->superblock.node_size - sizeof(tree_header)) / 2;
3773 
3774     for (level = 0; level <= max_level; level++) {
3775         LIST_ENTRY* le;
3776 
3777         le = Vcb->trees.Flink;
3778 
3779         while (le != &Vcb->trees) {
3780             t = CONTAINING_RECORD(le, tree, list_entry);
3781 
3782             if (t->write && t->header.level == level && t->header.num_items > 0 && t->parent && t->size < min_size &&
3783                 t->root->id != BTRFS_ROOT_FREE_SPACE && is_tree_unique(Vcb, t, Irp)) {
3784                 bool done;
3785 
3786                 do {
3787                     Status = try_tree_amalgamate(Vcb, t, &done, &done_deletions, Irp, rollback);
3788                     if (!NT_SUCCESS(Status)) {
3789                         ERR("try_tree_amalgamate returned %08lx\n", Status);
3790                         return Status;
3791                     }
3792                 } while (done && t->size < min_size);
3793             }
3794 
3795             le = le->Flink;
3796         }
3797     }
3798 
3799     // simplify trees if top tree only has one entry
3800 
3801     if (done_deletions) {
3802         for (level = max_level; level > 0; level--) {
3803             LIST_ENTRY *le, *nextle;
3804 
3805             le = Vcb->trees.Flink;
3806             while (le != &Vcb->trees) {
3807                 nextle = le->Flink;
3808                 t = CONTAINING_RECORD(le, tree, list_entry);
3809 
3810                 if (t->write && t->header.level == level) {
3811                     if (!t->parent && t->header.num_items == 1) {
3812                         LIST_ENTRY* le2 = t->itemlist.Flink;
3813                         tree_data* td = NULL;
3814                         tree* child_tree = NULL;
3815 
3816                         while (le2 != &t->itemlist) {
3817                             td = CONTAINING_RECORD(le2, tree_data, list_entry);
3818                             if (!td->ignore)
3819                                 break;
3820                             le2 = le2->Flink;
3821                         }
3822 
3823                         TRACE("deleting top-level tree in root %I64x with one item\n", t->root->id);
3824 
3825                         if (t->has_new_address) { // delete associated EXTENT_ITEM
3826                             Status = reduce_tree_extent(Vcb, t->new_address, t, t->header.tree_id, t->header.level, Irp, rollback);
3827 
3828                             if (!NT_SUCCESS(Status)) {
3829                                 ERR("reduce_tree_extent returned %08lx\n", Status);
3830                                 return Status;
3831                             }
3832 
3833                             t->has_new_address = false;
3834                         } else if (t->has_address) {
3835                             Status = reduce_tree_extent(Vcb,t->header.address, t, t->header.tree_id, t->header.level, Irp, rollback);
3836 
3837                             if (!NT_SUCCESS(Status)) {
3838                                 ERR("reduce_tree_extent returned %08lx\n", Status);
3839                                 return Status;
3840                             }
3841 
3842                             t->has_address = false;
3843                         }
3844 
3845                         if (!td->treeholder.tree) { // load first item if not already loaded
3846                             KEY searchkey = {0,0,0};
3847                             traverse_ptr tp;
3848 
3849                             Status = find_item(Vcb, t->root, &tp, &searchkey, false, Irp);
3850                             if (!NT_SUCCESS(Status)) {
3851                                 ERR("error - find_item returned %08lx\n", Status);
3852                                 return Status;
3853                             }
3854                         }
3855 
3856                         child_tree = td->treeholder.tree;
3857 
3858                         if (child_tree) {
3859                             child_tree->parent = NULL;
3860                             child_tree->paritem = NULL;
3861                         }
3862 
3863                         t->root->root_item.bytes_used -= Vcb->superblock.node_size;
3864 
3865                         free_tree(t);
3866 
3867                         if (child_tree)
3868                             child_tree->root->treeholder.tree = child_tree;
3869                     }
3870                 }
3871 
3872                 le = nextle;
3873             }
3874         }
3875     }
3876 
3877     return STATUS_SUCCESS;
3878 }
3879 
3880 static NTSTATUS remove_root_extents(device_extension* Vcb, root* r, tree_holder* th, uint8_t level, tree* parent, PIRP Irp, LIST_ENTRY* rollback) {
3881     NTSTATUS Status;
3882 
3883     if (!th->tree) {
3884         uint8_t* buf;
3885         chunk* c;
3886 
3887         buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);
3888         if (!buf) {
3889             ERR("out of memory\n");
3890             return STATUS_INSUFFICIENT_RESOURCES;
3891         }
3892 
3893         Status = read_data(Vcb, th->address, Vcb->superblock.node_size, NULL, true, buf, NULL,
3894                            &c, Irp, th->generation, false, NormalPagePriority);
3895         if (!NT_SUCCESS(Status)) {
3896             ERR("read_data returned 0x%08lx\n", Status);
3897             ExFreePool(buf);
3898             return Status;
3899         }
3900 
3901         Status = load_tree(Vcb, th->address, buf, r, &th->tree);
3902 
3903         if (!th->tree || th->tree->buf != buf)
3904             ExFreePool(buf);
3905 
3906         if (!NT_SUCCESS(Status)) {
3907             ERR("load_tree(%I64x) returned %08lx\n", th->address, Status);
3908             return Status;
3909         }
3910     }
3911 
3912     if (level > 0) {
3913         LIST_ENTRY* le = th->tree->itemlist.Flink;
3914 
3915         while (le != &th->tree->itemlist) {
3916             tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
3917 
3918             if (!td->ignore) {
3919                 Status = remove_root_extents(Vcb, r, &td->treeholder, th->tree->header.level - 1, th->tree, Irp, rollback);
3920 
3921                 if (!NT_SUCCESS(Status)) {
3922                     ERR("remove_root_extents returned %08lx\n", Status);
3923                     return Status;
3924                 }
3925             }
3926 
3927             le = le->Flink;
3928         }
3929     }
3930 
3931     if (th->tree && !th->tree->updated_extents && th->tree->has_address) {
3932         Status = update_tree_extents(Vcb, th->tree, Irp, rollback);
3933         if (!NT_SUCCESS(Status)) {
3934             ERR("update_tree_extents returned %08lx\n", Status);
3935             return Status;
3936         }
3937     }
3938 
3939     if (!th->tree || th->tree->has_address) {
3940         Status = reduce_tree_extent(Vcb, th->address, NULL, parent ? parent->header.tree_id : r->id, level, Irp, rollback);
3941 
3942         if (!NT_SUCCESS(Status)) {
3943             ERR("reduce_tree_extent(%I64x) returned %08lx\n", th->address, Status);
3944             return Status;
3945         }
3946     }
3947 
3948     return STATUS_SUCCESS;
3949 }
3950 
3951 static NTSTATUS drop_root(device_extension* Vcb, root* r, PIRP Irp, LIST_ENTRY* rollback) {
3952     NTSTATUS Status;
3953     KEY searchkey;
3954     traverse_ptr tp;
3955 
3956     Status = remove_root_extents(Vcb, r, &r->treeholder, r->root_item.root_level, NULL, Irp, rollback);
3957     if (!NT_SUCCESS(Status)) {
3958         ERR("remove_root_extents returned %08lx\n", Status);
3959         return Status;
3960     }
3961 
3962     // remove entries in uuid root (tree 9)
3963     if (Vcb->uuid_root) {
3964         RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid.uuid[0], sizeof(uint64_t));
3965         searchkey.obj_type = TYPE_SUBVOL_UUID;
3966         RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));
3967 
3968         if (searchkey.obj_id != 0 || searchkey.offset != 0) {
3969             Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);
3970             if (!NT_SUCCESS(Status)) {
3971                 WARN("find_item returned %08lx\n", Status);
3972             } else {
3973                 if (!keycmp(tp.item->key, searchkey)) {
3974                     Status = delete_tree_item(Vcb, &tp);
3975                     if (!NT_SUCCESS(Status)) {
3976                         ERR("delete_tree_item returned %08lx\n", Status);
3977                         return Status;
3978                     }
3979                 } else
3980                     WARN("could not find (%I64x,%x,%I64x) in uuid tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
3981             }
3982         }
3983 
3984         if (r->root_item.rtransid > 0) {
3985             RtlCopyMemory(&searchkey.obj_id, &r->root_item.received_uuid.uuid[0], sizeof(uint64_t));
3986             searchkey.obj_type = TYPE_SUBVOL_REC_UUID;
3987             RtlCopyMemory(&searchkey.offset, &r->root_item.received_uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));
3988 
3989             Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);
3990             if (!NT_SUCCESS(Status))
3991                 WARN("find_item returned %08lx\n", Status);
3992             else {
3993                 if (!keycmp(tp.item->key, searchkey)) {
3994                     if (tp.item->size == sizeof(uint64_t)) {
3995                         uint64_t* id = (uint64_t*)tp.item->data;
3996 
3997                         if (*id == r->id) {
3998                             Status = delete_tree_item(Vcb, &tp);
3999                             if (!NT_SUCCESS(Status)) {
4000                                 ERR("delete_tree_item returned %08lx\n", Status);
4001                                 return Status;
4002                             }
4003                         }
4004                     } else if (tp.item->size > sizeof(uint64_t)) {
4005                         ULONG i;
4006                         uint64_t* ids = (uint64_t*)tp.item->data;
4007 
4008                         for (i = 0; i < tp.item->size / sizeof(uint64_t); i++) {
4009                             if (ids[i] == r->id) {
4010                                 uint64_t* ne;
4011 
4012                                 ne = ExAllocatePoolWithTag(PagedPool, tp.item->size - sizeof(uint64_t), ALLOC_TAG);
4013                                 if (!ne) {
4014                                     ERR("out of memory\n");
4015                                     return STATUS_INSUFFICIENT_RESOURCES;
4016                                 }
4017 
4018                                 if (i > 0)
4019                                     RtlCopyMemory(ne, ids, sizeof(uint64_t) * i);
4020 
4021                                 if ((i + 1) * sizeof(uint64_t) < tp.item->size)
4022                                     RtlCopyMemory(&ne[i], &ids[i + 1], tp.item->size - ((i + 1) * sizeof(uint64_t)));
4023 
4024                                 Status = delete_tree_item(Vcb, &tp);
4025                                 if (!NT_SUCCESS(Status)) {
4026                                     ERR("delete_tree_item returned %08lx\n", Status);
4027                                     ExFreePool(ne);
4028                                     return Status;
4029                                 }
4030 
4031                                 Status = insert_tree_item(Vcb, Vcb->uuid_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
4032                                                           ne, tp.item->size - sizeof(uint64_t), NULL, Irp);
4033                                 if (!NT_SUCCESS(Status)) {
4034                                     ERR("insert_tree_item returned %08lx\n", Status);
4035                                     ExFreePool(ne);
4036                                     return Status;
4037                                 }
4038 
4039                                 break;
4040                             }
4041                         }
4042                     }
4043                 } else
4044                     WARN("could not find (%I64x,%x,%I64x) in uuid tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
4045             }
4046         }
4047     }
4048 
4049     // delete ROOT_ITEM
4050 
4051     searchkey.obj_id = r->id;
4052     searchkey.obj_type = TYPE_ROOT_ITEM;
4053     searchkey.offset = 0xffffffffffffffff;
4054 
4055     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
4056     if (!NT_SUCCESS(Status)) {
4057         ERR("find_item returned %08lx\n", Status);
4058         return Status;
4059     }
4060 
4061     if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
4062         Status = delete_tree_item(Vcb, &tp);
4063 
4064         if (!NT_SUCCESS(Status)) {
4065             ERR("delete_tree_item returned %08lx\n", Status);
4066             return Status;
4067         }
4068     } else
4069         WARN("could not find (%I64x,%x,%I64x) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
4070 
4071     // delete items in tree cache
4072 
4073     free_trees_root(Vcb, r);
4074 
4075     return STATUS_SUCCESS;
4076 }
4077 
4078 static NTSTATUS drop_roots(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {
4079     LIST_ENTRY *le = Vcb->drop_roots.Flink, *le2;
4080     NTSTATUS Status;
4081 
4082     while (le != &Vcb->drop_roots) {
4083         root* r = CONTAINING_RECORD(le, root, list_entry);
4084 
4085         le2 = le->Flink;
4086 
4087         Status = drop_root(Vcb, r, Irp, rollback);
4088         if (!NT_SUCCESS(Status)) {
4089             ERR("drop_root(%I64x) returned %08lx\n", r->id, Status);
4090             return Status;
4091         }
4092 
4093         le = le2;
4094     }
4095 
4096     return STATUS_SUCCESS;
4097 }
4098 
4099 NTSTATUS update_dev_item(device_extension* Vcb, device* device, PIRP Irp) {
4100     KEY searchkey;
4101     traverse_ptr tp;
4102     DEV_ITEM* di;
4103     NTSTATUS Status;
4104 
4105     searchkey.obj_id = 1;
4106     searchkey.obj_type = TYPE_DEV_ITEM;
4107     searchkey.offset = device->devitem.dev_id;
4108 
4109     Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);
4110     if (!NT_SUCCESS(Status)) {
4111         ERR("error - find_item returned %08lx\n", Status);
4112         return Status;
4113     }
4114 
4115     if (keycmp(tp.item->key, searchkey)) {
4116         ERR("error - could not find DEV_ITEM for device %I64x\n", device->devitem.dev_id);
4117         return STATUS_INTERNAL_ERROR;
4118     }
4119 
4120     Status = delete_tree_item(Vcb, &tp);
4121     if (!NT_SUCCESS(Status)) {
4122         ERR("delete_tree_item returned %08lx\n", Status);
4123         return Status;
4124     }
4125 
4126     di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG);
4127     if (!di) {
4128         ERR("out of memory\n");
4129         return STATUS_INSUFFICIENT_RESOURCES;
4130     }
4131 
4132     RtlCopyMemory(di, &device->devitem, sizeof(DEV_ITEM));
4133 
4134     Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, device->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, Irp);
4135     if (!NT_SUCCESS(Status)) {
4136         ERR("insert_tree_item returned %08lx\n", Status);
4137         ExFreePool(di);
4138         return Status;
4139     }
4140 
4141     return STATUS_SUCCESS;
4142 }
4143 
4144 static void regen_bootstrap(device_extension* Vcb) {
4145     sys_chunk* sc2;
4146     USHORT i = 0;
4147     LIST_ENTRY* le;
4148 
4149     i = 0;
4150     le = Vcb->sys_chunks.Flink;
4151     while (le != &Vcb->sys_chunks) {
4152         sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);
4153 
4154         TRACE("%I64x,%x,%I64x\n", sc2->key.obj_id, sc2->key.obj_type, sc2->key.offset);
4155 
4156         RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], &sc2->key, sizeof(KEY));
4157         i += sizeof(KEY);
4158 
4159         RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], sc2->data, sc2->size);
4160         i += sc2->size;
4161 
4162         le = le->Flink;
4163     }
4164 }
4165 
4166 static NTSTATUS add_to_bootstrap(device_extension* Vcb, uint64_t obj_id, uint8_t obj_type, uint64_t offset, void* data, uint16_t size) {
4167     sys_chunk* sc;
4168     LIST_ENTRY* le;
4169 
4170     if (Vcb->superblock.n + sizeof(KEY) + size > SYS_CHUNK_ARRAY_SIZE) {
4171         ERR("error - bootstrap is full\n");
4172         return STATUS_INTERNAL_ERROR;
4173     }
4174 
4175     sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG);
4176     if (!sc) {
4177         ERR("out of memory\n");
4178         return STATUS_INSUFFICIENT_RESOURCES;
4179     }
4180 
4181     sc->key.obj_id = obj_id;
4182     sc->key.obj_type = obj_type;
4183     sc->key.offset = offset;
4184     sc->size = size;
4185     sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG);
4186     if (!sc->data) {
4187         ERR("out of memory\n");
4188         ExFreePool(sc);
4189         return STATUS_INSUFFICIENT_RESOURCES;
4190     }
4191 
4192     RtlCopyMemory(sc->data, data, sc->size);
4193 
4194     le = Vcb->sys_chunks.Flink;
4195     while (le != &Vcb->sys_chunks) {
4196         sys_chunk* sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);
4197 
4198         if (keycmp(sc2->key, sc->key) == 1)
4199             break;
4200 
4201         le = le->Flink;
4202     }
4203     InsertTailList(le, &sc->list_entry);
4204 
4205     Vcb->superblock.n += sizeof(KEY) + size;
4206 
4207     regen_bootstrap(Vcb);
4208 
4209     return STATUS_SUCCESS;
4210 }
4211 
4212 static NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp) {
4213     CHUNK_ITEM* ci;
4214     CHUNK_ITEM_STRIPE* cis;
4215     BLOCK_GROUP_ITEM* bgi;
4216     uint16_t i, factor;
4217     NTSTATUS Status;
4218 
4219     ci = ExAllocatePoolWithTag(PagedPool, c->size, ALLOC_TAG);
4220     if (!ci) {
4221         ERR("out of memory\n");
4222         return STATUS_INSUFFICIENT_RESOURCES;
4223     }
4224 
4225     RtlCopyMemory(ci, c->chunk_item, c->size);
4226 
4227     Status = insert_tree_item(Vcb, Vcb->chunk_root, 0x100, TYPE_CHUNK_ITEM, c->offset, ci, c->size, NULL, Irp);
4228     if (!NT_SUCCESS(Status)) {
4229         ERR("insert_tree_item failed\n");
4230         ExFreePool(ci);
4231         return Status;
4232     }
4233 
4234     if (c->chunk_item->type & BLOCK_FLAG_SYSTEM) {
4235         Status = add_to_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, c->offset, ci, c->size);
4236         if (!NT_SUCCESS(Status)) {
4237             ERR("add_to_bootstrap returned %08lx\n", Status);
4238             return Status;
4239         }
4240     }
4241 
4242     // add BLOCK_GROUP_ITEM to tree 2
4243 
4244     bgi = ExAllocatePoolWithTag(PagedPool, sizeof(BLOCK_GROUP_ITEM), ALLOC_TAG);
4245     if (!bgi) {
4246         ERR("out of memory\n");
4247         return STATUS_INSUFFICIENT_RESOURCES;
4248     }
4249 
4250     bgi->used = c->used;
4251     bgi->chunk_tree = 0x100;
4252     bgi->flags = c->chunk_item->type;
4253 
4254     Status = insert_tree_item(Vcb, Vcb->extent_root, c->offset, TYPE_BLOCK_GROUP_ITEM, c->chunk_item->size, bgi, sizeof(BLOCK_GROUP_ITEM), NULL, Irp);
4255     if (!NT_SUCCESS(Status)) {
4256         ERR("insert_tree_item failed\n");
4257         ExFreePool(bgi);
4258         return Status;
4259     }
4260 
4261     if (c->chunk_item->type & BLOCK_FLAG_RAID0)
4262         factor = c->chunk_item->num_stripes;
4263     else if (c->chunk_item->type & BLOCK_FLAG_RAID10)
4264         factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;
4265     else if (c->chunk_item->type & BLOCK_FLAG_RAID5)
4266         factor = c->chunk_item->num_stripes - 1;
4267     else if (c->chunk_item->type & BLOCK_FLAG_RAID6)
4268         factor = c->chunk_item->num_stripes - 2;
4269     else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4
4270         factor = 1;
4271 
4272     // add DEV_EXTENTs to tree 4
4273 
4274     cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
4275 
4276     for (i = 0; i < c->chunk_item->num_stripes; i++) {
4277         DEV_EXTENT* de;
4278 
4279         de = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_EXTENT), ALLOC_TAG);
4280         if (!de) {
4281             ERR("out of memory\n");
4282             return STATUS_INSUFFICIENT_RESOURCES;
4283         }
4284 
4285         de->chunktree = Vcb->chunk_root->id;
4286         de->objid = 0x100;
4287         de->address = c->offset;
4288         de->length = c->chunk_item->size / factor;
4289         de->chunktree_uuid = Vcb->chunk_root->treeholder.tree->header.chunk_tree_uuid;
4290 
4291         Status = insert_tree_item(Vcb, Vcb->dev_root, c->devices[i]->devitem.dev_id, TYPE_DEV_EXTENT, cis[i].offset, de, sizeof(DEV_EXTENT), NULL, Irp);
4292         if (!NT_SUCCESS(Status)) {
4293             ERR("insert_tree_item returned %08lx\n", Status);
4294             ExFreePool(de);
4295             return Status;
4296         }
4297 
4298         // FIXME - no point in calling this twice for the same device
4299         Status = update_dev_item(Vcb, c->devices[i], Irp);
4300         if (!NT_SUCCESS(Status)) {
4301             ERR("update_dev_item returned %08lx\n", Status);
4302             return Status;
4303         }
4304     }
4305 
4306     c->created = false;
4307     c->oldused = c->used;
4308 
4309     Vcb->superblock.bytes_used += chunk_estimate_phys_size(Vcb, c, c->used);
4310 
4311     return STATUS_SUCCESS;
4312 }
4313 
4314 static void remove_from_bootstrap(device_extension* Vcb, uint64_t obj_id, uint8_t obj_type, uint64_t offset) {
4315     sys_chunk* sc2;
4316     LIST_ENTRY* le;
4317 
4318     le = Vcb->sys_chunks.Flink;
4319     while (le != &Vcb->sys_chunks) {
4320         sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);
4321 
4322         if (sc2->key.obj_id == obj_id && sc2->key.obj_type == obj_type && sc2->key.offset == offset) {
4323             RemoveEntryList(&sc2->list_entry);
4324 
4325             Vcb->superblock.n -= sizeof(KEY) + sc2->size;
4326 
4327             ExFreePool(sc2->data);
4328             ExFreePool(sc2);
4329             regen_bootstrap(Vcb);
4330             return;
4331         }
4332 
4333         le = le->Flink;
4334     }
4335 }
4336 
4337 static NTSTATUS set_xattr(device_extension* Vcb, LIST_ENTRY* batchlist, root* subvol, uint64_t inode, char* name, uint16_t namelen,
4338                           uint32_t crc32, uint8_t* data, uint16_t datalen) {
4339     NTSTATUS Status;
4340     uint16_t xasize;
4341     DIR_ITEM* xa;
4342 
4343     TRACE("(%p, %I64x, %I64x, %.*s, %08x, %p, %u)\n", Vcb, subvol->id, inode, namelen, name, crc32, data, datalen);
4344 
4345     xasize = (uint16_t)offsetof(DIR_ITEM, name[0]) + namelen + datalen;
4346 
4347     xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG);
4348     if (!xa) {
4349         ERR("out of memory\n");
4350         return STATUS_INSUFFICIENT_RESOURCES;
4351     }
4352 
4353     xa->key.obj_id = 0;
4354     xa->key.obj_type = 0;
4355     xa->key.offset = 0;
4356     xa->transid = Vcb->superblock.generation;
4357     xa->m = datalen;
4358     xa->n = namelen;
4359     xa->type = BTRFS_TYPE_EA;
4360     RtlCopyMemory(xa->name, name, namelen);
4361     RtlCopyMemory(xa->name + namelen, data, datalen);
4362 
4363     Status = insert_tree_item_batch(batchlist, Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, Batch_SetXattr);
4364     if (!NT_SUCCESS(Status)) {
4365         ERR("insert_tree_item_batch returned %08lx\n", Status);
4366         ExFreePool(xa);
4367         return Status;
4368     }
4369 
4370     return STATUS_SUCCESS;
4371 }
4372 
4373 static NTSTATUS delete_xattr(device_extension* Vcb, LIST_ENTRY* batchlist, root* subvol, uint64_t inode, char* name,
4374                              uint16_t namelen, uint32_t crc32) {
4375     NTSTATUS Status;
4376     uint16_t xasize;
4377     DIR_ITEM* xa;
4378 
4379     TRACE("(%p, %I64x, %I64x, %.*s, %08x)\n", Vcb, subvol->id, inode, namelen, name, crc32);
4380 
4381     xasize = (uint16_t)offsetof(DIR_ITEM, name[0]) + namelen;
4382 
4383     xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG);
4384     if (!xa) {
4385         ERR("out of memory\n");
4386         return STATUS_INSUFFICIENT_RESOURCES;
4387     }
4388 
4389     xa->key.obj_id = 0;
4390     xa->key.obj_type = 0;
4391     xa->key.offset = 0;
4392     xa->transid = Vcb->superblock.generation;
4393     xa->m = 0;
4394     xa->n = namelen;
4395     xa->type = BTRFS_TYPE_EA;
4396     RtlCopyMemory(xa->name, name, namelen);
4397 
4398     Status = insert_tree_item_batch(batchlist, Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, Batch_DeleteXattr);
4399     if (!NT_SUCCESS(Status)) {
4400         ERR("insert_tree_item_batch returned %08lx\n", Status);
4401         ExFreePool(xa);
4402         return Status;
4403     }
4404 
4405     return STATUS_SUCCESS;
4406 }
4407 
4408 static NTSTATUS insert_sparse_extent(fcb* fcb, LIST_ENTRY* batchlist, uint64_t start, uint64_t length) {
4409     NTSTATUS Status;
4410     EXTENT_DATA* ed;
4411     EXTENT_DATA2* ed2;
4412 
4413     TRACE("((%I64x, %I64x), %I64x, %I64x)\n", fcb->subvol->id, fcb->inode, start, length);
4414 
4415     ed = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
4416     if (!ed) {
4417         ERR("out of memory\n");
4418         return STATUS_INSUFFICIENT_RESOURCES;
4419     }
4420 
4421     ed->generation = fcb->Vcb->superblock.generation;
4422     ed->decoded_size = length;
4423     ed->compression = BTRFS_COMPRESSION_NONE;
4424     ed->encryption = BTRFS_ENCRYPTION_NONE;
4425     ed->encoding = BTRFS_ENCODING_NONE;
4426     ed->type = EXTENT_TYPE_REGULAR;
4427 
4428     ed2 = (EXTENT_DATA2*)ed->data;
4429     ed2->address = 0;
4430     ed2->size = 0;
4431     ed2->offset = 0;
4432     ed2->num_bytes = length;
4433 
4434     Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, start, ed, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), Batch_Insert);
4435     if (!NT_SUCCESS(Status)) {
4436         ERR("insert_tree_item_batch returned %08lx\n", Status);
4437         ExFreePool(ed);
4438         return Status;
4439     }
4440 
4441     return STATUS_SUCCESS;
4442 }
4443 
4444 #ifdef _MSC_VER
4445 #pragma warning(push)
4446 #pragma warning(suppress: 28194)
4447 #endif
4448 NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r, uint64_t objid, uint8_t objtype, uint64_t offset,
4449                                 _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* data, uint16_t datalen, enum batch_operation operation) {
4450     LIST_ENTRY* le;
4451     batch_root* br = NULL;
4452     batch_item* bi;
4453 
4454     le = batchlist->Flink;
4455     while (le != batchlist) {
4456         batch_root* br2 = CONTAINING_RECORD(le, batch_root, list_entry);
4457 
4458         if (br2->r == r) {
4459             br = br2;
4460             break;
4461         }
4462 
4463         le = le->Flink;
4464     }
4465 
4466     if (!br) {
4467         br = ExAllocatePoolWithTag(PagedPool, sizeof(batch_root), ALLOC_TAG);
4468         if (!br) {
4469             ERR("out of memory\n");
4470             return STATUS_INSUFFICIENT_RESOURCES;
4471         }
4472 
4473         br->r = r;
4474         InitializeListHead(&br->items);
4475         InsertTailList(batchlist, &br->list_entry);
4476     }
4477 
4478     bi = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside);
4479     if (!bi) {
4480         ERR("out of memory\n");
4481         return STATUS_INSUFFICIENT_RESOURCES;
4482     }
4483 
4484     bi->key.obj_id = objid;
4485     bi->key.obj_type = objtype;
4486     bi->key.offset = offset;
4487     bi->data = data;
4488     bi->datalen = datalen;
4489     bi->operation = operation;
4490 
4491     le = br->items.Blink;
4492     while (le != &br->items) {
4493         batch_item* bi2 = CONTAINING_RECORD(le, batch_item, list_entry);
4494         int cmp = keycmp(bi2->key, bi->key);
4495 
4496         if (cmp == -1 || (cmp == 0 && bi->operation >= bi2->operation)) {
4497             InsertHeadList(&bi2->list_entry, &bi->list_entry);
4498             return STATUS_SUCCESS;
4499         }
4500 
4501         le = le->Blink;
4502     }
4503 
4504     InsertHeadList(&br->items, &bi->list_entry);
4505 
4506     return STATUS_SUCCESS;
4507 }
4508 #ifdef _MSC_VER
4509 #pragma warning(pop)
4510 #endif
4511 
4512 typedef struct {
4513     uint64_t address;
4514     uint64_t length;
4515     uint64_t offset;
4516     bool changed;
4517     chunk* chunk;
4518     uint64_t skip_start;
4519     uint64_t skip_end;
4520     LIST_ENTRY list_entry;
4521 } extent_range;
4522 
4523 static void rationalize_extents(fcb* fcb, PIRP Irp) {
4524     LIST_ENTRY* le;
4525     LIST_ENTRY extent_ranges;
4526     extent_range* er;
4527     bool changed = false, truncating = false;
4528     uint32_t num_extents = 0;
4529 
4530     InitializeListHead(&extent_ranges);
4531 
4532     le = fcb->extents.Flink;
4533     while (le != &fcb->extents) {
4534         extent* ext = CONTAINING_RECORD(le, extent, list_entry);
4535 
4536         if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {
4537             EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
4538 
4539             if (ed2->size != 0) {
4540                 LIST_ENTRY* le2;
4541 
4542                 le2 = extent_ranges.Flink;
4543                 while (le2 != &extent_ranges) {
4544                     extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry);
4545 
4546                     if (er2->address == ed2->address) {
4547                         er2->skip_start = min(er2->skip_start, ed2->offset);
4548                         er2->skip_end = min(er2->skip_end, ed2->size - ed2->offset - ed2->num_bytes);
4549                         goto cont;
4550                     } else if (er2->address > ed2->address)
4551                         break;
4552 
4553                     le2 = le2->Flink;
4554                 }
4555 
4556                 er = ExAllocatePoolWithTag(PagedPool, sizeof(extent_range), ALLOC_TAG); // FIXME - should be from lookaside?
4557                 if (!er) {
4558                     ERR("out of memory\n");
4559                     goto end;
4560                 }
4561 
4562                 er->address = ed2->address;
4563                 er->length = ed2->size;
4564                 er->offset = ext->offset - ed2->offset;
4565                 er->changed = false;
4566                 er->chunk = NULL;
4567                 er->skip_start = ed2->offset;
4568                 er->skip_end = ed2->size - ed2->offset - ed2->num_bytes;
4569 
4570                 if (er->skip_start != 0 || er->skip_end != 0)
4571                     truncating = true;
4572 
4573                 InsertHeadList(le2->Blink, &er->list_entry);
4574                 num_extents++;
4575             }
4576         }
4577 
4578 cont:
4579         le = le->Flink;
4580     }
4581 
4582     if (num_extents == 0 || (num_extents == 1 && !truncating))
4583         goto end;
4584 
4585     le = extent_ranges.Flink;
4586     while (le != &extent_ranges) {
4587         er = CONTAINING_RECORD(le, extent_range, list_entry);
4588 
4589         if (!er->chunk) {
4590             LIST_ENTRY* le2;
4591 
4592             er->chunk = get_chunk_from_address(fcb->Vcb, er->address);
4593 
4594             if (!er->chunk) {
4595                 ERR("get_chunk_from_address(%I64x) failed\n", er->address);
4596                 goto end;
4597             }
4598 
4599             le2 = le->Flink;
4600             while (le2 != &extent_ranges) {
4601                 extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry);
4602 
4603                 if (!er2->chunk && er2->address >= er->chunk->offset && er2->address < er->chunk->offset + er->chunk->chunk_item->size)
4604                     er2->chunk = er->chunk;
4605 
4606                 le2 = le2->Flink;
4607             }
4608         }
4609 
4610         le = le->Flink;
4611     }
4612 
4613     if (truncating) {
4614         // truncate beginning or end of extent if unused
4615 
4616         le = extent_ranges.Flink;
4617         while (le != &extent_ranges) {
4618             er = CONTAINING_RECORD(le, extent_range, list_entry);
4619 
4620             if (er->skip_start > 0) {
4621                 LIST_ENTRY* le2 = fcb->extents.Flink;
4622                 while (le2 != &fcb->extents) {
4623                     extent* ext = CONTAINING_RECORD(le2, extent, list_entry);
4624 
4625                     if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {
4626                         EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
4627 
4628                         if (ed2->size != 0 && ed2->address == er->address) {
4629                             NTSTATUS Status;
4630 
4631                             Status = update_changed_extent_ref(fcb->Vcb, er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,
4632                                                                -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp);
4633                             if (!NT_SUCCESS(Status)) {
4634                                 ERR("update_changed_extent_ref returned %08lx\n", Status);
4635                                 goto end;
4636                             }
4637 
4638                             ext->extent_data.decoded_size -= er->skip_start;
4639                             ed2->size -= er->skip_start;
4640                             ed2->address += er->skip_start;
4641                             ed2->offset -= er->skip_start;
4642 
4643                             add_changed_extent_ref(er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,
4644                                                    1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);
4645                         }
4646                     }
4647 
4648                     le2 = le2->Flink;
4649                 }
4650 
4651                 if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM))
4652                     add_checksum_entry(fcb->Vcb, er->address, (ULONG)(er->skip_start / fcb->Vcb->superblock.sector_size), NULL, NULL);
4653 
4654                 acquire_chunk_lock(er->chunk, fcb->Vcb);
4655 
4656                 if (!er->chunk->cache_loaded) {
4657                     NTSTATUS Status = load_cache_chunk(fcb->Vcb, er->chunk, NULL);
4658 
4659                     if (!NT_SUCCESS(Status)) {
4660                         ERR("load_cache_chunk returned %08lx\n", Status);
4661                         release_chunk_lock(er->chunk, fcb->Vcb);
4662                         goto end;
4663                     }
4664                 }
4665 
4666                 er->chunk->used -= er->skip_start;
4667 
4668                 space_list_add(er->chunk, er->address, er->skip_start, NULL);
4669 
4670                 release_chunk_lock(er->chunk, fcb->Vcb);
4671 
4672                 er->address += er->skip_start;
4673                 er->length -= er->skip_start;
4674             }
4675 
4676             if (er->skip_end > 0) {
4677                 LIST_ENTRY* le2 = fcb->extents.Flink;
4678                 while (le2 != &fcb->extents) {
4679                     extent* ext = CONTAINING_RECORD(le2, extent, list_entry);
4680 
4681                     if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {
4682                         EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
4683 
4684                         if (ed2->size != 0 && ed2->address == er->address) {
4685                             NTSTATUS Status;
4686 
4687                             Status = update_changed_extent_ref(fcb->Vcb, er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,
4688                                                                -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp);
4689                             if (!NT_SUCCESS(Status)) {
4690                                 ERR("update_changed_extent_ref returned %08lx\n", Status);
4691                                 goto end;
4692                             }
4693 
4694                             ext->extent_data.decoded_size -= er->skip_end;
4695                             ed2->size -= er->skip_end;
4696 
4697                             add_changed_extent_ref(er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,
4698                                                    1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);
4699                         }
4700                     }
4701 
4702                     le2 = le2->Flink;
4703                 }
4704 
4705                 if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM))
4706                     add_checksum_entry(fcb->Vcb, er->address + er->length - er->skip_end, (ULONG)(er->skip_end / fcb->Vcb->superblock.sector_size), NULL, NULL);
4707 
4708                 acquire_chunk_lock(er->chunk, fcb->Vcb);
4709 
4710                 if (!er->chunk->cache_loaded) {
4711                     NTSTATUS Status = load_cache_chunk(fcb->Vcb, er->chunk, NULL);
4712 
4713                     if (!NT_SUCCESS(Status)) {
4714                         ERR("load_cache_chunk returned %08lx\n", Status);
4715                         release_chunk_lock(er->chunk, fcb->Vcb);
4716                         goto end;
4717                     }
4718                 }
4719 
4720                 er->chunk->used -= er->skip_end;
4721 
4722                 space_list_add(er->chunk, er->address + er->length - er->skip_end, er->skip_end, NULL);
4723 
4724                 release_chunk_lock(er->chunk, fcb->Vcb);
4725 
4726                 er->length -= er->skip_end;
4727             }
4728 
4729             le = le->Flink;
4730         }
4731     }
4732 
4733     if (num_extents < 2)
4734         goto end;
4735 
4736     // merge together adjacent extents
4737     le = extent_ranges.Flink;
4738     while (le != &extent_ranges) {
4739         er = CONTAINING_RECORD(le, extent_range, list_entry);
4740 
4741         if (le->Flink != &extent_ranges && er->length < MAX_EXTENT_SIZE) {
4742             extent_range* er2 = CONTAINING_RECORD(le->Flink, extent_range, list_entry);
4743 
4744             if (er->chunk == er2->chunk) {
4745                 if (er2->address == er->address + er->length && er2->offset >= er->offset + er->length) {
4746                     if (er->length + er2->length <= MAX_EXTENT_SIZE) {
4747                         er->length += er2->length;
4748                         er->changed = true;
4749 
4750                         RemoveEntryList(&er2->list_entry);
4751                         ExFreePool(er2);
4752 
4753                         changed = true;
4754                         continue;
4755                     }
4756                 }
4757             }
4758         }
4759 
4760         le = le->Flink;
4761     }
4762 
4763     if (!changed)
4764         goto end;
4765 
4766     le = fcb->extents.Flink;
4767     while (le != &fcb->extents) {
4768         extent* ext = CONTAINING_RECORD(le, extent, list_entry);
4769 
4770         if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {
4771             EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
4772 
4773             if (ed2->size != 0) {
4774                 LIST_ENTRY* le2;
4775 
4776                 le2 = extent_ranges.Flink;
4777                 while (le2 != &extent_ranges) {
4778                     extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry);
4779 
4780                     if (ed2->address >= er2->address && ed2->address + ed2->size <= er2->address + er2->length && er2->changed) {
4781                         NTSTATUS Status;
4782 
4783                         Status = update_changed_extent_ref(fcb->Vcb, er2->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,
4784                                                            -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp);
4785                         if (!NT_SUCCESS(Status)) {
4786                             ERR("update_changed_extent_ref returned %08lx\n", Status);
4787                             goto end;
4788                         }
4789 
4790                         ed2->offset += ed2->address - er2->address;
4791                         ed2->address = er2->address;
4792                         ed2->size = er2->length;
4793                         ext->extent_data.decoded_size = ed2->size;
4794 
4795                         add_changed_extent_ref(er2->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,
4796                                                1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);
4797 
4798                         break;
4799                     }
4800 
4801                     le2 = le2->Flink;
4802                 }
4803             }
4804         }
4805 
4806         le = le->Flink;
4807     }
4808 
4809 end:
4810     while (!IsListEmpty(&extent_ranges)) {
4811         le = RemoveHeadList(&extent_ranges);
4812         er = CONTAINING_RECORD(le, extent_range, list_entry);
4813 
4814         ExFreePool(er);
4815     }
4816 }
4817 
4818 NTSTATUS flush_fcb(fcb* fcb, bool cache, LIST_ENTRY* batchlist, PIRP Irp) {
4819     traverse_ptr tp;
4820     KEY searchkey;
4821     NTSTATUS Status;
4822     INODE_ITEM* ii;
4823     uint64_t ii_offset;
4824 #ifdef DEBUG_PARANOID
4825     uint64_t old_size = 0;
4826     bool extents_changed;
4827 #endif
4828 
4829     if (fcb->ads) {
4830         if (fcb->deleted) {
4831             Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adsxattr.Length, fcb->adshash);
4832             if (!NT_SUCCESS(Status)) {
4833                 ERR("delete_xattr returned %08lx\n", Status);
4834                 goto end;
4835             }
4836         } else {
4837             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adsxattr.Length,
4838                                fcb->adshash, (uint8_t*)fcb->adsdata.Buffer, fcb->adsdata.Length);
4839             if (!NT_SUCCESS(Status)) {
4840                 ERR("set_xattr returned %08lx\n", Status);
4841                 goto end;
4842             }
4843         }
4844 
4845         Status = STATUS_SUCCESS;
4846         goto end;
4847     }
4848 
4849     if (fcb->deleted) {
4850         Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0xffffffffffffffff, NULL, 0, Batch_DeleteInode);
4851         if (!NT_SUCCESS(Status)) {
4852             ERR("insert_tree_item_batch returned %08lx\n", Status);
4853             goto end;
4854         }
4855 
4856         if (fcb->marked_as_orphan) {
4857             Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, BTRFS_ORPHAN_INODE_OBJID, TYPE_ORPHAN_INODE,
4858                                             fcb->inode, NULL, 0, Batch_Delete);
4859             if (!NT_SUCCESS(Status)) {
4860                 ERR("insert_tree_item_batch returned %08lx\n", Status);
4861                 goto end;
4862             }
4863         }
4864 
4865         Status = STATUS_SUCCESS;
4866         goto end;
4867     }
4868 
4869 #ifdef DEBUG_PARANOID
4870     extents_changed = fcb->extents_changed;
4871 #endif
4872 
4873     if (fcb->extents_changed) {
4874         LIST_ENTRY* le;
4875         bool prealloc = false, extents_inline = false;
4876         uint64_t last_end;
4877 
4878         // delete ignored extent items
4879         le = fcb->extents.Flink;
4880         while (le != &fcb->extents) {
4881             LIST_ENTRY* le2 = le->Flink;
4882             extent* ext = CONTAINING_RECORD(le, extent, list_entry);
4883 
4884             if (ext->ignore) {
4885                 RemoveEntryList(&ext->list_entry);
4886 
4887                 if (ext->csum)
4888                     ExFreePool(ext->csum);
4889 
4890                 ExFreePool(ext);
4891             }
4892 
4893             le = le2;
4894         }
4895 
4896         le = fcb->extents.Flink;
4897         while (le != &fcb->extents) {
4898             extent* ext = CONTAINING_RECORD(le, extent, list_entry);
4899 
4900             if (ext->inserted && ext->csum && ext->extent_data.type == EXTENT_TYPE_REGULAR) {
4901                 EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
4902 
4903                 if (ed2->size > 0) { // not sparse
4904                     if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE)
4905                         add_checksum_entry(fcb->Vcb, ed2->address + ed2->offset, (ULONG)(ed2->num_bytes / fcb->Vcb->superblock.sector_size), ext->csum, Irp);
4906                     else
4907                         add_checksum_entry(fcb->Vcb, ed2->address, (ULONG)(ed2->size / fcb->Vcb->superblock.sector_size), ext->csum, Irp);
4908                 }
4909             }
4910 
4911             le = le->Flink;
4912         }
4913 
4914         if (!IsListEmpty(&fcb->extents)) {
4915             rationalize_extents(fcb, Irp);
4916 
4917             // merge together adjacent EXTENT_DATAs pointing to same extent
4918 
4919             le = fcb->extents.Flink;
4920             while (le != &fcb->extents) {
4921                 LIST_ENTRY* le2 = le->Flink;
4922                 extent* ext = CONTAINING_RECORD(le, extent, list_entry);
4923 
4924                 if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && le->Flink != &fcb->extents) {
4925                     extent* nextext = CONTAINING_RECORD(le->Flink, extent, list_entry);
4926 
4927                     if (ext->extent_data.type == nextext->extent_data.type) {
4928                         EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
4929                         EXTENT_DATA2* ned2 = (EXTENT_DATA2*)nextext->extent_data.data;
4930 
4931                         if (ed2->size != 0 && ed2->address == ned2->address && ed2->size == ned2->size &&
4932                             nextext->offset == ext->offset + ed2->num_bytes && ned2->offset == ed2->offset + ed2->num_bytes) {
4933                             chunk* c;
4934 
4935                             if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->csum) {
4936                                 ULONG len = (ULONG)((ed2->num_bytes + ned2->num_bytes) / fcb->Vcb->superblock.sector_size);
4937                                 void* csum;
4938 
4939                                 csum = ExAllocatePoolWithTag(NonPagedPool, len * fcb->Vcb->csum_size, ALLOC_TAG);
4940                                 if (!csum) {
4941                                     ERR("out of memory\n");
4942                                     Status = STATUS_INSUFFICIENT_RESOURCES;
4943                                     goto end;
4944                                 }
4945 
4946                                 RtlCopyMemory(csum, ext->csum, (ULONG)(ed2->num_bytes * fcb->Vcb->csum_size / fcb->Vcb->superblock.sector_size));
4947                                 RtlCopyMemory((uint8_t*)csum + (ed2->num_bytes * fcb->Vcb->csum_size / fcb->Vcb->superblock.sector_size), nextext->csum,
4948                                               (ULONG)(ned2->num_bytes * fcb->Vcb->csum_size / fcb->Vcb->superblock.sector_size));
4949 
4950                                 ExFreePool(ext->csum);
4951                                 ext->csum = csum;
4952                             }
4953 
4954                             ext->extent_data.generation = fcb->Vcb->superblock.generation;
4955                             ed2->num_bytes += ned2->num_bytes;
4956 
4957                             RemoveEntryList(&nextext->list_entry);
4958 
4959                             if (nextext->csum)
4960                                 ExFreePool(nextext->csum);
4961 
4962                             ExFreePool(nextext);
4963 
4964                             c = get_chunk_from_address(fcb->Vcb, ed2->address);
4965 
4966                             if (!c) {
4967                                 ERR("get_chunk_from_address(%I64x) failed\n", ed2->address);
4968                             } else {
4969                                 Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1,
4970                                                                 fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);
4971                                 if (!NT_SUCCESS(Status)) {
4972                                     ERR("update_changed_extent_ref returned %08lx\n", Status);
4973                                     goto end;
4974                                 }
4975                             }
4976 
4977                             le2 = le;
4978                         }
4979                     }
4980                 }
4981 
4982                 le = le2;
4983             }
4984         }
4985 
4986         if (!fcb->created) {
4987             // delete existing EXTENT_DATA items
4988 
4989             Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, NULL, 0, Batch_DeleteExtentData);
4990             if (!NT_SUCCESS(Status)) {
4991                 ERR("insert_tree_item_batch returned %08lx\n", Status);
4992                 goto end;
4993             }
4994         }
4995 
4996         // add new EXTENT_DATAs
4997 
4998         last_end = 0;
4999 
5000         le = fcb->extents.Flink;
5001         while (le != &fcb->extents) {
5002             extent* ext = CONTAINING_RECORD(le, extent, list_entry);
5003             EXTENT_DATA* ed;
5004 
5005             ext->inserted = false;
5006 
5007             if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES) && ext->offset > last_end) {
5008                 Status = insert_sparse_extent(fcb, batchlist, last_end, ext->offset - last_end);
5009                 if (!NT_SUCCESS(Status)) {
5010                     ERR("insert_sparse_extent returned %08lx\n", Status);
5011                     goto end;
5012                 }
5013             }
5014 
5015             ed = ExAllocatePoolWithTag(PagedPool, ext->datalen, ALLOC_TAG);
5016             if (!ed) {
5017                 ERR("out of memory\n");
5018                 Status = STATUS_INSUFFICIENT_RESOURCES;
5019                 goto end;
5020             }
5021 
5022             RtlCopyMemory(ed, &ext->extent_data, ext->datalen);
5023 
5024             Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, ext->offset,
5025                                             ed, ext->datalen, Batch_Insert);
5026             if (!NT_SUCCESS(Status)) {
5027                 ERR("insert_tree_item_batch returned %08lx\n", Status);
5028                 goto end;
5029             }
5030 
5031             if (ed->type == EXTENT_TYPE_PREALLOC)
5032                 prealloc = true;
5033 
5034             if (ed->type == EXTENT_TYPE_INLINE)
5035                 extents_inline = true;
5036 
5037             if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES)) {
5038                 if (ed->type == EXTENT_TYPE_INLINE)
5039                     last_end = ext->offset + ed->decoded_size;
5040                 else {
5041                     EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
5042 
5043                     last_end = ext->offset + ed2->num_bytes;
5044                 }
5045             }
5046 
5047             le = le->Flink;
5048         }
5049 
5050         if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES) && !extents_inline &&
5051             sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) > last_end) {
5052             Status = insert_sparse_extent(fcb, batchlist, last_end, sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) - last_end);
5053             if (!NT_SUCCESS(Status)) {
5054                 ERR("insert_sparse_extent returned %08lx\n", Status);
5055                 goto end;
5056             }
5057         }
5058 
5059         // update prealloc flag in INODE_ITEM
5060 
5061         if (!prealloc)
5062             fcb->inode_item.flags &= ~BTRFS_INODE_PREALLOC;
5063         else
5064             fcb->inode_item.flags |= BTRFS_INODE_PREALLOC;
5065 
5066         fcb->inode_item_changed = true;
5067 
5068         fcb->extents_changed = false;
5069     }
5070 
5071     if ((!fcb->created && fcb->inode_item_changed) || cache) {
5072         searchkey.obj_id = fcb->inode;
5073         searchkey.obj_type = TYPE_INODE_ITEM;
5074         searchkey.offset = 0xffffffffffffffff;
5075 
5076         Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, false, Irp);
5077         if (!NT_SUCCESS(Status)) {
5078             ERR("error - find_item returned %08lx\n", Status);
5079             goto end;
5080         }
5081 
5082         if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
5083             if (cache) {
5084                 ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
5085                 if (!ii) {
5086                     ERR("out of memory\n");
5087                     Status = STATUS_INSUFFICIENT_RESOURCES;
5088                     goto end;
5089                 }
5090 
5091                 RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
5092 
5093                 Status = insert_tree_item(fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp);
5094                 if (!NT_SUCCESS(Status)) {
5095                     ERR("insert_tree_item returned %08lx\n", Status);
5096                     goto end;
5097                 }
5098 
5099                 ii_offset = 0;
5100             } else {
5101                 ERR("could not find INODE_ITEM for inode %I64x in subvol %I64x\n", fcb->inode, fcb->subvol->id);
5102                 Status = STATUS_INTERNAL_ERROR;
5103                 goto end;
5104             }
5105         } else {
5106 #ifdef DEBUG_PARANOID
5107             INODE_ITEM* ii2 = (INODE_ITEM*)tp.item->data;
5108 
5109             old_size = ii2->st_size;
5110 #endif
5111 
5112             ii_offset = tp.item->key.offset;
5113         }
5114 
5115         if (!cache) {
5116             Status = delete_tree_item(fcb->Vcb, &tp);
5117             if (!NT_SUCCESS(Status)) {
5118                 ERR("delete_tree_item returned %08lx\n", Status);
5119                 goto end;
5120             }
5121         } else {
5122             searchkey.obj_id = fcb->inode;
5123             searchkey.obj_type = TYPE_INODE_ITEM;
5124             searchkey.offset = ii_offset;
5125 
5126             Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, false, Irp);
5127             if (!NT_SUCCESS(Status)) {
5128                 ERR("error - find_item returned %08lx\n", Status);
5129                 goto end;
5130             }
5131 
5132             if (keycmp(tp.item->key, searchkey)) {
5133                 ERR("could not find INODE_ITEM for inode %I64x in subvol %I64x\n", fcb->inode, fcb->subvol->id);
5134                 Status = STATUS_INTERNAL_ERROR;
5135                 goto end;
5136             } else
5137                 RtlCopyMemory(tp.item->data, &fcb->inode_item, min(tp.item->size, sizeof(INODE_ITEM)));
5138         }
5139 
5140 #ifdef DEBUG_PARANOID
5141         if (!extents_changed && fcb->type != BTRFS_TYPE_DIRECTORY && old_size != fcb->inode_item.st_size) {
5142             ERR("error - size has changed but extents not marked as changed\n");
5143             int3;
5144         }
5145 #endif
5146     } else
5147         ii_offset = 0;
5148 
5149     fcb->created = false;
5150 
5151     if (!cache && fcb->inode_item_changed) {
5152         ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
5153         if (!ii) {
5154             ERR("out of memory\n");
5155             Status = STATUS_INSUFFICIENT_RESOURCES;
5156             goto end;
5157         }
5158 
5159         RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
5160 
5161         Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, ii_offset, ii, sizeof(INODE_ITEM),
5162                                         Batch_Insert);
5163         if (!NT_SUCCESS(Status)) {
5164             ERR("insert_tree_item_batch returned %08lx\n", Status);
5165             goto end;
5166         }
5167 
5168         fcb->inode_item_changed = false;
5169     }
5170 
5171     if (fcb->sd_dirty) {
5172         if (!fcb->sd_deleted) {
5173             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_NTACL, sizeof(EA_NTACL) - 1,
5174                                EA_NTACL_HASH, (uint8_t*)fcb->sd, (uint16_t)RtlLengthSecurityDescriptor(fcb->sd));
5175             if (!NT_SUCCESS(Status)) {
5176                 ERR("set_xattr returned %08lx\n", Status);
5177                 goto end;
5178             }
5179         } else {
5180             Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_NTACL, sizeof(EA_NTACL) - 1, EA_NTACL_HASH);
5181             if (!NT_SUCCESS(Status)) {
5182                 ERR("delete_xattr returned %08lx\n", Status);
5183                 goto end;
5184             }
5185         }
5186 
5187         fcb->sd_deleted = false;
5188         fcb->sd_dirty = false;
5189     }
5190 
5191     if (fcb->atts_changed) {
5192         if (!fcb->atts_deleted) {
5193             uint8_t val[16], *val2;
5194             ULONG atts = fcb->atts;
5195 
5196             TRACE("inserting new DOSATTRIB xattr\n");
5197 
5198             if (fcb->inode == SUBVOL_ROOT_INODE)
5199                 atts &= ~FILE_ATTRIBUTE_READONLY;
5200 
5201             val2 = &val[sizeof(val) - 1];
5202 
5203             do {
5204                 uint8_t c = atts % 16;
5205                 *val2 = c <= 9 ? (c + '0') : (c - 0xa + 'a');
5206 
5207                 val2--;
5208                 atts >>= 4;
5209             } while (atts != 0);
5210 
5211             *val2 = 'x';
5212             val2--;
5213             *val2 = '0';
5214 
5215             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1,
5216                                EA_DOSATTRIB_HASH, val2, (uint16_t)(val + sizeof(val) - val2));
5217             if (!NT_SUCCESS(Status)) {
5218                 ERR("set_xattr returned %08lx\n", Status);
5219                 goto end;
5220             }
5221         } else {
5222             Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1, EA_DOSATTRIB_HASH);
5223             if (!NT_SUCCESS(Status)) {
5224                 ERR("delete_xattr returned %08lx\n", Status);
5225                 goto end;
5226             }
5227         }
5228 
5229         fcb->atts_changed = false;
5230         fcb->atts_deleted = false;
5231     }
5232 
5233     if (fcb->reparse_xattr_changed) {
5234         if (fcb->reparse_xattr.Buffer && fcb->reparse_xattr.Length > 0) {
5235             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_REPARSE, sizeof(EA_REPARSE) - 1,
5236                                EA_REPARSE_HASH, (uint8_t*)fcb->reparse_xattr.Buffer, (uint16_t)fcb->reparse_xattr.Length);
5237             if (!NT_SUCCESS(Status)) {
5238                 ERR("set_xattr returned %08lx\n", Status);
5239                 goto end;
5240             }
5241         } else {
5242             Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_REPARSE, sizeof(EA_REPARSE) - 1, EA_REPARSE_HASH);
5243             if (!NT_SUCCESS(Status)) {
5244                 ERR("delete_xattr returned %08lx\n", Status);
5245                 goto end;
5246             }
5247         }
5248 
5249         fcb->reparse_xattr_changed = false;
5250     }
5251 
5252     if (fcb->ea_changed) {
5253         if (fcb->ea_xattr.Buffer && fcb->ea_xattr.Length > 0) {
5254             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_EA, sizeof(EA_EA) - 1,
5255                                EA_EA_HASH, (uint8_t*)fcb->ea_xattr.Buffer, (uint16_t)fcb->ea_xattr.Length);
5256             if (!NT_SUCCESS(Status)) {
5257                 ERR("set_xattr returned %08lx\n", Status);
5258                 goto end;
5259             }
5260         } else {
5261             Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_EA, sizeof(EA_EA) - 1, EA_EA_HASH);
5262             if (!NT_SUCCESS(Status)) {
5263                 ERR("delete_xattr returned %08lx\n", Status);
5264                 goto end;
5265             }
5266         }
5267 
5268         fcb->ea_changed = false;
5269     }
5270 
5271     if (fcb->prop_compression_changed) {
5272         if (fcb->prop_compression == PropCompression_None) {
5273             Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1, EA_PROP_COMPRESSION_HASH);
5274             if (!NT_SUCCESS(Status)) {
5275                 ERR("delete_xattr returned %08lx\n", Status);
5276                 goto end;
5277             }
5278         } else if (fcb->prop_compression == PropCompression_Zlib) {
5279             static const char zlib[] = "zlib";
5280 
5281             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1,
5282                                EA_PROP_COMPRESSION_HASH, (uint8_t*)zlib, sizeof(zlib) - 1);
5283             if (!NT_SUCCESS(Status)) {
5284                 ERR("set_xattr returned %08lx\n", Status);
5285                 goto end;
5286             }
5287         } else if (fcb->prop_compression == PropCompression_LZO) {
5288             static const char lzo[] = "lzo";
5289 
5290             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1,
5291                                EA_PROP_COMPRESSION_HASH, (uint8_t*)lzo, sizeof(lzo) - 1);
5292             if (!NT_SUCCESS(Status)) {
5293                 ERR("set_xattr returned %08lx\n", Status);
5294                 goto end;
5295             }
5296         } else if (fcb->prop_compression == PropCompression_ZSTD) {
5297             static const char zstd[] = "zstd";
5298 
5299             Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1,
5300                                EA_PROP_COMPRESSION_HASH, (uint8_t*)zstd, sizeof(zstd) - 1);
5301             if (!NT_SUCCESS(Status)) {
5302                 ERR("set_xattr returned %08lx\n", Status);
5303                 goto end;
5304             }
5305         }
5306 
5307         fcb->prop_compression_changed = false;
5308     }
5309 
5310     if (fcb->xattrs_changed) {
5311         LIST_ENTRY* le;
5312 
5313         le = fcb->xattrs.Flink;
5314         while (le != &fcb->xattrs) {
5315             xattr* xa = CONTAINING_RECORD(le, xattr, list_entry);
5316             LIST_ENTRY* le2 = le->Flink;
5317 
5318             if (xa->dirty) {
5319                 uint32_t hash = calc_crc32c(0xfffffffe, (uint8_t*)xa->data, xa->namelen);
5320 
5321                 if (xa->valuelen == 0) {
5322                     Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, xa->data, xa->namelen, hash);
5323                     if (!NT_SUCCESS(Status)) {
5324                         ERR("delete_xattr returned %08lx\n", Status);
5325                         goto end;
5326                     }
5327 
5328                     RemoveEntryList(&xa->list_entry);
5329                     ExFreePool(xa);
5330                 } else {
5331                     Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, xa->data, xa->namelen,
5332                                        hash, (uint8_t*)&xa->data[xa->namelen], xa->valuelen);
5333                     if (!NT_SUCCESS(Status)) {
5334                         ERR("set_xattr returned %08lx\n", Status);
5335                         goto end;
5336                     }
5337 
5338                     xa->dirty = false;
5339                 }
5340             }
5341 
5342             le = le2;
5343         }
5344 
5345         fcb->xattrs_changed = false;
5346     }
5347 
5348     if ((fcb->case_sensitive_set && !fcb->case_sensitive)) {
5349         Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_CASE_SENSITIVE,
5350                               sizeof(EA_CASE_SENSITIVE) - 1, EA_CASE_SENSITIVE_HASH);
5351         if (!NT_SUCCESS(Status)) {
5352             ERR("delete_xattr returned %08lx\n", Status);
5353             goto end;
5354         }
5355 
5356         fcb->case_sensitive_set = false;
5357     } else if ((!fcb->case_sensitive_set && fcb->case_sensitive)) {
5358         Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_CASE_SENSITIVE,
5359                            sizeof(EA_CASE_SENSITIVE) - 1, EA_CASE_SENSITIVE_HASH, (uint8_t*)"1", 1);
5360         if (!NT_SUCCESS(Status)) {
5361             ERR("set_xattr returned %08lx\n", Status);
5362             goto end;
5363         }
5364 
5365         fcb->case_sensitive_set = true;
5366     }
5367 
5368     if (fcb->inode_item.st_nlink == 0 && !fcb->marked_as_orphan) { // mark as orphan
5369         Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, BTRFS_ORPHAN_INODE_OBJID, TYPE_ORPHAN_INODE,
5370                                         fcb->inode, NULL, 0, Batch_Insert);
5371         if (!NT_SUCCESS(Status)) {
5372             ERR("insert_tree_item_batch returned %08lx\n", Status);
5373             goto end;
5374         }
5375 
5376         fcb->marked_as_orphan = true;
5377     }
5378 
5379     Status = STATUS_SUCCESS;
5380 
5381 end:
5382     if (fcb->dirty) {
5383         bool lock = false;
5384 
5385         fcb->dirty = false;
5386 
5387         if (!ExIsResourceAcquiredExclusiveLite(&fcb->Vcb->dirty_fcbs_lock)) {
5388             ExAcquireResourceExclusiveLite(&fcb->Vcb->dirty_fcbs_lock, true);
5389             lock = true;
5390         }
5391 
5392         RemoveEntryList(&fcb->list_entry_dirty);
5393 
5394         if (lock)
5395             ExReleaseResourceLite(&fcb->Vcb->dirty_fcbs_lock);
5396     }
5397 
5398     return Status;
5399 }
5400 
5401 void add_trim_entry_avoid_sb(device_extension* Vcb, device* dev, uint64_t address, uint64_t size) {
5402     int i;
5403     ULONG sblen = (ULONG)sector_align(sizeof(superblock), Vcb->superblock.sector_size);
5404 
5405     i = 0;
5406     while (superblock_addrs[i] != 0) {
5407         if (superblock_addrs[i] + sblen >= address && superblock_addrs[i] < address + size) {
5408             if (superblock_addrs[i] > address)
5409                 add_trim_entry(dev, address, superblock_addrs[i] - address);
5410 
5411             if (size <= superblock_addrs[i] + sblen - address)
5412                 return;
5413 
5414             size -= superblock_addrs[i] + sblen - address;
5415             address = superblock_addrs[i] + sblen;
5416         } else if (superblock_addrs[i] > address + size)
5417             break;
5418 
5419         i++;
5420     }
5421 
5422     add_trim_entry(dev, address, size);
5423 }
5424 
5425 static NTSTATUS drop_chunk(device_extension* Vcb, chunk* c, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {
5426     NTSTATUS Status;
5427     KEY searchkey;
5428     traverse_ptr tp;
5429     uint64_t i, factor;
5430 #ifdef __REACTOS__
5431     uint64_t phys_used;
5432 #endif
5433     CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];;
5434 
5435     TRACE("dropping chunk %I64x\n", c->offset);
5436 
5437     if (c->chunk_item->type & BLOCK_FLAG_RAID0)
5438         factor = c->chunk_item->num_stripes;
5439     else if (c->chunk_item->type & BLOCK_FLAG_RAID10)
5440         factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;
5441     else if (c->chunk_item->type & BLOCK_FLAG_RAID5)
5442         factor = c->chunk_item->num_stripes - 1;
5443     else if (c->chunk_item->type & BLOCK_FLAG_RAID6)
5444         factor = c->chunk_item->num_stripes - 2;
5445     else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4
5446         factor = 1;
5447 
5448     // do TRIM
5449     if (Vcb->trim && !Vcb->options.no_trim) {
5450         uint64_t len = c->chunk_item->size / factor;
5451 
5452         for (i = 0; i < c->chunk_item->num_stripes; i++) {
5453             if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim)
5454                 add_trim_entry_avoid_sb(Vcb, c->devices[i], cis[i].offset, len);
5455         }
5456     }
5457 
5458     if (!c->cache) {
5459         Status = load_stored_free_space_cache(Vcb, c, true, Irp);
5460 
5461         if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND)
5462             WARN("load_stored_free_space_cache returned %08lx\n", Status);
5463     }
5464 
5465     // remove free space cache
5466     if (c->cache) {
5467         c->cache->deleted = true;
5468 
5469         Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, rollback);
5470         if (!NT_SUCCESS(Status)) {
5471             ERR("excise_extents returned %08lx\n", Status);
5472             return Status;
5473         }
5474 
5475         Status = flush_fcb(c->cache, true, batchlist, Irp);
5476 
5477         free_fcb(c->cache);
5478 
5479         if (c->cache->refcount == 0)
5480             reap_fcb(c->cache);
5481 
5482         if (!NT_SUCCESS(Status)) {
5483             ERR("flush_fcb returned %08lx\n", Status);
5484             return Status;
5485         }
5486 
5487         searchkey.obj_id = FREE_SPACE_CACHE_ID;
5488         searchkey.obj_type = 0;
5489         searchkey.offset = c->offset;
5490 
5491         Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
5492         if (!NT_SUCCESS(Status)) {
5493             ERR("error - find_item returned %08lx\n", Status);
5494             return Status;
5495         }
5496 
5497         if (!keycmp(tp.item->key, searchkey)) {
5498             Status = delete_tree_item(Vcb, &tp);
5499             if (!NT_SUCCESS(Status)) {
5500                 ERR("delete_tree_item returned %08lx\n", Status);
5501                 return Status;
5502             }
5503         }
5504     }
5505 
5506     if (Vcb->space_root) {
5507         Status = insert_tree_item_batch(batchlist, Vcb, Vcb->space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size,
5508                                         NULL, 0, Batch_DeleteFreeSpace);
5509         if (!NT_SUCCESS(Status)) {
5510             ERR("insert_tree_item_batch returned %08lx\n", Status);
5511             return Status;
5512         }
5513     }
5514 
5515     for (i = 0; i < c->chunk_item->num_stripes; i++) {
5516         if (!c->created) {
5517             // remove DEV_EXTENTs from tree 4
5518             searchkey.obj_id = cis[i].dev_id;
5519             searchkey.obj_type = TYPE_DEV_EXTENT;
5520             searchkey.offset = cis[i].offset;
5521 
5522             Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);
5523             if (!NT_SUCCESS(Status)) {
5524                 ERR("error - find_item returned %08lx\n", Status);
5525                 return Status;
5526             }
5527 
5528             if (!keycmp(tp.item->key, searchkey)) {
5529                 Status = delete_tree_item(Vcb, &tp);
5530                 if (!NT_SUCCESS(Status)) {
5531                     ERR("delete_tree_item returned %08lx\n", Status);
5532                     return Status;
5533                 }
5534 
5535                 if (tp.item->size >= sizeof(DEV_EXTENT)) {
5536                     DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data;
5537 
5538                     c->devices[i]->devitem.bytes_used -= de->length;
5539 
5540                     if (Vcb->balance.thread && Vcb->balance.shrinking && Vcb->balance.opts[0].devid == c->devices[i]->devitem.dev_id) {
5541                         if (cis[i].offset < Vcb->balance.opts[0].drange_start && cis[i].offset + de->length > Vcb->balance.opts[0].drange_start)
5542                             space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, Vcb->balance.opts[0].drange_start - cis[i].offset, NULL, rollback);
5543                     } else
5544                         space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, de->length, NULL, rollback);
5545                 }
5546             } else
5547                 WARN("could not find (%I64x,%x,%I64x) in dev tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
5548         } else {
5549             uint64_t len = c->chunk_item->size / factor;
5550 
5551             c->devices[i]->devitem.bytes_used -= len;
5552 
5553             if (Vcb->balance.thread && Vcb->balance.shrinking && Vcb->balance.opts[0].devid == c->devices[i]->devitem.dev_id) {
5554                 if (cis[i].offset < Vcb->balance.opts[0].drange_start && cis[i].offset + len > Vcb->balance.opts[0].drange_start)
5555                     space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, Vcb->balance.opts[0].drange_start - cis[i].offset, NULL, rollback);
5556             } else
5557                 space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, len, NULL, rollback);
5558         }
5559     }
5560 
5561     // modify DEV_ITEMs in chunk tree
5562     for (i = 0; i < c->chunk_item->num_stripes; i++) {
5563         if (c->devices[i]) {
5564             uint64_t j;
5565             DEV_ITEM* di;
5566 
5567             searchkey.obj_id = 1;
5568             searchkey.obj_type = TYPE_DEV_ITEM;
5569             searchkey.offset = c->devices[i]->devitem.dev_id;
5570 
5571             Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);
5572             if (!NT_SUCCESS(Status)) {
5573                 ERR("error - find_item returned %08lx\n", Status);
5574                 return Status;
5575             }
5576 
5577             if (!keycmp(tp.item->key, searchkey)) {
5578                 Status = delete_tree_item(Vcb, &tp);
5579                 if (!NT_SUCCESS(Status)) {
5580                     ERR("delete_tree_item returned %08lx\n", Status);
5581                     return Status;
5582                 }
5583 
5584                 di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG);
5585                 if (!di) {
5586                     ERR("out of memory\n");
5587                     return STATUS_INSUFFICIENT_RESOURCES;
5588                 }
5589 
5590                 RtlCopyMemory(di, &c->devices[i]->devitem, sizeof(DEV_ITEM));
5591 
5592                 Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, c->devices[i]->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, Irp);
5593                 if (!NT_SUCCESS(Status)) {
5594                     ERR("insert_tree_item returned %08lx\n", Status);
5595                     return Status;
5596                 }
5597             }
5598 
5599             for (j = i + 1; j < c->chunk_item->num_stripes; j++) {
5600                 if (c->devices[j] == c->devices[i])
5601                     c->devices[j] = NULL;
5602             }
5603         }
5604     }
5605 
5606     if (!c->created) {
5607         // remove CHUNK_ITEM from chunk tree
5608         searchkey.obj_id = 0x100;
5609         searchkey.obj_type = TYPE_CHUNK_ITEM;
5610         searchkey.offset = c->offset;
5611 
5612         Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);
5613         if (!NT_SUCCESS(Status)) {
5614             ERR("error - find_item returned %08lx\n", Status);
5615             return Status;
5616         }
5617 
5618         if (!keycmp(tp.item->key, searchkey)) {
5619             Status = delete_tree_item(Vcb, &tp);
5620 
5621             if (!NT_SUCCESS(Status)) {
5622                 ERR("delete_tree_item returned %08lx\n", Status);
5623                 return Status;
5624             }
5625         } else
5626             WARN("could not find CHUNK_ITEM for chunk %I64x\n", c->offset);
5627 
5628         // remove BLOCK_GROUP_ITEM from extent tree
5629         searchkey.obj_id = c->offset;
5630         searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;
5631         searchkey.offset = 0xffffffffffffffff;
5632 
5633         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
5634         if (!NT_SUCCESS(Status)) {
5635             ERR("error - find_item returned %08lx\n", Status);
5636             return Status;
5637         }
5638 
5639         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
5640             Status = delete_tree_item(Vcb, &tp);
5641 
5642             if (!NT_SUCCESS(Status)) {
5643                 ERR("delete_tree_item returned %08lx\n", Status);
5644                 return Status;
5645             }
5646         } else
5647             WARN("could not find BLOCK_GROUP_ITEM for chunk %I64x\n", c->offset);
5648     }
5649 
5650     if (c->chunk_item->type & BLOCK_FLAG_SYSTEM)
5651         remove_from_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, c->offset);
5652 
5653     RemoveEntryList(&c->list_entry);
5654 
5655     // clear raid56 incompat flag if dropping last RAID5/6 chunk
5656 
5657     if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) {
5658         LIST_ENTRY* le;
5659         bool clear_flag = true;
5660 
5661         le = Vcb->chunks.Flink;
5662         while (le != &Vcb->chunks) {
5663             chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);
5664 
5665             if (c2->chunk_item->type & BLOCK_FLAG_RAID5 || c2->chunk_item->type & BLOCK_FLAG_RAID6) {
5666                 clear_flag = false;
5667                 break;
5668             }
5669 
5670             le = le->Flink;
5671         }
5672 
5673         if (clear_flag)
5674             Vcb->superblock.incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_RAID56;
5675     }
5676 
5677     // clear raid1c34 incompat flag if dropping last RAID5/6 chunk
5678 
5679     if (c->chunk_item->type & BLOCK_FLAG_RAID1C3 || c->chunk_item->type & BLOCK_FLAG_RAID1C4) {
5680         LIST_ENTRY* le;
5681         bool clear_flag = true;
5682 
5683         le = Vcb->chunks.Flink;
5684         while (le != &Vcb->chunks) {
5685             chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);
5686 
5687             if (c2->chunk_item->type & BLOCK_FLAG_RAID1C3 || c2->chunk_item->type & BLOCK_FLAG_RAID1C4) {
5688                 clear_flag = false;
5689                 break;
5690             }
5691 
5692             le = le->Flink;
5693         }
5694 
5695         if (clear_flag)
5696             Vcb->superblock.incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_RAID1C34;
5697     }
5698 
5699 #ifndef __REACTOS__
5700     uint64_t phys_used = chunk_estimate_phys_size(Vcb, c, c->oldused);
5701 #else
5702     phys_used = chunk_estimate_phys_size(Vcb, c, c->oldused);
5703 #endif // __REACTOS__
5704 
5705     if (phys_used < Vcb->superblock.bytes_used)
5706         Vcb->superblock.bytes_used -= phys_used;
5707     else
5708         Vcb->superblock.bytes_used = 0;
5709 
5710     ExFreePool(c->chunk_item);
5711     ExFreePool(c->devices);
5712 
5713     while (!IsListEmpty(&c->space)) {
5714         space* s = CONTAINING_RECORD(c->space.Flink, space, list_entry);
5715 
5716         RemoveEntryList(&s->list_entry);
5717         ExFreePool(s);
5718     }
5719 
5720     while (!IsListEmpty(&c->deleting)) {
5721         space* s = CONTAINING_RECORD(c->deleting.Flink, space, list_entry);
5722 
5723         RemoveEntryList(&s->list_entry);
5724         ExFreePool(s);
5725     }
5726 
5727     release_chunk_lock(c, Vcb);
5728 
5729     ExDeleteResourceLite(&c->partial_stripes_lock);
5730     ExDeleteResourceLite(&c->range_locks_lock);
5731     ExDeleteResourceLite(&c->lock);
5732     ExDeleteResourceLite(&c->changed_extents_lock);
5733 
5734     ExFreePool(c);
5735 
5736     return STATUS_SUCCESS;
5737 }
5738 
5739 static NTSTATUS partial_stripe_read(device_extension* Vcb, chunk* c, partial_stripe* ps, uint64_t startoff, uint16_t parity, ULONG offset, ULONG len) {
5740     NTSTATUS Status;
5741     ULONG sl = (ULONG)(c->chunk_item->stripe_length / Vcb->superblock.sector_size);
5742     CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
5743 
5744     while (len > 0) {
5745         ULONG readlen = min(offset + len, offset + (sl - (offset % sl))) - offset;
5746         uint16_t stripe;
5747 
5748         stripe = (parity + (offset / sl) + 1) % c->chunk_item->num_stripes;
5749 
5750         if (c->devices[stripe]->devobj) {
5751             Status = sync_read_phys(c->devices[stripe]->devobj, c->devices[stripe]->fileobj, cis[stripe].offset + startoff + ((offset % sl) * Vcb->superblock.sector_size),
5752                                     readlen * Vcb->superblock.sector_size, ps->data + (offset * Vcb->superblock.sector_size), false);
5753             if (!NT_SUCCESS(Status)) {
5754                 ERR("sync_read_phys returned %08lx\n", Status);
5755                 return Status;
5756             }
5757         } else if (c->chunk_item->type & BLOCK_FLAG_RAID5) {
5758             uint16_t i;
5759             uint8_t* scratch;
5760 
5761             scratch = ExAllocatePoolWithTag(NonPagedPool, readlen * Vcb->superblock.sector_size, ALLOC_TAG);
5762             if (!scratch) {
5763                 ERR("out of memory\n");
5764                 return STATUS_INSUFFICIENT_RESOURCES;
5765             }
5766 
5767             for (i = 0; i < c->chunk_item->num_stripes; i++) {
5768                 if (i != stripe) {
5769                     if (!c->devices[i]->devobj) {
5770                         ExFreePool(scratch);
5771                         return STATUS_UNEXPECTED_IO_ERROR;
5772                     }
5773 
5774                     if (i == 0 || (stripe == 0 && i == 1)) {
5775                         Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) * Vcb->superblock.sector_size),
5776                                                 readlen * Vcb->superblock.sector_size, ps->data + (offset * Vcb->superblock.sector_size), false);
5777                         if (!NT_SUCCESS(Status)) {
5778                             ERR("sync_read_phys returned %08lx\n", Status);
5779                             ExFreePool(scratch);
5780                             return Status;
5781                         }
5782                     } else {
5783                         Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) * Vcb->superblock.sector_size),
5784                                                 readlen * Vcb->superblock.sector_size, scratch, false);
5785                         if (!NT_SUCCESS(Status)) {
5786                             ERR("sync_read_phys returned %08lx\n", Status);
5787                             ExFreePool(scratch);
5788                             return Status;
5789                         }
5790 
5791                         do_xor(ps->data + (offset * Vcb->superblock.sector_size), scratch, readlen * Vcb->superblock.sector_size);
5792                     }
5793                 }
5794             }
5795 
5796             ExFreePool(scratch);
5797         } else {
5798             uint8_t* scratch;
5799             uint16_t k, i, logstripe, error_stripe, num_errors = 0;
5800 
5801             scratch = ExAllocatePoolWithTag(NonPagedPool, (c->chunk_item->num_stripes + 2) * readlen * Vcb->superblock.sector_size, ALLOC_TAG);
5802             if (!scratch) {
5803                 ERR("out of memory\n");
5804                 return STATUS_INSUFFICIENT_RESOURCES;
5805             }
5806 
5807             i = (parity + 1) % c->chunk_item->num_stripes;
5808             for (k = 0; k < c->chunk_item->num_stripes; k++) {
5809                 if (i != stripe) {
5810                     if (c->devices[i]->devobj) {
5811                         Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) * Vcb->superblock.sector_size),
5812                                                 readlen * Vcb->superblock.sector_size, scratch + (k * readlen * Vcb->superblock.sector_size), false);
5813                         if (!NT_SUCCESS(Status)) {
5814                             ERR("sync_read_phys returned %08lx\n", Status);
5815                             num_errors++;
5816                             error_stripe = k;
5817                         }
5818                     } else {
5819                         num_errors++;
5820                         error_stripe = k;
5821                     }
5822 
5823                     if (num_errors > 1) {
5824                         ExFreePool(scratch);
5825                         return STATUS_UNEXPECTED_IO_ERROR;
5826                     }
5827                 } else
5828                     logstripe = k;
5829 
5830                 i = (i + 1) % c->chunk_item->num_stripes;
5831             }
5832 
5833             if (num_errors == 0 || error_stripe == c->chunk_item->num_stripes - 1) {
5834                 for (k = 0; k < c->chunk_item->num_stripes - 1; k++) {
5835                     if (k != logstripe) {
5836                         if (k == 0 || (k == 1 && logstripe == 0)) {
5837                             RtlCopyMemory(ps->data + (offset * Vcb->superblock.sector_size), scratch + (k * readlen * Vcb->superblock.sector_size),
5838                                           readlen * Vcb->superblock.sector_size);
5839                         } else {
5840                             do_xor(ps->data + (offset * Vcb->superblock.sector_size), scratch + (k * readlen * Vcb->superblock.sector_size),
5841                                    readlen * Vcb->superblock.sector_size);
5842                         }
5843                     }
5844                 }
5845             } else {
5846                 raid6_recover2(scratch, c->chunk_item->num_stripes, readlen * Vcb->superblock.sector_size, logstripe,
5847                                error_stripe, scratch + (c->chunk_item->num_stripes * readlen * Vcb->superblock.sector_size));
5848 
5849                 RtlCopyMemory(ps->data + (offset * Vcb->superblock.sector_size), scratch + (c->chunk_item->num_stripes * readlen * Vcb->superblock.sector_size),
5850                               readlen * Vcb->superblock.sector_size);
5851             }
5852 
5853             ExFreePool(scratch);
5854         }
5855 
5856         offset += readlen;
5857         len -= readlen;
5858     }
5859 
5860     return STATUS_SUCCESS;
5861 }
5862 
5863 NTSTATUS flush_partial_stripe(device_extension* Vcb, chunk* c, partial_stripe* ps) {
5864     NTSTATUS Status;
5865     uint16_t parity2, stripe, startoffstripe;
5866     uint8_t* data;
5867     uint64_t startoff;
5868     ULONG runlength, index, last1;
5869     CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
5870     LIST_ENTRY* le;
5871     uint16_t k, num_data_stripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2);
5872     uint64_t ps_length = num_data_stripes * c->chunk_item->stripe_length;
5873     ULONG stripe_length = (ULONG)c->chunk_item->stripe_length;
5874 
5875     // FIXME - do writes asynchronously?
5876 
5877     get_raid0_offset(ps->address - c->offset, stripe_length, num_data_stripes, &startoff, &startoffstripe);
5878 
5879     parity2 = (((ps->address - c->offset) / ps_length) + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes;
5880 
5881     // read data (or reconstruct if degraded)
5882 
5883     runlength = RtlFindFirstRunClear(&ps->bmp, &index);
5884     last1 = 0;
5885 
5886     while (runlength != 0) {
5887         if (index >= ps->bmplen)
5888             break;
5889 
5890         if (index + runlength >= ps->bmplen) {
5891             runlength = ps->bmplen - index;
5892 
5893             if (runlength == 0)
5894                 break;
5895         }
5896 
5897         if (index > last1) {
5898             Status = partial_stripe_read(Vcb, c, ps, startoff, parity2, last1, index - last1);
5899             if (!NT_SUCCESS(Status)) {
5900                 ERR("partial_stripe_read returned %08lx\n", Status);
5901                 return Status;
5902             }
5903         }
5904 
5905         last1 = index + runlength;
5906 
5907         runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index);
5908     }
5909 
5910     if (last1 < ps_length / Vcb->superblock.sector_size) {
5911         Status = partial_stripe_read(Vcb, c, ps, startoff, parity2, last1, (ULONG)((ps_length / Vcb->superblock.sector_size) - last1));
5912         if (!NT_SUCCESS(Status)) {
5913             ERR("partial_stripe_read returned %08lx\n", Status);
5914             return Status;
5915         }
5916     }
5917 
5918     // set unallocated data to 0
5919     le = c->space.Flink;
5920     while (le != &c->space) {
5921         space* s = CONTAINING_RECORD(le, space, list_entry);
5922 
5923         if (s->address + s->size > ps->address && s->address < ps->address + ps_length) {
5924             uint64_t start = max(ps->address, s->address);
5925             uint64_t end = min(ps->address + ps_length, s->address + s->size);
5926 
5927             RtlZeroMemory(ps->data + start - ps->address, (ULONG)(end - start));
5928         } else if (s->address >= ps->address + ps_length)
5929             break;
5930 
5931         le = le->Flink;
5932     }
5933 
5934     le = c->deleting.Flink;
5935     while (le != &c->deleting) {
5936         space* s = CONTAINING_RECORD(le, space, list_entry);
5937 
5938         if (s->address + s->size > ps->address && s->address < ps->address + ps_length) {
5939             uint64_t start = max(ps->address, s->address);
5940             uint64_t end = min(ps->address + ps_length, s->address + s->size);
5941 
5942             RtlZeroMemory(ps->data + start - ps->address, (ULONG)(end - start));
5943         } else if (s->address >= ps->address + ps_length)
5944             break;
5945 
5946         le = le->Flink;
5947     }
5948 
5949     stripe = (parity2 + 1) % c->chunk_item->num_stripes;
5950 
5951     data = ps->data;
5952     for (k = 0; k < num_data_stripes; k++) {
5953         if (c->devices[stripe]->devobj) {
5954             Status = write_data_phys(c->devices[stripe]->devobj, c->devices[stripe]->fileobj, cis[stripe].offset + startoff, data, stripe_length);
5955             if (!NT_SUCCESS(Status)) {
5956                 ERR("write_data_phys returned %08lx\n", Status);
5957                 return Status;
5958             }
5959         }
5960 
5961         data += stripe_length;
5962         stripe = (stripe + 1) % c->chunk_item->num_stripes;
5963     }
5964 
5965     // write parity
5966     if (c->chunk_item->type & BLOCK_FLAG_RAID5) {
5967         if (c->devices[parity2]->devobj) {
5968             uint16_t i;
5969 
5970             for (i = 1; i < c->chunk_item->num_stripes - 1; i++) {
5971                 do_xor(ps->data, ps->data + (i * stripe_length), stripe_length);
5972             }
5973 
5974             Status = write_data_phys(c->devices[parity2]->devobj, c->devices[parity2]->fileobj, cis[parity2].offset + startoff, ps->data, stripe_length);
5975             if (!NT_SUCCESS(Status)) {
5976                 ERR("write_data_phys returned %08lx\n", Status);
5977                 return Status;
5978             }
5979         }
5980     } else {
5981         uint16_t parity1 = (parity2 + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes;
5982 
5983         if (c->devices[parity1]->devobj || c->devices[parity2]->devobj) {
5984             uint8_t* scratch;
5985             uint16_t i;
5986 
5987             scratch = ExAllocatePoolWithTag(NonPagedPool, stripe_length * 2, ALLOC_TAG);
5988             if (!scratch) {
5989                 ERR("out of memory\n");
5990                 return STATUS_INSUFFICIENT_RESOURCES;
5991             }
5992 
5993             i = c->chunk_item->num_stripes - 3;
5994 
5995             while (true) {
5996                 if (i == c->chunk_item->num_stripes - 3) {
5997                     RtlCopyMemory(scratch, ps->data + (i * stripe_length), stripe_length);
5998                     RtlCopyMemory(scratch + stripe_length, ps->data + (i * stripe_length), stripe_length);
5999                 } else {
6000                     do_xor(scratch, ps->data + (i * stripe_length), stripe_length);
6001 
6002                     galois_double(scratch + stripe_length, stripe_length);
6003                     do_xor(scratch + stripe_length, ps->data + (i * stripe_length), stripe_length);
6004                 }
6005 
6006                 if (i == 0)
6007                     break;
6008 
6009                 i--;
6010             }
6011 
6012             if (c->devices[parity1]->devobj) {
6013                 Status = write_data_phys(c->devices[parity1]->devobj, c->devices[parity1]->fileobj, cis[parity1].offset + startoff, scratch, stripe_length);
6014                 if (!NT_SUCCESS(Status)) {
6015                     ERR("write_data_phys returned %08lx\n", Status);
6016                     ExFreePool(scratch);
6017                     return Status;
6018                 }
6019             }
6020 
6021             if (c->devices[parity2]->devobj) {
6022                 Status = write_data_phys(c->devices[parity2]->devobj, c->devices[parity2]->fileobj, cis[parity2].offset + startoff,
6023                                          scratch + stripe_length, stripe_length);
6024                 if (!NT_SUCCESS(Status)) {
6025                     ERR("write_data_phys returned %08lx\n", Status);
6026                     ExFreePool(scratch);
6027                     return Status;
6028                 }
6029             }
6030 
6031             ExFreePool(scratch);
6032         }
6033     }
6034 
6035     return STATUS_SUCCESS;
6036 }
6037 
6038 static NTSTATUS update_chunks(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {
6039     LIST_ENTRY *le, *le2;
6040     NTSTATUS Status;
6041     uint64_t used_minus_cache;
6042 
6043     ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);
6044 
6045     // FIXME - do tree chunks before data chunks
6046 
6047     le = Vcb->chunks.Flink;
6048     while (le != &Vcb->chunks) {
6049         chunk* c = CONTAINING_RECORD(le, chunk, list_entry);
6050 
6051         le2 = le->Flink;
6052 
6053         if (c->changed) {
6054             acquire_chunk_lock(c, Vcb);
6055 
6056             // flush partial stripes
6057             if (!Vcb->readonly && (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)) {
6058                 ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true);
6059 
6060                 while (!IsListEmpty(&c->partial_stripes)) {
6061                     partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry);
6062 
6063                     Status = flush_partial_stripe(Vcb, c, ps);
6064 
6065                     if (ps->bmparr)
6066                         ExFreePool(ps->bmparr);
6067 
6068                     ExFreePool(ps);
6069 
6070                     if (!NT_SUCCESS(Status)) {
6071                         ERR("flush_partial_stripe returned %08lx\n", Status);
6072                         ExReleaseResourceLite(&c->partial_stripes_lock);
6073                         release_chunk_lock(c, Vcb);
6074                         ExReleaseResourceLite(&Vcb->chunk_lock);
6075                         return Status;
6076                     }
6077                 }
6078 
6079                 ExReleaseResourceLite(&c->partial_stripes_lock);
6080             }
6081 
6082             if (c->list_entry_balance.Flink) {
6083                 release_chunk_lock(c, Vcb);
6084                 le = le2;
6085                 continue;
6086             }
6087 
6088             if (c->space_changed || c->created) {
6089                 bool created = c->created;
6090 
6091                 used_minus_cache = c->used;
6092 
6093                 // subtract self-hosted cache
6094                 if (used_minus_cache > 0 && c->chunk_item->type & BLOCK_FLAG_DATA && c->cache && c->cache->inode_item.st_size == c->used) {
6095                     LIST_ENTRY* le3;
6096 
6097                     le3 = c->cache->extents.Flink;
6098                     while (le3 != &c->cache->extents) {
6099                         extent* ext = CONTAINING_RECORD(le3, extent, list_entry);
6100                         EXTENT_DATA* ed = &ext->extent_data;
6101 
6102                         if (!ext->ignore) {
6103                             if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
6104                                 EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
6105 
6106                                 if (ed2->size != 0 && ed2->address >= c->offset && ed2->address + ed2->size <= c->offset + c->chunk_item->size)
6107                                     used_minus_cache -= ed2->size;
6108                             }
6109                         }
6110 
6111                         le3 = le3->Flink;
6112                     }
6113                 }
6114 
6115                 if (used_minus_cache == 0) {
6116                     Status = drop_chunk(Vcb, c, batchlist, Irp, rollback);
6117                     if (!NT_SUCCESS(Status)) {
6118                         ERR("drop_chunk returned %08lx\n", Status);
6119                         release_chunk_lock(c, Vcb);
6120                         ExReleaseResourceLite(&Vcb->chunk_lock);
6121                         return Status;
6122                     }
6123 
6124                     // c is now freed, so avoid releasing non-existent lock
6125                     le = le2;
6126                     continue;
6127                 } else if (c->created) {
6128                     Status = create_chunk(Vcb, c, Irp);
6129                     if (!NT_SUCCESS(Status)) {
6130                         ERR("create_chunk returned %08lx\n", Status);
6131                         release_chunk_lock(c, Vcb);
6132                         ExReleaseResourceLite(&Vcb->chunk_lock);
6133                         return Status;
6134                     }
6135                 }
6136 
6137                 if (used_minus_cache > 0 || created)
6138                     release_chunk_lock(c, Vcb);
6139             } else
6140                 release_chunk_lock(c, Vcb);
6141         }
6142 
6143         le = le2;
6144     }
6145 
6146     ExReleaseResourceLite(&Vcb->chunk_lock);
6147 
6148     return STATUS_SUCCESS;
6149 }
6150 
6151 static NTSTATUS delete_root_ref(device_extension* Vcb, uint64_t subvolid, uint64_t parsubvolid, uint64_t parinode, PANSI_STRING utf8, PIRP Irp) {
6152     KEY searchkey;
6153     traverse_ptr tp;
6154     NTSTATUS Status;
6155 
6156     searchkey.obj_id = parsubvolid;
6157     searchkey.obj_type = TYPE_ROOT_REF;
6158     searchkey.offset = subvolid;
6159 
6160     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
6161     if (!NT_SUCCESS(Status)) {
6162         ERR("error - find_item returned %08lx\n", Status);
6163         return Status;
6164     }
6165 
6166     if (!keycmp(searchkey, tp.item->key)) {
6167         if (tp.item->size < sizeof(ROOT_REF)) {
6168             ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF));
6169             return STATUS_INTERNAL_ERROR;
6170         } else {
6171             ROOT_REF* rr;
6172             ULONG len;
6173 
6174             rr = (ROOT_REF*)tp.item->data;
6175             len = tp.item->size;
6176 
6177             do {
6178                 uint16_t itemlen;
6179 
6180                 if (len < sizeof(ROOT_REF) || len < offsetof(ROOT_REF, name[0]) + rr->n) {
6181                     ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
6182                     break;
6183                 }
6184 
6185                 itemlen = (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n;
6186 
6187                 if (rr->dir == parinode && rr->n == utf8->Length && RtlCompareMemory(rr->name, utf8->Buffer, rr->n) == rr->n) {
6188                     uint16_t newlen = tp.item->size - itemlen;
6189 
6190                     Status = delete_tree_item(Vcb, &tp);
6191                     if (!NT_SUCCESS(Status)) {
6192                         ERR("delete_tree_item returned %08lx\n", Status);
6193                         return Status;
6194                     }
6195 
6196                     if (newlen == 0) {
6197                         TRACE("deleting (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
6198                     } else {
6199                         uint8_t *newrr = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *rroff;
6200 
6201                         if (!newrr) {
6202                             ERR("out of memory\n");
6203                             return STATUS_INSUFFICIENT_RESOURCES;
6204                         }
6205 
6206                         TRACE("modifying (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
6207 
6208                         if ((uint8_t*)rr > tp.item->data) {
6209                             RtlCopyMemory(newrr, tp.item->data, (uint8_t*)rr - tp.item->data);
6210                             rroff = newrr + ((uint8_t*)rr - tp.item->data);
6211                         } else {
6212                             rroff = newrr;
6213                         }
6214 
6215                         if ((uint8_t*)&rr->name[rr->n] < tp.item->data + tp.item->size)
6216                             RtlCopyMemory(rroff, &rr->name[rr->n], tp.item->size - ((uint8_t*)&rr->name[rr->n] - tp.item->data));
6217 
6218                         Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newrr, newlen, NULL, Irp);
6219                         if (!NT_SUCCESS(Status)) {
6220                             ERR("insert_tree_item returned %08lx\n", Status);
6221                             ExFreePool(newrr);
6222                             return Status;
6223                         }
6224                     }
6225 
6226                     break;
6227                 }
6228 
6229                 if (len > itemlen) {
6230                     len -= itemlen;
6231                     rr = (ROOT_REF*)&rr->name[rr->n];
6232                 } else
6233                     break;
6234             } while (len > 0);
6235         }
6236     } else {
6237         WARN("could not find ROOT_REF entry for subvol %I64x in %I64x\n", searchkey.offset, searchkey.obj_id);
6238         return STATUS_NOT_FOUND;
6239     }
6240 
6241     return STATUS_SUCCESS;
6242 }
6243 
6244 #ifdef _MSC_VER
6245 #pragma warning(push)
6246 #pragma warning(suppress: 28194)
6247 #endif
6248 static NTSTATUS add_root_ref(_In_ device_extension* Vcb, _In_ uint64_t subvolid, _In_ uint64_t parsubvolid, _In_ __drv_aliasesMem ROOT_REF* rr, _In_opt_ PIRP Irp) {
6249     KEY searchkey;
6250     traverse_ptr tp;
6251     NTSTATUS Status;
6252 
6253     searchkey.obj_id = parsubvolid;
6254     searchkey.obj_type = TYPE_ROOT_REF;
6255     searchkey.offset = subvolid;
6256 
6257     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
6258     if (!NT_SUCCESS(Status)) {
6259         ERR("error - find_item returned %08lx\n", Status);
6260         return Status;
6261     }
6262 
6263     if (!keycmp(searchkey, tp.item->key)) {
6264         uint16_t rrsize = tp.item->size + (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n;
6265         uint8_t* rr2;
6266 
6267         rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
6268         if (!rr2) {
6269             ERR("out of memory\n");
6270             return STATUS_INSUFFICIENT_RESOURCES;
6271         }
6272 
6273         if (tp.item->size > 0)
6274             RtlCopyMemory(rr2, tp.item->data, tp.item->size);
6275 
6276         RtlCopyMemory(rr2 + tp.item->size, rr, offsetof(ROOT_REF, name[0]) + rr->n);
6277         ExFreePool(rr);
6278 
6279         Status = delete_tree_item(Vcb, &tp);
6280         if (!NT_SUCCESS(Status)) {
6281             ERR("delete_tree_item returned %08lx\n", Status);
6282             ExFreePool(rr2);
6283             return Status;
6284         }
6285 
6286         Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr2, rrsize, NULL, Irp);
6287         if (!NT_SUCCESS(Status)) {
6288             ERR("insert_tree_item returned %08lx\n", Status);
6289             ExFreePool(rr2);
6290             return Status;
6291         }
6292     } else {
6293         Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr, (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n, NULL, Irp);
6294         if (!NT_SUCCESS(Status)) {
6295             ERR("insert_tree_item returned %08lx\n", Status);
6296             ExFreePool(rr);
6297             return Status;
6298         }
6299     }
6300 
6301     return STATUS_SUCCESS;
6302 }
6303 #ifdef _MSC_VER
6304 #pragma warning(pop)
6305 #endif
6306 
6307 static NTSTATUS update_root_backref(device_extension* Vcb, uint64_t subvolid, uint64_t parsubvolid, PIRP Irp) {
6308     KEY searchkey;
6309     traverse_ptr tp;
6310     uint8_t* data;
6311     uint16_t datalen;
6312     NTSTATUS Status;
6313 
6314     searchkey.obj_id = parsubvolid;
6315     searchkey.obj_type = TYPE_ROOT_REF;
6316     searchkey.offset = subvolid;
6317 
6318     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
6319     if (!NT_SUCCESS(Status)) {
6320         ERR("error - find_item returned %08lx\n", Status);
6321         return Status;
6322     }
6323 
6324     if (!keycmp(tp.item->key, searchkey) && tp.item->size > 0) {
6325         datalen = tp.item->size;
6326 
6327         data = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);
6328         if (!data) {
6329             ERR("out of memory\n");
6330             return STATUS_INSUFFICIENT_RESOURCES;
6331         }
6332 
6333         RtlCopyMemory(data, tp.item->data, datalen);
6334     } else {
6335         datalen = 0;
6336         data = NULL;
6337     }
6338 
6339     searchkey.obj_id = subvolid;
6340     searchkey.obj_type = TYPE_ROOT_BACKREF;
6341     searchkey.offset = parsubvolid;
6342 
6343     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
6344     if (!NT_SUCCESS(Status)) {
6345         ERR("error - find_item returned %08lx\n", Status);
6346 
6347         if (datalen > 0)
6348             ExFreePool(data);
6349 
6350         return Status;
6351     }
6352 
6353     if (!keycmp(tp.item->key, searchkey)) {
6354         Status = delete_tree_item(Vcb, &tp);
6355         if (!NT_SUCCESS(Status)) {
6356             ERR("delete_tree_item returned %08lx\n", Status);
6357 
6358             if (datalen > 0)
6359                 ExFreePool(data);
6360 
6361             return Status;
6362         }
6363     }
6364 
6365     if (datalen > 0) {
6366         Status = insert_tree_item(Vcb, Vcb->root_root, subvolid, TYPE_ROOT_BACKREF, parsubvolid, data, datalen, NULL, Irp);
6367         if (!NT_SUCCESS(Status)) {
6368             ERR("insert_tree_item returned %08lx\n", Status);
6369             ExFreePool(data);
6370             return Status;
6371         }
6372     }
6373 
6374     return STATUS_SUCCESS;
6375 }
6376 
6377 static NTSTATUS add_root_item_to_cache(device_extension* Vcb, uint64_t root, PIRP Irp) {
6378     KEY searchkey;
6379     traverse_ptr tp;
6380     NTSTATUS Status;
6381 
6382     searchkey.obj_id = root;
6383     searchkey.obj_type = TYPE_ROOT_ITEM;
6384     searchkey.offset = 0xffffffffffffffff;
6385 
6386     Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
6387     if (!NT_SUCCESS(Status)) {
6388         ERR("error - find_item returned %08lx\n", Status);
6389         return Status;
6390     }
6391 
6392     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
6393         ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id);
6394         return STATUS_INTERNAL_ERROR;
6395     }
6396 
6397     if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, create new entry with new bits zeroed
6398         ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
6399         if (!ri) {
6400             ERR("out of memory\n");
6401             return STATUS_INSUFFICIENT_RESOURCES;
6402         }
6403 
6404         if (tp.item->size > 0)
6405             RtlCopyMemory(ri, tp.item->data, tp.item->size);
6406 
6407         RtlZeroMemory(((uint8_t*)ri) + tp.item->size, sizeof(ROOT_ITEM) - tp.item->size);
6408 
6409         Status = delete_tree_item(Vcb, &tp);
6410         if (!NT_SUCCESS(Status)) {
6411             ERR("delete_tree_item returned %08lx\n", Status);
6412             ExFreePool(ri);
6413             return Status;
6414         }
6415 
6416         Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp);
6417         if (!NT_SUCCESS(Status)) {
6418             ERR("insert_tree_item returned %08lx\n", Status);
6419             ExFreePool(ri);
6420             return Status;
6421         }
6422     } else {
6423         tp.tree->write = true;
6424     }
6425 
6426     return STATUS_SUCCESS;
6427 }
6428 
6429 static NTSTATUS flush_fileref(file_ref* fileref, LIST_ENTRY* batchlist, PIRP Irp) {
6430     NTSTATUS Status;
6431 
6432     // if fileref created and then immediately deleted, do nothing
6433     if (fileref->created && fileref->deleted) {
6434         fileref->dirty = false;
6435         return STATUS_SUCCESS;
6436     }
6437 
6438     if (fileref->fcb->ads) {
6439         fileref->dirty = false;
6440         return STATUS_SUCCESS;
6441     }
6442 
6443     if (fileref->created) {
6444         uint16_t disize;
6445         DIR_ITEM *di, *di2;
6446         uint32_t crc32;
6447 
6448         crc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
6449 
6450         disize = (uint16_t)(offsetof(DIR_ITEM, name[0]) + fileref->dc->utf8.Length);
6451         di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
6452         if (!di) {
6453             ERR("out of memory\n");
6454             return STATUS_INSUFFICIENT_RESOURCES;
6455         }
6456 
6457         if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {
6458             di->key.obj_id = fileref->fcb->inode;
6459             di->key.obj_type = TYPE_INODE_ITEM;
6460             di->key.offset = 0;
6461         } else { // subvolume
6462             di->key.obj_id = fileref->fcb->subvol->id;
6463             di->key.obj_type = TYPE_ROOT_ITEM;
6464             di->key.offset = 0xffffffffffffffff;
6465         }
6466 
6467         di->transid = fileref->fcb->Vcb->superblock.generation;
6468         di->m = 0;
6469         di->n = (uint16_t)fileref->dc->utf8.Length;
6470         di->type = fileref->fcb->type;
6471         RtlCopyMemory(di->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
6472 
6473         di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
6474         if (!di2) {
6475             ERR("out of memory\n");
6476             return STATUS_INSUFFICIENT_RESOURCES;
6477         }
6478 
6479         RtlCopyMemory(di2, di, disize);
6480 
6481         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,
6482                                         fileref->dc->index, di, disize, Batch_Insert);
6483         if (!NT_SUCCESS(Status)) {
6484             ERR("insert_tree_item_batch returned %08lx\n", Status);
6485             return Status;
6486         }
6487 
6488         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32,
6489                                         di2, disize, Batch_DirItem);
6490         if (!NT_SUCCESS(Status)) {
6491             ERR("insert_tree_item_batch returned %08lx\n", Status);
6492             return Status;
6493         }
6494 
6495         if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {
6496             INODE_REF* ir;
6497 
6498             ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + fileref->dc->utf8.Length, ALLOC_TAG);
6499             if (!ir) {
6500                 ERR("out of memory\n");
6501                 return STATUS_INSUFFICIENT_RESOURCES;
6502             }
6503 
6504             ir->index = fileref->dc->index;
6505             ir->n = fileref->dc->utf8.Length;
6506             RtlCopyMemory(ir->name, fileref->dc->utf8.Buffer, ir->n);
6507 
6508             Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode,
6509                                             ir, sizeof(INODE_REF) - 1 + ir->n, Batch_InodeRef);
6510             if (!NT_SUCCESS(Status)) {
6511                 ERR("insert_tree_item_batch returned %08lx\n", Status);
6512                 return Status;
6513             }
6514         } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) {
6515             ULONG rrlen;
6516             ROOT_REF* rr;
6517 
6518             rrlen = sizeof(ROOT_REF) - 1 + fileref->dc->utf8.Length;
6519 
6520             rr = ExAllocatePoolWithTag(PagedPool, rrlen, ALLOC_TAG);
6521             if (!rr) {
6522                 ERR("out of memory\n");
6523                 return STATUS_INSUFFICIENT_RESOURCES;
6524             }
6525 
6526             rr->dir = fileref->parent->fcb->inode;
6527             rr->index = fileref->dc->index;
6528             rr->n = fileref->dc->utf8.Length;
6529             RtlCopyMemory(rr->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
6530 
6531             Status = add_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rr, Irp);
6532             if (!NT_SUCCESS(Status)) {
6533                 ERR("add_root_ref returned %08lx\n", Status);
6534                 return Status;
6535             }
6536 
6537             Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp);
6538             if (!NT_SUCCESS(Status)) {
6539                 ERR("update_root_backref returned %08lx\n", Status);
6540                 return Status;
6541             }
6542         }
6543 
6544         fileref->created = false;
6545     } else if (fileref->deleted) {
6546         uint32_t crc32;
6547         ANSI_STRING* name;
6548         DIR_ITEM* di;
6549 
6550         name = &fileref->oldutf8;
6551 
6552         crc32 = calc_crc32c(0xfffffffe, (uint8_t*)name->Buffer, name->Length);
6553 
6554         di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + name->Length, ALLOC_TAG);
6555         if (!di) {
6556             ERR("out of memory\n");
6557             return STATUS_INSUFFICIENT_RESOURCES;
6558         }
6559 
6560         di->m = 0;
6561         di->n = name->Length;
6562         RtlCopyMemory(di->name, name->Buffer, name->Length);
6563 
6564         // delete DIR_ITEM (0x54)
6565 
6566         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM,
6567                                         crc32, di, sizeof(DIR_ITEM) - 1 + name->Length, Batch_DeleteDirItem);
6568         if (!NT_SUCCESS(Status)) {
6569             ERR("insert_tree_item_batch returned %08lx\n", Status);
6570             return Status;
6571         }
6572 
6573         if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {
6574             INODE_REF* ir;
6575 
6576             // delete INODE_REF (0xc)
6577 
6578             ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + name->Length, ALLOC_TAG);
6579             if (!ir) {
6580                 ERR("out of memory\n");
6581                 return STATUS_INSUFFICIENT_RESOURCES;
6582             }
6583 
6584             ir->index = fileref->oldindex;
6585             ir->n = name->Length;
6586             RtlCopyMemory(ir->name, name->Buffer, name->Length);
6587 
6588             Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF,
6589                                             fileref->parent->fcb->inode, ir, sizeof(INODE_REF) - 1 + name->Length, Batch_DeleteInodeRef);
6590             if (!NT_SUCCESS(Status)) {
6591                 ERR("insert_tree_item_batch returned %08lx\n", Status);
6592                 return Status;
6593             }
6594         } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { // subvolume
6595             Status = delete_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, name, Irp);
6596             if (!NT_SUCCESS(Status)) {
6597                 ERR("delete_root_ref returned %08lx\n", Status);
6598                 return Status;
6599             }
6600 
6601             Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp);
6602             if (!NT_SUCCESS(Status)) {
6603                 ERR("update_root_backref returned %08lx\n", Status);
6604                 return Status;
6605             }
6606         }
6607 
6608         // delete DIR_INDEX (0x60)
6609 
6610         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,
6611                                         fileref->oldindex, NULL, 0, Batch_Delete);
6612         if (!NT_SUCCESS(Status)) {
6613             ERR("insert_tree_item_batch returned %08lx\n", Status);
6614             return Status;
6615         }
6616 
6617         if (fileref->oldutf8.Buffer) {
6618             ExFreePool(fileref->oldutf8.Buffer);
6619             fileref->oldutf8.Buffer = NULL;
6620         }
6621     } else { // rename or change type
6622         PANSI_STRING oldutf8 = fileref->oldutf8.Buffer ? &fileref->oldutf8 : &fileref->dc->utf8;
6623         uint32_t crc32, oldcrc32;
6624         uint16_t disize;
6625         DIR_ITEM *olddi, *di, *di2;
6626 
6627         crc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
6628 
6629         if (!fileref->oldutf8.Buffer)
6630             oldcrc32 = crc32;
6631         else
6632             oldcrc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->oldutf8.Buffer, fileref->oldutf8.Length);
6633 
6634         olddi = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + oldutf8->Length, ALLOC_TAG);
6635         if (!olddi) {
6636             ERR("out of memory\n");
6637             return STATUS_INSUFFICIENT_RESOURCES;
6638         }
6639 
6640         olddi->m = 0;
6641         olddi->n = (uint16_t)oldutf8->Length;
6642         RtlCopyMemory(olddi->name, oldutf8->Buffer, oldutf8->Length);
6643 
6644         // delete DIR_ITEM (0x54)
6645 
6646         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM,
6647                                         oldcrc32, olddi, sizeof(DIR_ITEM) - 1 + oldutf8->Length, Batch_DeleteDirItem);
6648         if (!NT_SUCCESS(Status)) {
6649             ERR("insert_tree_item_batch returned %08lx\n", Status);
6650             ExFreePool(olddi);
6651             return Status;
6652         }
6653 
6654         // add DIR_ITEM (0x54)
6655 
6656         disize = (uint16_t)(offsetof(DIR_ITEM, name[0]) + fileref->dc->utf8.Length);
6657         di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
6658         if (!di) {
6659             ERR("out of memory\n");
6660             return STATUS_INSUFFICIENT_RESOURCES;
6661         }
6662 
6663         di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
6664         if (!di2) {
6665             ERR("out of memory\n");
6666             ExFreePool(di);
6667             return STATUS_INSUFFICIENT_RESOURCES;
6668         }
6669 
6670         if (fileref->dc)
6671             di->key = fileref->dc->key;
6672         else if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {
6673             di->key.obj_id = fileref->fcb->inode;
6674             di->key.obj_type = TYPE_INODE_ITEM;
6675             di->key.offset = 0;
6676         } else { // subvolume
6677             di->key.obj_id = fileref->fcb->subvol->id;
6678             di->key.obj_type = TYPE_ROOT_ITEM;
6679             di->key.offset = 0xffffffffffffffff;
6680         }
6681 
6682         di->transid = fileref->fcb->Vcb->superblock.generation;
6683         di->m = 0;
6684         di->n = (uint16_t)fileref->dc->utf8.Length;
6685         di->type = fileref->fcb->type;
6686         RtlCopyMemory(di->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
6687 
6688         RtlCopyMemory(di2, di, disize);
6689 
6690         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32,
6691                                         di, disize, Batch_DirItem);
6692         if (!NT_SUCCESS(Status)) {
6693             ERR("insert_tree_item_batch returned %08lx\n", Status);
6694             ExFreePool(di2);
6695             ExFreePool(di);
6696             return Status;
6697         }
6698 
6699         if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {
6700             INODE_REF *ir, *ir2;
6701 
6702             // delete INODE_REF (0xc)
6703 
6704             ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + oldutf8->Length, ALLOC_TAG);
6705             if (!ir) {
6706                 ERR("out of memory\n");
6707                 ExFreePool(di2);
6708                 return STATUS_INSUFFICIENT_RESOURCES;
6709             }
6710 
6711             ir->index = fileref->dc->index;
6712             ir->n = oldutf8->Length;
6713             RtlCopyMemory(ir->name, oldutf8->Buffer, ir->n);
6714 
6715             Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode,
6716                                             ir, sizeof(INODE_REF) - 1 + ir->n, Batch_DeleteInodeRef);
6717             if (!NT_SUCCESS(Status)) {
6718                 ERR("insert_tree_item_batch returned %08lx\n", Status);
6719                 ExFreePool(ir);
6720                 ExFreePool(di2);
6721                 return Status;
6722             }
6723 
6724             // add INODE_REF (0xc)
6725 
6726             ir2 = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + fileref->dc->utf8.Length, ALLOC_TAG);
6727             if (!ir2) {
6728                 ERR("out of memory\n");
6729                 ExFreePool(di2);
6730                 return STATUS_INSUFFICIENT_RESOURCES;
6731             }
6732 
6733             ir2->index = fileref->dc->index;
6734             ir2->n = fileref->dc->utf8.Length;
6735             RtlCopyMemory(ir2->name, fileref->dc->utf8.Buffer, ir2->n);
6736 
6737             Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode,
6738                                             ir2, sizeof(INODE_REF) - 1 + ir2->n, Batch_InodeRef);
6739             if (!NT_SUCCESS(Status)) {
6740                 ERR("insert_tree_item_batch returned %08lx\n", Status);
6741                 ExFreePool(ir2);
6742                 ExFreePool(di2);
6743                 return Status;
6744             }
6745         } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { // subvolume
6746             ULONG rrlen;
6747             ROOT_REF* rr;
6748 
6749             Status = delete_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, oldutf8, Irp);
6750             if (!NT_SUCCESS(Status)) {
6751                 ERR("delete_root_ref returned %08lx\n", Status);
6752                 ExFreePool(di2);
6753                 return Status;
6754             }
6755 
6756             rrlen = sizeof(ROOT_REF) - 1 + fileref->dc->utf8.Length;
6757 
6758             rr = ExAllocatePoolWithTag(PagedPool, rrlen, ALLOC_TAG);
6759             if (!rr) {
6760                 ERR("out of memory\n");
6761                 ExFreePool(di2);
6762                 return STATUS_INSUFFICIENT_RESOURCES;
6763             }
6764 
6765             rr->dir = fileref->parent->fcb->inode;
6766             rr->index = fileref->dc->index;
6767             rr->n = fileref->dc->utf8.Length;
6768             RtlCopyMemory(rr->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
6769 
6770             Status = add_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rr, Irp);
6771             if (!NT_SUCCESS(Status)) {
6772                 ERR("add_root_ref returned %08lx\n", Status);
6773                 ExFreePool(di2);
6774                 return Status;
6775             }
6776 
6777             Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp);
6778             if (!NT_SUCCESS(Status)) {
6779                 ERR("update_root_backref returned %08lx\n", Status);
6780                 ExFreePool(di2);
6781                 return Status;
6782             }
6783         }
6784 
6785         // delete DIR_INDEX (0x60)
6786 
6787         Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,
6788                                         fileref->dc->index, NULL, 0, Batch_Delete);
6789         if (!NT_SUCCESS(Status)) {
6790             ERR("insert_tree_item_batch returned %08lx\n", Status);
6791             ExFreePool(di2);
6792             return Status;
6793         }
6794 
6795         // add DIR_INDEX (0x60)
6796 
6797        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,
6798                                        fileref->dc->index, di2, disize, Batch_Insert);
6799        if (!NT_SUCCESS(Status)) {
6800             ERR("insert_tree_item_batch returned %08lx\n", Status);
6801             ExFreePool(di2);
6802             return Status;
6803         }
6804 
6805         if (fileref->oldutf8.Buffer) {
6806             ExFreePool(fileref->oldutf8.Buffer);
6807             fileref->oldutf8.Buffer = NULL;
6808         }
6809     }
6810 
6811     fileref->dirty = false;
6812 
6813     return STATUS_SUCCESS;
6814 }
6815 
6816 static void flush_disk_caches(device_extension* Vcb) {
6817     LIST_ENTRY* le;
6818     ioctl_context context;
6819     ULONG num;
6820 #ifdef __REACTOS__
6821     unsigned int i;
6822 #endif
6823 
6824     context.left = 0;
6825 
6826     le = Vcb->devices.Flink;
6827 
6828     while (le != &Vcb->devices) {
6829         device* dev = CONTAINING_RECORD(le, device, list_entry);
6830 
6831         if (dev->devobj && !dev->readonly && dev->can_flush)
6832             context.left++;
6833 
6834         le = le->Flink;
6835     }
6836 
6837     if (context.left == 0)
6838         return;
6839 
6840     num = 0;
6841 
6842     KeInitializeEvent(&context.Event, NotificationEvent, false);
6843 
6844     context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG);
6845     if (!context.stripes) {
6846         ERR("out of memory\n");
6847         return;
6848     }
6849 
6850     RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left);
6851 
6852     le = Vcb->devices.Flink;
6853 
6854     while (le != &Vcb->devices) {
6855         device* dev = CONTAINING_RECORD(le, device, list_entry);
6856 
6857         if (dev->devobj && !dev->readonly && dev->can_flush) {
6858             PIO_STACK_LOCATION IrpSp;
6859             ioctl_context_stripe* stripe = &context.stripes[num];
6860 
6861             RtlZeroMemory(&stripe->apte, sizeof(ATA_PASS_THROUGH_EX));
6862 
6863             stripe->apte.Length = sizeof(ATA_PASS_THROUGH_EX);
6864             stripe->apte.TimeOutValue = 5;
6865             stripe->apte.CurrentTaskFile[6] = IDE_COMMAND_FLUSH_CACHE;
6866 
6867             stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false);
6868 
6869             if (!stripe->Irp) {
6870                 ERR("IoAllocateIrp failed\n");
6871                 goto nextdev;
6872             }
6873 
6874             IrpSp = IoGetNextIrpStackLocation(stripe->Irp);
6875             IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
6876             IrpSp->FileObject = dev->fileobj;
6877 
6878             IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_ATA_PASS_THROUGH;
6879             IrpSp->Parameters.DeviceIoControl.InputBufferLength = sizeof(ATA_PASS_THROUGH_EX);
6880             IrpSp->Parameters.DeviceIoControl.OutputBufferLength = sizeof(ATA_PASS_THROUGH_EX);
6881 
6882             stripe->Irp->AssociatedIrp.SystemBuffer = &stripe->apte;
6883             stripe->Irp->Flags |= IRP_BUFFERED_IO | IRP_INPUT_OPERATION;
6884             stripe->Irp->UserBuffer = &stripe->apte;
6885             stripe->Irp->UserIosb = &stripe->iosb;
6886 
6887             IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true);
6888 
6889             IoCallDriver(dev->devobj, stripe->Irp);
6890 
6891 nextdev:
6892             num++;
6893         }
6894 
6895         le = le->Flink;
6896     }
6897 
6898     KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);
6899 
6900 #ifndef __REACTOS__
6901     for (unsigned int i = 0; i < num; i++) {
6902 #else
6903     for (i = 0; i < num; i++) {
6904 #endif
6905         if (context.stripes[i].Irp)
6906             IoFreeIrp(context.stripes[i].Irp);
6907     }
6908 
6909     ExFreePool(context.stripes);
6910 }
6911 
6912 static NTSTATUS flush_changed_dev_stats(device_extension* Vcb, device* dev, PIRP Irp) {
6913     NTSTATUS Status;
6914     KEY searchkey;
6915     traverse_ptr tp;
6916     uint16_t statslen;
6917     uint64_t* stats;
6918 
6919     searchkey.obj_id = 0;
6920     searchkey.obj_type = TYPE_DEV_STATS;
6921     searchkey.offset = dev->devitem.dev_id;
6922 
6923     Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);
6924     if (!NT_SUCCESS(Status)) {
6925         ERR("find_item returned %08lx\n", Status);
6926         return Status;
6927     }
6928 
6929     if (!keycmp(tp.item->key, searchkey)) {
6930         Status = delete_tree_item(Vcb, &tp);
6931         if (!NT_SUCCESS(Status)) {
6932             ERR("delete_tree_item returned %08lx\n", Status);
6933             return Status;
6934         }
6935     }
6936 
6937     statslen = sizeof(uint64_t) * 5;
6938     stats = ExAllocatePoolWithTag(PagedPool, statslen, ALLOC_TAG);
6939     if (!stats) {
6940         ERR("out of memory\n");
6941         return STATUS_INSUFFICIENT_RESOURCES;
6942     }
6943 
6944     RtlCopyMemory(stats, dev->stats, statslen);
6945 
6946     Status = insert_tree_item(Vcb, Vcb->dev_root, 0, TYPE_DEV_STATS, dev->devitem.dev_id, stats, statslen, NULL, Irp);
6947     if (!NT_SUCCESS(Status)) {
6948         ERR("insert_tree_item returned %08lx\n", Status);
6949         ExFreePool(stats);
6950         return Status;
6951     }
6952 
6953     return STATUS_SUCCESS;
6954 }
6955 
6956 static NTSTATUS flush_subvol(device_extension* Vcb, root* r, PIRP Irp) {
6957     NTSTATUS Status;
6958 
6959     if (r != Vcb->root_root && r != Vcb->chunk_root) {
6960         KEY searchkey;
6961         traverse_ptr tp;
6962         ROOT_ITEM* ri;
6963 
6964         searchkey.obj_id = r->id;
6965         searchkey.obj_type = TYPE_ROOT_ITEM;
6966         searchkey.offset = 0xffffffffffffffff;
6967 
6968         Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
6969         if (!NT_SUCCESS(Status)) {
6970             ERR("error - find_item returned %08lx\n", Status);
6971             return Status;
6972         }
6973 
6974         if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
6975             ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id);
6976             return STATUS_INTERNAL_ERROR;
6977         }
6978 
6979         ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
6980         if (!ri) {
6981             ERR("out of memory\n");
6982             return STATUS_INSUFFICIENT_RESOURCES;
6983         }
6984 
6985         RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM));
6986 
6987         Status = delete_tree_item(Vcb, &tp);
6988         if (!NT_SUCCESS(Status)) {
6989             ERR("delete_tree_item returned %08lx\n", Status);
6990             return Status;
6991         }
6992 
6993         Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp);
6994         if (!NT_SUCCESS(Status)) {
6995             ERR("insert_tree_item returned %08lx\n", Status);
6996             return Status;
6997         }
6998     }
6999 
7000     if (r->received) {
7001         KEY searchkey;
7002         traverse_ptr tp;
7003 
7004         if (!Vcb->uuid_root) {
7005             root* uuid_root;
7006 
7007             TRACE("uuid root doesn't exist, creating it\n");
7008 
7009             Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp);
7010 
7011             if (!NT_SUCCESS(Status)) {
7012                 ERR("create_root returned %08lx\n", Status);
7013                 return Status;
7014             }
7015 
7016             Vcb->uuid_root = uuid_root;
7017         }
7018 
7019         RtlCopyMemory(&searchkey.obj_id, &r->root_item.received_uuid, sizeof(uint64_t));
7020         searchkey.obj_type = TYPE_SUBVOL_REC_UUID;
7021         RtlCopyMemory(&searchkey.offset, &r->root_item.received_uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));
7022 
7023         Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);
7024         if (!NT_SUCCESS(Status)) {
7025             ERR("find_item returned %08lx\n", Status);
7026             return Status;
7027         }
7028 
7029         if (!keycmp(tp.item->key, searchkey)) {
7030             if (tp.item->size + sizeof(uint64_t) <= Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node)) {
7031                 uint64_t* ids;
7032 
7033                 ids = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(uint64_t), ALLOC_TAG);
7034                 if (!ids) {
7035                     ERR("out of memory\n");
7036                     return STATUS_INSUFFICIENT_RESOURCES;
7037                 }
7038 
7039                 RtlCopyMemory(ids, tp.item->data, tp.item->size);
7040                 RtlCopyMemory((uint8_t*)ids + tp.item->size, &r->id, sizeof(uint64_t));
7041 
7042                 Status = delete_tree_item(Vcb, &tp);
7043                 if (!NT_SUCCESS(Status)) {
7044                     ERR("delete_tree_item returned %08lx\n", Status);
7045                     ExFreePool(ids);
7046                     return Status;
7047                 }
7048 
7049                 Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ids, tp.item->size + sizeof(uint64_t), NULL, Irp);
7050                 if (!NT_SUCCESS(Status)) {
7051                     ERR("insert_tree_item returned %08lx\n", Status);
7052                     ExFreePool(ids);
7053                     return Status;
7054                 }
7055             }
7056         } else {
7057             uint64_t* root_num;
7058 
7059             root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG);
7060             if (!root_num) {
7061                 ERR("out of memory\n");
7062                 return STATUS_INSUFFICIENT_RESOURCES;
7063             }
7064 
7065             *root_num = r->id;
7066 
7067             Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp);
7068             if (!NT_SUCCESS(Status)) {
7069                 ERR("insert_tree_item returned %08lx\n", Status);
7070                 ExFreePool(root_num);
7071                 return Status;
7072             }
7073         }
7074 
7075         r->received = false;
7076     }
7077 
7078     r->dirty = false;
7079 
7080     return STATUS_SUCCESS;
7081 }
7082 
7083 static NTSTATUS test_not_full(device_extension* Vcb) {
7084     uint64_t reserve, could_alloc, free_space;
7085     LIST_ENTRY* le;
7086 
7087     // This function ensures we drop into readonly mode if we're about to leave very little
7088     // space for metadata - this is similar to the "global reserve" of the Linux driver.
7089     // Otherwise we might completely fill our space, at which point due to COW we can't
7090     // delete anything in order to fix this.
7091 
7092     reserve = Vcb->extent_root->root_item.bytes_used;
7093     reserve += Vcb->root_root->root_item.bytes_used;
7094     if (Vcb->checksum_root) reserve += Vcb->checksum_root->root_item.bytes_used;
7095 
7096     reserve = max(reserve, 0x1000000); // 16 M
7097     reserve = min(reserve, 0x20000000); // 512 M
7098 
7099     // Find out how much space would be available for new metadata chunks
7100 
7101     could_alloc = 0;
7102 
7103     if (Vcb->metadata_flags & BLOCK_FLAG_RAID5) {
7104         uint64_t s1 = 0, s2 = 0, s3 = 0;
7105 
7106         le = Vcb->devices.Flink;
7107         while (le != &Vcb->devices) {
7108             device* dev = CONTAINING_RECORD(le, device, list_entry);
7109 
7110             if (!dev->readonly) {
7111                 uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;
7112 
7113                 if (space >= s1) {
7114                     s3 = s2;
7115                     s2 = s1;
7116                     s1 = space;
7117                 } else if (space >= s2) {
7118                     s3 = s2;
7119                     s2 = space;
7120                 } else if (space >= s3)
7121                     s3 = space;
7122             }
7123 
7124             le = le->Flink;
7125         }
7126 
7127         could_alloc = s3 * 2;
7128     } else if (Vcb->metadata_flags & (BLOCK_FLAG_RAID10 | BLOCK_FLAG_RAID6)) {
7129         uint64_t s1 = 0, s2 = 0, s3 = 0, s4 = 0;
7130 
7131         le = Vcb->devices.Flink;
7132         while (le != &Vcb->devices) {
7133             device* dev = CONTAINING_RECORD(le, device, list_entry);
7134 
7135             if (!dev->readonly) {
7136                 uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;
7137 
7138                 if (space >= s1) {
7139                     s4 = s3;
7140                     s3 = s2;
7141                     s2 = s1;
7142                     s1 = space;
7143                 } else if (space >= s2) {
7144                     s4 = s3;
7145                     s3 = s2;
7146                     s2 = space;
7147                 } else if (space >= s3) {
7148                     s4 = s3;
7149                     s3 = space;
7150                 } else if (space >= s4)
7151                     s4 = space;
7152             }
7153 
7154             le = le->Flink;
7155         }
7156 
7157         could_alloc = s4 * 2;
7158     } else if (Vcb->metadata_flags & (BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1)) {
7159         uint64_t s1 = 0, s2 = 0;
7160 
7161         le = Vcb->devices.Flink;
7162         while (le != &Vcb->devices) {
7163             device* dev = CONTAINING_RECORD(le, device, list_entry);
7164 
7165             if (!dev->readonly) {
7166                 uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;
7167 
7168                 if (space >= s1) {
7169                     s2 = s1;
7170                     s1 = space;
7171                 } else if (space >= s2)
7172                     s2 = space;
7173             }
7174 
7175             le = le->Flink;
7176         }
7177 
7178         if (Vcb->metadata_flags & BLOCK_FLAG_RAID1)
7179             could_alloc = s2;
7180         else // RAID0
7181             could_alloc = s2 * 2;
7182     } else if (Vcb->metadata_flags & BLOCK_FLAG_DUPLICATE) {
7183         le = Vcb->devices.Flink;
7184         while (le != &Vcb->devices) {
7185             device* dev = CONTAINING_RECORD(le, device, list_entry);
7186 
7187             if (!dev->readonly) {
7188                 uint64_t space = (dev->devitem.num_bytes - dev->devitem.bytes_used) / 2;
7189 
7190                 could_alloc = max(could_alloc, space);
7191             }
7192 
7193             le = le->Flink;
7194         }
7195     } else if (Vcb->metadata_flags & BLOCK_FLAG_RAID1C3) {
7196         uint64_t s1 = 0, s2 = 0, s3 = 0;
7197 
7198         le = Vcb->devices.Flink;
7199         while (le != &Vcb->devices) {
7200             device* dev = CONTAINING_RECORD(le, device, list_entry);
7201 
7202             if (!dev->readonly) {
7203                 uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;
7204 
7205                 if (space >= s1) {
7206                     s3 = s2;
7207                     s2 = s1;
7208                     s1 = space;
7209                 } else if (space >= s2) {
7210                     s3 = s2;
7211                     s2 = space;
7212                 } else if (space >= s3)
7213                     s3 = space;
7214             }
7215 
7216             le = le->Flink;
7217         }
7218 
7219         could_alloc = s3;
7220     } else if (Vcb->metadata_flags & BLOCK_FLAG_RAID1C4) {
7221         uint64_t s1 = 0, s2 = 0, s3 = 0, s4 = 0;
7222 
7223         le = Vcb->devices.Flink;
7224         while (le != &Vcb->devices) {
7225             device* dev = CONTAINING_RECORD(le, device, list_entry);
7226 
7227             if (!dev->readonly) {
7228                 uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;
7229 
7230                 if (space >= s1) {
7231                     s4 = s3;
7232                     s3 = s2;
7233                     s2 = s1;
7234                     s1 = space;
7235                 } else if (space >= s2) {
7236                     s4 = s3;
7237                     s3 = s2;
7238                     s2 = space;
7239                 } else if (space >= s3) {
7240                     s4 = s3;
7241                     s3 = space;
7242                 } else if (space >= s4)
7243                     s4 = space;
7244             }
7245 
7246             le = le->Flink;
7247         }
7248 
7249         could_alloc = s4;
7250     } else { // SINGLE
7251         le = Vcb->devices.Flink;
7252         while (le != &Vcb->devices) {
7253             device* dev = CONTAINING_RECORD(le, device, list_entry);
7254 
7255             if (!dev->readonly) {
7256                 uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;
7257 
7258                 could_alloc = max(could_alloc, space);
7259             }
7260 
7261             le = le->Flink;
7262         }
7263     }
7264 
7265     if (could_alloc >= reserve)
7266         return STATUS_SUCCESS;
7267 
7268     free_space = 0;
7269 
7270     le = Vcb->chunks.Flink;
7271     while (le != &Vcb->chunks) {
7272         chunk* c = CONTAINING_RECORD(le, chunk, list_entry);
7273 
7274         if (!c->reloc && !c->readonly && c->chunk_item->type & BLOCK_FLAG_METADATA) {
7275             free_space += c->chunk_item->size - c->used;
7276 
7277             if (free_space + could_alloc >= reserve)
7278                 return STATUS_SUCCESS;
7279         }
7280 
7281         le = le->Flink;
7282     }
7283 
7284     return STATUS_DISK_FULL;
7285 }
7286 
7287 static NTSTATUS check_for_orphans_root(device_extension* Vcb, root* r, PIRP Irp) {
7288     NTSTATUS Status;
7289     KEY searchkey;
7290     traverse_ptr tp;
7291     LIST_ENTRY rollback;
7292 
7293     TRACE("(%p, %p)\n", Vcb, r);
7294 
7295     InitializeListHead(&rollback);
7296 
7297     searchkey.obj_id = BTRFS_ORPHAN_INODE_OBJID;
7298     searchkey.obj_type = TYPE_ORPHAN_INODE;
7299     searchkey.offset = 0;
7300 
7301     Status = find_item(Vcb, r, &tp, &searchkey, false, Irp);
7302     if (!NT_SUCCESS(Status)) {
7303         ERR("find_item returned %08lx\n", Status);
7304         return Status;
7305     }
7306 
7307     do {
7308         traverse_ptr next_tp;
7309 
7310         if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))
7311             break;
7312 
7313         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
7314             fcb* fcb;
7315 
7316             TRACE("removing orphaned inode %I64x\n", tp.item->key.offset);
7317 
7318             Status = open_fcb(Vcb, r, tp.item->key.offset, 0, NULL, false, NULL, &fcb, PagedPool, Irp);
7319             if (!NT_SUCCESS(Status))
7320                 ERR("open_fcb returned %08lx\n", Status);
7321             else {
7322                 if (fcb->inode_item.st_nlink == 0) {
7323                     if (fcb->type != BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) {
7324                         Status = excise_extents(Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size), Irp, &rollback);
7325                         if (!NT_SUCCESS(Status)) {
7326                             ERR("excise_extents returned %08lx\n", Status);
7327                             goto end;
7328                         }
7329                     }
7330 
7331                     fcb->deleted = true;
7332 
7333                     mark_fcb_dirty(fcb);
7334                 }
7335 
7336                 free_fcb(fcb);
7337 
7338                 Status = delete_tree_item(Vcb, &tp);
7339                 if (!NT_SUCCESS(Status)) {
7340                     ERR("delete_tree_item returned %08lx\n", Status);
7341                     goto end;
7342                 }
7343             }
7344         }
7345 
7346         if (find_next_item(Vcb, &tp, &next_tp, false, Irp))
7347             tp = next_tp;
7348         else
7349             break;
7350     } while (true);
7351 
7352     Status = STATUS_SUCCESS;
7353 
7354     clear_rollback(&rollback);
7355 
7356 end:
7357     do_rollback(Vcb, &rollback);
7358 
7359     return Status;
7360 }
7361 
7362 static NTSTATUS check_for_orphans(device_extension* Vcb, PIRP Irp) {
7363     NTSTATUS Status;
7364     LIST_ENTRY* le;
7365 
7366     if (IsListEmpty(&Vcb->dirty_filerefs))
7367         return STATUS_SUCCESS;
7368 
7369     le = Vcb->dirty_filerefs.Flink;
7370     while (le != &Vcb->dirty_filerefs) {
7371         file_ref* fr = CONTAINING_RECORD(le, file_ref, list_entry_dirty);
7372 
7373         if (!fr->fcb->subvol->checked_for_orphans) {
7374             Status = check_for_orphans_root(Vcb, fr->fcb->subvol, Irp);
7375             if (!NT_SUCCESS(Status)) {
7376                 ERR("check_for_orphans_root returned %08lx\n", Status);
7377                 return Status;
7378             }
7379 
7380             fr->fcb->subvol->checked_for_orphans = true;
7381         }
7382 
7383         le = le->Flink;
7384     }
7385 
7386     return STATUS_SUCCESS;
7387 }
7388 
7389 static NTSTATUS do_write2(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {
7390     NTSTATUS Status;
7391     LIST_ENTRY *le, batchlist;
7392     bool cache_changed = false;
7393     volume_device_extension* vde;
7394     bool no_cache = false;
7395 #ifdef DEBUG_FLUSH_TIMES
7396     uint64_t filerefs = 0, fcbs = 0;
7397     LARGE_INTEGER freq, time1, time2;
7398 #endif
7399 #ifdef DEBUG_WRITE_LOOPS
7400     UINT loops = 0;
7401 #endif
7402 
7403     TRACE("(%p)\n", Vcb);
7404 
7405     InitializeListHead(&batchlist);
7406 
7407 #ifdef DEBUG_FLUSH_TIMES
7408     time1 = KeQueryPerformanceCounter(&freq);
7409 #endif
7410 
7411     Status = check_for_orphans(Vcb, Irp);
7412     if (!NT_SUCCESS(Status)) {
7413         ERR("check_for_orphans returned %08lx\n", Status);
7414         return Status;
7415     }
7416 
7417     ExAcquireResourceExclusiveLite(&Vcb->dirty_filerefs_lock, true);
7418 
7419     while (!IsListEmpty(&Vcb->dirty_filerefs)) {
7420         file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&Vcb->dirty_filerefs), file_ref, list_entry_dirty);
7421 
7422         flush_fileref(fr, &batchlist, Irp);
7423         free_fileref(fr);
7424 
7425 #ifdef DEBUG_FLUSH_TIMES
7426         filerefs++;
7427 #endif
7428     }
7429 
7430     ExReleaseResourceLite(&Vcb->dirty_filerefs_lock);
7431 
7432     Status = commit_batch_list(Vcb, &batchlist, Irp);
7433     if (!NT_SUCCESS(Status)) {
7434         ERR("commit_batch_list returned %08lx\n", Status);
7435         return Status;
7436     }
7437 
7438 #ifdef DEBUG_FLUSH_TIMES
7439     time2 = KeQueryPerformanceCounter(NULL);
7440 
7441     ERR("flushed %I64u filerefs in %I64u (freq = %I64u)\n", filerefs, time2.QuadPart - time1.QuadPart, freq.QuadPart);
7442 
7443     time1 = KeQueryPerformanceCounter(&freq);
7444 #endif
7445 
7446     // We process deleted streams first, so we don't run over our xattr
7447     // limit unless we absolutely have to.
7448     // We also process deleted normal files, to avoid any problems
7449     // caused by inode collisions.
7450 
7451     ExAcquireResourceExclusiveLite(&Vcb->dirty_fcbs_lock, true);
7452 
7453     le = Vcb->dirty_fcbs.Flink;
7454     while (le != &Vcb->dirty_fcbs) {
7455         fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_dirty);
7456         LIST_ENTRY* le2 = le->Flink;
7457 
7458         if (fcb->deleted) {
7459             ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
7460             Status = flush_fcb(fcb, false, &batchlist, Irp);
7461             ExReleaseResourceLite(fcb->Header.Resource);
7462 
7463             free_fcb(fcb);
7464 
7465             if (!NT_SUCCESS(Status)) {
7466                 ERR("flush_fcb returned %08lx\n", Status);
7467                 clear_batch_list(Vcb, &batchlist);
7468                 ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);
7469                 return Status;
7470             }
7471 
7472 #ifdef DEBUG_FLUSH_TIMES
7473             fcbs++;
7474 #endif
7475         }
7476 
7477         le = le2;
7478     }
7479 
7480     Status = commit_batch_list(Vcb, &batchlist, Irp);
7481     if (!NT_SUCCESS(Status)) {
7482         ERR("commit_batch_list returned %08lx\n", Status);
7483         ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);
7484         return Status;
7485     }
7486 
7487     le = Vcb->dirty_fcbs.Flink;
7488     while (le != &Vcb->dirty_fcbs) {
7489         fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_dirty);
7490         LIST_ENTRY* le2 = le->Flink;
7491 
7492         if (fcb->subvol != Vcb->root_root) {
7493             ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
7494             Status = flush_fcb(fcb, false, &batchlist, Irp);
7495             ExReleaseResourceLite(fcb->Header.Resource);
7496             free_fcb(fcb);
7497 
7498             if (!NT_SUCCESS(Status)) {
7499                 ERR("flush_fcb returned %08lx\n", Status);
7500                 ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);
7501                 return Status;
7502             }
7503 
7504 #ifdef DEBUG_FLUSH_TIMES
7505             fcbs++;
7506 #endif
7507         }
7508 
7509         le = le2;
7510     }
7511 
7512     ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);
7513 
7514     Status = commit_batch_list(Vcb, &batchlist, Irp);
7515     if (!NT_SUCCESS(Status)) {
7516         ERR("commit_batch_list returned %08lx\n", Status);
7517         return Status;
7518     }
7519 
7520 #ifdef DEBUG_FLUSH_TIMES
7521     time2 = KeQueryPerformanceCounter(NULL);
7522 
7523     ERR("flushed %I64u fcbs in %I64u (freq = %I64u)\n", filerefs, time2.QuadPart - time1.QuadPart, freq.QuadPart);
7524 #endif
7525 
7526     // no need to get dirty_subvols_lock here, as we have tree_lock exclusively
7527     while (!IsListEmpty(&Vcb->dirty_subvols)) {
7528         root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->dirty_subvols), root, list_entry_dirty);
7529 
7530         Status = flush_subvol(Vcb, r, Irp);
7531         if (!NT_SUCCESS(Status)) {
7532             ERR("flush_subvol returned %08lx\n", Status);
7533             return Status;
7534         }
7535     }
7536 
7537     if (!IsListEmpty(&Vcb->drop_roots)) {
7538         Status = drop_roots(Vcb, Irp, rollback);
7539 
7540         if (!NT_SUCCESS(Status)) {
7541             ERR("drop_roots returned %08lx\n", Status);
7542             return Status;
7543         }
7544     }
7545 
7546     Status = update_chunks(Vcb, &batchlist, Irp, rollback);
7547 
7548     if (!NT_SUCCESS(Status)) {
7549         ERR("update_chunks returned %08lx\n", Status);
7550         return Status;
7551     }
7552 
7553     Status = commit_batch_list(Vcb, &batchlist, Irp);
7554 
7555     // If only changing superblock, e.g. changing label, we still need to rewrite
7556     // the root tree so the generations match, otherwise you won't be able to mount on Linux.
7557     if (!Vcb->root_root->treeholder.tree || !Vcb->root_root->treeholder.tree->write) {
7558         KEY searchkey;
7559 
7560         traverse_ptr tp;
7561 
7562         searchkey.obj_id = 0;
7563         searchkey.obj_type = 0;
7564         searchkey.offset = 0;
7565 
7566         Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);
7567         if (!NT_SUCCESS(Status)) {
7568             ERR("error - find_item returned %08lx\n", Status);
7569             return Status;
7570         }
7571 
7572         Vcb->root_root->treeholder.tree->write = true;
7573     }
7574 
7575     // make sure we always update the extent tree
7576     Status = add_root_item_to_cache(Vcb, BTRFS_ROOT_EXTENT, Irp);
7577     if (!NT_SUCCESS(Status)) {
7578         ERR("add_root_item_to_cache returned %08lx\n", Status);
7579         return Status;
7580     }
7581 
7582     if (Vcb->stats_changed) {
7583         le = Vcb->devices.Flink;
7584         while (le != &Vcb->devices) {
7585             device* dev = CONTAINING_RECORD(le, device, list_entry);
7586 
7587             if (dev->stats_changed) {
7588                 Status = flush_changed_dev_stats(Vcb, dev, Irp);
7589                 if (!NT_SUCCESS(Status)) {
7590                     ERR("flush_changed_dev_stats returned %08lx\n", Status);
7591                     return Status;
7592                 }
7593                 dev->stats_changed = false;
7594             }
7595 
7596             le = le->Flink;
7597         }
7598 
7599         Vcb->stats_changed = false;
7600     }
7601 
7602     do {
7603         Status = add_parents(Vcb, Irp);
7604         if (!NT_SUCCESS(Status)) {
7605             ERR("add_parents returned %08lx\n", Status);
7606             goto end;
7607         }
7608 
7609         Status = allocate_tree_extents(Vcb, Irp, rollback);
7610         if (!NT_SUCCESS(Status)) {
7611             ERR("allocate_tree_extents returned %08lx\n", Status);
7612             goto end;
7613         }
7614 
7615         Status = do_splits(Vcb, Irp, rollback);
7616         if (!NT_SUCCESS(Status)) {
7617             ERR("do_splits returned %08lx\n", Status);
7618             goto end;
7619         }
7620 
7621         Status = update_chunk_usage(Vcb, Irp, rollback);
7622         if (!NT_SUCCESS(Status)) {
7623             ERR("update_chunk_usage returned %08lx\n", Status);
7624             goto end;
7625         }
7626 
7627         if (!(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)) {
7628             if (!no_cache) {
7629                 Status = allocate_cache(Vcb, &cache_changed, Irp, rollback);
7630                 if (!NT_SUCCESS(Status)) {
7631                     WARN("allocate_cache returned %08lx\n", Status);
7632                     no_cache = true;
7633                     cache_changed = false;
7634                 }
7635             }
7636         } else {
7637             Status = update_chunk_caches_tree(Vcb, Irp);
7638             if (!NT_SUCCESS(Status)) {
7639                 ERR("update_chunk_caches_tree returned %08lx\n", Status);
7640                 goto end;
7641             }
7642         }
7643 
7644 #ifdef DEBUG_WRITE_LOOPS
7645         loops++;
7646 
7647         if (cache_changed)
7648             ERR("cache has changed, looping again\n");
7649 #endif
7650     } while (cache_changed || !trees_consistent(Vcb));
7651 
7652 #ifdef DEBUG_WRITE_LOOPS
7653     ERR("%u loops\n", loops);
7654 #endif
7655 
7656     TRACE("trees consistent\n");
7657 
7658     Status = update_root_root(Vcb, no_cache, Irp, rollback);
7659     if (!NT_SUCCESS(Status)) {
7660         ERR("update_root_root returned %08lx\n", Status);
7661         goto end;
7662     }
7663 
7664     Status = write_trees(Vcb, Irp);
7665     if (!NT_SUCCESS(Status)) {
7666         ERR("write_trees returned %08lx\n", Status);
7667         goto end;
7668     }
7669 
7670     Status = test_not_full(Vcb);
7671     if (!NT_SUCCESS(Status)) {
7672         ERR("test_not_full returned %08lx\n", Status);
7673         goto end;
7674     }
7675 
7676 #ifdef DEBUG_PARANOID
7677     le = Vcb->trees.Flink;
7678     while (le != &Vcb->trees) {
7679         tree* t = CONTAINING_RECORD(le, tree, list_entry);
7680         KEY searchkey;
7681         traverse_ptr tp;
7682 
7683         searchkey.obj_id = t->header.address;
7684         searchkey.obj_type = TYPE_METADATA_ITEM;
7685         searchkey.offset = 0xffffffffffffffff;
7686 
7687         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
7688         if (!NT_SUCCESS(Status)) {
7689             ERR("error - find_item returned %08lx\n", Status);
7690             goto end;
7691         }
7692 
7693         if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
7694             searchkey.obj_id = t->header.address;
7695             searchkey.obj_type = TYPE_EXTENT_ITEM;
7696             searchkey.offset = 0xffffffffffffffff;
7697 
7698             Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
7699             if (!NT_SUCCESS(Status)) {
7700                 ERR("error - find_item returned %08lx\n", Status);
7701                 goto end;
7702             }
7703 
7704             if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
7705                 ERR("error - could not find entry in extent tree for tree at %I64x\n", t->header.address);
7706                 Status = STATUS_INTERNAL_ERROR;
7707                 goto end;
7708             }
7709         }
7710 
7711         le = le->Flink;
7712     }
7713 #endif
7714 
7715     Vcb->superblock.cache_generation = Vcb->superblock.generation;
7716 
7717     if (!Vcb->options.no_barrier)
7718         flush_disk_caches(Vcb);
7719 
7720     Status = write_superblocks(Vcb, Irp);
7721     if (!NT_SUCCESS(Status)) {
7722         ERR("write_superblocks returned %08lx\n", Status);
7723         goto end;
7724     }
7725 
7726     vde = Vcb->vde;
7727 
7728     if (vde) {
7729         pdo_device_extension* pdode = vde->pdode;
7730 
7731         ExAcquireResourceSharedLite(&pdode->child_lock, true);
7732 
7733         le = pdode->children.Flink;
7734 
7735         while (le != &pdode->children) {
7736             volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
7737 
7738             vc->generation = Vcb->superblock.generation;
7739             le = le->Flink;
7740         }
7741 
7742         ExReleaseResourceLite(&pdode->child_lock);
7743     }
7744 
7745     clean_space_cache(Vcb);
7746 
7747     le = Vcb->chunks.Flink;
7748     while (le != &Vcb->chunks) {
7749         chunk* c = CONTAINING_RECORD(le, chunk, list_entry);
7750 
7751         c->changed = false;
7752         c->space_changed = false;
7753 
7754         le = le->Flink;
7755     }
7756 
7757     Vcb->superblock.generation++;
7758 
7759     Status = STATUS_SUCCESS;
7760 
7761     le = Vcb->trees.Flink;
7762     while (le != &Vcb->trees) {
7763         tree* t = CONTAINING_RECORD(le, tree, list_entry);
7764 
7765         t->write = false;
7766 
7767         le = le->Flink;
7768     }
7769 
7770     Vcb->need_write = false;
7771 
7772     while (!IsListEmpty(&Vcb->drop_roots)) {
7773         root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->drop_roots), root, list_entry);
7774 
7775         if (IsListEmpty(&r->fcbs)) {
7776             ExDeleteResourceLite(&r->nonpaged->load_tree_lock);
7777             ExFreePool(r->nonpaged);
7778             ExFreePool(r);
7779         } else
7780             r->dropped = true;
7781     }
7782 
7783 end:
7784     TRACE("do_write returning %08lx\n", Status);
7785 
7786     return Status;
7787 }
7788 
7789 NTSTATUS do_write(device_extension* Vcb, PIRP Irp) {
7790     LIST_ENTRY rollback;
7791     NTSTATUS Status;
7792 
7793     InitializeListHead(&rollback);
7794 
7795     Status = do_write2(Vcb, Irp, &rollback);
7796 
7797     if (!NT_SUCCESS(Status)) {
7798         ERR("do_write2 returned %08lx, dropping into readonly mode\n", Status);
7799         Vcb->readonly = true;
7800         FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_FORCED_CLOSED);
7801         do_rollback(Vcb, &rollback);
7802     } else
7803         clear_rollback(&rollback);
7804 
7805     return Status;
7806 }
7807 
7808 static void do_flush(device_extension* Vcb) {
7809     NTSTATUS Status;
7810 
7811     ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);
7812 
7813     if (Vcb->need_write && !Vcb->readonly)
7814         Status = do_write(Vcb, NULL);
7815     else
7816         Status = STATUS_SUCCESS;
7817 
7818     free_trees(Vcb);
7819 
7820     if (!NT_SUCCESS(Status))
7821         ERR("do_write returned %08lx\n", Status);
7822 
7823     ExReleaseResourceLite(&Vcb->tree_lock);
7824 }
7825 
7826 _Function_class_(KSTART_ROUTINE)
7827 void __stdcall flush_thread(void* context) {
7828     DEVICE_OBJECT* devobj = context;
7829     device_extension* Vcb = devobj->DeviceExtension;
7830     LARGE_INTEGER due_time;
7831 
7832     ObReferenceObject(devobj);
7833 
7834     KeInitializeTimer(&Vcb->flush_thread_timer);
7835 
7836     due_time.QuadPart = (uint64_t)Vcb->options.flush_interval * -10000000;
7837 
7838     KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);
7839 
7840     while (true) {
7841         KeWaitForSingleObject(&Vcb->flush_thread_timer, Executive, KernelMode, false, NULL);
7842 
7843         if (!(devobj->Vpb->Flags & VPB_MOUNTED) || Vcb->removing)
7844             break;
7845 
7846         if (!Vcb->locked)
7847             do_flush(Vcb);
7848 
7849         KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);
7850     }
7851 
7852     ObDereferenceObject(devobj);
7853     KeCancelTimer(&Vcb->flush_thread_timer);
7854 
7855     KeSetEvent(&Vcb->flush_thread_finished, 0, false);
7856 
7857     PsTerminateSystemThread(STATUS_SUCCESS);
7858 }
7859