[BTRFS]
authorPierre Schweitzer <pierre@reactos.org>
Thu, 5 May 2016 17:26:47 +0000 (17:26 +0000)
committerPierre Schweitzer <pierre@reactos.org>
Thu, 5 May 2016 17:26:47 +0000 (17:26 +0000)
Upgrade the WinBtrfs to release 0.4.

CORE-11172 #resolve #Committed in r71265

svn path=/trunk/; revision=71265

20 files changed:
reactos/drivers/filesystems/btrfs/CMakeLists.txt
reactos/drivers/filesystems/btrfs/btrfs.c
reactos/drivers/filesystems/btrfs/btrfs.h
reactos/drivers/filesystems/btrfs/btrfs.rc
reactos/drivers/filesystems/btrfs/btrfs_drv.h
reactos/drivers/filesystems/btrfs/btrfsioctl.h
reactos/drivers/filesystems/btrfs/create.c
reactos/drivers/filesystems/btrfs/dirctrl.c
reactos/drivers/filesystems/btrfs/extent-tree.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/fileinfo.c
reactos/drivers/filesystems/btrfs/flushthread.c
reactos/drivers/filesystems/btrfs/free-space.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/fsctl.c
reactos/drivers/filesystems/btrfs/loader.c [deleted file]
reactos/drivers/filesystems/btrfs/pnp.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/read.c
reactos/drivers/filesystems/btrfs/reparse.c
reactos/drivers/filesystems/btrfs/security.c
reactos/drivers/filesystems/btrfs/treefuncs.c
reactos/drivers/filesystems/btrfs/write.c

index 2e3d1ea..cd3d68d 100644 (file)
@@ -8,10 +8,13 @@ list(APPEND SOURCE
     crc32c.c
     create.c
     dirctrl.c
+    extent-tree.c
     fastio.c
     fileinfo.c
     flushthread.c
+    free-space.c
     fsctl.c
+    pnp.c
     read.c
     reparse.c
     search.c
@@ -30,7 +33,7 @@ endif()
 
 add_definitions(-D__KERNEL__)
 set_module_type(btrfs kernelmodedriver)
-target_link_libraries(btrfs ntoskrnl_vista)
+target_link_libraries(btrfs ntoskrnl_vista ${PSEH_LIB})
 add_importlibs(btrfs ntoskrnl hal)
 add_pch(btrfs btrfs_drv.h SOURCE)
 add_cd_file(TARGET btrfs DESTINATION reactos/system32/drivers NO_CAB FOR all)
index 6a1d256..a9fb3e5 100644 (file)
@@ -61,6 +61,8 @@ PDEVICE_OBJECT comdo = NULL;
 HANDLE log_handle = NULL;
 #endif
 
+int __security_cookie = __LINE__;
+
 static NTSTATUS STDCALL close_file(device_extension* Vcb, PFILE_OBJECT FileObject);
 
 typedef struct {
@@ -82,9 +84,9 @@ static NTSTATUS STDCALL dbg_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PV
 }
 
 #ifdef DEBUG_LONG_MESSAGES
-void STDCALL _debug_message(const char* func, UINT8 priority, const char* file, unsigned int line, char* s, ...) {
+void STDCALL _debug_message(const char* func, const char* file, unsigned int line, char* s, ...) {
 #else
-void STDCALL _debug_message(const char* func, UINT8 priority, char* s, ...) {
+void STDCALL _debug_message(const char* func, char* s, ...) {
 #endif
     LARGE_INTEGER offset;
     PIO_STACK_LOCATION IrpSp;
@@ -95,9 +97,6 @@ void STDCALL _debug_message(const char* func, UINT8 priority, char* s, ...) {
     read_context* context = NULL;
     UINT32 length;
     
-    if (log_started && priority > debug_log_level)
-        return;
-    
     buf2 = ExAllocatePoolWithTag(NonPagedPool, 1024, ALLOC_TAG);
     
     if (!buf2) {
@@ -327,21 +326,17 @@ BOOL STDCALL get_last_inode(device_extension* Vcb, root* r) {
     }
     
     while (find_prev_item(Vcb, &tp, &prev_tp, FALSE)) {
-        free_traverse_ptr(&tp);
         tp = prev_tp;
         
         TRACE("moving on to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
         
-        if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
+        if (tp.item->key.obj_type == TYPE_INODE_ITEM || (tp.item->key.obj_type == TYPE_ROOT_ITEM && !(tp.item->key.obj_id & 0x8000000000000000))) {
             r->lastinode = tp.item->key.obj_id;
-            free_traverse_ptr(&tp);
             TRACE("last inode for tree %llx is %llx\n", r->id, r->lastinode);
             return TRUE;
         }
     }
     
-    free_traverse_ptr(&tp);
-    
     r->lastinode = SUBVOL_ROOT_INODE;
     
     WARN("no INODE_ITEMs in tree %llx\n", r->id);
@@ -370,13 +365,11 @@ BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char*
     
     if (keycmp(&tp.item->key, &searchkey)) {
         TRACE("could not find item (%llx,%x,%llx)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
-        free_traverse_ptr(&tp);
         return FALSE;
     }
     
     if (tp.item->size < sizeof(DIR_ITEM)) {
         ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
-        free_traverse_ptr(&tp);
         return FALSE;
     }
     
@@ -386,7 +379,6 @@ BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char*
     while (TRUE) {
         if (size < sizeof(DIR_ITEM) || size < (sizeof(DIR_ITEM) - 1 + xa->m + xa->n)) {
             WARN("(%llx,%x,%llx) is truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
-            free_traverse_ptr(&tp);
             return FALSE;
         }
         
@@ -399,7 +391,6 @@ BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char*
                 *data = ExAllocatePoolWithTag(PagedPool, xa->m, ALLOC_TAG);
                 if (!*data) {
                     ERR("out of memory\n");
-                    free_traverse_ptr(&tp);
                     return FALSE;
                 }
                 
@@ -407,7 +398,6 @@ BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char*
             } else
                 *data = NULL;
             
-            free_traverse_ptr(&tp);
             return TRUE;
         }
         
@@ -422,8 +412,6 @@ BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char*
     
     TRACE("xattr %s not found in (%llx,%x,%llx)\n", name, searchkey.obj_id, searchkey.obj_type, searchkey.offset);
     
-    free_traverse_ptr(&tp);
-    
     return FALSE;
 }
 
@@ -448,6 +436,8 @@ NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, ch
     
     xasize = sizeof(DIR_ITEM) - 1 + (ULONG)strlen(name) + datalen;
     
+    // FIXME - make sure xasize not too big
+    
     if (!keycmp(&tp.item->key, &searchkey)) { // key exists
         UINT8* newdata;
         ULONG size = tp.item->size;
@@ -474,7 +464,6 @@ NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, ch
                     newdata = ExAllocatePoolWithTag(PagedPool, tp.item->size + xasize - oldxasize, ALLOC_TAG);
                     if (!newdata) {
                         ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
                         return STATUS_INSUFFICIENT_RESOURCES;
                     }
                     
@@ -510,7 +499,6 @@ NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, ch
                     newdata = ExAllocatePoolWithTag(PagedPool, tp.item->size + xasize, ALLOC_TAG);
                     if (!newdata) {
                         ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
                         return STATUS_INSUFFICIENT_RESOURCES;
                     }
                     
@@ -543,7 +531,6 @@ NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, ch
         xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG);
         if (!xa) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
@@ -560,8 +547,6 @@ NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, ch
         insert_tree_item(Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, NULL, rollback);
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
@@ -589,7 +574,6 @@ BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, cha
         if (tp.item->size < sizeof(DIR_ITEM)) {
             ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
             
-            free_traverse_ptr(&tp);
             return FALSE;
         } else {
             xa = (DIR_ITEM*)tp.item->data;
@@ -599,7 +583,6 @@ BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, cha
                 
                 if (size < sizeof(DIR_ITEM) || size < sizeof(DIR_ITEM) - 1 + xa->m + xa->n) {
                     ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
-                    free_traverse_ptr(&tp);
                         
                     return FALSE;
                 }
@@ -616,7 +599,6 @@ BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, cha
                     
                     if (newsize == 0) {
                         TRACE("xattr %s deleted\n", name);
-                        free_traverse_ptr(&tp);
                         
                         return TRUE;
                     }
@@ -625,7 +607,6 @@ BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, cha
                     newdata = ExAllocatePoolWithTag(PagedPool, newsize, ALLOC_TAG);
                     if (!newdata) {
                         ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
                         return FALSE;
                     }
 
@@ -641,14 +622,12 @@ BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, cha
                     
                     insert_tree_item(Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, newdata, newsize, NULL, rollback);
                     
-                    free_traverse_ptr(&tp);
                         
                     return TRUE;
                 }
                 
                 if (xa->m + xa->n >= size) { // FIXME - test this works
                     WARN("xattr %s not found\n", name);
-                    free_traverse_ptr(&tp);
 
                     return FALSE;
                 } else {
@@ -659,7 +638,6 @@ BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, cha
         }
     } else {
         WARN("xattr %s not found\n", name);
-        free_traverse_ptr(&tp);
         
         return FALSE;
     }
@@ -686,14 +664,12 @@ NTSTATUS add_dir_item(device_extension* Vcb, root* subvol, UINT64 inode, UINT32
         
         if (tp.item->size + disize > maxlen) {
             WARN("DIR_ITEM was longer than maxlen (%u + %u > %u)\n", tp.item->size, disize, maxlen);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
         
         di2 = ExAllocatePoolWithTag(PagedPool, tp.item->size + disize, ALLOC_TAG);
         if (!di2) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
@@ -710,8 +686,6 @@ NTSTATUS add_dir_item(device_extension* Vcb, root* subvol, UINT64 inode, UINT32
     } else {
         insert_tree_item(Vcb, subvol, inode, TYPE_DIR_ITEM, crc32, di, disize, NULL, rollback);
     }
-
-    free_traverse_ptr(&tp);
     
     return STATUS_SUCCESS;
 }
@@ -734,7 +708,6 @@ UINT64 find_next_dir_index(device_extension* Vcb, root* subvol, UINT64 inode) {
     
     if (!keycmp(&searchkey, &tp.item->key)) {
         if (!find_prev_item(Vcb, &tp, &prev_tp, FALSE)) {
-            free_traverse_ptr(&tp);
             tp = prev_tp;
             
             TRACE("moving back to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
@@ -746,8 +719,6 @@ UINT64 find_next_dir_index(device_extension* Vcb, root* subvol, UINT64 inode) {
     } else
         dirpos = 2;
     
-    free_traverse_ptr(&tp);
-    
     return dirpos;
 }
 
@@ -794,6 +765,7 @@ exit:
 static NTSTATUS STDCALL drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
     NTSTATUS Status;
     BOOL top_level;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
 
     FsRtlEnterFileSystem();
 
@@ -801,7 +773,18 @@ static NTSTATUS STDCALL drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
     
 //     ERR("recursive = %s\n", Irp != IoGetTopLevelIrp() ? "TRUE" : "FALSE");
     
-    Status = write_file(DeviceObject, Irp);
+    _SEH2_TRY {
+        if (IrpSp->MinorFunction & IRP_MN_COMPLETE) {
+            CcMdlWriteComplete(IrpSp->FileObject, &IrpSp->Parameters.Write.ByteOffset, Irp->MdlAddress);
+            
+            Irp->MdlAddress = NULL;
+            Irp->IoStatus.Status = STATUS_SUCCESS;
+        } else {
+            Status = write_file(DeviceObject, Irp);
+        }
+    } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
+        Status = _SEH2_GetExceptionCode();
+    } _SEH2_END;
     
     Irp->IoStatus.Status = Status;
 
@@ -1180,6 +1163,151 @@ static NTSTATUS STDCALL read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, P
 //     }
 // }
 
+// static void test_dropping_tree(device_extension* Vcb) {
+//     LIST_ENTRY* le = Vcb->roots.Flink;
+//     
+//     while (le != &Vcb->roots) {
+//         root* r = CONTAINING_RECORD(le, root, list_entry);
+//         
+//         if (r->id == 0x101) {
+//             RemoveEntryList(&r->list_entry);
+//             InsertTailList(&Vcb->drop_roots, &r->list_entry);
+//             return;
+//         }
+//         
+//         le = le->Flink;
+//     }
+// }
+
+NTSTATUS create_root(device_extension* Vcb, UINT64 id, root** rootptr, BOOL no_tree, UINT64 offset, LIST_ENTRY* rollback) {
+    root* r;
+    tree* t;
+    ROOT_ITEM* ri;
+    traverse_ptr tp;
+    
+    r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG);
+    if (!r) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);
+    if (!r->nonpaged) {
+        ERR("out of memory\n");
+        ExFreePool(r);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    if (!no_tree) {
+        t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
+        if (!t) {
+            ERR("out of memory\n");
+            ExFreePool(r->nonpaged);
+            ExFreePool(r);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+    }
+    
+    ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
+    if (!ri) {
+        ERR("out of memory\n");
+        
+        if (!no_tree)
+            ExFreePool(t);
+        
+        ExFreePool(r->nonpaged);
+        ExFreePool(r);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    r->id = id;
+    r->treeholder.address = 0;
+    r->treeholder.generation = Vcb->superblock.generation;
+    r->treeholder.tree = no_tree ? NULL : t;
+    r->lastinode = 0;
+    r->path.Buffer = NULL;
+    RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM));
+    r->root_item.num_references = 1;
+    InitializeListHead(&r->fcbs);
+    
+    RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM));
+    
+    // We ask here for a traverse_ptr to the item we're inserting, so we can
+    // copy some of the tree's variables
+    
+    if (!insert_tree_item(Vcb, Vcb->root_root, id, TYPE_ROOT_ITEM, offset, ri, sizeof(ROOT_ITEM), &tp, rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(ri);
+        
+        if (!no_tree)
+            ExFreePool(t);
+        
+        ExFreePool(r->nonpaged);
+        ExFreePool(r);
+        return STATUS_INTERNAL_ERROR;
+    }
+        
+    ExInitializeResourceLite(&r->nonpaged->load_tree_lock);
+    
+    InsertTailList(&Vcb->roots, &r->list_entry);
+    
+    if (!no_tree) {
+        t->header.fs_uuid = tp.tree->header.fs_uuid;
+        t->header.address = 0;
+        t->header.flags = HEADER_FLAG_MIXED_BACKREF | 1; // 1 == "written"? Why does the Linux driver record this?
+        t->header.chunk_tree_uuid = tp.tree->header.chunk_tree_uuid;
+        t->header.generation = Vcb->superblock.generation;
+        t->header.tree_id = id;
+        t->header.num_items = 0;
+        t->header.level = 0;
+
+        t->has_address = FALSE;
+        t->size = 0;
+        t->Vcb = Vcb;
+        t->parent = NULL;
+        t->paritem = NULL;
+        t->root = r;
+        
+        InitializeListHead(&t->itemlist);
+    
+        t->new_address = 0;
+        t->has_new_address = FALSE;
+        t->flags = tp.tree->flags;
+        
+        InsertTailList(&Vcb->trees, &t->list_entry);
+        
+        t->write = TRUE;
+        Vcb->write_trees++;
+    }
+    
+    *rootptr = r;
+
+    return STATUS_SUCCESS;
+}
+
+// static void test_creating_root(device_extension* Vcb) {
+//     NTSTATUS Status;
+//     LIST_ENTRY rollback;
+//     UINT64 id;
+//     root* r;
+//     
+//     InitializeListHead(&rollback);
+//     
+//     if (Vcb->root_root->lastinode == 0)
+//         get_last_inode(Vcb, Vcb->root_root);
+//     
+//     id = Vcb->root_root->lastinode > 0x100 ? (Vcb->root_root->lastinode + 1) : 0x101;
+//     Status = create_root(Vcb, id, &r, &rollback);
+//     
+//     if (!NT_SUCCESS(Status)) {
+//         ERR("create_root returned %08x\n", Status);
+//         do_rollback(Vcb, &rollback);
+//     } else {
+//         Vcb->root_root->lastinode = id;
+//         clear_rollback(&rollback);
+//     }
+// }
+
 static NTSTATUS STDCALL set_label(device_extension* Vcb, FILE_FS_LABEL_INFORMATION* ffli) {
     ULONG utf8len;
     NTSTATUS Status;
@@ -1210,6 +1338,8 @@ static NTSTATUS STDCALL set_label(device_extension* Vcb, FILE_FS_LABEL_INFORMATI
     
 //     test_tree_deletion(Vcb); // TESTING
 //     test_tree_splitting(Vcb);
+//     test_dropping_tree(Vcb);
+//     test_creating_root(Vcb);
     
     Status = consider_write(Vcb);
     
@@ -1241,6 +1371,11 @@ static NTSTATUS STDCALL drv_set_volume_information(IN PDEVICE_OBJECT DeviceObjec
         goto end;
     }
     
+    if (Vcb->removing) {
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
     switch (IrpSp->Parameters.SetVolume.FsInformationClass) {
         case FileFsControlInformation:
             FIXME("STUB: FileFsControlInformation\n");
@@ -1313,7 +1448,6 @@ NTSTATUS delete_dir_item(device_extension* Vcb, root* subvol, UINT64 parinode, U
                         
                         if (!newdi) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             return STATUS_INSUFFICIENT_RESOURCES;
                         }
                         
@@ -1343,8 +1477,6 @@ NTSTATUS delete_dir_item(device_extension* Vcb, root* subvol, UINT64 parinode, U
         WARN("could not find DIR_ITEM for crc32 %08x\n", crc32);
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
@@ -1397,7 +1529,6 @@ NTSTATUS delete_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UIN
                         
                         if (!newir) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             return STATUS_INSUFFICIENT_RESOURCES;
                         }
                         
@@ -1437,8 +1568,6 @@ NTSTATUS delete_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UIN
         WARN("could not find INODE_REF entry for inode %llx in %llx\n", searchkey.obj_id, searchkey.offset);
     }
     
-    free_traverse_ptr(&tp);
-    
     if (changed)
         return STATUS_SUCCESS;
     
@@ -1488,7 +1617,6 @@ NTSTATUS delete_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UIN
                         
                         if (!newier) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             return STATUS_INSUFFICIENT_RESOURCES;
                         }
                         
@@ -1524,12 +1652,224 @@ NTSTATUS delete_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UIN
         WARN("couldn't find INODE_EXTREF entry either (offset = %08x)\n", (UINT32)searchkey.offset);
     }
     
-    free_traverse_ptr(&tp);
-    
     return changed ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR;
 }
 
-NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
+static NTSTATUS delete_subvol(file_ref* fileref, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    UINT64 index;
+    KEY searchkey;
+    traverse_ptr tp;
+    UINT32 crc32;
+    ROOT_ITEM* ri;
+    BOOL no_ref = FALSE;
+    fcb* fcb = fileref->fcb;
+    
+    // delete ROOT_REF in root tree
+    
+    Status = delete_root_ref(fcb->Vcb, fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, &fileref->utf8, &index, rollback);
+    
+    // A bug in Linux means that if you create a snapshot of a subvol containing another subvol,
+    // the ROOT_REF and ROOT_BACKREF items won't be created, nor will num_references of ROOT_ITEM
+    // be increased. In this case, we just unlink the subvol from its parent, and don't worry
+    // about anything else.
+    
+    if (Status == STATUS_NOT_FOUND)
+        no_ref = TRUE;
+    else if (!NT_SUCCESS(Status)) {
+        ERR("delete_root_ref returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!no_ref) {
+        // delete ROOT_BACKREF in root tree
+        
+        Status = update_root_backref(fcb->Vcb, fcb->subvol->id, fileref->parent->fcb->subvol->id, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("update_root_backref returned %08x\n", Status);
+            return Status;
+        }
+    }
+    
+    // delete DIR_ITEM in parent
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)fileref->utf8.Buffer, fileref->utf8.Length);
+    Status = delete_dir_item(fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, crc32, &fileref->utf8, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("delete_dir_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    // delete DIR_INDEX in parent
+    
+    if (!no_ref) {
+        searchkey.obj_id = fileref->parent->fcb->inode;
+        searchkey.obj_type = TYPE_DIR_INDEX;
+        searchkey.offset = index;
+        
+        Status = find_item(fcb->Vcb, fileref->parent->fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("find_item 1 returned %08x\n", Status);
+            return Status;
+        }
+    
+        if (!keycmp(&searchkey, &tp.item->key)) {
+            delete_tree_item(fcb->Vcb, &tp, rollback);
+            TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        }
+    } else {
+        BOOL b;
+        traverse_ptr next_tp;
+        
+        // If we have no ROOT_REF, we have to look through all the DIR_INDEX entries manually :-(
+        
+        searchkey.obj_id = fileref->parent->fcb->inode;
+        searchkey.obj_type = TYPE_DIR_INDEX;
+        searchkey.offset = 0;
+        
+        Status = find_item(fcb->Vcb, fileref->parent->fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("find_item 1 returned %08x\n", Status);
+            return Status;
+        }
+        
+        do {
+            if (tp.item->key.obj_type == TYPE_DIR_INDEX && tp.item->size >= sizeof(DIR_ITEM)) {
+                DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
+                
+                if (di->key.obj_id == fcb->subvol->id && di->key.obj_type == TYPE_ROOT_ITEM && di->n == fileref->utf8.Length &&
+                    tp.item->size >= sizeof(DIR_ITEM) - 1 + di->m + di->n && RtlCompareMemory(fileref->utf8.Buffer, di->name, di->n) == di->n) {
+                    delete_tree_item(fcb->Vcb, &tp, rollback);
+                    break;
+                }
+            }
+        
+            b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
+            
+            if (b) {
+                tp = next_tp;
+                
+                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))
+                    break;
+            }
+        } while (b);
+    }
+    
+    if (no_ref)
+        return STATUS_SUCCESS;
+    
+    if (fcb->subvol->root_item.num_references > 1) {
+        UINT64 offset;
+        
+        // change ROOT_ITEM num_references
+        
+        fcb->subvol->root_item.num_references--;
+        
+        searchkey.obj_id = fcb->subvol->id;
+        searchkey.obj_type = TYPE_ROOT_ITEM;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(fcb->Vcb, fcb->Vcb->root_root, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("find_item 2 returned %08x\n", Status);
+            return Status;
+        }
+        
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
+            delete_tree_item(fcb->Vcb, &tp, rollback);
+            TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+            offset = tp.item->key.offset;
+        } else {
+            ERR("could not find ROOT_ITEM for subvol %llx\n", fcb->subvol->id);
+            offset = 0;
+        }
+        
+        ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
+        if (!ri) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        RtlCopyMemory(ri, &fcb->subvol->root_item, sizeof(ROOT_ITEM));
+        
+        if (!insert_tree_item(fcb->Vcb, fcb->Vcb->root_root, fcb->subvol->id, TYPE_ROOT_ITEM, offset, ri, sizeof(ROOT_ITEM), NULL, rollback)) {
+            ERR("insert_tree_item failed\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+    } else {
+        RemoveEntryList(&fcb->subvol->list_entry);
+        
+        InsertTailList(&fcb->Vcb->drop_roots, &fcb->subvol->list_entry);
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static WCHAR* file_desc_fcb(fcb* fcb) {
+    char s[60];
+    UNICODE_STRING us;
+    ANSI_STRING as;
+    
+    if (fcb->debug_desc)
+        return fcb->debug_desc;
+    
+    fcb->debug_desc = ExAllocatePoolWithTag(PagedPool, 60 * sizeof(WCHAR), ALLOC_TAG);
+    if (!fcb->debug_desc)
+        return L"(memory error)";
+    
+    // I know this is pretty hackish...
+    // GCC doesn't like %llx in sprintf, and MSVC won't let us use swprintf
+    // without the CRT, which breaks drivers.
+    
+    sprintf(s, "subvol %x, inode %x", (UINT32)fcb->subvol->id, (UINT32)fcb->inode);
+    
+    as.Buffer = s;
+    as.Length = as.MaximumLength = strlen(s);
+    
+    us.Buffer = fcb->debug_desc;
+    us.MaximumLength = 60 * sizeof(WCHAR);
+    us.Length = 0;
+    
+    RtlAnsiStringToUnicodeString(&us, &as, FALSE);
+    
+    us.Buffer[us.Length / sizeof(WCHAR)] = 0;
+    
+    return fcb->debug_desc;
+}
+
+WCHAR* file_desc_fileref(file_ref* fileref) {
+    if (fileref->debug_desc)
+        return fileref->debug_desc;
+    
+    fileref->debug_desc = ExAllocatePoolWithTag(PagedPool, fileref->full_filename.Length + sizeof(WCHAR), ALLOC_TAG);
+    if (!fileref->debug_desc)
+        return L"(memory error)";
+    
+    RtlCopyMemory(fileref->debug_desc, fileref->full_filename.Buffer, fileref->full_filename.Length);
+    fileref->debug_desc[fileref->full_filename.Length / sizeof(WCHAR)] = 0;
+    
+    return fileref->debug_desc;
+}
+
+WCHAR* file_desc(PFILE_OBJECT FileObject) {
+    fcb* fcb = FileObject->FsContext;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
+    
+    if (fileref)
+        return file_desc_fileref(fileref);
+    else
+        return file_desc_fcb(fcb);
+}
+
+void send_notification_fileref(file_ref* fileref, ULONG filter_match, ULONG action) {
+    fcb* fcb = fileref->fcb;
+    
+    FsRtlNotifyFullReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fileref->full_filename, fileref->name_offset * sizeof(WCHAR),
+                                NULL, NULL, filter_match, action, NULL);
+}
+
+NTSTATUS delete_fileref(file_ref* fileref, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
     ULONG bytecount;
     NTSTATUS Status;
     char* utf8 = NULL;
@@ -1537,28 +1877,39 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
     KEY searchkey;
     traverse_ptr tp, tp2;
     UINT64 parinode, index;
+    root* parsubvol;
     INODE_ITEM *ii, *dirii;
     LARGE_INTEGER time;
     BTRFS_TIME now;
     LIST_ENTRY changed_sector_list;
+    fcb* fcb = fileref->fcb;
 #ifdef _DEBUG
     LARGE_INTEGER freq, time1, time2;
 #endif
     
-    // FIXME - throw error if try to delete subvol root(?)
-    
-    // FIXME - delete all children if deleting directory
-    
-    if (fcb->deleted) {
+    if (fileref->deleted || fcb->deleted) {
         WARN("trying to delete already-deleted file\n");
         return STATUS_SUCCESS;
     }
     
-    if (!fcb->par) {
+    if (fileref == fcb->Vcb->root_fileref) {
         ERR("error - trying to delete root FCB\n");
         return STATUS_INTERNAL_ERROR;
     }
     
+    if (fcb->inode == SUBVOL_ROOT_INODE) {
+        Status = delete_subvol(fileref, rollback);
+        
+        if (!NT_SUCCESS(Status))
+            goto exit;
+        else {
+            parinode = fileref->parent->fcb->inode;
+            parsubvol = fileref->parent->fcb->subvol;
+            bytecount = fileref->utf8.Length;
+            goto success2;
+        }
+    }
+    
 #ifdef _DEBUG
     time1 = KeQueryPerformanceCounter(&freq);
 #endif
@@ -1580,29 +1931,28 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
         RtlCopyMemory(s, fcb->adsxattr.Buffer, fcb->adsxattr.Length);
         s[fcb->adsxattr.Length] = 0;
         
-        if (!delete_xattr(fcb->Vcb, fcb->par->subvol, fcb->par->inode, s, fcb->adshash, rollback)) {
+        if (!delete_xattr(fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, s, fcb->adshash, rollback)) {
             ERR("failed to delete xattr %s\n", s);
         }
         
         ExFreePool(s);
         
-        fcb->par->inode_item.transid = fcb->Vcb->superblock.generation;
-        fcb->par->inode_item.sequence++;
-        fcb->par->inode_item.st_ctime = now;
+        fileref->parent->fcb->inode_item.transid = fcb->Vcb->superblock.generation;
+        fileref->parent->fcb->inode_item.sequence++;
+        fileref->parent->fcb->inode_item.st_ctime = now;
         
-        searchkey.obj_id = fcb->par->inode;
+        searchkey.obj_id = fileref->parent->fcb->inode;
         searchkey.obj_type = TYPE_INODE_ITEM;
         searchkey.offset = 0xffffffffffffffff;
         
-        Status = find_item(fcb->Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
+        Status = find_item(fcb->Vcb, fileref->parent->fcb->subvol, &tp, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
             goto exit;
         }
         
         if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
-            ERR("error - could not find INODE_ITEM for inode %llx in subvol %llx\n", fcb->par->inode, fcb->par->subvol->id);
-            free_traverse_ptr(&tp);
+            ERR("error - could not find INODE_ITEM for inode %llx in subvol %llx\n", fileref->parent->fcb->inode, fileref->parent->fcb->subvol->id);
             Status = STATUS_INTERNAL_ERROR;
             goto exit;
         }
@@ -1610,25 +1960,22 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
         ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
         if (!ii) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto exit;
         }
         
-        RtlCopyMemory(ii, &fcb->par->inode_item, sizeof(INODE_ITEM));
+        RtlCopyMemory(ii, &fileref->parent->fcb->inode_item, sizeof(INODE_ITEM));
         delete_tree_item(fcb->Vcb, &tp, rollback);
         
-        insert_tree_item(fcb->Vcb, fcb->par->subvol, searchkey.obj_id, searchkey.obj_type, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
+        insert_tree_item(fcb->Vcb, fileref->parent->fcb->subvol, searchkey.obj_id, searchkey.obj_type, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
         
-        free_traverse_ptr(&tp);
-        
-        fcb->par->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
-        fcb->par->subvol->root_item.ctime = now;
+        fileref->parent->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+        fileref->parent->fcb->subvol->root_item.ctime = now;
         
         goto success;
     }
     
-    Status = RtlUnicodeToUTF8N(NULL, 0, &bytecount, fcb->filepart.Buffer, fcb->filepart.Length);
+    Status = RtlUnicodeToUTF8N(NULL, 0, &bytecount, fileref->filepart.Buffer, fileref->filepart.Length);
     if (!NT_SUCCESS(Status)) {
         ERR("RtlUnicodeToUTF8N failed with error %08x\n", Status);
         return Status;
@@ -1640,21 +1987,23 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlUnicodeToUTF8N(utf8, bytecount, &bytecount, fcb->filepart.Buffer, fcb->filepart.Length);
+    RtlUnicodeToUTF8N(utf8, bytecount, &bytecount, fileref->filepart.Buffer, fileref->filepart.Length);
     utf8[bytecount] = 0;
     
     crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8, bytecount);
 
-    TRACE("deleting %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    TRACE("deleting %.*S\n", file_desc_fileref(fileref));
     
-    if (fcb->par->subvol == fcb->subvol)
-        parinode = fcb->par->inode;
+    if (fileref->parent->fcb->subvol == fcb->subvol)
+        parinode = fileref->parent->fcb->inode;
     else
         parinode = SUBVOL_ROOT_INODE;
     
+    parsubvol = fcb->subvol;
+    
     // delete DIR_ITEM (0x54)
     
-    Status = delete_dir_item(fcb->Vcb, fcb->subvol, parinode, crc32, &fcb->utf8, rollback);
+    Status = delete_dir_item(fcb->Vcb, fcb->subvol, parinode, crc32, &fileref->utf8, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("delete_dir_item returned %08x\n", Status);
         return Status;
@@ -1664,7 +2013,7 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
     
     index = 0;
     
-    Status = delete_inode_ref(fcb->Vcb, fcb->subvol, fcb->inode, parinode, &fcb->utf8, &index, rollback);
+    Status = delete_inode_ref(fcb->Vcb, fcb->subvol, fcb->inode, parinode, &fileref->utf8, &index, rollback);
     
     // delete DIR_INDEX (0x60)
     
@@ -1675,7 +2024,6 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
     Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        free_traverse_ptr(&tp);
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
     }
@@ -1694,23 +2042,19 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
     Status = find_item(fcb->Vcb, fcb->subvol, &tp2, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        free_traverse_ptr(&tp);
         goto exit;
     }
     
-    free_traverse_ptr(&tp);
     tp = tp2;
     
     if (keycmp(&searchkey, &tp.item->key)) {
         ERR("error - INODE_ITEM not found\n");
-        free_traverse_ptr(&tp);
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
     }
     
     if (tp.item->size < sizeof(INODE_ITEM)) {
         ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_ITEM));
-        free_traverse_ptr(&tp);
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
     }
@@ -1724,7 +2068,6 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
         newii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
         if (!newii) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto exit;
         }
@@ -1742,18 +2085,17 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
         if (!insert_tree_item(fcb->Vcb, fcb->subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newii, sizeof(INODE_ITEM), NULL, rollback))
             ERR("error - failed to insert item\n");
         
-        free_traverse_ptr(&tp);
-        
         goto success2;
     }
     
     delete_tree_item(fcb->Vcb, &tp, rollback);
     TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
     
+    fcb->deleted = TRUE;
+    
     // delete XATTR_ITEM (0x18)
     
     while (find_next_item(fcb->Vcb, &tp, &tp2, FALSE)) {
-        free_traverse_ptr(&tp);
         tp = tp2;
         
         if (tp.item->key.obj_id == fcb->inode) {
@@ -1766,8 +2108,6 @@ NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
             break;
     }
     
-    free_traverse_ptr(&tp);
-    
     // excise extents
     
     InitializeListHead(&changed_sector_list);
@@ -1790,49 +2130,45 @@ success2:
     searchkey.obj_type = TYPE_INODE_ITEM;
     searchkey.offset = 0;
     
-    Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(fcb->Vcb, parsubvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_tree returned %08x\n", Status);
         goto exit;
     }
     
     if (keycmp(&searchkey, &tp.item->key)) {
-        ERR("error - could not find INODE_ITEM for parent directory %llx in subvol %llx\n", parinode, fcb->subvol->id);
-        free_traverse_ptr(&tp);
+        ERR("error - could not find INODE_ITEM for parent directory %llx in subvol %llx\n", parinode, parsubvol->id);
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
     }
     
-    TRACE("fcb->par->inode_item.st_size was %llx\n", fcb->par->inode_item.st_size);
-    fcb->par->inode_item.st_size -= bytecount * 2;
-    TRACE("fcb->par->inode_item.st_size now %llx\n", fcb->par->inode_item.st_size);
-    fcb->par->inode_item.transid = fcb->Vcb->superblock.generation;
-    fcb->par->inode_item.sequence++;
-    fcb->par->inode_item.st_ctime = now;
-    fcb->par->inode_item.st_mtime = now;
+    TRACE("fileref->parent->fcb->inode_item.st_size was %llx\n", fileref->parent->fcb->inode_item.st_size);
+    fileref->parent->fcb->inode_item.st_size -= bytecount * 2;
+    TRACE("fileref->parent->fcb->inode_item.st_size now %llx\n", fileref->parent->fcb->inode_item.st_size);
+    fileref->parent->fcb->inode_item.transid = fcb->Vcb->superblock.generation;
+    fileref->parent->fcb->inode_item.sequence++;
+    fileref->parent->fcb->inode_item.st_ctime = now;
+    fileref->parent->fcb->inode_item.st_mtime = now;
 
     dirii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!dirii) {
         ERR("out of memory\n");
-        free_traverse_ptr(&tp);
         Status = STATUS_INSUFFICIENT_RESOURCES;
         goto exit;
     }
     
-    RtlCopyMemory(dirii, &fcb->par->inode_item, sizeof(INODE_ITEM));
+    RtlCopyMemory(dirii, &fileref->parent->fcb->inode_item, sizeof(INODE_ITEM));
     delete_tree_item(fcb->Vcb, &tp, rollback);
     
-    insert_tree_item(fcb->Vcb, fcb->subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, dirii, sizeof(INODE_ITEM), NULL, rollback);
-    
-    free_traverse_ptr(&tp);
+    insert_tree_item(fcb->Vcb, parsubvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, dirii, sizeof(INODE_ITEM), NULL, rollback);
     
-    fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
-    fcb->subvol->root_item.ctime = now;
+    parsubvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+    parsubvol->root_item.ctime = now;
     
 success:
     consider_write(fcb->Vcb);
     
-    fcb->deleted = TRUE;
+    fileref->deleted = TRUE;
     
     fcb->Header.AllocationSize.QuadPart = 0;
     fcb->Header.FileSize.QuadPart = 0;
@@ -1850,11 +2186,12 @@ success:
     
     // FIXME - set deleted flag of any open FCBs for ADS
     
-    TRACE("sending notification for deletion of %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
-    
-    FsRtlNotifyFullReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fcb->full_filename, fcb->name_offset * sizeof(WCHAR), NULL, NULL,
-                                fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
-                                FILE_ACTION_REMOVED, NULL);
+    if (FileObject && FileObject->FsContext2) {
+        ccb* ccb = FileObject->FsContext2;
+        
+        if (ccb->fileref)
+            send_notification_fileref(ccb->fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED);
+    }
     
 #ifdef _DEBUG
     time2 = KeQueryPerformanceCounter(NULL);
@@ -1872,16 +2209,16 @@ exit:
 }
 
 void _free_fcb(fcb* fcb, const char* func, const char* file, unsigned int line) {
-    ULONG rc;
-    
+    LONG rc;
+
     rc = InterlockedDecrement(&fcb->refcount);
     
 #ifdef DEBUG_FCB_REFCOUNTS
 //     WARN("fcb %p: refcount now %i (%.*S)\n", fcb, rc, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
 #ifdef DEBUG_LONG_MESSAGES
-    _debug_message(func, 1, file, line, "fcb %p: refcount now %i (%.*S)\n", fcb, rc, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    _debug_message(func, file, line, "fcb %p: refcount now %i (subvol %llx, inode %llx)\n", fcb, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode);
 #else
-    _debug_message(func, 1, "fcb %p: refcount now %i (%.*S)\n", fcb, rc, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    _debug_message(func, "fcb %p: refcount now %i (subvol %llx, inode %llx)\n", fcb, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode);
 #endif
 #endif
     
@@ -1889,30 +2226,13 @@ void _free_fcb(fcb* fcb, const char* func, const char* file, unsigned int line)
         return;
     
     ExAcquireResourceExclusiveLite(&fcb->Vcb->fcb_lock, TRUE);
-
-    if (fcb->filepart.Buffer)
-        RtlFreeUnicodeString(&fcb->filepart);
    
     ExDeleteResourceLite(&fcb->nonpaged->resource);
     ExDeleteResourceLite(&fcb->nonpaged->paging_resource);
     ExFreePool(fcb->nonpaged);
     
-    if (fcb->par/* && fcb->par != fcb->par->Vcb->root_fcb*/) {
+    if (fcb->list_entry.Flink)
         RemoveEntryList(&fcb->list_entry);
-        _free_fcb(fcb->par, func, file, line);
-    }
-    
-    if (fcb->prev)
-        fcb->prev->next = fcb->next;
-    
-    if (fcb->next)
-        fcb->next->prev = fcb->prev;
-    
-    if (fcb->Vcb->fcbs == fcb)
-        fcb->Vcb->fcbs = fcb->next;
-    
-    if (fcb->full_filename.Buffer)
-        ExFreePool(fcb->full_filename.Buffer);
     
     if (fcb->sd)
         ExFreePool(fcb->sd);
@@ -1920,8 +2240,8 @@ void _free_fcb(fcb* fcb, const char* func, const char* file, unsigned int line)
     if (fcb->adsxattr.Buffer)
         ExFreePool(fcb->adsxattr.Buffer);
     
-    if (fcb->utf8.Buffer)
-        ExFreePool(fcb->utf8.Buffer);
+    if (fcb->debug_desc)
+        ExFreePool(fcb->debug_desc);
     
     FsRtlUninitializeFileLock(&fcb->lock);
     
@@ -1930,16 +2250,69 @@ void _free_fcb(fcb* fcb, const char* func, const char* file, unsigned int line)
     ExFreePool(fcb);
 #ifdef DEBUG_FCB_REFCOUNTS
 #ifdef DEBUG_LONG_MESSAGES
-    _debug_message(func, 1, file, line, "freeing fcb %p\n", fcb);
+    _debug_message(func, file, line, "freeing fcb %p\n", fcb);
+#else
+    _debug_message(func, "freeing fcb %p\n", fcb);
+#endif
+#endif
+}
+
+void _free_fileref(file_ref* fr, const char* func, const char* file, unsigned int line) {
+    LONG rc;
+
+    rc = InterlockedDecrement(&fr->refcount);
+    
+#ifdef DEBUG_FCB_REFCOUNTS
+#ifdef DEBUG_LONG_MESSAGES
+    _debug_message(func, file, line, "fileref %p: refcount now %i\n", fr, rc);
 #else
-    _debug_message(func, 1, "freeing fcb %p\n", fcb);
+    _debug_message(func, "fileref %p: refcount now %i\n", fr, rc);
+#endif
 #endif
+    
+#ifdef _DEBUG
+    if (rc < 0) {
+        ERR("fileref %p: refcount now %i\n", fr, rc);
+        int3;
+    }
 #endif
+    
+    if (rc > 0)
+        return;
+    
+    // FIXME - do we need a file_ref lock?
+    
+    // FIXME - do delete if needed
+    
+    if (fr->filepart.Buffer)
+        ExFreePool(fr->filepart.Buffer);
+    
+    if (fr->utf8.Buffer)
+        ExFreePool(fr->utf8.Buffer);
+    
+    if (fr->full_filename.Buffer)
+        ExFreePool(fr->full_filename.Buffer);
+    
+    if (fr->debug_desc)
+        ExFreePool(fr->debug_desc);
+    
+    // FIXME - throw error if children not empty
+    
+    free_fcb(fr->fcb);
+    
+    if (fr->list_entry.Flink)
+        RemoveEntryList(&fr->list_entry);
+    
+    if (fr->parent)
+        free_fileref((file_ref*)fr->parent);
+    
+    ExFreePool(fr);
 }
 
 static NTSTATUS STDCALL close_file(device_extension* Vcb, PFILE_OBJECT FileObject) {
     fcb* fcb;
     ccb* ccb;
+    file_ref* fileref = NULL;
     
     TRACE("FileObject = %p\n", FileObject);
     
@@ -1951,7 +2324,7 @@ static NTSTATUS STDCALL close_file(device_extension* Vcb, PFILE_OBJECT FileObjec
     
     ccb = FileObject->FsContext2;
     
-    TRACE("close called for %.*S (fcb == %p)\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb);
+    TRACE("close called for %S (fcb == %p)\n", file_desc(FileObject), fcb);
     
     FsRtlNotifyCleanup(Vcb->NotifySync, &Vcb->DirNotifyList, ccb);
     
@@ -1961,43 +2334,54 @@ static NTSTATUS STDCALL close_file(device_extension* Vcb, PFILE_OBJECT FileObjec
         if (ccb->query_string.Buffer)
             RtlFreeUnicodeString(&ccb->query_string);
         
+        // FIXME - use refcounts for fileref
+        fileref = ccb->fileref;
+        
         ExFreePool(ccb);
     }
     
     CcUninitializeCacheMap(FileObject, NULL, NULL);
     
-    free_fcb(fcb);
+    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+    
+    if (fileref)
+        free_fileref(fileref);
+    else
+        free_fcb(fcb);
+    
+    ExReleaseResourceLite(&Vcb->fcb_lock);
     
     return STATUS_SUCCESS;
 }
 
-static void STDCALL uninit(device_extension* Vcb) {
+void STDCALL uninit(device_extension* Vcb, BOOL flush) {
     chunk* c;
     space* s;
     UINT64 i;
     LIST_ENTRY rollback;
     
-    InitializeListHead(&rollback);
-    
-    acquire_tree_lock(Vcb, TRUE);
+    if (flush) {
+        InitializeListHead(&rollback);
+        
+        acquire_tree_lock(Vcb, TRUE);
 
-    if (Vcb->write_trees > 0)
-        do_write(Vcb, &rollback);
-    
-    free_tree_cache(&Vcb->tree_cache);
-    
-    clear_rollback(&rollback);
+        if (Vcb->write_trees > 0)
+            do_write(Vcb, &rollback);
+        
+        free_trees(Vcb);
+        
+        clear_rollback(&rollback);
 
-    release_tree_lock(Vcb, TRUE);
+        release_tree_lock(Vcb, TRUE);
+    }
 
-    while (Vcb->roots) {
-        root* r = Vcb->roots->next;
+    while (!IsListEmpty(&Vcb->roots)) {
+        LIST_ENTRY* le = RemoveHeadList(&Vcb->roots);
+        root* r = CONTAINING_RECORD(le, root, list_entry);
 
-        ExDeleteResourceLite(&Vcb->roots->nonpaged->load_tree_lock);
-        ExFreePool(Vcb->roots->nonpaged);
-        ExFreePool(Vcb->roots);
-        
-        Vcb->roots = r;
+        ExDeleteResourceLite(&r->nonpaged->load_tree_lock);
+        ExFreePool(r->nonpaged);
+        ExFreePool(r);
     }
     
     while (!IsListEmpty(&Vcb->chunks)) {
@@ -2019,7 +2403,7 @@ static void STDCALL uninit(device_extension* Vcb) {
     }
     
     free_fcb(Vcb->volume_fcb);
-    free_fcb(Vcb->root_fcb);
+    free_fileref(Vcb->root_fileref);
     
     for (i = 0; i < Vcb->superblock.num_devices; i++) {
         while (!IsListEmpty(&Vcb->devices[i].disk_holes)) {
@@ -2058,13 +2442,17 @@ static NTSTATUS STDCALL drv_cleanup(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
         goto exit;
     }
     
-    if (FileObject) {
+    if (FileObject && FileObject->FsContext) {
         LONG oc;
+        ccb* ccb;
+        file_ref* fileref;
         
         fcb = FileObject->FsContext;
+        ccb = FileObject->FsContext2;
+        fileref = ccb ? ccb->fileref : NULL;
         
         TRACE("cleanup called for FileObject %p\n", FileObject);
-        TRACE("fcb %p (%.*S), refcount = %u, open_count = %u\n", fcb, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb->refcount, fcb->open_count);
+        TRACE("fcb %p (%S), refcount = %u, open_count = %u\n", fcb, file_desc(FileObject), fcb->refcount, fcb->open_count);
         
         IoRemoveShareAccess(FileObject, &fcb->share_access);
         
@@ -2073,14 +2461,20 @@ static NTSTATUS STDCALL drv_cleanup(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
         ERR("fcb %p: open_count now %i\n", fcb, oc);
 #endif
         
+        if (ccb && ccb->options & FILE_DELETE_ON_CLOSE && fileref)
+            fileref->delete_on_close = TRUE;
+        
+        if (fileref && fileref->delete_on_close && fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0)
+            fileref->delete_on_close = FALSE;
+        
         if (oc == 0) {
-            if (fcb->delete_on_close && fcb != fcb->Vcb->root_fcb && fcb != fcb->Vcb->volume_fcb) {
+            if (fileref && fileref->delete_on_close && fileref != fcb->Vcb->root_fileref && fcb != fcb->Vcb->volume_fcb) {
                 LIST_ENTRY rollback;
                 InitializeListHead(&rollback);
                 
                 acquire_tree_lock(fcb->Vcb, TRUE);
                 
-                Status = delete_fcb(fcb, FileObject, &rollback);
+                Status = delete_fileref(fileref, FileObject, &rollback);
                 
                 if (NT_SUCCESS(Status)) {
                     LARGE_INTEGER newlength;
@@ -2144,6 +2538,8 @@ ULONG STDCALL get_file_attributes(device_extension* Vcb, INODE_ITEM* ii, root* r
     char* eaval;
     UINT16 ealen;
     
+    // ii can be NULL
+    
     if (!ignore_xa && get_xattr(Vcb, r, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8**)&eaval, &ealen)) {
         if (ealen > 2) {
             if (eaval[0] == '0' && eaval[1] == 'x') {
@@ -2165,6 +2561,11 @@ ULONG STDCALL get_file_attributes(device_extension* Vcb, INODE_ITEM* ii, root* r
 
                 ExFreePool(eaval);
                 
+                if (type == BTRFS_TYPE_DIRECTORY)
+                    dosnum |= FILE_ATTRIBUTE_DIRECTORY;
+                else if (type == BTRFS_TYPE_SYMLINK)
+                    dosnum |= FILE_ATTRIBUTE_REPARSE_POINT;
+                
                 return dosnum;
             }
         }
@@ -2359,8 +2760,8 @@ static NTSTATUS STDCALL read_superblock(device_extension* Vcb, PDEVICE_OBJECT de
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL dev_ioctl(PDEVICE_OBJECT DeviceObject, ULONG ControlCode, PVOID InputBuffer,
-                    ULONG InputBufferSize, PVOID OutputBuffer, ULONG OutputBufferSize, BOOLEAN Override)
+NTSTATUS STDCALL dev_ioctl(PDEVICE_OBJECT DeviceObject, ULONG ControlCode, PVOID InputBuffer, ULONG InputBufferSize,
+                           PVOID OutputBuffer, ULONG OutputBufferSize, BOOLEAN Override, IO_STATUS_BLOCK* iosb)
 {
     PIRP Irp;
     KEVENT Event;
@@ -2393,6 +2794,9 @@ static NTSTATUS STDCALL dev_ioctl(PDEVICE_OBJECT DeviceObject, ULONG ControlCode
         KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
         Status = IoStatus.Status;
     }
+    
+    if (iosb)
+        *iosb = IoStatus;
 
     return Status;
 }
@@ -2430,11 +2834,11 @@ static NTSTATUS STDCALL add_root(device_extension* Vcb, UINT64 id, UINT64 addr,
     }
     
     r->id = id;
+    r->path.Buffer = NULL;
     r->treeholder.address = addr;
     r->treeholder.tree = NULL;
     init_tree_holder(&r->treeholder);
-    r->prev = NULL;
-    r->next = Vcb->roots;
+    InitializeListHead(&r->fcbs);
 
     r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);
     if (!r->nonpaged) {
@@ -2453,10 +2857,7 @@ static NTSTATUS STDCALL add_root(device_extension* Vcb, UINT64 id, UINT64 addr,
             RtlZeroMemory(((UINT8*)&r->root_item) + tp->item->size, sizeof(ROOT_ITEM) - tp->item->size);
     }
     
-    if (Vcb->roots)
-        Vcb->roots->prev = r;
-    
-    Vcb->roots = r;
+    InsertTailList(&Vcb->roots, &r->list_entry);
     
     switch (r->id) {
         case BTRFS_ROOT_ROOT:
@@ -2478,6 +2879,10 @@ static NTSTATUS STDCALL add_root(device_extension* Vcb, UINT64 id, UINT64 addr,
         case BTRFS_ROOT_CHECKSUM:
             Vcb->checksum_root = r;
             break;
+            
+        case BTRFS_ROOT_UUID:
+            Vcb->uuid_root = r;
+            break;
     }
     
     return STATUS_SUCCESS;
@@ -2520,14 +2925,10 @@ static NTSTATUS STDCALL look_for_roots(device_extension* Vcb) {
     
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         
-        if (b) {
-            free_traverse_ptr(&tp);
+        if (b)
             tp = next_tp;
-        }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
@@ -2591,15 +2992,12 @@ static NTSTATUS find_disk_holes(device_extension* Vcb, device* dev) {
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
                 break;
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     if (lastaddr < dev->devitem.num_bytes) {
         Status = add_disk_hole(&dev->disk_holes, lastaddr, dev->devitem.num_bytes - lastaddr);
         if (!NT_SUCCESS(Status)) {
@@ -2674,6 +3072,8 @@ static NTSTATUS STDCALL load_chunk_root(device_extension* Vcb) {
                 c->offset = tp.item->key.offset;
                 c->used = c->oldused = 0;
                 c->space_changed = FALSE;
+                c->cache_inode = 0;
+                c->cache_size = 0;
                 
                 c->chunk_item = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
                 
@@ -2709,317 +3109,18 @@ static NTSTATUS STDCALL load_chunk_root(device_extension* Vcb) {
     
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         
-        if (b) {
-            free_traverse_ptr(&tp);
+        if (b)
             tp = next_tp;
-        }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     Vcb->log_to_phys_loaded = TRUE;
     
     return STATUS_SUCCESS;
 }
 
-static BOOL load_stored_free_space_cache(device_extension* Vcb, chunk* c) {
-    KEY searchkey;
-    traverse_ptr tp, tp2;
-    FREE_SPACE_ITEM* fsi;
-    UINT64 inode, num_sectors, i, generation;
-    INODE_ITEM* ii;
-    UINT8* data;
-    NTSTATUS Status;
-    UINT32 *checksums, crc32;
-#ifdef _DEBUG
-    FREE_SPACE_ENTRY* fse;
-    UINT64 num_entries;
-#endif
-    
-    TRACE("(%p, %llx)\n", Vcb, c->offset);
-    
-    if (Vcb->superblock.generation != Vcb->superblock.cache_generation)
-        return FALSE;
-    
-    searchkey.obj_id = FREE_SPACE_CACHE_ID;
-    searchkey.obj_type = 0;
-    searchkey.offset = c->offset;
-    
-    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return FALSE;
-    }
-    
-    if (keycmp(&tp.item->key, &searchkey)) {
-        WARN("(%llx,%x,%llx) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
-        free_traverse_ptr(&tp);
-        return FALSE;
-    }
-    
-    if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {
-        WARN("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));
-        free_traverse_ptr(&tp);
-        return FALSE;
-    }
-    
-    fsi = (FREE_SPACE_ITEM*)tp.item->data;
-    
-    if (fsi->generation != Vcb->superblock.cache_generation) {
-        WARN("cache had generation %llx, expecting %llx\n", fsi->generation, Vcb->superblock.cache_generation);
-        free_traverse_ptr(&tp);
-        return FALSE;
-    }
-    
-    if (fsi->key.obj_type != TYPE_INODE_ITEM) {
-        WARN("cache pointed to something other than an INODE_ITEM\n");
-        free_traverse_ptr(&tp);
-        return FALSE;
-    }
-    
-    if (fsi->num_bitmaps > 0) {
-        WARN("cache had bitmaps, unsure of how to deal with these\n");
-        free_traverse_ptr(&tp);
-        return FALSE;
-    }
-    
-    inode = fsi->key.obj_id;
-    
-    searchkey = fsi->key;
-#ifdef _DEBUG
-    num_entries = fsi->num_entries;
-#endif
-    
-    Status = find_item(Vcb, Vcb->root_root, &tp2, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        free_traverse_ptr(&tp);
-        return FALSE;
-    }
-    
-    free_traverse_ptr(&tp);
-    
-    if (keycmp(&tp2.item->key, &searchkey)) {
-        WARN("(%llx,%x,%llx) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
-        free_traverse_ptr(&tp2);
-        return FALSE;
-    }
-    
-    if (tp2.item->size < sizeof(INODE_ITEM)) {
-        WARN("(%llx,%x,%llx) was %u bytes, expected %u\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, sizeof(INODE_ITEM));
-        free_traverse_ptr(&tp2);
-        return FALSE;
-    }
-    
-    ii = (INODE_ITEM*)tp2.item->data;
-    
-    data = ExAllocatePoolWithTag(PagedPool, ii->st_size, ALLOC_TAG);
-    
-    if (!data) {
-        ERR("out of memory\n");
-        free_traverse_ptr(&tp2);
-        return FALSE;
-    }
-    
-    Status = read_file(Vcb, Vcb->root_root, inode, data, 0, ii->st_size, NULL);
-    if (!NT_SUCCESS(Status)) {
-        ERR("read_file returned %08x\n", Status);
-        ExFreePool(data);
-        free_traverse_ptr(&tp2);
-        return FALSE;
-    }
-    
-    num_sectors = ii->st_size / Vcb->superblock.sector_size;
-    
-    generation = *(data + (num_sectors * sizeof(UINT32)));
-    
-    if (generation != Vcb->superblock.cache_generation) {
-        ERR("generation was %llx, expected %llx\n", generation, Vcb->superblock.cache_generation);
-        ExFreePool(data);
-        free_traverse_ptr(&tp2);
-        return FALSE;
-    }
-    
-    checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * num_sectors, ALLOC_TAG); // FIXME - get rid of this
-    
-    if (!checksums) {
-        ERR("out of memory\n");
-        ExFreePool(data);
-        free_traverse_ptr(&tp2);
-        return FALSE;
-    }
-    
-    RtlCopyMemory(checksums, data, sizeof(UINT32) * num_sectors);
-    
-    for (i = 0; i < num_sectors; i++) {
-        if (i * Vcb->superblock.sector_size > sizeof(UINT32) * num_sectors)
-            crc32 = ~calc_crc32c(0xffffffff, &data[i * Vcb->superblock.sector_size], Vcb->superblock.sector_size);
-        else if ((i + 1) * Vcb->superblock.sector_size < sizeof(UINT32) * num_sectors)
-            crc32 = 0; // FIXME - test this
-        else
-            crc32 = ~calc_crc32c(0xffffffff, &data[sizeof(UINT32) * num_sectors], ((i + 1) * Vcb->superblock.sector_size) - (sizeof(UINT32) * num_sectors));
-        
-        if (crc32 != checksums[i]) {
-            WARN("checksum %llu was %08x, expected %08x\n", i, crc32, checksums[i]);
-            ExFreePool(checksums);
-            ExFreePool(data);
-            free_traverse_ptr(&tp2);
-            return FALSE;
-        }
-    }
-    
-    ExFreePool(checksums);
-    
-#ifdef _DEBUG
-    fse = (FREE_SPACE_ENTRY*)&data[(sizeof(UINT32) * num_sectors) + sizeof(UINT64)];
-
-    for (i = 0; i < num_entries; i++) {
-        TRACE("(%llx,%llx,%x)\n", fse[i].offset, fse[i].size, fse[i].type);
-    }
-#endif
-    
-    FIXME("FIXME - read cache\n");
-    
-    ExFreePool(data);
-    free_traverse_ptr(&tp2);
-    
-    return FALSE;
-}
-
-static NTSTATUS load_free_space_cache(device_extension* Vcb, chunk* c) {
-    traverse_ptr tp, next_tp;
-    KEY searchkey;
-    UINT64 lastaddr;
-    BOOL b;
-    space *s, *s2;
-    LIST_ENTRY* le;
-    NTSTATUS Status;
-    
-    load_stored_free_space_cache(Vcb, c);
-    
-    TRACE("generating free space cache for chunk %llx\n", c->offset);
-    
-    searchkey.obj_id = c->offset;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = 0;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return Status;
-    }
-    
-    lastaddr = c->offset;
-    
-    do {
-        if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)
-            break;
-        
-        if (tp.item->key.obj_id >= c->offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) {
-            if (tp.item->key.obj_id > lastaddr) {
-                s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
-                
-                if (!s) {
-                    ERR("out of memory\n");
-                    return STATUS_INSUFFICIENT_RESOURCES;
-                }
-                
-                s->offset = lastaddr;
-                s->size = tp.item->key.obj_id - lastaddr;
-                s->type = SPACE_TYPE_FREE;
-                InsertTailList(&c->space, &s->list_entry);
-                
-                TRACE("(%llx,%llx)\n", s->offset, s->size);
-            }
-            
-            if (tp.item->key.obj_type == TYPE_METADATA_ITEM)
-                lastaddr = tp.item->key.obj_id + Vcb->superblock.node_size;
-            else
-                lastaddr = tp.item->key.obj_id + tp.item->key.offset;
-        }
-        
-        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
-        if (b) {
-            free_traverse_ptr(&tp);
-            tp = next_tp;
-        }
-    } while (b);
-    
-    if (lastaddr < c->offset + c->chunk_item->size) {
-        s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
-        
-        if (!s) {
-            ERR("out of memory\n");
-            return STATUS_INSUFFICIENT_RESOURCES;
-        }
-        
-        s->offset = lastaddr;
-        s->size = c->offset + c->chunk_item->size - lastaddr;
-        s->type = SPACE_TYPE_FREE;
-        InsertTailList(&c->space, &s->list_entry);
-        
-        TRACE("(%llx,%llx)\n", s->offset, s->size);
-    }
-    
-    free_traverse_ptr(&tp);
-    
-    // add allocated space
-    
-    lastaddr = c->offset;
-    
-    le = c->space.Flink;
-    while (le != &c->space) {
-        s = CONTAINING_RECORD(le, space, list_entry);
-        
-        if (s->offset > lastaddr) {
-            s2 = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
-            
-            if (!s2) {
-                ERR("out of memory\n");
-                return STATUS_INSUFFICIENT_RESOURCES;
-            }
-            
-            s2->offset = lastaddr;
-            s2->size = s->offset - lastaddr;
-            s2->type = SPACE_TYPE_USED;
-            
-            InsertTailList(&s->list_entry, &s2->list_entry);
-        }
-        
-        lastaddr = s->offset + s->size;
-        
-        le = le->Flink;
-    }
-    
-    if (lastaddr < c->offset + c->chunk_item->size) {
-        s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
-        
-        if (!s) {
-            ERR("out of memory\n");
-            return STATUS_INSUFFICIENT_RESOURCES;
-        }
-        
-        s->offset = lastaddr;
-        s->size = c->offset + c->chunk_item->size - lastaddr;
-        s->type = SPACE_TYPE_USED;
-        InsertTailList(&c->space, &s->list_entry);
-    }
-    
-    le = c->space.Flink;
-    while (le != &c->space) {
-        s = CONTAINING_RECORD(le, space, list_entry);
-        
-        TRACE("%llx,%llx,%u\n", s->offset, s->size, s->type);
-        
-        le = le->Flink;
-    }
-    
-    return STATUS_SUCCESS;
-}
-
 void protect_superblocks(device_extension* Vcb, chunk* c) {
     int i = 0, j;
-    UINT64 addr;
+    UINT64 off_start, off_end;
     
     // FIXME - this will need modifying for RAID
     
@@ -3029,23 +3130,14 @@ void protect_superblocks(device_extension* Vcb, chunk* c) {
         
         for (j = 0; j < ci->num_stripes; j++) {
             if (cis[j].offset + ci->size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {
-                UINT32 size;
-                
                 TRACE("cut out superblock in chunk %llx\n", c->offset);
                 
-                addr = (superblock_addrs[i] - cis[j].offset) + c->offset;
-                TRACE("addr %llx\n", addr);
+                // The Linux driver protects the whole stripe in which the superblock lives
                 
-                // This prevents trees from spanning a stripe boundary, which btrfs check complains
-                // about. It also prevents the chunk tree being placed at 0x11000, which for some
-                // reason makes the FS unmountable on Linux (it tries to read 0x10000, i.e. the 
-                // superblock, instead).
-                if (ci->type & BLOCK_FLAG_SYSTEM || ci->type & BLOCK_FLAG_METADATA)
-                    size = max(sizeof(superblock), Vcb->superblock.node_size);
-                else
-                    size = sizeof(superblock);
+                off_start = ((superblock_addrs[i] - cis[j].offset) / c->chunk_item->stripe_length) * c->chunk_item->stripe_length;
+                off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), c->chunk_item->stripe_length);
                 
-                add_to_space_list(c, addr, size, SPACE_TYPE_USED);
+                add_to_space_list(c, c->offset + off_start, off_end - off_start, SPACE_TYPE_USED);
             }
         }
         
@@ -3091,7 +3183,6 @@ static NTSTATUS STDCALL find_chunk_usage(device_extension* Vcb) {
             }
         }
         
-        free_traverse_ptr(&tp);
 //         if (addr >= c->offset && (addr - c->offset) < c->chunk_item->size && c->chunk_item->num_stripes > 0) {
 //             cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
 // 
@@ -3218,6 +3309,7 @@ static root* find_default_subvol(device_extension* Vcb) {
     UINT64 inode;
     UINT8 type;
     UNICODE_STRING filename;
+    LIST_ENTRY* le;
     
     static WCHAR fn[] = L"default";
     static UINT32 crc32 = 0x8dbfc2d2;
@@ -3232,11 +3324,51 @@ static root* find_default_subvol(device_extension* Vcb) {
             return subvol;
     }
     
-    subvol = Vcb->roots;
-    while (subvol && subvol->id != BTRFS_ROOT_FSTREE)
-        subvol = subvol->next;
+    le = Vcb->roots.Flink;
+    while (le != &Vcb->roots) {
+        root* r = CONTAINING_RECORD(le, root, list_entry);
+        
+        if (r->id == BTRFS_ROOT_FSTREE)
+            return r;
+        
+        le = le->Flink;
+    }
     
-    return subvol;
+    return NULL;
+}
+
+static BOOL is_device_removable(PDEVICE_OBJECT devobj) {
+    NTSTATUS Status;
+    STORAGE_HOTPLUG_INFO shi;
+    
+    Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_HOTPLUG_INFO, NULL, 0, &shi, sizeof(STORAGE_HOTPLUG_INFO), TRUE, NULL);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("dev_ioctl returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    return shi.MediaRemovable != 0 ? TRUE : FALSE;
+}
+
+static ULONG get_device_change_count(PDEVICE_OBJECT devobj) {
+    NTSTATUS Status;
+    ULONG cc;
+    IO_STATUS_BLOCK iosb;
+    
+    Status = dev_ioctl(devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), TRUE, &iosb);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("dev_ioctl returned %08x\n", Status);
+        return 0;
+    }
+    
+    if (iosb.Information < sizeof(ULONG)) {
+        ERR("iosb.Information was too short\n");
+        return 0;
+    }
+    
+    return cc;
 }
 
 static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
@@ -3250,6 +3382,7 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     LIST_ENTRY* le;
     KEY searchkey;
     traverse_ptr tp;
+    fcb* root_fcb = NULL;
     
     TRACE("mount_vol called\n");
     
@@ -3269,9 +3402,10 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
 //     }
 
     Status = dev_ioctl(DeviceToMount, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0,
-                       &piex, sizeof(piex), TRUE);
+                       &piex, sizeof(piex), TRUE, NULL);
     if (!NT_SUCCESS(Status)) {
         ERR("error reading partition information: %08x\n", Status);
+        Status = STATUS_UNRECOGNIZED_VOLUME;
         goto exit;
     }
 
@@ -3282,8 +3416,11 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                             0,
                             FALSE,
                             &NewDeviceObject);
-    if (!NT_SUCCESS(Status))
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoCreateDevice returned %08x\n", Status);
+        Status = STATUS_UNRECOGNIZED_VOLUME;
         goto exit;
+    }
     
 //     TRACE("DEV_ITEM = %x, superblock = %x\n", sizeof(DEV_ITEM), sizeof(superblock));
 
@@ -3291,8 +3428,6 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     Vcb = (PVOID)NewDeviceObject->DeviceExtension;
     RtlZeroMemory(Vcb, sizeof(device_extension));
     
-    InitializeListHead(&Vcb->tree_cache);
-    
     ExInitializeResourceLite(&Vcb->tree_lock);
     Vcb->tree_lock_counter = 0;
     Vcb->open_trees = 0;
@@ -3392,6 +3527,8 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     
     Vcb->devices[0].devobj = DeviceToMount;
     RtlCopyMemory(&Vcb->devices[0].devitem, &Vcb->superblock.dev_item, sizeof(DEV_ITEM));
+    Vcb->devices[0].removable = is_device_removable(Vcb->devices[0].devobj);
+    Vcb->devices[0].change_count = Vcb->devices[0].removable ? get_device_change_count(Vcb->devices[0].devobj) : 0;
     
     if (Vcb->superblock.num_devices > 1)
         RtlZeroMemory(&Vcb->devices[1], sizeof(DEV_ITEM) * (Vcb->superblock.num_devices - 1));
@@ -3409,7 +3546,9 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     
 //     Vcb->root_tree_phys_addr = logical_to_physical(Vcb, Vcb->superblock.root_tree_addr);
     
-    Vcb->roots = NULL;
+    InitializeListHead(&Vcb->roots);
+    InitializeListHead(&Vcb->drop_roots);
+    
     Vcb->log_to_phys_loaded = FALSE;
     
     Vcb->max_inline = Vcb->superblock.node_size / 2;
@@ -3463,6 +3602,16 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         ERR("find_chunk_usage returned %08x\n", Status);
         goto exit;
     }
+        
+    // We've already increased the generation by one
+    if (!Vcb->readonly && Vcb->superblock.generation - 1 != Vcb->superblock.cache_generation) {
+        WARN("generation was %llx, free-space cache generation was %llx; clearing cache...\n", Vcb->superblock.generation - 1, Vcb->superblock.cache_generation);
+        Status = clear_free_space_cache(Vcb);
+        if (!NT_SUCCESS(Status)) {
+            ERR("clear_free_space_cache returned %08x\n", Status);
+            goto exit;
+        }
+    }
     
     Vcb->volume_fcb = create_fcb();
     if (!Vcb->volume_fcb) {
@@ -3474,48 +3623,35 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     Vcb->volume_fcb->Vcb = Vcb;
     Vcb->volume_fcb->sd = NULL;
     
-    Vcb->root_fcb = create_fcb();
-    if (!Vcb->root_fcb) {
-        ERR("out of memory\n");
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto exit;
-    }
-    
-    Vcb->root_fcb->Vcb = Vcb;
-    Vcb->root_fcb->inode = SUBVOL_ROOT_INODE;
-    Vcb->root_fcb->type = BTRFS_TYPE_DIRECTORY;
-    
-    Vcb->root_fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, sizeof(WCHAR), ALLOC_TAG);
-    
-    if (!Vcb->root_fcb->full_filename.Buffer) {
+    root_fcb = create_fcb();
+    if (!root_fcb) {
         ERR("out of memory\n");
         Status = STATUS_INSUFFICIENT_RESOURCES;
         goto exit;
     }
     
-    Vcb->root_fcb->full_filename.Buffer[0] = '\\';
-    Vcb->root_fcb->full_filename.Length = Vcb->root_fcb->full_filename.MaximumLength = sizeof(WCHAR);
+    root_fcb->Vcb = Vcb;
+    root_fcb->inode = SUBVOL_ROOT_INODE;
+    root_fcb->type = BTRFS_TYPE_DIRECTORY;
     
 #ifdef DEBUG_FCB_REFCOUNTS
     WARN("volume FCB = %p\n", Vcb->volume_fcb);
-    WARN("root FCB = %p\n", Vcb->root_fcb);
+    WARN("root FCB = %p\n", root_fcb);
 #endif
     
-    Vcb->root_fcb->subvol = find_default_subvol(Vcb);
+    root_fcb->subvol = find_default_subvol(Vcb);
 
-    if (!Vcb->root_fcb->subvol) {
+    if (!root_fcb->subvol) {
         ERR("could not find top subvol\n");
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
     }
     
-    Vcb->fcbs = Vcb->root_fcb;
-    
-    searchkey.obj_id = Vcb->root_fcb->inode;
+    searchkey.obj_id = root_fcb->inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
     searchkey.offset = 0xffffffffffffffff;
     
-    Status = find_item(Vcb, Vcb->root_fcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, root_fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         goto exit;
@@ -3524,20 +3660,37 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
         ERR("couldn't find INODE_ITEM for root directory\n");
         Status = STATUS_INTERNAL_ERROR;
-        free_traverse_ptr(&tp);
         goto exit;
     }
     
     if (tp.item->size > 0)
-        RtlCopyMemory(&Vcb->root_fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
+        RtlCopyMemory(&root_fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
     
-    free_traverse_ptr(&tp);
+    fcb_get_sd(root_fcb, NULL);
     
-    fcb_get_sd(Vcb->root_fcb);
+    root_fcb->atts = get_file_attributes(Vcb, &root_fcb->inode_item, root_fcb->subvol, root_fcb->inode, root_fcb->type, FALSE, FALSE);
     
-    Vcb->root_fcb->atts = get_file_attributes(Vcb, &Vcb->root_fcb->inode_item, Vcb->root_fcb->subvol, Vcb->root_fcb->inode, Vcb->root_fcb->type,
-                                              FALSE, FALSE);
-      
+    Vcb->root_fileref = create_fileref();
+    if (!Vcb->root_fileref) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto exit;
+    }
+    
+    Vcb->root_fileref->fcb = root_fcb;
+    InsertTailList(&root_fcb->subvol->fcbs, &root_fcb->list_entry);
+    
+    Vcb->root_fileref->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, sizeof(WCHAR), ALLOC_TAG);
+    
+    if (!Vcb->root_fileref->full_filename.Buffer) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto exit;
+    }
+    
+    Vcb->root_fileref->full_filename.Buffer[0] = '\\';
+    Vcb->root_fileref->full_filename.Length = Vcb->root_fileref->full_filename.MaximumLength = sizeof(WCHAR);
+
     for (i = 0; i < Vcb->superblock.num_devices; i++) {
         Status = find_disk_holes(Vcb, &Vcb->devices[i]);
         if (!NT_SUCCESS(Status)) {
@@ -3607,12 +3760,6 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
 //     RtlCopyMemory(NewDeviceObject->Vpb->VolumeLabel,
 //                   Vcb->NtfsInfo.VolumeLabel,
 //                   Vcb->NtfsInfo.VolumeLabelLength);
-    
-    Status = PsCreateSystemThread(&Vcb->flush_thread_handle, 0, NULL, NULL, NULL, flush_thread, Vcb);
-    if (!NT_SUCCESS(Status)) {
-        ERR("PsCreateSystemThread returned %08x\n", Status);
-        goto exit;
-    }
 
     NewDeviceObject->Vpb = Stack->Parameters.MountVolume.Vpb;
     Stack->Parameters.MountVolume.Vpb->DeviceObject = NewDeviceObject;
@@ -3623,6 +3770,12 @@ static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     NewDeviceObject->Vpb->VolumeLabel[1] = 0;
     NewDeviceObject->Vpb->ReferenceCount++; // FIXME - should we deref this at any point?
     
+    Status = PsCreateSystemThread(&Vcb->flush_thread_handle, 0, NULL, NULL, NULL, flush_thread, NewDeviceObject);
+    if (!NT_SUCCESS(Status)) {
+        ERR("PsCreateSystemThread returned %08x\n", Status);
+        goto exit;
+    }
+    
     Status = STATUS_SUCCESS;
 
 exit:
@@ -3648,8 +3801,10 @@ exit:
 
     if (!NT_SUCCESS(Status)) {
         if (Vcb) {
-            if (Vcb->root_fcb)
-                free_fcb(Vcb->root_fcb);
+            if (Vcb->root_fileref)
+                free_fileref(Vcb->root_fileref);
+            else if (root_fcb)
+                free_fcb(root_fcb);
 
             if (Vcb->volume_fcb)
                 free_fcb(Vcb->volume_fcb);
@@ -3707,10 +3862,8 @@ static NTSTATUS STDCALL drv_file_system_control(IN PDEVICE_OBJECT DeviceObject,
             
         case IRP_MN_KERNEL_CALL:
             TRACE("IRP_MN_KERNEL_CALL\n");
-            break;
             
-        case IRP_MN_LOAD_FILE_SYSTEM:
-            TRACE("IRP_MN_LOAD_FILE_SYSTEM\n");
+            status = fsctl_request(DeviceObject, Irp, IrpSp->Parameters.FileSystemControl.FsControlCode, FALSE);
             break;
             
         case IRP_MN_USER_FS_REQUEST:
@@ -3720,13 +3873,11 @@ static NTSTATUS STDCALL drv_file_system_control(IN PDEVICE_OBJECT DeviceObject,
             break;
             
         case IRP_MN_VERIFY_VOLUME:
-            TRACE("IRP_MN_VERIFY_VOLUME\n");
+            FIXME("STUB: IRP_MN_VERIFY_VOLUME\n");
             break;
            
         default:
-            WARN("unknown minor %u\n", IrpSp->MinorFunction);
             break;
-            
     }
 
     Irp->IoStatus.Status = status;
@@ -3754,6 +3905,8 @@ static NTSTATUS STDCALL drv_lock_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP
     TRACE("lock control\n");
     
     Status = FsRtlProcessFileLock(&fcb->lock, Irp, NULL);
+
+    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
     
     if (top_level) 
         IoSetTopLevelIrp(NULL);
@@ -3800,7 +3953,7 @@ static NTSTATUS STDCALL drv_device_control(IN PDEVICE_OBJECT DeviceObject, IN PI
         FIXME("FIXME - pass through\n");
         Status = STATUS_NOT_IMPLEMENTED;
     } else {
-        TRACE("filename = %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+        TRACE("filename = %S\n", file_desc(FileObject));
         
         switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {
             case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME:
@@ -3849,7 +4002,7 @@ static NTSTATUS STDCALL drv_shutdown(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp
         
         TRACE("shutting down Vcb %p\n", Vcb);
         
-        uninit(Vcb);
+        uninit(Vcb, TRUE);
     }
     
     Irp->IoStatus.Status = Status;
@@ -3865,65 +4018,25 @@ static NTSTATUS STDCALL drv_shutdown(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp
     return Status;
 }
 
-static NTSTATUS STDCALL drv_pnp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
-    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
-    device_extension* Vcb = DeviceObject->DeviceExtension;
-    NTSTATUS Status;
-    BOOL top_level;
-
-    FIXME("STUB: pnp\n");
+BOOL is_file_name_valid(PUNICODE_STRING us) {
+    ULONG i;
     
-    FsRtlEnterFileSystem();
-
-    top_level = is_top_level(Irp);
+    if (us->Length < sizeof(WCHAR))
+        return FALSE;
     
-    Status = STATUS_NOT_IMPLEMENTED;
+    if (us->Length > 255 * sizeof(WCHAR))
+        return FALSE;
     
-    switch (IrpSp->MinorFunction) {
-        case IRP_MN_CANCEL_REMOVE_DEVICE:
-            TRACE("    IRP_MN_CANCEL_REMOVE_DEVICE\n");
-            break;
-
-        case IRP_MN_QUERY_REMOVE_DEVICE:
-            TRACE("    IRP_MN_QUERY_REMOVE_DEVICE\n");
-            break;
-
-        case IRP_MN_REMOVE_DEVICE:
-            TRACE("    IRP_MN_REMOVE_DEVICE\n");
-            break;
-
-        case IRP_MN_START_DEVICE:
-            TRACE("    IRP_MN_START_DEVICE\n");
-            break;
-
-        case IRP_MN_SURPRISE_REMOVAL:
-            TRACE("    IRP_MN_SURPRISE_REMOVAL\n");
-            break;
-            
-        case IRP_MN_QUERY_DEVICE_RELATIONS:
-            TRACE("    IRP_MN_QUERY_DEVICE_RELATIONS\n");
-            break;
-        
-        default:
-            WARN("Unrecognized minor function 0x%x\n", IrpSp->MinorFunction);
-            break;
+    for (i = 0; i < us->Length / sizeof(WCHAR); i++) {
+        if (us->Buffer[i] == '/' || us->Buffer[i] == '<' || us->Buffer[i] == '>' || us->Buffer[i] == ':' || us->Buffer[i] == '"' ||
+            us->Buffer[i] == '|' || us->Buffer[i] == '?' || us->Buffer[i] == '*' || (us->Buffer[i] >= 1 && us->Buffer[i] <= 31))
+            return FALSE;
     }
-
-//     Irp->IoStatus.Status = Status;
-//     Irp->IoStatus.Information = 0;
-
-    IoSkipCurrentIrpStackLocation(Irp);
     
-    Status = IoCallDriver(Vcb->devices[0].devobj, Irp);
-
-//     IoCompleteRequest(Irp, IO_NO_INCREMENT);
-    
-    if (top_level) 
-        IoSetTopLevelIrp(NULL);
+    if (us->Buffer[0] == '.' && (us->Length == sizeof(WCHAR) || (us->Length == 2 * sizeof(WCHAR) && us->Buffer[1] == '.')))
+        return FALSE;
     
-    FsRtlExitFileSystem();
-
-    return Status;
+    return TRUE;
 }
 
 #ifdef _DEBUG
index ad5ef71..b3a2398 100644 (file)
@@ -37,6 +37,7 @@ static const UINT64 superblock_addrs[] = { 0x10000, 0x4000000, 0x4000000000, 0x4
 #define TYPE_DEV_EXTENT        0xCC
 #define TYPE_DEV_ITEM          0xD8
 #define TYPE_CHUNK_ITEM        0xE4
+#define TYPE_SUBVOL_UUID       0xFB
 
 #define BTRFS_ROOT_ROOT         1
 #define BTRFS_ROOT_EXTENT       2
@@ -44,6 +45,7 @@ static const UINT64 superblock_addrs[] = { 0x10000, 0x4000000, 0x4000000000, 0x4
 #define BTRFS_ROOT_DEVTREE      4
 #define BTRFS_ROOT_FSTREE       5
 #define BTRFS_ROOT_CHECKSUM     7
+#define BTRFS_ROOT_UUID         9
 
 #define BTRFS_COMPRESSION_NONE  0
 #define BTRFS_COMPRESSION_ZLIB  1
@@ -155,6 +157,33 @@ typedef struct {
 } DEV_ITEM;
 
 #define SYS_CHUNK_ARRAY_SIZE 0x800
+#define BTRFS_NUM_BACKUP_ROOTS 4
+
+typedef struct {
+    UINT64 root_tree_addr;
+    UINT64 root_tree_generation;
+    UINT64 chunk_tree_addr;
+    UINT64 chunk_tree_generation;
+    UINT64 extent_tree_addr;
+    UINT64 extent_tree_generation;
+    UINT64 fs_tree_addr;
+    UINT64 fs_tree_generation;
+    UINT64 dev_root_addr;
+    UINT64 dev_root_generation;
+    UINT64 csum_root_addr;
+    UINT64 csum_root_generation;
+    UINT64 total_bytes;
+    UINT64 bytes_used;
+    UINT64 num_devices;
+    UINT64 reserved[4];
+    UINT8 root_level;
+    UINT8 chunk_root_level;
+    UINT8 extent_root_level;
+    UINT8 fs_root_level;
+    UINT8 dev_root_level;
+    UINT8 csum_root_level;
+    UINT8 reserved2[10];
+} superblock_backup;
 
 typedef struct {
     UINT8 checksum[32];
@@ -190,8 +219,8 @@ typedef struct {
     UINT64 uuid_tree_generation;
     UINT64 reserved[30];
     UINT8 sys_chunk_array[SYS_CHUNK_ARRAY_SIZE];
-//     struct btrfs_root_backup super_roots[BTRFS_NUM_BACKUP_ROOTS];
-    UINT8 reserved2[1237];
+    superblock_backup backup[BTRFS_NUM_BACKUP_ROOTS];
+    UINT8 reserved2[565];
 } superblock;
 
 #define BTRFS_TYPE_UNKNOWN   0
@@ -324,6 +353,11 @@ typedef struct {
     UINT64 flags;
 } EXTENT_ITEM;
 
+typedef struct {
+    KEY firstitem;
+    UINT8 level;
+} EXTENT_ITEM2;
+
 typedef struct {
     UINT32 refcount;
 } EXTENT_ITEM_V0;
@@ -355,7 +389,7 @@ typedef struct {
     UINT64 root;
     UINT64 gen;
     UINT64 objid;
-    UINT64 count;
+    UINT32 count;
 } EXTENT_REF_V0;
 
 typedef struct {
index b4b5116..b3ba777 100644 (file)
@@ -70,12 +70,12 @@ BEGIN
         BLOCK "080904b0"
         BEGIN
             VALUE "FileDescription", "WinBtrfs"
-            VALUE "FileVersion", "0.2"
+            VALUE "FileVersion", "0.4"
             VALUE "InternalName", "btrfs"
             VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2016"
             VALUE "OriginalFilename", "btrfs.sys"
             VALUE "ProductName", "WinBtrfs"
-            VALUE "ProductVersion", "0.2"
+            VALUE "ProductVersion", "0.4"
         END
     END
     BLOCK "VarFileInfo"
index df44b60..0aae320 100644 (file)
@@ -36,6 +36,7 @@
 #ifdef __REACTOS__
 #include <rtlfuncs.h>
 #include <iotypes.h>
+#include <pseh/pseh2.h>
 #endif /* __REACTOS__ */
 //#include <windows.h>
 #include <windef.h>
@@ -46,7 +47,6 @@
 #include "btrfs.h"
 
 #ifdef _DEBUG
-// #define DEBUG_TREE_REFCOUNTS
 // #define DEBUG_FCB_REFCOUNTS
 // #define DEBUG_LONG_MESSAGES
 #define DEBUG_PARANOID
@@ -68,6 +68,9 @@
 #define EA_DOSATTRIB "user.DOSATTRIB"
 #define EA_DOSATTRIB_HASH 0x914f9939
 
+#define EA_REPARSE "system.reparse"
+#define EA_REPARSE_HASH 0x786f6167
+
 #define READ_AHEAD_GRANULARITY 0x10000 // 64 KB
 
 #ifdef _MSC_VER
@@ -108,26 +111,18 @@ typedef struct _fcb {
     struct _fcb_nonpaged* nonpaged;
     LONG refcount;
     LONG open_count;
-    UNICODE_STRING filepart;
-    ANSI_STRING utf8;
     struct _device_extension* Vcb;
-    struct _fcb* par;
-    struct _fcb* prev;
-    struct _fcb* next;
     struct _root* subvol;
-    LIST_ENTRY children;
     UINT64 inode;
     UINT8 type;
-    BOOL delete_on_close;
     INODE_ITEM inode_item;
-    UNICODE_STRING full_filename;
-    ULONG name_offset;
     SECURITY_DESCRIPTOR* sd;
     FILE_LOCK lock;
     BOOL deleted;
     PKTHREAD lazy_writer_thread;
     ULONG atts;
     SHARE_ACCESS share_access;
+    WCHAR* debug_desc;
     
     BOOL ads;
     UINT32 adssize;
@@ -137,6 +132,24 @@ typedef struct _fcb {
     LIST_ENTRY list_entry;
 } fcb;
 
+struct _file_ref;
+
+typedef struct _file_ref {
+    fcb* fcb;
+    UNICODE_STRING filepart;
+    ANSI_STRING utf8;
+    UNICODE_STRING full_filename;
+    ULONG name_offset;
+    BOOL delete_on_close;
+    BOOL deleted;
+    LIST_ENTRY children;
+    LONG refcount;
+    struct _file_ref* parent;
+    WCHAR* debug_desc;
+    
+    LIST_ENTRY list_entry;
+} file_ref;
+
 typedef struct _ccb {
     USHORT NodeType;
     CSHORT NodeSize;
@@ -147,6 +160,8 @@ typedef struct _ccb {
     UNICODE_STRING query_string;
     BOOL has_wildcard;
     BOOL specific_file;
+    ACCESS_MASK access;
+    file_ref* fileref;
 } ccb;
 
 // typedef struct _log_to_phys {
@@ -203,7 +218,6 @@ typedef struct _tree {
 //     UINT64 address;
 //     UINT8 level;
     tree_header header;
-    LONG refcount;
     BOOL has_address;
     UINT32 size;
     struct _device_extension* Vcb;
@@ -216,6 +230,7 @@ typedef struct _tree {
     UINT64 new_address;
     BOOL has_new_address;
     UINT64 flags;
+    BOOL write;
 } tree;
 
 typedef struct {
@@ -229,9 +244,9 @@ typedef struct _root {
     root_nonpaged* nonpaged;
     UINT64 lastinode;
     ROOT_ITEM root_item;
-    
-    struct _root* prev;
-    struct _root* next;
+    UNICODE_STRING path;
+    LIST_ENTRY fcbs;
+    LIST_ENTRY list_entry;
 } root;
 
 typedef struct {
@@ -239,12 +254,6 @@ typedef struct {
     tree_data* item;
 } traverse_ptr;
 
-typedef struct _tree_cache {
-    tree* tree;
-    BOOL write;
-    LIST_ENTRY list_entry;
-} tree_cache;
-
 typedef struct _root_cache {
     root* root;
     struct _root_cache* next;
@@ -272,6 +281,8 @@ typedef struct {
 typedef struct {
     PDEVICE_OBJECT devobj;
     DEV_ITEM devitem;
+    BOOL removable;
+    ULONG change_count;
     LIST_ENTRY disk_holes;
 } device;
 
@@ -283,6 +294,8 @@ typedef struct {
     UINT32 oldused;
     BOOL space_changed;
     device** devices;
+    UINT64 cache_size;
+    UINT64 cache_inode;
     LIST_ENTRY space;
     LIST_ENTRY list_entry;
 } chunk;
@@ -301,9 +314,9 @@ typedef struct _device_extension {
     superblock superblock;
 //     WCHAR label[MAX_LABEL_SIZE];
     BOOL readonly;
-    fcb* fcbs;
+    BOOL removing;
     fcb* volume_fcb;
-    fcb* root_fcb;
+    file_ref* root_fileref;
     ERESOURCE DirResource;
     KSPIN_LOCK FcbListLock;
     ERESOURCE fcb_lock;
@@ -318,20 +331,20 @@ typedef struct _device_extension {
 //     UINT64 chunk_root_phys_addr;
     UINT64 root_tree_phys_addr;
 //     log_to_phys* log_to_phys;
-    root* roots;
+    LIST_ENTRY roots;
+    LIST_ENTRY drop_roots;
     root* chunk_root;
     root* root_root;
     root* extent_root;
     root* checksum_root;
     root* dev_root;
+    root* uuid_root;
     BOOL log_to_phys_loaded;
     UINT32 max_inline;
     LIST_ENTRY sys_chunks;
     LIST_ENTRY chunks;
     LIST_ENTRY trees;
-    LIST_ENTRY tree_cache;
     HANDLE flush_thread_handle;
-    KTIMER flush_thread_timer;
     LIST_ENTRY list_entry;
 } device_extension;
 
@@ -341,6 +354,43 @@ typedef struct {
     UINT32 uid;
 } uid_map;
 
+typedef struct {
+    LIST_ENTRY list_entry;
+    UINT64 key;
+} ordered_list;
+
+typedef struct {
+    ordered_list ol;
+    ULONG length;
+    UINT32* checksums;
+    BOOL deleted;
+} changed_sector;
+
+enum write_tree_status {
+    WriteTreeStatus_Pending,
+    WriteTreeStatus_Success,
+    WriteTreeStatus_Error,
+    WriteTreeStatus_Cancelling,
+    WriteTreeStatus_Cancelled
+};
+
+struct write_tree_context;
+
+typedef struct {
+    struct write_tree_context* context;
+    UINT8* buf;
+    device* device;
+    PIRP Irp;
+    IO_STATUS_BLOCK iosb;
+    enum write_tree_status status;
+    LIST_ENTRY list_entry;
+} write_tree_stripe;
+
+typedef struct {
+    KEVENT Event;
+    LIST_ENTRY stripes;
+} write_tree_context;
+
 // #pragma pack(pop)
 
 static __inline void init_tree_holder(tree_holder* th) {
@@ -368,6 +418,27 @@ static __inline void win_time_to_unix(LARGE_INTEGER t, BTRFS_TIME* out) {
     out->nanoseconds = (l % 10000000) * 100;
 }
 
+static __inline void insert_into_ordered_list(LIST_ENTRY* list, ordered_list* ins) {
+    LIST_ENTRY* le = list->Flink;
+    ordered_list* ol;
+    
+    while (le != list) {
+        ol = (ordered_list*)le;
+        
+        if (ol->key > ins->key) {
+            le->Blink->Flink = &ins->list_entry;
+            ins->list_entry.Blink = le->Blink;
+            le->Blink = &ins->list_entry;
+            ins->list_entry.Flink = le;
+            return;
+        }
+        
+        le = le->Flink;
+    }
+    
+    InsertTailList(list, &ins->list_entry);
+}
+
 // in btrfs.c
 device* find_device_from_uuid(device_extension* Vcb, BTRFS_UUID* uuid);
 ULONG sector_align( ULONG NumberToBeAligned, ULONG Alignment );
@@ -377,15 +448,25 @@ BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char*
 NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char* name, UINT32 crc32, UINT8* data, UINT16 datalen, LIST_ENTRY* rollback);
 BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char* name, UINT32 crc32, LIST_ENTRY* rollback);
 void _free_fcb(fcb* fcb, const char* func, const char* file, unsigned int line);
+void _free_fileref(file_ref* fr, const char* func, const char* file, unsigned int line);
 BOOL STDCALL get_last_inode(device_extension* Vcb, root* r);
 NTSTATUS add_dir_item(device_extension* Vcb, root* subvol, UINT64 inode, UINT32 crc32, DIR_ITEM* di, ULONG disize, LIST_ENTRY* rollback);
 NTSTATUS delete_dir_item(device_extension* Vcb, root* subvol, UINT64 parinode, UINT32 crc32, PANSI_STRING utf8, LIST_ENTRY* rollback);
 UINT64 find_next_dir_index(device_extension* Vcb, root* subvol, UINT64 inode);
 NTSTATUS delete_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, PANSI_STRING utf8, UINT64* index, LIST_ENTRY* rollback);
-NTSTATUS delete_fcb(fcb* fcb, PFILE_OBJECT FileObject, LIST_ENTRY* rollback);
+NTSTATUS delete_fileref(file_ref* fileref, PFILE_OBJECT FileObject, LIST_ENTRY* rollback);
 fcb* create_fcb();
+file_ref* create_fileref();
 void protect_superblocks(device_extension* Vcb, chunk* c);
 BOOL is_top_level(PIRP Irp);
+NTSTATUS create_root(device_extension* Vcb, UINT64 id, root** rootptr, BOOL no_tree, UINT64 offset, LIST_ENTRY* rollback);
+void STDCALL uninit(device_extension* Vcb, BOOL flush);
+NTSTATUS STDCALL dev_ioctl(PDEVICE_OBJECT DeviceObject, ULONG ControlCode, PVOID InputBuffer,
+                           ULONG InputBufferSize, PVOID OutputBuffer, ULONG OutputBufferSize, BOOLEAN Override, IO_STATUS_BLOCK* iosb);
+BOOL is_file_name_valid(PUNICODE_STRING us);
+void send_notification_fileref(file_ref* fileref, ULONG filter_match, ULONG action);
+WCHAR* file_desc(PFILE_OBJECT FileObject);
+WCHAR* file_desc_fileref(file_ref* fileref);
 
 #ifdef _MSC_VER
 #define funcname __FUNCTION__
@@ -395,26 +476,34 @@ BOOL is_top_level(PIRP Irp);
 
 // FIXME - we probably shouldn't be moving funcname etc. around if we're not printing debug messages
 #define free_fcb(fcb) _free_fcb(fcb, funcname, __FILE__, __LINE__)
+#define free_fileref(fileref) _free_fileref(fileref, funcname, __FILE__, __LINE__)
 
 #ifdef _DEBUG
 
+extern BOOL log_started;
+extern UINT32 debug_log_level;
+
 #ifdef DEBUG_LONG_MESSAGES
 
-#define TRACE(s, ...) _debug_message(funcname, 3, __FILE__, __LINE__, s, ##__VA_ARGS__)
-#define WARN(s, ...) _debug_message(funcname, 2, __FILE__, __LINE__, s, ##__VA_ARGS__)
-#define FIXME(s, ...) _debug_message(funcname, 1, __FILE__, __LINE__, s, ##__VA_ARGS__)
-#define ERR(s, ...) _debug_message(funcname, 1, __FILE__, __LINE__, s, ##__VA_ARGS__)
+#define MSG(fn, file, line, s, level, ...) (!log_started || level <= debug_log_level) ? _debug_message(fn, file, line, s, ##__VA_ARGS__) : 0
 
-void STDCALL _debug_message(const char* func, UINT8 priority, const char* file, unsigned int line, char* s, ...);
+#define TRACE(s, ...) MSG(funcname, __FILE__, __LINE__, s, 3, ##__VA_ARGS__)
+#define WARN(s, ...) MSG(funcname, __FILE__, __LINE__, s, 2, ##__VA_ARGS__)
+#define FIXME(s, ...) MSG(funcname, __FILE__, __LINE__, s, 1, ##__VA_ARGS__)
+#define ERR(s, ...) MSG(funcname, __FILE__, __LINE__, s, 1, ##__VA_ARGS__)
+
+void STDCALL _debug_message(const char* func, const char* file, unsigned int line, char* s, ...);
 
 #else
 
-#define TRACE(s, ...) _debug_message(funcname, 3, s, ##__VA_ARGS__)
-#define WARN(s, ...) _debug_message(funcname, 2, s, ##__VA_ARGS__)
-#define FIXME(s, ...) _debug_message(funcname, 1, s, ##__VA_ARGS__)
-#define ERR(s, ...) _debug_message(funcname, 1, s, ##__VA_ARGS__)
+#define MSG(fn, s, level, ...) (!log_started || level <= debug_log_level) ? _debug_message(fn, s, ##__VA_ARGS__) : 0
+
+#define TRACE(s, ...) MSG(funcname, s, 3, ##__VA_ARGS__)
+#define WARN(s, ...) MSG(funcname, s, 2, ##__VA_ARGS__)
+#define FIXME(s, ...) MSG(funcname, s, 1, ##__VA_ARGS__)
+#define ERR(s, ...) MSG(funcname, s, 1, ##__VA_ARGS__)
 
-void STDCALL _debug_message(const char* func, UINT8 priority, char* s, ...);
+void STDCALL _debug_message(const char* func, char* s, ...);
 
 #endif
 
@@ -432,6 +521,12 @@ void STDCALL _debug_message(const char* func, UINT8 priority, char* s, ...);
 
 #endif
 
+static __inline void increase_chunk_usage(chunk* c, UINT64 delta) {
+    c->used += delta;
+    
+    TRACE("increasing size of chunk %llx by %llx\n", c->offset, delta);
+}
+
 // in fastio.c
 void STDCALL init_fast_io_dispatch(FAST_IO_DISPATCH** fiod);
 
@@ -442,23 +537,22 @@ UINT32 STDCALL calc_crc32c(UINT32 seed, UINT8* msg, ULONG msglen);
 NTSTATUS STDCALL _find_item(device_extension* Vcb, root* r, traverse_ptr* tp, const KEY* searchkey, BOOL ignore, const char* func, const char* file, unsigned int line);
 BOOL STDCALL _find_next_item(device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* next_tp, BOOL ignore, const char* func, const char* file, unsigned int line);
 BOOL STDCALL _find_prev_item(device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* prev_tp, BOOL ignore, const char* func, const char* file, unsigned int line);
-void STDCALL _free_traverse_ptr(traverse_ptr* tp, const char* func, const char* file, unsigned int line);
-void STDCALL free_tree_cache(LIST_ENTRY* tc);
+void STDCALL free_trees(device_extension* Vcb);
 BOOL STDCALL insert_tree_item(device_extension* Vcb, root* r, UINT64 obj_id, UINT8 obj_type, UINT64 offset, void* data, UINT32 size, traverse_ptr* ptp, LIST_ENTRY* rollback);
 void STDCALL delete_tree_item(device_extension* Vcb, traverse_ptr* tp, LIST_ENTRY* rollback);
-void STDCALL add_to_tree_cache(device_extension* Vcb, tree* t, BOOL write);
 tree* STDCALL _free_tree(tree* t, const char* func, const char* file, unsigned int line);
 NTSTATUS STDCALL _load_tree(device_extension* Vcb, UINT64 addr, root* r, tree** pt, const char* func, const char* file, unsigned int line);
 NTSTATUS STDCALL _do_load_tree(device_extension* Vcb, tree_holder* th, root* r, tree* t, tree_data* td, BOOL* loaded, const char* func, const char* file, unsigned int line);
 void clear_rollback(LIST_ENTRY* rollback);
 void do_rollback(device_extension* Vcb, LIST_ENTRY* rollback);
+void free_trees_root(device_extension* Vcb, root* r);
+NTSTATUS STDCALL read_tree(device_extension* Vcb, UINT64 addr, UINT8* buf);
 
 #define find_item(Vcb, r, tp, searchkey, ignore) _find_item(Vcb, r, tp, searchkey, ignore, funcname, __FILE__, __LINE__)
 #define find_next_item(Vcb, tp, next_tp, ignore) _find_next_item(Vcb, tp, next_tp, ignore, funcname, __FILE__, __LINE__)
 #define find_prev_item(Vcb, tp, prev_tp, ignore) _find_prev_item(Vcb, tp, prev_tp, ignore, funcname, __FILE__, __LINE__)
 #define free_tree(t) _free_tree(t, funcname, __FILE__, __LINE__)
 #define load_tree(t, addr, r, pt) _load_tree(t, addr, r, pt, funcname, __FILE__, __LINE__)
-#define free_traverse_ptr(tp) _free_traverse_ptr(tp, funcname, __FILE__, __LINE__)
 #define do_load_tree(Vcb, th, r, t, td, loaded) _do_load_tree(Vcb, th, r, t, td, loaded, funcname, __FILE__, __LINE__)  
 
 // in search.c
@@ -474,43 +568,55 @@ NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback);
 NTSTATUS write_file(PDEVICE_OBJECT DeviceObject, PIRP Irp);
 NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void* buf, ULONG* length, BOOL paging_io, BOOL no_cache, LIST_ENTRY* rollback);
 NTSTATUS truncate_file(fcb* fcb, UINT64 end, LIST_ENTRY* rollback);
-NTSTATUS extend_file(fcb* fcb, UINT64 end, LIST_ENTRY* rollback);
+NTSTATUS extend_file(fcb* fcb, file_ref* fileref, UINT64 end, BOOL prealloc, LIST_ENTRY* rollback);
+NTSTATUS excise_extents_inode(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* ii, UINT64 start_data, UINT64 end_data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback);
 NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 end_data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback);
 void update_checksum_tree(device_extension* Vcb, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback);
 NTSTATUS insert_sparse_extent(device_extension* Vcb, root* r, UINT64 inode, UINT64 start, UINT64 length, LIST_ENTRY* rollback);
-NTSTATUS STDCALL add_extent_ref(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, LIST_ENTRY* rollback);
-NTSTATUS STDCALL remove_extent_ref(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback);
-void print_trees(LIST_ENTRY* tc);
 chunk* get_chunk_from_address(device_extension* Vcb, UINT64 address);
 void add_to_space_list(chunk* c, UINT64 offset, UINT64 size, UINT8 type);
 NTSTATUS consider_write(device_extension* Vcb);
+BOOL insert_extent_chunk_inode(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* inode_item, chunk* c, UINT64 start_data,
+                               UINT64 length, BOOL prealloc, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback);
+chunk* alloc_chunk(device_extension* Vcb, UINT64 flags, LIST_ENTRY* rollback);
+NTSTATUS STDCALL write_data(device_extension* Vcb, UINT64 address, void* data, UINT32 length);
+NTSTATUS write_tree(device_extension* Vcb, UINT64 addr, UINT8* data, write_tree_context* wtc);
+void free_write_tree_stripes(write_tree_context* wtc);
+NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, LIST_ENTRY* rollback);
 
 // in dirctrl.c
 NTSTATUS STDCALL drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
+ULONG STDCALL get_reparse_tag(device_extension* Vcb, root* subvol, UINT64 inode, UINT8 type);
 
 // in security.c
 NTSTATUS STDCALL drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
 NTSTATUS STDCALL drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
-void fcb_get_sd(fcb* fcb);
+void fcb_get_sd(fcb* fcb, struct _fcb* parent);
 // UINT32 STDCALL get_uid();
 void add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, UINT32 uid);
-NTSTATUS fcb_get_new_sd(fcb* fcb, ACCESS_STATE* as);
+UINT32 sid_to_uid(PSID sid);
+NTSTATUS fcb_get_new_sd(fcb* fcb, file_ref* fileref, ACCESS_STATE* as);
 
 // in fileinfo.c
 NTSTATUS STDCALL drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
 NTSTATUS STDCALL drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
 NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, UINT64 index, PANSI_STRING utf8, LIST_ENTRY* rollback);
+NTSTATUS delete_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, UINT64 parinode, PANSI_STRING utf8, UINT64* index, LIST_ENTRY* rollback);
+NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, LIST_ENTRY* rollback);
+BOOL has_open_children(file_ref* fileref);
+NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb, UINT64 end, fcb* fcb, file_ref* fileref, PFILE_OBJECT FileObject, BOOL advance_only, LIST_ENTRY* rollback);
 
 // in reparse.c
-BOOL follow_symlink(fcb* fcb, PFILE_OBJECT FileObject);
 NTSTATUS get_reparse_point(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject, void* buffer, DWORD buflen, DWORD* retlen);
 NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp);
+NTSTATUS delete_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp);
 
 // in create.c
 NTSTATUS STDCALL drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
-NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* relatedfcb, BOOL parent);
 BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING filename, UINT32 crc32, root* r, UINT64 parinode, root** subvol,
                                          UINT64* inode, UINT8* type, PANSI_STRING utf8);
+NTSTATUS update_inode_item(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* ii, LIST_ENTRY* rollback);
+NTSTATUS open_fileref(device_extension* Vcb, file_ref** pfr, PUNICODE_STRING fnus, file_ref* related, BOOL parent, USHORT* unparsed);
 
 // in fsctl.c
 NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL user);
@@ -522,13 +628,31 @@ void STDCALL flush_thread(void* context);
 NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp);
 NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UINT8* data, UINT64 start, UINT64 length, ULONG* pbr);
 
+// in pnp.c
+NTSTATUS STDCALL drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp);
+
+// in free-space.c
+NTSTATUS load_free_space_cache(device_extension* Vcb, chunk* c);
+NTSTATUS clear_free_space_cache(device_extension* Vcb);
+NTSTATUS allocate_cache(device_extension* Vcb, BOOL* changed, LIST_ENTRY* rollback);
+NTSTATUS update_chunk_caches(device_extension* Vcb, LIST_ENTRY* rollback);
+
+// in extent-tree.c
+NTSTATUS increase_extent_refcount_data(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, UINT32 refcount, LIST_ENTRY* rollback);
+NTSTATUS decrease_extent_refcount_data(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, UINT32 refcount, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback);
+void decrease_chunk_usage(chunk* c, UINT64 delta);
+NTSTATUS convert_shared_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback);
+NTSTATUS convert_old_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback);
+
+#define fast_io_possible(fcb) (!FsRtlAreThereCurrentFileLocks(&fcb->lock) && !fcb->Vcb->readonly ? FastIoIsPossible : FastIoIsQuestionable)
+
 static __inline void print_open_trees(device_extension* Vcb) {
     LIST_ENTRY* le = Vcb->trees.Flink;
     while (le != &Vcb->trees) {
         tree* t = CONTAINING_RECORD(le, tree, list_entry);
         tree_data* td = CONTAINING_RECORD(t->itemlist.Flink, tree_data, list_entry);
-        ERR("tree %p: root %llx, level %u, refcount %u, first key (%llx,%x,%llx)\n",
-                      t, t->root->id, t->header.level, t->refcount, td->key.obj_id, td->key.obj_type, td->key.offset);
+        ERR("tree %p: root %llx, level %u, first key (%llx,%x,%llx)\n",
+                      t, t->root->id, t->header.level, td->key.obj_id, td->key.obj_type, td->key.offset);
 
         le = le->Flink;
     }
@@ -580,24 +704,6 @@ static __inline void InsertAfter(LIST_ENTRY* head, LIST_ENTRY* item, LIST_ENTRY*
     ExReleaseResourceLite(&Vcb->tree_lock); \
 }
 
-#ifdef DEBUG_TREE_REFCOUNTS
-#ifdef DEBUG_LONG_MESSAGES
-#define _increase_tree_rc(t, func, file, line) { \
-    LONG rc = InterlockedIncrement(&t->refcount); \
-    _debug_message(func, file, line, "tree %p: refcount increased to %i (increase_tree_rc)\n", t, rc); \
-}
-#else
-#define _increase_tree_rc(t, func, file, line) { \
-    LONG rc = InterlockedIncrement(&t->refcount); \
-    _debug_message(func, "tree %p: refcount increased to %i (increase_tree_rc)\n", t, rc); \
-}
-#endif
-#define increase_tree_rc(t) _increase_tree_rc(t, funcname, __FILE__, __LINE__)
-#else
-#define increase_tree_rc(t) InterlockedIncrement(&t->refcount);
-#define _increase_tree_rc(t, func, file, line) increase_tree_rc(t)
-#endif
-
 // from sys/stat.h
 #define __S_IFMT        0170000 /* These bits determine file type.  */
 #define __S_IFDIR       0040000 /* Directory.  */
@@ -613,6 +719,14 @@ static __inline void InsertAfter(LIST_ENTRY* head, LIST_ENTRY* item, LIST_ENTRY*
 #define S_ISDIR(mode)    __S_ISTYPE((mode), __S_IFDIR)
 #endif
 
+#ifndef S_IRUSR
+#define S_IRUSR 0000400
+#endif
+
+#ifndef S_IWUSR
+#define S_IWUSR 0000200
+#endif
+
 #ifndef S_IXUSR
 #define S_IXUSR 0000100
 #endif
@@ -622,10 +736,26 @@ static __inline void InsertAfter(LIST_ENTRY* head, LIST_ENTRY* item, LIST_ENTRY*
 #define S_IFREG __S_IFREG
 #endif /* __REACTOS__ */
 
+#ifndef S_IRGRP
+#define S_IRGRP (S_IRUSR >> 3)
+#endif
+
+#ifndef S_IWGRP
+#define S_IWGRP (S_IWUSR >> 3)
+#endif
+
 #ifndef S_IXGRP
 #define S_IXGRP (S_IXUSR >> 3)
 #endif
 
+#ifndef S_IROTH
+#define S_IROTH (S_IRGRP >> 3)
+#endif
+
+#ifndef S_IWOTH
+#define S_IWOTH (S_IWGRP >> 3)
+#endif
+
 #ifndef S_IXOTH
 #define S_IXOTH (S_IXGRP >> 3)
 #endif
@@ -641,6 +771,8 @@ NTSTATUS WINAPI RtlUTF8ToUnicodeN(WCHAR *uni_dest, ULONG uni_bytes_max,
 #if defined(__REACTOS__) && (NTDDI_VERSION < NTDDI_VISTA)
 NTSTATUS NTAPI FsRtlRemoveDotsFromPath(PWSTR OriginalString,
                                        USHORT PathLength, USHORT *NewLength);
+NTSTATUS NTAPI FsRtlValidateReparsePointBuffer(ULONG BufferLength,
+                                               PREPARSE_DATA_BUFFER ReparseBuffer);
 #endif /* defined(__REACTOS__) && (NTDDI_VERSION < NTDDI_WIN7) */
 
 #endif
index c1171a3..3071431 100644 (file)
@@ -2,6 +2,8 @@
 #define BTRFSIOCTL_H_DEFINED
 
 #define FSCTL_BTRFS_GET_FILE_IDS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x829, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
+#define FSCTL_BTRFS_CREATE_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82a, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
+#define FSCTL_BTRFS_CREATE_SNAPSHOT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82b, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
 
 typedef struct {
     UINT64 subvol;
@@ -9,4 +11,10 @@ typedef struct {
     BOOL top;
 } btrfs_get_file_ids;
 
+typedef struct {
+    HANDLE subvol;
+    UINT32 namelen;
+    WCHAR name[1];
+} btrfs_create_snapshot;
+
 #endif
index 3756bdf..5a3c4b7 100644 (file)
@@ -72,7 +72,6 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                     
                     if (!utf16) {
                         ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
                         return FALSE;
                     }
                     
@@ -86,17 +85,37 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                         
                         if (FsRtlAreNamesEqual(filename, &us, TRUE, NULL)) {
                             if (di->key.obj_type == TYPE_ROOT_ITEM) {
-                                root* fcbroot = Vcb->roots;
-                                while (fcbroot && fcbroot->id != di->key.obj_id)
-                                    fcbroot = fcbroot->next;
+                                LIST_ENTRY* le = Vcb->roots.Flink;
                                 
-                                *subvol = fcbroot;
-                                *inode = SUBVOL_ROOT_INODE;
-                                *type = BTRFS_TYPE_DIRECTORY;
+                                if (subvol) {
+                                    *subvol = NULL;
+                                    
+                                    while (le != &Vcb->roots) {
+                                        root* r2 = CONTAINING_RECORD(le, root, list_entry);
+                                        
+                                        if (r2->id == di->key.obj_id) {
+                                            *subvol = r2;
+                                            break;
+                                        }
+                                        
+                                        le = le->Flink;
+                                    }
+                                }
+
+                                if (inode)
+                                    *inode = SUBVOL_ROOT_INODE;
+                                
+                                if (type)
+                                    *type = BTRFS_TYPE_DIRECTORY;
                             } else {
-                                *subvol = r;
-                                *inode = di->key.obj_id;
-                                *type = di->type;
+                                if (subvol)
+                                    *subvol = r;
+                                
+                                if (inode)
+                                    *inode = di->key.obj_id;
+                                
+                                if (type)
+                                    *type = di->type;
                             }
                             
                             if (utf8) {
@@ -105,7 +124,6 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                                 utf8->Buffer = ExAllocatePoolWithTag(PagedPool, utf8->MaximumLength, ALLOC_TAG);
                                 if (!utf8->Buffer) {
                                     ERR("out of memory\n");
-                                    free_traverse_ptr(&tp);
                                     ExFreePool(utf16);
                                     return FALSE;
                                 }
@@ -113,10 +131,9 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                                 RtlCopyMemory(utf8->Buffer, di->name, di->n);
                             }
                             
-                            free_traverse_ptr(&tp);
                             ExFreePool(utf16);
                             
-                            TRACE("found %.*S by hash at (%llx,%llx)\n", filename->Length / sizeof(WCHAR), filename->Buffer, (*subvol)->id, *inode);
+//                             TRACE("found %.*S by hash at (%llx,%llx)\n", filename->Length / sizeof(WCHAR), filename->Buffer, (*subvol)->id, *inode);
                             
                             return TRUE;
                         }
@@ -137,28 +154,23 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
     Status = find_item(Vcb, r, &tp2, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        free_traverse_ptr(&tp);
         return FALSE;
     }
     
-    free_traverse_ptr(&tp);
     tp = tp2;
     
     TRACE("found item %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
     
     if (keycmp(&tp.item->key, &searchkey) == -1) {
         if (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             TRACE("moving on to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
         }
     }
     
-    if (tp.item->key.obj_id != parinode || tp.item->key.obj_type != TYPE_DIR_INDEX) {
-        free_traverse_ptr(&tp);
+    if (tp.item->key.obj_id != parinode || tp.item->key.obj_type != TYPE_DIR_INDEX)
         return FALSE;
-    }
     
     b = TRUE;
     do {
@@ -179,8 +191,6 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                 
                 if (!utf16) {
                     ERR("out of memory\n");
-                    
-                    free_traverse_ptr(&tp);
                     return FALSE;
                 }
                 
@@ -194,19 +204,39 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                     
                     if (FsRtlAreNamesEqual(filename, &us, TRUE, NULL)) {
                         if (di->key.obj_type == TYPE_ROOT_ITEM) {
-                            root* fcbroot = Vcb->roots;
-                            while (fcbroot && fcbroot->id != di->key.obj_id)
-                                fcbroot = fcbroot->next;
+                            LIST_ENTRY* le = Vcb->roots.Flink;
+
+                            if (subvol) {
+                                *subvol = NULL;
+                                
+                                while (le != &Vcb->roots) {
+                                    root* r2 = CONTAINING_RECORD(le, root, list_entry);
+                                    
+                                    if (r2->id == di->key.obj_id) {
+                                        *subvol = r2;
+                                        break;
+                                    }
+                                    
+                                    le = le->Flink;
+                                }
+                            }
+                            
+                            if (inode)
+                                *inode = SUBVOL_ROOT_INODE;
                             
-                            *subvol = fcbroot;
-                            *inode = SUBVOL_ROOT_INODE;
-                            *type = BTRFS_TYPE_DIRECTORY;
+                            if (type)
+                                *type = BTRFS_TYPE_DIRECTORY;
                         } else {
-                            *subvol = r;
-                            *inode = di->key.obj_id;
-                            *type = di->type;
+                            if (subvol)
+                                *subvol = r;
+                            
+                            if (inode)
+                                *inode = di->key.obj_id;
+                            
+                            if (type)
+                                *type = di->type;
                         }
-                        TRACE("found %.*S at (%llx,%llx)\n", filename->Length / sizeof(WCHAR), filename->Buffer, (*subvol)->id, *inode);
+//                         TRACE("found %.*S at (%llx,%llx)\n", filename->Length / sizeof(WCHAR), filename->Buffer, (*subvol)->id, *inode);
                         
                         if (utf8) {
                             utf8->MaximumLength = di->n;
@@ -214,7 +244,6 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                             utf8->Buffer = ExAllocatePoolWithTag(PagedPool, utf8->MaximumLength, ALLOC_TAG);
                             if (!utf8->Buffer) {
                                 ERR("out of memory\n");
-                                free_traverse_ptr(&tp);
                                 ExFreePool(utf16);
                                 
                                 return FALSE;
@@ -223,7 +252,6 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
                             RtlCopyMemory(utf8->Buffer, di->name, di->n);
                         }
                         
-                        free_traverse_ptr(&tp);
                         ExFreePool(utf16);
                         
                         return TRUE;
@@ -237,15 +265,12 @@ BOOL STDCALL find_file_in_dir_with_crc32(device_extension* Vcb, PUNICODE_STRING
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
          
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             b = tp.item->key.obj_id == parinode && tp.item->key.obj_type == TYPE_DIR_INDEX;
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-
     return FALSE;
 }
 
@@ -290,11 +315,31 @@ fcb* create_fcb() {
     
     FsRtlInitializeFileLock(&fcb->lock, NULL, NULL);
     
-    InitializeListHead(&fcb->children);
-    
     return fcb;
 }
 
+file_ref* create_fileref() {
+    file_ref* fr;
+    
+    fr = ExAllocatePoolWithTag(PagedPool, sizeof(file_ref), ALLOC_TAG);
+    if (!fr) {
+        ERR("out of memory\n");
+        return NULL;
+    }
+    
+    RtlZeroMemory(fr, sizeof(file_ref));
+    
+    fr->refcount = 1;
+    
+#ifdef DEBUG_FCB_REFCOUNTS
+    WARN("fileref %p: refcount now %i\n", fr, fr->refcount);
+#endif
+    
+    InitializeListHead(&fr->children);
+    
+    return fr;
+}
+
 static BOOL STDCALL find_file_in_dir(device_extension* Vcb, PUNICODE_STRING filename, root* r,
                                      UINT64 parinode, root** subvol, UINT64* inode, UINT8* type, PANSI_STRING utf8) {
     char* fn;
@@ -410,7 +455,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
                     xattr->Buffer = ExAllocatePoolWithTag(PagedPool, di->n + 1, ALLOC_TAG);
                     if (!xattr->Buffer) {
                         ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
                         goto end;
                     }
                     
@@ -418,8 +462,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
                     RtlCopyMemory(xattr->Buffer, di->name, di->n);
                     xattr->Buffer[di->n] = 0;
                     
-                    free_traverse_ptr(&tp);
-                    
                     success = TRUE;
                     goto end;
                 }
@@ -435,8 +477,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
         }
     }
     
-    free_traverse_ptr(&tp);
-    
     searchkey.offset = 0;
     
     Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
@@ -473,7 +513,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
                         WCHAR* utf16 = ExAllocatePoolWithTag(PagedPool, utf16len, ALLOC_TAG);
                         if (!utf16) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             goto end;
                         }
                         
@@ -497,7 +536,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
                                 xattr->Buffer = ExAllocatePoolWithTag(PagedPool, di->n + 1, ALLOC_TAG);
                                 if (!xattr->Buffer) {
                                     ERR("out of memory\n");
-                                    free_traverse_ptr(&tp);
                                     ExFreePool(utf16);
                                     goto end;
                                 }
@@ -506,8 +544,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
                                 RtlCopyMemory(xattr->Buffer, di->name, di->n);
                                 xattr->Buffer[di->n] = 0;
                                 
-                                free_traverse_ptr(&tp);
-                                
                                 success = TRUE;
                                 goto end;
                             }
@@ -529,7 +565,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
         
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
@@ -537,8 +572,6 @@ static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream,
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-   
 end:
     ExFreePool(utf8);
     
@@ -645,22 +678,38 @@ static NTSTATUS split_path(PUNICODE_STRING path, UNICODE_STRING** parts, ULONG*
     return STATUS_SUCCESS;
 }
 
-static fcb* search_fcb_children(fcb* dir, PUNICODE_STRING name) {
+// #ifdef DEBUG_FCB_REFCOUNTS
+// static void print_fcbs(device_extension* Vcb) {
+//     fcb* fcb = Vcb->fcbs;
+//     
+//     while (fcb) {
+//         ERR("fcb %p (%.*S): refcount %u\n", fcb, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb->refcount);
+//         
+//         fcb = fcb->next;
+//     }
+// }
+// #endif
+
+static file_ref* search_fileref_children(file_ref* dir, PUNICODE_STRING name) {
     LIST_ENTRY* le;
-    fcb *c, *deleted = NULL;
+    file_ref *c, *deleted = NULL;
+#ifdef DEBUG_FCB_REFCOUNTS
     ULONG rc;
+#endif
     
     le = dir->children.Flink;
     while (le != &dir->children) {
-        c = CONTAINING_RECORD(le, fcb, list_entry);
+        c = CONTAINING_RECORD(le, file_ref, list_entry);
         
         if (c->refcount > 0 && FsRtlAreNamesEqual(&c->filepart, name, TRUE, NULL)) {
             if (c->deleted) {
                 deleted = c;
             } else {
-                rc = InterlockedIncrement(&c->refcount);
 #ifdef DEBUG_FCB_REFCOUNTS
-                WARN("fcb %p: refcount now %i (%.*S)\n", c, rc, c->full_filename.Length / sizeof(WCHAR), c->full_filename.Buffer);
+                rc = InterlockedIncrement(&c->refcount);
+                WARN("fileref %p: refcount now %i (%S)\n", c, rc, file_desc_fileref(c));
+#else
+                InterlockedIncrement(&c->refcount);
 #endif
                 return c;
             }
@@ -670,50 +719,226 @@ static fcb* search_fcb_children(fcb* dir, PUNICODE_STRING name) {
     }
     
     if (deleted) {
-        rc = InterlockedIncrement(&deleted->refcount);
 #ifdef DEBUG_FCB_REFCOUNTS
-        WARN("fcb %p: refcount now %i (%.*S)\n", deleted, rc, deleted->full_filename.Length / sizeof(WCHAR), deleted->full_filename.Buffer);
+        rc = InterlockedIncrement(&deleted->refcount);
+        WARN("fileref %p: refcount now %i (%S)\n", deleted, rc, file_desc_fileref(deleted));
+#else
+        InterlockedIncrement(&deleted->refcount);
 #endif
     }
     
     return deleted;
 }
 
-// #ifdef DEBUG_FCB_REFCOUNTS
-// static void print_fcbs(device_extension* Vcb) {
-//     fcb* fcb = Vcb->fcbs;
-//     
-//     while (fcb) {
-//         ERR("fcb %p (%.*S): refcount %u\n", fcb, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb->refcount);
-//         
-//         fcb = fcb->next;
-//     }
-// }
-// #endif
+static NTSTATUS open_fcb(device_extension* Vcb, root* subvol, UINT64 inode, UINT8 type, PANSI_STRING utf8, fcb* parent, fcb** pfcb) {
+    KEY searchkey;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    fcb* fcb;
+    
+    if (!IsListEmpty(&subvol->fcbs)) {
+        LIST_ENTRY* le = subvol->fcbs.Flink;
+                                
+        while (le != &subvol->fcbs) {
+            fcb = CONTAINING_RECORD(le, struct _fcb, list_entry);
+            
+            if (fcb->inode == inode && !fcb->ads) {
+#ifdef DEBUG_FCB_REFCOUNTS
+                LONG rc = InterlockedIncrement(&fcb->refcount);
+                
+                WARN("fcb %p: refcount now %i (subvol %llx, inode %llx)\n", fcb, rc, fcb->subvol->id, fcb->inode);
+#else
+                InterlockedIncrement(&fcb->refcount);
+#endif
 
-NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* relatedfcb, BOOL parent) {
-    fcb *dir, *sf, *sf2;
-    ULONG i, num_parts;
+                *pfcb = fcb;
+                return STATUS_SUCCESS;
+            }
+            
+            le = le->Flink;
+        }
+    }
+    
+    fcb = create_fcb();
+    if (!fcb) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    fcb->Vcb = Vcb;
+    
+    fcb->subvol = subvol;
+    fcb->inode = inode;
+    fcb->type = type;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        free_fcb(fcb);
+        return Status;
+    }
+    
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+        ERR("couldn't find INODE_ITEM for inode %llx in subvol %llx\n", inode, subvol->id);
+        free_fcb(fcb);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp.item->size > 0)
+        RtlCopyMemory(&fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
+    
+    fcb->atts = get_file_attributes(Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, utf8->Buffer[0] == '.', FALSE);
+    
+    fcb_get_sd(fcb, parent);
+    
+    InsertTailList(&subvol->fcbs, &fcb->list_entry);
+    
+    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
+    
+    if (fcb->inode_item.st_size == 0 || (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK)) {
+        fcb->Header.AllocationSize.QuadPart = 0;
+        fcb->Header.FileSize.QuadPart = 0;
+        fcb->Header.ValidDataLength.QuadPart = 0;
+    } else {
+        EXTENT_DATA* ed;
+        
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_EXTENT_DATA;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            free_fcb(fcb);
+            return Status;
+        }
+        
+        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+            ERR("error - could not find EXTENT_DATA items for inode %llx in subvol %llx\n", fcb->inode, fcb->subvol->id);
+            free_fcb(fcb);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (tp.item->size < sizeof(EXTENT_DATA)) {
+            ERR("(%llx,%x,%llx) was %llx bytes, expected at least %llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
+                tp.item->size, sizeof(EXTENT_DATA));
+            
+            free_fcb(fcb);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        ed = (EXTENT_DATA*)tp.item->data;
+        
+        if (ed->type == EXTENT_TYPE_INLINE)
+            fcb->Header.AllocationSize.QuadPart = fcb->inode_item.st_size;
+        else
+            fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
+        
+        fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;
+        fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;
+    }
+    
+    *pfcb = fcb;
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS open_fcb_stream(device_extension* Vcb, root* subvol, UINT64 inode, ANSI_STRING* xattr,
+                                UINT32 streamsize, UINT32 streamhash, fcb* parent, fcb** pfcb) {
+    fcb* fcb;
+    
+    if (!IsListEmpty(&subvol->fcbs)) {
+        LIST_ENTRY* le = subvol->fcbs.Flink;
+                                
+        while (le != &subvol->fcbs) {
+            fcb = CONTAINING_RECORD(le, struct _fcb, list_entry);
+            
+            if (fcb->inode == inode && fcb->ads && fcb->adsxattr.Length == xattr->Length &&
+                RtlCompareMemory(fcb->adsxattr.Buffer, xattr->Buffer, fcb->adsxattr.Length) == fcb->adsxattr.Length) {
+#ifdef DEBUG_FCB_REFCOUNTS
+                LONG rc = InterlockedIncrement(&fcb->refcount);
+                
+                WARN("fcb %p: refcount now %i (subvol %llx, inode %llx)\n", fcb, rc, fcb->subvol->id, fcb->inode);
+#else
+                InterlockedIncrement(&fcb->refcount);
+#endif
+
+                *pfcb = fcb;
+                return STATUS_SUCCESS;
+            }
+                            
+            le = le->Flink;
+        }
+    }
+    
+    fcb = create_fcb();
+    if (!fcb) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    fcb->Vcb = Vcb;
+    
+    fcb->subvol = parent->subvol;
+    fcb->inode = parent->inode;
+    fcb->type = parent->type;
+    fcb->ads = TRUE;
+    fcb->adssize = streamsize;
+    fcb->adshash = streamhash;
+    fcb->adsxattr = *xattr;
+    
+    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
+    fcb->Header.AllocationSize.QuadPart = fcb->adssize;
+    fcb->Header.FileSize.QuadPart = fcb->adssize;
+    fcb->Header.ValidDataLength.QuadPart = fcb->adssize;
+    
+    TRACE("stream found: size = %x, hash = %08x\n", fcb->adssize, fcb->adshash);
+    
+    InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry);
+    
+    *pfcb = fcb;
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS open_fileref(device_extension* Vcb, file_ref** pfr, PUNICODE_STRING fnus, file_ref* related, BOOL parent, USHORT* unparsed) {
     UNICODE_STRING fnus2;
+    file_ref *dir, *sf, *sf2;
+    ULONG i, num_parts;
     UNICODE_STRING* parts = NULL;
     BOOL has_stream;
     NTSTATUS Status;
     
-    TRACE("(%p, %p, %.*S, %p, %s)\n", Vcb, pfcb, fnus->Length / sizeof(WCHAR), fnus->Buffer, relatedfcb, parent ? "TRUE" : "FALSE");
+    TRACE("(%p, %p, %p, %u, %p)\n", Vcb, pfr, related, parent, unparsed);
     
-// #ifdef DEBUG_FCB_REFCOUNTS
-//     print_fcbs(Vcb);
-// #endif
+    if (Vcb->removing)
+        return STATUS_ACCESS_DENIED;
     
     fnus2 = *fnus;
     
-    if (fnus2.Length < sizeof(WCHAR) && !relatedfcb) {
+    if (fnus2.Length < sizeof(WCHAR) && !related) {
         ERR("error - fnus was too short\n");
         return STATUS_INTERNAL_ERROR;
     }
     
-    if (relatedfcb) {
-        dir = relatedfcb;
+    if (related && fnus->Length == 0) {
+#ifdef DEBUG_FCB_REFCOUNTS
+        LONG rc = InterlockedIncrement(&related->refcount);
+        WARN("fileref %p: refcount now %i\n", related, rc);
+#else
+        InterlockedIncrement(&related->refcount);
+#endif
+        
+        
+        *pfr = related;
+        return STATUS_SUCCESS;
+    }
+    
+    if (related) {
+        dir = related;
     } else {
         if (fnus2.Buffer[0] != '\\') {
             ERR("error - filename %.*S did not begin with \\\n", fnus2.Length / sizeof(WCHAR), fnus2.Buffer);
@@ -721,26 +946,26 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
         }
         
         if (fnus2.Length == sizeof(WCHAR)) {
-            LONG rc;
-            
-            *pfcb = Vcb->root_fcb;
-            rc = InterlockedIncrement(&Vcb->root_fcb->refcount);
 #ifdef DEBUG_FCB_REFCOUNTS
-            WARN("fcb %p: refcount now %i (root)\n", Vcb->root_fcb, rc);
+            LONG rc = InterlockedIncrement(&Vcb->root_fileref->refcount);
+            WARN("fileref %p: refcount now %i (root)\n", Vcb->root_fileref, rc);
+#else
+            InterlockedIncrement(&Vcb->root_fileref->refcount);
 #endif
+            *pfr = Vcb->root_fileref;
             return STATUS_SUCCESS;
         }
         
-        dir = Vcb->root_fcb;
+        dir = Vcb->root_fileref;
         
         fnus2.Buffer++;
         fnus2.Length -= sizeof(WCHAR);
         fnus2.MaximumLength -= sizeof(WCHAR);
     }
     
-    if (dir->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) {
-        WARN("passed relatedfcb which isn't a directory (%.*S) (fnus = %.*S)\n",
-             relatedfcb->full_filename.Length / sizeof(WCHAR), relatedfcb->full_filename.Buffer, fnus->Length / sizeof(WCHAR), fnus->Buffer);
+    if (dir->fcb->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) {
+        WARN("passed related fileref which isn't a directory (%S) (fnus = %.*S)\n",
+             file_desc_fileref(related), fnus->Length / sizeof(WCHAR), fnus->Buffer);
         return STATUS_OBJECT_PATH_NOT_FOUND;
     }
     
@@ -754,17 +979,16 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
         }
     }
     
-    // FIXME - handle refcounts(?)
     sf = dir;
     dir->refcount++;
 #ifdef DEBUG_FCB_REFCOUNTS
-    WARN("fcb %p: refcount now %i (%.*S)\n", dir, dir->refcount, dir->full_filename.Length / sizeof(WCHAR), dir->full_filename.Buffer);
+    WARN("fileref %p: refcount now %i (%S)\n", dir, dir->refcount, file_desc_fileref(dir));
 #endif
     
     if (parent) {
         num_parts--;
         
-        if (has_stream) {
+        if (has_stream && num_parts > 0) {
             num_parts--;
             has_stream = FALSE;
         }
@@ -772,17 +996,18 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
     
     if (num_parts == 0) {
         Status = STATUS_SUCCESS;
-        *pfcb = dir;
+        *pfr = dir;
         goto end2;
     }
     
     for (i = 0; i < num_parts; i++) {
         BOOL lastpart = (i == num_parts-1) || (i == num_parts-2 && has_stream);
         
-        sf2 = search_fcb_children(sf, &parts[i]);
+        sf2 = search_fileref_children(sf, &parts[i]);
         
-        if (sf2 && sf2->type != BTRFS_TYPE_DIRECTORY && !lastpart) {
+        if (sf2 && sf2->fcb->type != BTRFS_TYPE_DIRECTORY && !lastpart) {
             WARN("passed path including file as subdirectory\n");
+            free_fileref(sf2);
             
             Status = STATUS_OBJECT_PATH_NOT_FOUND;
             goto end;
@@ -799,13 +1024,19 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                 xattr.Buffer = NULL;
                 xattr.Length = xattr.MaximumLength = 0;
                 
-                if (!find_stream(Vcb, sf, &parts[i], &streamname, &streamsize, &streamhash, &xattr)) {
+                // FIXME - check if already opened
+                
+                if (!find_stream(Vcb, sf->fcb, &parts[i], &streamname, &streamsize, &streamhash, &xattr)) {
                     TRACE("could not find stream %.*S\n", parts[i].Length / sizeof(WCHAR), parts[i].Buffer);
                     
                     Status = STATUS_OBJECT_NAME_NOT_FOUND;
                     goto end;
                 } else {
                     ULONG fnlen;
+                    fcb* fcb;
+#ifdef DEBUG_FCB_REFCOUNTS
+                    LONG rc;
+#endif
                     
                     if (streamhash == EA_DOSATTRIB_HASH && xattr.Length == strlen(EA_DOSATTRIB) &&
                         RtlCompareMemory(xattr.Buffer, EA_DOSATTRIB, xattr.Length) == xattr.Length) {
@@ -815,14 +1046,21 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                         goto end;
                     }
                     
-                    sf2 = create_fcb();
+                    Status = open_fcb_stream(Vcb, sf->fcb->subvol, sf->fcb->inode, &xattr, streamsize, streamhash, sf->fcb, &fcb);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("open_fcb_stream returned %08x\n", Status);
+                        goto end;
+                    }
+                    
+                    sf2 = create_fileref();
                     if (!sf2) {
                         ERR("out of memory\n");
+                        free_fcb(fcb);
                         Status = STATUS_INSUFFICIENT_RESOURCES;
                         goto end;
                     }
-        
-                    sf2->Vcb = Vcb;
+                    
+                    sf2->fcb = fcb;
         
                     if (streamname.Buffer) // case has changed
                         sf2->filepart = streamname;
@@ -831,7 +1069,7 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                         sf2->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, sf2->filepart.MaximumLength, ALLOC_TAG);
                         if (!sf2->filepart.Buffer) {
                             ERR("out of memory\n");
-                            free_fcb(sf2);
+                            free_fileref(sf2);
                             Status = STATUS_INSUFFICIENT_RESOURCES;
                             goto end;
                         }   
@@ -839,32 +1077,9 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                         RtlCopyMemory(sf2->filepart.Buffer, parts[i].Buffer, parts[i].Length);
                     }
                     
-                    sf2->par = sf;
-                    
-                    sf->refcount++;
-#ifdef DEBUG_FCB_REFCOUNTS
-                    WARN("fcb %p: refcount now %i (%.*S)\n", sf, sf->refcount, sf->full_filename.Length / sizeof(WCHAR), sf->full_filename.Buffer);
-#endif
-                    
-                    sf2->subvol = sf->subvol;
-                    sf2->inode = sf->inode;
-                    sf2->type = sf->type;
-                    sf2->ads = TRUE;
-                    sf2->adssize = streamsize;
-                    sf2->adshash = streamhash;
-                    sf2->adsxattr = xattr;
-                    
-                    TRACE("stream found: size = %x, hash = %08x\n", sf2->adssize, sf2->adshash);
-                    
-                    if (Vcb->fcbs)
-                        Vcb->fcbs->prev = sf2;
-                    
-                    sf2->next = Vcb->fcbs;
-                    Vcb->fcbs = sf2;
-                    
                     sf2->name_offset = sf->full_filename.Length / sizeof(WCHAR);
                     
-                    if (sf != Vcb->root_fcb)
+                    if (sf != Vcb->root_fileref)
                         sf2->name_offset++;
                     
                     fnlen = (sf2->name_offset * sizeof(WCHAR)) + sf2->filepart.Length;
@@ -872,7 +1087,7 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                     sf2->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
                     if (!sf2->full_filename.Buffer) {
                         ERR("out of memory\n");
-                        free_fcb(sf2);
+                        free_fileref(sf2);
                         Status = STATUS_INSUFFICIENT_RESOURCES;
                         goto end;
                     }
@@ -886,84 +1101,85 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                     
                     // FIXME - make sure all functions know that ADS FCBs won't have a valid SD or INODE_ITEM
 
-                    TRACE("found stream %.*S (subvol = %p)\n", sf2->full_filename.Length / sizeof(WCHAR), sf2->full_filename.Buffer, sf->subvol);
-                    
+                    sf2->parent = (struct _file_ref*)sf;
                     InsertTailList(&sf->children, &sf2->list_entry);
+                    
+#ifdef DEBUG_FCB_REFCOUNTS
+                    rc = InterlockedIncrement(&sf->refcount);
+                    WARN("fileref %p: refcount now %i\n", sf, rc);
+#else
+                    InterlockedIncrement(&sf->refcount);
+#endif
                 }
             } else {
                 root* subvol;
                 UINT64 inode;
                 UINT8 type;
                 ANSI_STRING utf8;
-                KEY searchkey;
-                traverse_ptr tp;
+#ifdef DEBUG_FCB_REFCOUNTS
+                LONG rc;
+#endif
                 
-                if (!find_file_in_dir(Vcb, &parts[i], sf->subvol, sf->inode, &subvol, &inode, &type, &utf8)) {
+                if (!find_file_in_dir(Vcb, &parts[i], sf->fcb->subvol, sf->fcb->inode, &subvol, &inode, &type, &utf8)) {
                     TRACE("could not find %.*S\n", parts[i].Length / sizeof(WCHAR), parts[i].Buffer);
 
                     Status = lastpart ? STATUS_OBJECT_NAME_NOT_FOUND : STATUS_OBJECT_PATH_NOT_FOUND;
                     goto end;
-                } else if (type != BTRFS_TYPE_DIRECTORY && !lastpart) {
-                    WARN("passed path including file as subdirectory\n");
-                    
-                    Status = STATUS_OBJECT_PATH_NOT_FOUND;
-                    goto end;
                 } else {
-                    ULONG fnlen, strlen;
+                    fcb* fcb;
+                    ULONG strlen, fnlen;
                     
-                    sf2 = create_fcb();
+                    if (type != BTRFS_TYPE_DIRECTORY && !lastpart) {
+                        WARN("passed path including file as subdirectory\n");
+                        
+                        Status = STATUS_OBJECT_PATH_NOT_FOUND;
+                        goto end;
+                    }
+                    
+                    Status = open_fcb(Vcb, subvol, inode, type, &utf8, sf->fcb, &fcb);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("open_fcb returned %08x\n", Status);
+                        goto end;
+                    }
+                    
+                    sf2 = create_fileref();
                     if (!sf2) {
                         ERR("out of memory\n");
+                        free_fcb(fcb);
                         Status = STATUS_INSUFFICIENT_RESOURCES;
                         goto end;
                     }
                     
-                    sf2->Vcb = Vcb;
-
+                    sf2->fcb = fcb;
+                    
+                    sf2->utf8 = utf8;
+                    
                     Status = RtlUTF8ToUnicodeN(NULL, 0, &strlen, utf8.Buffer, utf8.Length);
                     if (!NT_SUCCESS(Status)) {
                         ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
-                        free_fcb(sf2);
+                        free_fileref(sf2);
                         goto end;
-                    } else {
-                        sf2->filepart.MaximumLength = sf2->filepart.Length = strlen;
-                        sf2->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, sf2->filepart.MaximumLength, ALLOC_TAG);
-                        if (!sf2->filepart.Buffer) {
-                            ERR("out of memory\n");
-                            free_fcb(sf2);
-                            Status = STATUS_INSUFFICIENT_RESOURCES;
-                            goto end;
-                        }
-                        
-                        Status = RtlUTF8ToUnicodeN(sf2->filepart.Buffer, strlen, &strlen, utf8.Buffer, utf8.Length);
-                        
-                        if (!NT_SUCCESS(Status)) {
-                            ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
-                            free_fcb(sf2);
-                            goto end;
-                        }
                     }
                     
-                    sf2->par = sf;
-                    
-                    sf->refcount++;
-#ifdef DEBUG_FCB_REFCOUNTS
-                    WARN("fcb %p: refcount now %i (%.*S)\n", sf, sf->refcount, sf->full_filename.Length / sizeof(WCHAR), sf->full_filename.Buffer);
-#endif
-                    
-                    sf2->subvol = subvol;
-                    sf2->inode = inode;
-                    sf2->type = type;
-                    
-                    if (Vcb->fcbs)
-                        Vcb->fcbs->prev = sf2;
+                    sf2->filepart.MaximumLength = sf2->filepart.Length = strlen;
+                    sf2->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, sf2->filepart.MaximumLength, ALLOC_TAG);
+                    if (!sf2->filepart.Buffer) {
+                        ERR("out of memory\n");
+                        free_fileref(sf2);
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
                     
-                    sf2->next = Vcb->fcbs;
-                    Vcb->fcbs = sf2;
+                    Status = RtlUTF8ToUnicodeN(sf2->filepart.Buffer, strlen, &strlen, utf8.Buffer, utf8.Length);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+                        free_fileref(sf2);
+                        goto end;
+                    }
                     
                     sf2->name_offset = sf->full_filename.Length / sizeof(WCHAR);
-                    
-                    if (sf != Vcb->root_fcb)
+    
+                    if (sf != Vcb->root_fileref)
                         sf2->name_offset++;
                     
                     fnlen = (sf2->name_offset * sizeof(WCHAR)) + sf2->filepart.Length;
@@ -971,7 +1187,7 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                     sf2->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
                     if (!sf2->full_filename.Buffer) {
                         ERR("out of memory\n");
-                        free_fcb(sf2);
+                        free_fileref(sf2);
                         Status = STATUS_INSUFFICIENT_RESOURCES;
                         goto end;
                     }
@@ -979,44 +1195,19 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
                     sf2->full_filename.Length = sf2->full_filename.MaximumLength = fnlen;
                     RtlCopyMemory(sf2->full_filename.Buffer, sf->full_filename.Buffer, sf->full_filename.Length);
                     
-                    if (sf != Vcb->root_fcb)
+                    if (sf != Vcb->root_fileref)
                         sf2->full_filename.Buffer[sf->full_filename.Length / sizeof(WCHAR)] = '\\';
                     
                     RtlCopyMemory(&sf2->full_filename.Buffer[sf2->name_offset], sf2->filepart.Buffer, sf2->filepart.Length);
                     
-                    sf2->utf8 = utf8;
-                    
-                    searchkey.obj_id = sf2->inode;
-                    searchkey.obj_type = TYPE_INODE_ITEM;
-                    searchkey.offset = 0xffffffffffffffff;
-                    
-                    Status = find_item(sf2->Vcb, sf2->subvol, &tp, &searchkey, FALSE);
-                    if (!NT_SUCCESS(Status)) {
-                        ERR("error - find_item returned %08x\n", Status);
-                        free_fcb(sf2);
-                        goto end;
-                    }
-                    
-                    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
-                        ERR("couldn't find INODE_ITEM for inode %llx in subvol %llx\n", sf2->inode, sf2->subvol->id);
-                        Status = STATUS_INTERNAL_ERROR;
-                        free_fcb(sf2);
-                        free_traverse_ptr(&tp);
-                        goto end;
-                    }
-                    
-                    if (tp.item->size > 0)
-                        RtlCopyMemory(&sf2->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
-                    
-                    free_traverse_ptr(&tp);
-                    
-                    sf2->atts = get_file_attributes(Vcb, &sf2->inode_item, sf2->subvol, sf2->inode, sf2->type, sf2->filepart.Buffer[0] == '.', FALSE);
-                    
-                    fcb_get_sd(sf2);
-                    
-                    TRACE("found %.*S (subvol = %p)\n", sf2->full_filename.Length / sizeof(WCHAR), sf2->full_filename.Buffer, subvol);
-                    
+                    sf2->parent = (struct _file_ref*)sf;
                     InsertTailList(&sf->children, &sf2->list_entry);
+#ifdef DEBUG_FCB_REFCOUNTS
+                    rc = InterlockedIncrement(&sf->refcount);
+                    WARN("fileref %p: refcount now %i\n", sf, rc);
+#else
+                    InterlockedIncrement(&sf->refcount);
+#endif
                 }
             }
         }
@@ -1024,39 +1215,36 @@ NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* r
         if (i == num_parts - 1)
             break;
         
-        free_fcb(sf);
+        if (sf2->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {
+            Status = STATUS_REPARSE;
+            
+            if (unparsed)
+                *unparsed = fnus->Length - ((parts[i+1].Buffer - fnus->Buffer - 1) * sizeof(WCHAR));
+            
+            break;
+        }
+        
+        free_fileref(sf);
         sf = sf2;
     }
     
-    Status = STATUS_SUCCESS;
-    *pfcb = sf2;
+    if (Status != STATUS_REPARSE)
+        Status = STATUS_SUCCESS;
+    *pfr = sf2;
     
 end:
-    free_fcb(sf);
+    free_fileref(sf);
     
 end2:
     if (parts)
         ExFreePool(parts);
     
-// #ifdef DEBUG_FCB_REFCOUNTS
-//     print_fcbs(Vcb);
-// #endif
-    
     TRACE("returning %08x\n", Status);
     
     return Status;
 }
 
-static NTSTATUS STDCALL attach_fcb_to_fileobject(device_extension* Vcb, fcb* fcb, PFILE_OBJECT FileObject) {
-    FileObject->FsContext = fcb;
-//     FileObject->FsContext2 = 0x0badc0de;//NULL;
-    
-    // FIXME - cache stuff
-    
-    return STATUS_SUCCESS;
-}
-
-static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_STRING fpus, fcb* parfcb, ULONG options, fcb** pfcb, LIST_ENTRY* rollback) {
+static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_STRING fpus, file_ref* parfileref, ULONG options, file_ref** pfr, LIST_ENTRY* rollback) {
     NTSTATUS Status;
     fcb* fcb;
     ULONG utf8len;
@@ -1074,7 +1262,10 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
     ANSI_STRING utf8as;
     ULONG defda;
+    file_ref* fileref;
+#ifdef DEBUG_FCB_REFCOUNTS
     LONG rc;
+#endif
     
     Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, fpus->Buffer, fpus->Length);
     if (!NT_SUCCESS(Status))
@@ -1096,7 +1287,7 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     
     crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8, utf8len);
     
-    dirpos = find_next_dir_index(Vcb, parfcb->subvol, parfcb->inode);
+    dirpos = find_next_dir_index(Vcb, parfileref->fcb->subvol, parfileref->fcb->inode);
     if (dirpos == 0) {
         Status = STATUS_INTERNAL_ERROR;
         ExFreePool(utf8);
@@ -1108,28 +1299,27 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     KeQuerySystemTime(&time);
     win_time_to_unix(time, &now);
     
-    TRACE("parfcb->inode_item.st_size was %llx\n", parfcb->inode_item.st_size);
-    parfcb->inode_item.st_size += utf8len * 2;
-    TRACE("parfcb->inode_item.st_size was %llx\n", parfcb->inode_item.st_size);
-    parfcb->inode_item.transid = Vcb->superblock.generation;
-    parfcb->inode_item.sequence++;
-    parfcb->inode_item.st_ctime = now;
-    parfcb->inode_item.st_mtime = now;
+//     TRACE("parfcb->inode_item.st_size was %llx\n", parfcb->inode_item.st_size);
+    parfileref->fcb->inode_item.st_size += utf8len * 2;
+//     TRACE("parfcb->inode_item.st_size was %llx\n", parfcb->inode_item.st_size);
+    parfileref->fcb->inode_item.transid = Vcb->superblock.generation;
+    parfileref->fcb->inode_item.sequence++;
+    parfileref->fcb->inode_item.st_ctime = now;
+    parfileref->fcb->inode_item.st_mtime = now;
     
-    searchkey.obj_id = parfcb->inode;
+    searchkey.obj_id = parfileref->fcb->inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
-    searchkey.offset = 0;
+    searchkey.offset = 0xffffffffffffffff;
     
-    Status = find_item(Vcb, parfcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, parfileref->fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         ExFreePool(utf8);
         return Status;
     }
     
-    if (keycmp(&searchkey, &tp.item->key)) {
-        ERR("error - could not find INODE_ITEM for parent directory %llx in subvol %llx\n", parfcb->inode, parfcb->subvol->id);
-        free_traverse_ptr(&tp);
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+        ERR("error - could not find INODE_ITEM for parent directory %llx in subvol %llx\n", parfileref->fcb->inode, parfileref->fcb->subvol->id);
         ExFreePool(utf8);
         return STATUS_INTERNAL_ERROR;
     }
@@ -1137,22 +1327,19 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     dirii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!dirii) {
         ERR("out of memory\n");
-        free_traverse_ptr(&tp);
         ExFreePool(utf8);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(dirii, &parfcb->inode_item, sizeof(INODE_ITEM));
+    RtlCopyMemory(dirii, &parfileref->fcb->inode_item, sizeof(INODE_ITEM));
     delete_tree_item(Vcb, &tp, rollback);
     
-    insert_tree_item(Vcb, parfcb->subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, dirii, sizeof(INODE_ITEM), NULL, rollback);
+    insert_tree_item(Vcb, parfileref->fcb->subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, dirii, sizeof(INODE_ITEM), NULL, rollback);
     
-    free_traverse_ptr(&tp);
+    if (parfileref->fcb->subvol->lastinode == 0)
+        get_last_inode(Vcb, parfileref->fcb->subvol);
     
-    if (parfcb->subvol->lastinode == 0)
-        get_last_inode(Vcb, parfcb->subvol);
-    
-    inode = parfcb->subvol->lastinode + 1;
+    inode = parfileref->fcb->subvol->lastinode + 1;
     
     type = options & FILE_DIRECTORY_FILE ? BTRFS_TYPE_DIRECTORY : BTRFS_TYPE_FILE;
     
@@ -1173,7 +1360,7 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     di->type = type;
     RtlCopyMemory(di->name, utf8, utf8len);
     
-    insert_tree_item(Vcb, parfcb->subvol, parfcb->inode, TYPE_DIR_INDEX, dirpos, di, disize, NULL, rollback);
+    insert_tree_item(Vcb, parfileref->fcb->subvol, parfileref->fcb->inode, TYPE_DIR_INDEX, dirpos, di, disize, NULL, rollback);
     
     di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
     if (!di2) {
@@ -1184,19 +1371,17 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     
     RtlCopyMemory(di2, di, disize);
     
-    Status = add_dir_item(Vcb, parfcb->subvol, parfcb->inode, crc32, di2, disize, rollback);
+    Status = add_dir_item(Vcb, parfileref->fcb->subvol, parfileref->fcb->inode, crc32, di2, disize, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("add_dir_item returned %08x\n", Status);
         ExFreePool(utf8);
         return Status;
     }
     
-    // FIXME - handle Irp->Overlay.AllocationSize
-    
     utf8as.Buffer = utf8;
     utf8as.Length = utf8as.MaximumLength = utf8len;
     
-    Status = add_inode_ref(Vcb, parfcb->subvol, inode, parfcb->inode, dirpos, &utf8as, rollback);
+    Status = add_inode_ref(Vcb, parfileref->fcb->subvol, inode, parfileref->fcb->inode, dirpos, &utf8as, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("add_inode_ref returned %08x\n", Status);
         ExFreePool(utf8);
@@ -1229,7 +1414,7 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     
         sprintf(val, "0x%x", IrpSp->Parameters.Create.FileAttributes);
     
-        Status = set_xattr(Vcb, parfcb->subvol, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), rollback);
+        Status = set_xattr(Vcb, parfileref->fcb->subvol, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("set_xattr returned %08x\n", Status);
             ExFreePool(utf8);
@@ -1237,7 +1422,7 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
         }
     }
     
-    parfcb->subvol->lastinode++;
+    parfileref->fcb->subvol->lastinode++;
     
     fcb = create_fcb();
     if (!fcb) {
@@ -1248,7 +1433,6 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
         
     fcb->Vcb = Vcb;
     
-    RtlZeroMemory(&fcb->inode_item, sizeof(INODE_ITEM));
     fcb->inode_item.generation = Vcb->superblock.generation;
     fcb->inode_item.transid = Vcb->superblock.generation;
     fcb->inode_item.st_size = 0;
@@ -1257,7 +1441,7 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     fcb->inode_item.st_nlink = 1;
 //     fcb->inode_item.st_uid = UID_NOBODY; // FIXME?
     fcb->inode_item.st_gid = GID_NOBODY; // FIXME?
-    fcb->inode_item.st_mode = parfcb ? (parfcb->inode_item.st_mode & ~S_IFDIR) : 0755; // use parent's permissions by default
+    fcb->inode_item.st_mode = parfileref->fcb ? (parfileref->fcb->inode_item.st_mode & ~S_IFDIR) : 0755; // use parent's permissions by default
     fcb->inode_item.st_rdev = 0;
     fcb->inode_item.flags = 0;
     fcb->inode_item.sequence = 1;
@@ -1274,99 +1458,132 @@ static NTSTATUS STDCALL file_create2(PIRP Irp, device_extension* Vcb, PUNICODE_S
     }
     
     // inherit nodatacow flag from parent directory
-    if (parfcb->inode_item.flags & BTRFS_INODE_NODATACOW) {
+    if (parfileref->fcb->inode_item.flags & BTRFS_INODE_NODATACOW) {
         fcb->inode_item.flags |= BTRFS_INODE_NODATACOW;
         
         if (type != BTRFS_TYPE_DIRECTORY)
             fcb->inode_item.flags |= BTRFS_INODE_NODATASUM;
     }
     
-//     fcb->Header.IsFastIoPossible = TRUE;
+    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
     fcb->Header.AllocationSize.QuadPart = 0;
     fcb->Header.FileSize.QuadPart = 0;
     fcb->Header.ValidDataLength.QuadPart = 0;
     
     fcb->atts = IrpSp->Parameters.Create.FileAttributes;
     
-    if (options & FILE_DELETE_ON_CLOSE)
-        fcb->delete_on_close = TRUE;
-    
-    fcb->par = parfcb;
-    rc = InterlockedIncrement(&parfcb->refcount);
 #ifdef DEBUG_FCB_REFCOUNTS
-    WARN("fcb %p: refcount now %i (%.*S)\n", parfcb, rc, parfcb->full_filename.Length / sizeof(WCHAR), parfcb->full_filename.Buffer);
+    rc = InterlockedIncrement(&parfileref->fcb->refcount);
+    WARN("fcb %p: refcount now %i (%S)\n", parfileref->fcb, rc, file_desc_fileref(parfileref));
+#else
+    InterlockedIncrement(&parfileref->fcb->refcount);
 #endif
-    fcb->subvol = parfcb->subvol;
+    fcb->subvol = parfileref->fcb->subvol;
     fcb->inode = inode;
     fcb->type = type;
     
-    fcb->utf8.MaximumLength = fcb->utf8.Length = utf8len;
-    fcb->utf8.Buffer = utf8;
-    
-    Status = fcb_get_new_sd(fcb, IrpSp->Parameters.Create.SecurityContext->AccessState);
+    Status = fcb_get_new_sd(fcb, parfileref, IrpSp->Parameters.Create.SecurityContext->AccessState);
     
     if (!NT_SUCCESS(Status)) {
         ERR("fcb_get_new_sd returned %08x\n", Status);
-        ExFreePool(utf8);
+        free_fcb(fcb);
         return Status;
     }
+    
+    fileref = create_fileref();
+    if (!fileref) {
+        ERR("out of memory\n");
+        free_fcb(fcb);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    fileref->fcb = fcb;
 
-    fcb->filepart = *fpus;
+    fileref->utf8.MaximumLength = fileref->utf8.Length = utf8len;
+    fileref->utf8.Buffer = utf8;
+    
+    fileref->filepart = *fpus;
         
-    Status = set_xattr(Vcb, parfcb->subvol, inode, EA_NTACL, EA_NTACL_HASH, (UINT8*)fcb->sd, RtlLengthSecurityDescriptor(fcb->sd), rollback);
+    Status = set_xattr(Vcb, parfileref->fcb->subvol, inode, EA_NTACL, EA_NTACL_HASH, (UINT8*)fcb->sd, RtlLengthSecurityDescriptor(fcb->sd), rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("set_xattr returned %08x\n", Status);
-        ExFreePool(utf8);
+        free_fileref(fileref);
         return Status;
     }
     
-    fcb->full_filename.Length = parfcb->full_filename.Length + (parfcb->full_filename.Length == sizeof(WCHAR) ? 0 : sizeof(WCHAR)) + fcb->filepart.Length;
-    fcb->full_filename.MaximumLength = fcb->full_filename.Length;
-    fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->full_filename.Length, ALLOC_TAG);
-    if (!fcb->full_filename.Buffer) {
+    fileref->full_filename.Length = parfileref->full_filename.Length + (parfileref->full_filename.Length == sizeof(WCHAR) ? 0 : sizeof(WCHAR)) + fileref->filepart.Length;
+    fileref->full_filename.MaximumLength = fileref->full_filename.Length;
+    fileref->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->full_filename.Length, ALLOC_TAG);
+    if (!fileref->full_filename.Buffer) {
         ERR("out of memory\n");
-        ExFreePool(utf8);
+        free_fileref(fileref);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(fcb->full_filename.Buffer, parfcb->full_filename.Buffer, parfcb->full_filename.Length);
+    RtlCopyMemory(fileref->full_filename.Buffer, parfileref->full_filename.Buffer, parfileref->full_filename.Length);
     
-    if (parfcb->full_filename.Length > sizeof(WCHAR))
-        fcb->full_filename.Buffer[parfcb->full_filename.Length / sizeof(WCHAR)] = '\\';
+    if (parfileref->full_filename.Length > sizeof(WCHAR))
+        fileref->full_filename.Buffer[parfileref->full_filename.Length / sizeof(WCHAR)] = '\\';
     
-    RtlCopyMemory(&fcb->full_filename.Buffer[(parfcb->full_filename.Length / sizeof(WCHAR)) + (parfcb->full_filename.Length == sizeof(WCHAR) ? 0 : 1)], fcb->filepart.Buffer, fcb->filepart.Length);
+    RtlCopyMemory(&fileref->full_filename.Buffer[(parfileref->full_filename.Length / sizeof(WCHAR)) + (parfileref->full_filename.Length == sizeof(WCHAR) ? 0 : 1)],
+                  fileref->filepart.Buffer, fileref->filepart.Length);
     
     ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!ii) {
         ERR("out of memory\n");
-        ExFreePool(utf8);
+        free_fileref(fileref);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
-    insert_tree_item(Vcb, parfcb->subvol, inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
+    if (Irp->Overlay.AllocationSize.QuadPart > 0) {
+        Status = extend_file(fcb, fileref, Irp->Overlay.AllocationSize.QuadPart, TRUE, rollback);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("extend_file returned %08x\n", Status);
+            free_fileref(fileref);
+            return Status;
+        }
+    }
     
-    *pfcb = fcb;
+    RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+    insert_tree_item(Vcb, fcb->subvol, inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
     
     fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
     fcb->subvol->root_item.ctime = now;
     
-    InsertTailList(&fcb->par->children, &fcb->list_entry);
+    fileref->parent = parfileref;
+    InsertTailList(&parfileref->children, &fileref->list_entry);
+#ifdef DEBUG_FCB_REFCOUNTS
+    rc = InterlockedIncrement(&parfileref->refcount);
+    WARN("fileref %p: refcount now %i\n", parfileref, rc);
+#else
+    InterlockedIncrement(&parfileref->refcount);
+#endif
+    InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry);
+    
+    *pfr = fileref;
     
-    TRACE("created new file %.*S in subvol %llx, inode %llx\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb->subvol->id, fcb->inode);
+    TRACE("created new file %S in subvol %llx, inode %llx\n", file_desc_fileref(fileref), fcb->subvol->id, fcb->inode);
     
     return STATUS_SUCCESS;
 }
 
 static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJECT FileObject, PUNICODE_STRING fnus, ULONG disposition, ULONG options, LIST_ENTRY* rollback) {
     NTSTATUS Status;
-    fcb *fcb, *parfcb = NULL;
+//     fcb *fcb, *parfcb = NULL;
+    file_ref *fileref, *parfileref = NULL, *related;
     ULONG i, j;
-    ULONG utf8len;
+//     ULONG utf8len;
     ccb* ccb;
     static WCHAR datasuf[] = {':','$','D','A','T','A',0};
     UNICODE_STRING dsus, fpus, stream;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    ULONG access;
+    PACCESS_STATE access_state = IrpSp->Parameters.Create.SecurityContext->AccessState;
+#ifdef DEBUG_FCB_REFCOUNTS
     LONG oc;
+#endif
             
     TRACE("(%p, %p, %p, %.*S, %x, %x)\n", Irp, Vcb, FileObject, fnus->Length / sizeof(WCHAR), fnus->Buffer, disposition, options);
     
@@ -1377,20 +1594,25 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
     dsus.Length = dsus.MaximumLength = wcslen(datasuf) * sizeof(WCHAR);
     fpus.Buffer = NULL;
     
-    // FIXME - apparently you can open streams using RelatedFileObject. How can we test this?
+    if (FileObject->RelatedFileObject && FileObject->RelatedFileObject->FsContext2) {
+        struct _ccb* relatedccb = FileObject->RelatedFileObject->FsContext2;
+        
+        related = relatedccb->fileref;
+    } else
+        related = NULL;
     
     ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
-    Status = get_fcb(Vcb, &parfcb, fnus, FileObject->RelatedFileObject ? FileObject->RelatedFileObject->FsContext : NULL, TRUE);
+    Status = open_fileref(Vcb, &parfileref, &FileObject->FileName, related, TRUE, NULL);
     ExReleaseResourceLite(&Vcb->fcb_lock);
     if (!NT_SUCCESS(Status))
         goto end;
     
-    if (parfcb->type != BTRFS_TYPE_DIRECTORY) {
+    if (parfileref->fcb->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) {
         Status = STATUS_OBJECT_PATH_NOT_FOUND;
         goto end;
     }
     
-    if (parfcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
+    if (parfileref->fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
         Status = STATUS_ACCESS_DENIED;
         goto end;
     }
@@ -1448,7 +1670,8 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
     }
     
     if (stream.Length > 0) {
-        struct _fcb* newpar;
+        file_ref* newpar;
+        fcb* fcb;
         static char xapref[] = "user.";
         ULONG xapreflen = strlen(xapref), fnlen;
         LARGE_INTEGER time;
@@ -1456,31 +1679,66 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
         KEY searchkey;
         traverse_ptr tp;
         INODE_ITEM* ii;
+        ULONG utf8len;
+        UINT64 offset;
+#ifdef DEBUG_FCB_REFCOUNTS
         LONG rc;
+#endif
         
         TRACE("fpus = %.*S\n", fpus.Length / sizeof(WCHAR), fpus.Buffer);
         TRACE("stream = %.*S\n", stream.Length / sizeof(WCHAR), stream.Buffer);
         
         ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
-        Status = get_fcb(Vcb, &newpar, &fpus, parfcb, FALSE);
+        Status = open_fileref(Vcb, &newpar, &fpus, parfileref, FALSE, NULL);
         ExReleaseResourceLite(&Vcb->fcb_lock);
         
         if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
-            Status = file_create2(Irp, Vcb, &fpus, parfcb, options, &newpar, rollback);
+            UNICODE_STRING fpus2;
+            
+            if (!SeAccessCheck(parfileref->fcb->sd, &access_state->SubjectSecurityContext, FALSE, options & FILE_DIRECTORY_FILE ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL,
+                               IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &access, &Status)) {
+                WARN("SeAccessCheck failed, returning %08x\n", Status);
+                goto end;
+            }
+            
+            if (!is_file_name_valid(&fpus))
+                return STATUS_OBJECT_NAME_INVALID;
+            
+            fpus2.Length = fpus2.MaximumLength = fpus.Length;
+            fpus2.Buffer = ExAllocatePoolWithTag(PagedPool, fpus2.MaximumLength, ALLOC_TAG);
+            
+            if (!fpus2.Buffer) {
+                ERR("out of memory\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+            
+            RtlCopyMemory(fpus2.Buffer, fpus.Buffer, fpus2.Length);
+            
+            Status = file_create2(Irp, Vcb, &fpus2, parfileref, options, &newpar, rollback);
         
             if (!NT_SUCCESS(Status)) {
                 ERR("file_create2 returned %08x\n", Status);
+                ExFreePool(fpus2.Buffer);
                 goto end;
             }
+            
+            // FIXME - send notification
         } else if (!NT_SUCCESS(Status)) {
-            ERR("get_fcb returned %08x\n", Status);
+            ERR("open_fileref returned %08x\n", Status);
             goto end;
         }
         
-        free_fcb(parfcb);
-        parfcb = newpar;
+        free_fileref(parfileref);
+        parfileref = newpar;
+        
+        if (!SeAccessCheck(parfileref->fcb->sd, &access_state->SubjectSecurityContext, FALSE, access_state->OriginalDesiredAccess, 0, NULL,
+                           IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &access, &Status)) {
+            WARN("SeAccessCheck failed, returning %08x\n", Status);
+            goto end;
+        }
         
-        if (newpar->type != BTRFS_TYPE_FILE && newpar->type != BTRFS_TYPE_SYMLINK) {
+        if (parfileref->fcb->type != BTRFS_TYPE_FILE && parfileref->fcb->type != BTRFS_TYPE_SYMLINK) {
             WARN("parent not file or symlink\n");
             Status = STATUS_INVALID_PARAMETER;
             goto end;
@@ -1501,29 +1759,30 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
         
         fcb->Vcb = Vcb;
         
-//         fcb->Header.IsFastIoPossible = TRUE;
+        fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
         fcb->Header.AllocationSize.QuadPart = 0;
         fcb->Header.FileSize.QuadPart = 0;
         fcb->Header.ValidDataLength.QuadPart = 0;
         
-        if (options & FILE_DELETE_ON_CLOSE)
-            fcb->delete_on_close = TRUE;
-        
-        fcb->par = parfcb;
-        rc = InterlockedIncrement(&parfcb->refcount);
 #ifdef DEBUG_FCB_REFCOUNTS
-        WARN("fcb %p: refcount now %i (%.*S)\n", parfcb, rc, parfcb->full_filename.Length / sizeof(WCHAR), parfcb->full_filename.Buffer);
+        rc = InterlockedIncrement(&parfileref->fcb->refcount);
+        WARN("fcb %p: refcount now %i (%S)\n", parfileref->fcb, rc, file_desc_fileref(parfileref));
+#else
+        InterlockedIncrement(&parfileref->fcb->refcount);
 #endif
-        fcb->subvol = parfcb->subvol;
-        fcb->inode = parfcb->inode;
-        fcb->type = parfcb->type;
+        fcb->subvol = parfileref->fcb->subvol;
+        fcb->inode = parfileref->fcb->inode;
+        fcb->type = parfileref->fcb->type;
         
         fcb->ads = TRUE;
         fcb->adssize = 0;
         
         Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, stream.Buffer, stream.Length);
-        if (!NT_SUCCESS(Status))
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlUnicodeToUTF8N 1 returned %08x\n", Status);
+            free_fcb(fcb);
             goto end;
+        }
         
         fcb->adsxattr.Length = utf8len + xapreflen;
         fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1;
@@ -1539,6 +1798,7 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
         
         Status = RtlUnicodeToUTF8N(&fcb->adsxattr.Buffer[xapreflen], utf8len, &utf8len, stream.Buffer, stream.Length);
         if (!NT_SUCCESS(Status)) {
+            ERR("RtlUnicodeToUTF8N 2 returned %08x\n", Status);
             free_fcb(fcb);
             goto end;
         }
@@ -1550,91 +1810,123 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
         fcb->adshash = calc_crc32c(0xfffffffe, (UINT8*)fcb->adsxattr.Buffer, fcb->adsxattr.Length);
         TRACE("adshash = %08x\n", fcb->adshash);
 
-        fcb->name_offset = parfcb->full_filename.Length / sizeof(WCHAR);
-        if (parfcb != Vcb->root_fcb)
-            fcb->name_offset++;
-
-        fcb->filepart.MaximumLength = fcb->filepart.Length = stream.Length;
-        fcb->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->filepart.MaximumLength, ALLOC_TAG);
-        if (!fcb->filepart.Buffer) {
+        fileref = create_fileref();
+        if (!fileref) {
             ERR("out of memory\n");
             free_fcb(fcb);
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto end;
         }
         
-        RtlCopyMemory(fcb->filepart.Buffer, stream.Buffer, stream.Length);
+        fileref->fcb = fcb;
         
-        fnlen = (fcb->name_offset * sizeof(WCHAR)) + fcb->filepart.Length;
+        fileref->name_offset = parfileref->full_filename.Length / sizeof(WCHAR);
+        if (parfileref != Vcb->root_fileref)
+            fileref->name_offset++;
 
-        fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
-        if (!fcb->full_filename.Buffer) {
+        fileref->filepart.MaximumLength = fileref->filepart.Length = stream.Length;
+        fileref->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->filepart.MaximumLength, ALLOC_TAG);
+        if (!fileref->filepart.Buffer) {
             ERR("out of memory\n");
-            free_fcb(fcb);
+            free_fileref(fileref);
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto end;
         }
         
-        fcb->full_filename.Length = fcb->full_filename.MaximumLength = fnlen;
-        RtlCopyMemory(fcb->full_filename.Buffer, parfcb->full_filename.Buffer, parfcb->full_filename.Length);
-
-        fcb->full_filename.Buffer[parfcb->full_filename.Length / sizeof(WCHAR)] = ':';
+        RtlCopyMemory(fileref->filepart.Buffer, stream.Buffer, stream.Length);
+        
+        fnlen = (fileref->name_offset * sizeof(WCHAR)) + fileref->filepart.Length;
 
-        RtlCopyMemory(&fcb->full_filename.Buffer[fcb->name_offset], fcb->filepart.Buffer, fcb->filepart.Length);
-        TRACE("full_filename = %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+        fileref->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
+        if (!fileref->full_filename.Buffer) {
+            ERR("out of memory\n");
+            free_fileref(fileref);
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
         
-        InsertTailList(&fcb->par->children, &fcb->list_entry);
+        fileref->full_filename.Length = fileref->full_filename.MaximumLength = fnlen;
+        RtlCopyMemory(fileref->full_filename.Buffer, parfileref->full_filename.Buffer, parfileref->full_filename.Length);
+
+        fileref->full_filename.Buffer[parfileref->full_filename.Length / sizeof(WCHAR)] = ':';
+
+        RtlCopyMemory(&fileref->full_filename.Buffer[fileref->name_offset], fileref->filepart.Buffer, fileref->filepart.Length);
+        TRACE("full_filename = %.*S\n", fileref->full_filename.Length / sizeof(WCHAR), fileref->full_filename.Buffer);
         
-        Status = set_xattr(Vcb, parfcb->subvol, parfcb->inode, fcb->adsxattr.Buffer, fcb->adshash, (UINT8*)"", 0, rollback);
+        Status = set_xattr(Vcb, parfileref->fcb->subvol, parfileref->fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, (UINT8*)"", 0, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("set_xattr returned %08x\n", Status);
-            free_fcb(fcb);
+            free_fileref(fileref);
             goto end;
         }
         
+        InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry);
+        
         KeQuerySystemTime(&time);
         win_time_to_unix(time, &now);
         
-        parfcb->inode_item.transid = Vcb->superblock.generation;
-        parfcb->inode_item.sequence++;
-        parfcb->inode_item.st_ctime = now;
+        parfileref->fcb->inode_item.transid = Vcb->superblock.generation;
+        parfileref->fcb->inode_item.sequence++;
+        parfileref->fcb->inode_item.st_ctime = now;
         
-        searchkey.obj_id = parfcb->inode;
+        searchkey.obj_id = parfileref->fcb->inode;
         searchkey.obj_type = TYPE_INODE_ITEM;
         searchkey.offset = 0xffffffffffffffff;
         
-        Status = find_item(Vcb, parfcb->subvol, &tp, &searchkey, FALSE);
+        Status = find_item(Vcb, parfileref->fcb->subvol, &tp, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
+            free_fileref(fileref);
             goto end;
         }
         
         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
             delete_tree_item(Vcb, &tp, rollback);
+            offset = tp.item->key.offset;
         } else {
-            WARN("could not find INODE_ITEM for inode %llx in subvol %llx\n", searchkey.obj_id, parfcb->subvol->id);
+            WARN("could not find INODE_ITEM for inode %llx in subvol %llx\n", searchkey.obj_id, parfileref->fcb->subvol->id);
+            offset = 0;
         }
         
-        free_traverse_ptr(&tp);
-        
         ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
         if (!ii) {
             ERR("out of memory\n");
             Status = STATUS_INSUFFICIENT_RESOURCES;
+            free_fileref(fileref);
             goto end;
         }
     
-        RtlCopyMemory(ii, &parfcb->inode_item, sizeof(INODE_ITEM));
+        RtlCopyMemory(ii, &parfileref->fcb->inode_item, sizeof(INODE_ITEM));
+        
+        insert_tree_item(Vcb, parfileref->fcb->subvol, parfileref->fcb->inode, TYPE_INODE_ITEM, offset, ii, sizeof(INODE_ITEM), NULL, rollback);
         
-        insert_tree_item(Vcb, parfcb->subvol, parfcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
+        parfileref->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+        parfileref->fcb->subvol->root_item.ctime = now;
         
-        parfcb->subvol->root_item.ctransid = Vcb->superblock.generation;
-        parfcb->subvol->root_item.ctime = now;
+        fileref->parent = (struct _file_ref*)parfileref;
+        InsertTailList(&parfileref->children, &fileref->list_entry);
+#ifdef DEBUG_FCB_REFCOUNTS
+        rc = InterlockedIncrement(&parfileref->refcount);
+        WARN("fileref %p: refcount now %i\n", parfileref, rc);
+#else
+        InterlockedIncrement(&parfileref->refcount);
+#endif
         
         ExFreePool(fpus.Buffer);
         fpus.Buffer = NULL;
     } else {
-        Status = file_create2(Irp, Vcb, &fpus, parfcb, options, &fcb, rollback);
+        if (!SeAccessCheck(parfileref->fcb->sd, &access_state->SubjectSecurityContext, FALSE, options & FILE_DIRECTORY_FILE ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL,
+                           IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &access, &Status)) {
+            WARN("SeAccessCheck failed, returning %08x\n", Status);
+            goto end;
+        }
+        
+        if (!is_file_name_valid(&fpus)) {
+            Status = STATUS_OBJECT_NAME_INVALID;
+            goto end;
+        }
+        
+        Status = file_create2(Irp, Vcb, &fpus, parfileref, options, &fileref, rollback);
         
         if (!NT_SUCCESS(Status)) {
             ERR("file_create2 returned %08x\n", Status);
@@ -1642,26 +1934,20 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
         }
     }
     
-    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
-    
-    if (Vcb->fcbs)
-        Vcb->fcbs->prev = fcb;
-    
-    fcb->next = Vcb->fcbs;
-    Vcb->fcbs = fcb;
-    
-    ExReleaseResourceLite(&Vcb->fcb_lock);
-    
-    Status = attach_fcb_to_fileobject(Vcb, fcb, FileObject);
+    FileObject->FsContext = fileref->fcb;
     
     ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);
     if (!ccb) {
         ERR("out of memory\n");
         Status = STATUS_INSUFFICIENT_RESOURCES;
+        free_fileref(fileref);
         goto end;
     }
     
     RtlZeroMemory(ccb, sizeof(*ccb));
+    
+    ccb->fileref = fileref;
+    
     ccb->NodeType = BTRFS_NODE_TYPE_CCB;
     ccb->NodeSize = sizeof(ccb);
     ccb->disposition = disposition;
@@ -1670,48 +1956,49 @@ static NTSTATUS STDCALL file_create(PIRP Irp, device_extension* Vcb, PFILE_OBJEC
     RtlInitUnicodeString(&ccb->query_string, NULL);
     ccb->has_wildcard = FALSE;
     ccb->specific_file = FALSE;
+    ccb->access = access;
     
-    oc = InterlockedIncrement(&fcb->open_count);
 #ifdef DEBUG_FCB_REFCOUNTS
-    ERR("fcb %p: open_count now %i\n", fcb, oc);
+    oc = InterlockedIncrement(&fileref->fcb->open_count);
+    ERR("fcb %p: open_count now %i\n", fileref->fcb, oc);
+#else
+    InterlockedIncrement(&fileref->fcb->open_count);
 #endif
     
     FileObject->FsContext2 = ccb;
 
-    FileObject->SectionObjectPointer = &fcb->nonpaged->segment_object;
+    FileObject->SectionObjectPointer = &fileref->fcb->nonpaged->segment_object;
     
-    TRACE("returning FCB %p with parent %p\n", fcb, parfcb);
+//     TRACE("returning FCB %p with parent %p\n", fcb, parfcb);
     
     Status = consider_write(Vcb);
     
     if (NT_SUCCESS(Status)) {
-        ULONG fnlen;
-
-        fcb->name_offset = fcb->par->full_filename.Length / sizeof(WCHAR);
-                
-        if (fcb->par != Vcb->root_fcb)
-            fcb->name_offset++;
-        
-        fnlen = (fcb->name_offset * sizeof(WCHAR)) + fcb->filepart.Length;
-        
-        fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
-        if (!fcb->full_filename.Buffer) {
-            ERR("out of memory\n");
-            Status = STATUS_INSUFFICIENT_RESOURCES;
-            goto end;
-        }   
-        
-        fcb->full_filename.Length = fcb->full_filename.MaximumLength = fnlen;
-        RtlCopyMemory(fcb->full_filename.Buffer, fcb->par->full_filename.Buffer, fcb->par->full_filename.Length);
-        
-        if (fcb->par != Vcb->root_fcb)
-            fcb->full_filename.Buffer[fcb->par->full_filename.Length / sizeof(WCHAR)] = '\\';
-        
-        RtlCopyMemory(&fcb->full_filename.Buffer[fcb->name_offset], fcb->filepart.Buffer, fcb->filepart.Length);
+//         ULONG fnlen;
+// 
+//         fcb->name_offset = fcb->par->full_filename.Length / sizeof(WCHAR);
+//                 
+//         if (fcb->par != Vcb->root_fcb)
+//             fcb->name_offset++;
+//         
+//         fnlen = (fcb->name_offset * sizeof(WCHAR)) + fcb->filepart.Length;
+//         
+//         fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
+//         if (!fcb->full_filename.Buffer) {
+//             ERR("out of memory\n");
+//             Status = STATUS_INSUFFICIENT_RESOURCES;
+//             goto end;
+//         }   
+//         
+//         fcb->full_filename.Length = fcb->full_filename.MaximumLength = fnlen;
+//         RtlCopyMemory(fcb->full_filename.Buffer, fcb->par->full_filename.Buffer, fcb->par->full_filename.Length);
+//         
+//         if (fcb->par != Vcb->root_fcb)
+//             fcb->full_filename.Buffer[fcb->par->full_filename.Length / sizeof(WCHAR)] = '\\';
+//         
+//         RtlCopyMemory(&fcb->full_filename.Buffer[fcb->name_offset], fcb->filepart.Buffer, fcb->filepart.Length);
         
-        FsRtlNotifyFullReportChange(Vcb->NotifySync, &Vcb->DirNotifyList, (PSTRING)&fcb->full_filename, fcb->name_offset * sizeof(WCHAR), NULL, NULL,
-                                    options & FILE_DIRECTORY_FILE ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
-                                    FILE_ACTION_ADDED, NULL);
+        send_notification_fileref(fileref, options & FILE_DIRECTORY_FILE ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED);
         
         goto end2;
     }
@@ -1721,8 +2008,8 @@ end:
         ExFreePool(fpus.Buffer);
     
 end2:
-    if (parfcb)
-        free_fcb(parfcb);
+    if (parfileref)
+        free_fileref(parfileref);
     
     return Status;
 }
@@ -1852,11 +2139,12 @@ static __inline void debug_create_options(ULONG RequestedOptions) {
     }
 }
 
-static NTSTATUS update_inode_item(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* ii, LIST_ENTRY* rollback) {
+NTSTATUS update_inode_item(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* ii, LIST_ENTRY* rollback) {
     KEY searchkey;
     traverse_ptr tp;
     INODE_ITEM* newii;
     NTSTATUS Status;
+    UINT64 offset = 0;
     
     searchkey.obj_id = inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
@@ -1870,12 +2158,12 @@ static NTSTATUS update_inode_item(device_extension* Vcb, root* subvol, UINT64 in
     
     if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
         delete_tree_item(Vcb, &tp, rollback);
+        
+        offset = tp.item->key.offset;
     } else {
         WARN("could not find INODE_ITEM for inode %llx in subvol %llx\n", searchkey.obj_id, subvol->id);
     }
     
-    free_traverse_ptr(&tp);
-    
     newii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!newii) {
         ERR("out of memory\n");
@@ -1884,23 +2172,143 @@ static NTSTATUS update_inode_item(device_extension* Vcb, root* subvol, UINT64 in
 
     RtlCopyMemory(newii, ii, sizeof(INODE_ITEM));
     
-    insert_tree_item(Vcb, subvol, inode, TYPE_INODE_ITEM, 0, newii, sizeof(INODE_ITEM), NULL, rollback);
+    insert_tree_item(Vcb, subvol, inode, TYPE_INODE_ITEM, offset, newii, sizeof(INODE_ITEM), NULL, rollback);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS get_reparse_block(fcb* fcb, UINT8** data) {
+    NTSTATUS Status;
+    
+    if (fcb->type == BTRFS_TYPE_FILE || fcb->type == BTRFS_TYPE_SYMLINK) {
+        ULONG size, bytes_read, i;
+        
+        if (fcb->inode_item.st_size < sizeof(ULONG)) {
+            WARN("file was too short to be a reparse point\n");
+            return STATUS_INVALID_PARAMETER;
+        }
+        
+        // 0x10007 = 0xffff (maximum length of data buffer) + 8 bytes header
+        size = min(0x10007, fcb->inode_item.st_size);
+        
+        *data = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);
+        if (!*data) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        Status = read_file(fcb->Vcb, fcb->subvol, fcb->inode, *data, 0, size, &bytes_read);
+        if (!NT_SUCCESS(Status)) {
+            ERR("read_file returned %08x\n", Status);
+            ExFreePool(*data);
+            return Status;
+        }
+        
+        if (fcb->type == BTRFS_TYPE_SYMLINK) {
+            ULONG stringlen, subnamelen, printnamelen, reqlen;
+            REPARSE_DATA_BUFFER* rdb;
+            
+            Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, (char*)*data, bytes_read);
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+                ExFreePool(*data);
+                return Status;
+            }
+            
+            subnamelen = stringlen;
+            printnamelen = stringlen;
+            
+            reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;
+            
+            rdb = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);
+            
+            if (!rdb) {
+                ERR("out of memory\n");
+                ExFreePool(*data);
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
+            rdb->ReparseDataLength = reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer);
+            rdb->Reserved = 0;
+            
+            rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
+            rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;
+            rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;
+            rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;
+            rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
+            
+            Status = RtlUTF8ToUnicodeN(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
+                                    stringlen, &stringlen, (char*)*data, size);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+                ExFreePool(rdb);
+                ExFreePool(*data);
+                return Status;
+            }
+            
+            for (i = 0; i < stringlen / sizeof(WCHAR); i++) {
+                if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')
+                    rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\';
+            }
+            
+            RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],
+                        &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
+                        rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
+            
+            ExFreePool(*data);
+            
+            *data = (UINT8*)rdb;
+        } else {
+            Status = FsRtlValidateReparsePointBuffer(bytes_read, (REPARSE_DATA_BUFFER*)*data);
+            if (!NT_SUCCESS(Status)) {
+                ERR("FsRtlValidateReparsePointBuffer returned %08x\n", Status);
+                ExFreePool(*data);
+                return Status;
+            }
+        }
+    } else {
+        UINT16 datalen;
+        
+        if (!get_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_REPARSE, EA_REPARSE_HASH, data, &datalen))
+            return STATUS_INTERNAL_ERROR;
+        
+        if (!*data)
+            return STATUS_INTERNAL_ERROR;
+        
+        if (datalen < sizeof(ULONG)) {
+            WARN("xattr was too short to be a reparse point\n");
+            ExFreePool(*data);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        Status = FsRtlValidateReparsePointBuffer(datalen, (REPARSE_DATA_BUFFER*)*data);
+        if (!NT_SUCCESS(Status)) {
+            ERR("FsRtlValidateReparsePointBuffer returned %08x\n", Status);
+            ExFreePool(*data);
+            return Status;
+        }
+    }
     
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_ENTRY* rollback) {
+static NTSTATUS STDCALL open_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_ENTRY* rollback) {
     PFILE_OBJECT FileObject;
     ULONG RequestedDisposition;
     ULONG options;
     NTSTATUS Status;
-    fcb* fcb;
     ccb* ccb;
     device_extension* Vcb = DeviceObject->DeviceExtension;
     PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
     ULONG access;
     PACCESS_STATE access_state = Stack->Parameters.Create.SecurityContext->AccessState;
+    USHORT unparsed;
+    file_ref *related, *fileref;
+#ifdef DEBUG_FCB_REFCOUNTS
     LONG oc;
+#endif
     
     Irp->IoStatus.Information = 0;
     
@@ -1912,6 +2320,12 @@ static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_
         Status = STATUS_INVALID_PARAMETER;
         goto exit;
     }
+    
+    if (options & FILE_OPEN_BY_FILE_ID) {
+        WARN("FILE_OPEN_BY_FILE_ID not supported\n");
+        Status = STATUS_NOT_IMPLEMENTED;
+        goto exit;
+    }
 
     FileObject = Stack->FileObject;
 
@@ -1957,22 +2371,58 @@ static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_
     }
     
     // FIXME - if Vcb->readonly or subvol readonly, don't allow the write ACCESS_MASK flags
+    
+    if (FileObject->RelatedFileObject && FileObject->RelatedFileObject->FsContext2) {
+        struct _ccb* relatedccb = FileObject->RelatedFileObject->FsContext2;
+        
+        related = relatedccb->fileref;
+    } else
+        related = NULL;
 
     ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
-    Status = get_fcb(Vcb, &fcb, &FileObject->FileName, FileObject->RelatedFileObject ? FileObject->RelatedFileObject->FsContext : NULL, Stack->Flags & SL_OPEN_TARGET_DIRECTORY);
+    Status = open_fileref(Vcb, &fileref, &FileObject->FileName, related, Stack->Flags & SL_OPEN_TARGET_DIRECTORY, &unparsed);
     ExReleaseResourceLite(&Vcb->fcb_lock);
     
-    if (NT_SUCCESS(Status) && fcb->deleted) {
-        free_fcb(fcb);
-        Status = STATUS_OBJECT_NAME_NOT_FOUND;
+    if (Status == STATUS_REPARSE) {
+        REPARSE_DATA_BUFFER* data;
+        
+        Status = get_reparse_block(fileref->fcb, (UINT8**)&data);
+        if (!NT_SUCCESS(Status)) {
+            ERR("get_reparse_block returned %08x\n", Status);
+            
+            ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+            free_fileref(fileref);
+            ExReleaseResourceLite(&Vcb->fcb_lock);
+            goto exit;
+        }
+        
+        Status = STATUS_REPARSE;
+        RtlCopyMemory(&Irp->IoStatus.Information, data, sizeof(ULONG));
+        
+        data->Reserved = unparsed;
+        
+        Irp->Tail.Overlay.AuxiliaryBuffer = (void*)data;
+        
+        ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+        free_fileref(fileref);
+        ExReleaseResourceLite(&Vcb->fcb_lock);
         goto exit;
     }
     
+    if (NT_SUCCESS(Status) && fileref->deleted) {
+        ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+        free_fileref(fileref);
+        ExReleaseResourceLite(&Vcb->fcb_lock);
+        
+        Status = STATUS_OBJECT_NAME_NOT_FOUND;
+        goto exit; // FIXME?
+    }
+    
     if (NT_SUCCESS(Status)) {
         if (RequestedDisposition == FILE_CREATE) {
-            TRACE("file %.*S already exists, returning STATUS_OBJECT_NAME_COLLISION\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+            TRACE("file %S already exists, returning STATUS_OBJECT_NAME_COLLISION\n", file_desc_fileref(fileref));
             Status = STATUS_OBJECT_NAME_COLLISION;
-            free_fcb(fcb);
+            free_fileref(fileref);
             goto exit;
         }
     } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
@@ -1981,88 +2431,122 @@ static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_
             goto exit;
         }
     } else {
-        TRACE("get_fcb returned %08x\n", Status);
+        TRACE("open_fileref returned %08x\n", Status);
         goto exit;
     }
     
     if (NT_SUCCESS(Status)) { // file already exists
-        struct _fcb* sf;
+        file_ref* sf;
         
         if (Vcb->readonly && RequestedDisposition == FILE_OVERWRITE_IF) {
             Status = STATUS_MEDIA_WRITE_PROTECTED;
-            free_fcb(fcb);
+            free_fileref(fileref);
             goto exit;
         }
         
-        if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY && (RequestedDisposition == FILE_SUPERSEDE ||
+        if (fileref->fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY && (RequestedDisposition == FILE_SUPERSEDE ||
             RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF)) {
             Status = STATUS_ACCESS_DENIED;
-            free_fcb(fcb);
+            free_fileref(fileref);
             goto exit;
         }
         
-        TRACE("deleted = %s\n", fcb->deleted ? "TRUE" : "FALSE");
+        TRACE("deleted = %s\n", fileref->deleted ? "TRUE" : "FALSE");
         
-        sf = fcb;
+        sf = fileref;
         while (sf) {
             if (sf->delete_on_close) {
                 WARN("could not open as deletion pending\n");
                 Status = STATUS_DELETE_PENDING;
-                free_fcb(fcb);
+                
+                ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+                free_fileref(fileref);
+                ExReleaseResourceLite(&Vcb->fcb_lock);
                 goto exit;
             }
-            sf = sf->par;
+            sf = sf->parent;
+        }
+        
+        if (fileref->fcb->type == BTRFS_TYPE_DIRECTORY && (RequestedDisposition == FILE_SUPERSEDE || RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF)) {
+            Status = STATUS_ACCESS_DENIED;
+            free_fileref(fileref);
+            goto exit;
         }
         
-        if (fcb->type == BTRFS_TYPE_SYMLINK && !(options & FILE_OPEN_REPARSE_POINT))  {
-            if (!follow_symlink(fcb, FileObject)) {
-                ERR("follow_symlink failed\n");
-                Status = STATUS_INTERNAL_ERROR;
-                free_fcb(fcb);
+        if ((fileref->fcb->type == BTRFS_TYPE_SYMLINK || fileref->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) && !(options & FILE_OPEN_REPARSE_POINT))  {
+            UINT8* data;
+            
+            /* How reparse points work from the point of view of the filesystem appears to
+             * undocumented. When returning STATUS_REPARSE, MSDN encourages us to return
+             * IO_REPARSE in Irp->IoStatus.Information, but that means we have to do our own
+             * translation. If we instead return the reparse tag in Information, and store
+             * a pointer to the reparse data buffer in Irp->Tail.Overlay.AuxiliaryBuffer,
+             * IopSymlinkProcessReparse will do the translation for us. */
+            
+            Status = get_reparse_block(fileref->fcb, &data);
+            if (!NT_SUCCESS(Status)) {
+                ERR("get_reparse_block returned %08x\n", Status);
+                
+                ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+                free_fileref(fileref);
+                ExReleaseResourceLite(&Vcb->fcb_lock);
                 goto exit;
             }
             
             Status = STATUS_REPARSE;
-            Irp->IoStatus.Information = IO_REPARSE;
-            free_fcb(fcb);
+            RtlCopyMemory(&Irp->IoStatus.Information, data, sizeof(ULONG));
+            
+            Irp->Tail.Overlay.AuxiliaryBuffer = (void*)data;
+            
+            ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+            free_fileref(fileref);
+            ExReleaseResourceLite(&Vcb->fcb_lock);
             goto exit;
         }
         
-        if (!SeAccessCheck(fcb->sd, &access_state->SubjectSecurityContext, FALSE, access_state->OriginalDesiredAccess, 0, NULL,
+        if (!SeAccessCheck(fileref->fcb->ads ? fileref->parent->fcb->sd : fileref->fcb->sd, &access_state->SubjectSecurityContext, FALSE, access_state->OriginalDesiredAccess, 0, NULL,
             IoGetFileObjectGenericMapping(), Stack->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &access, &Status)) {
             WARN("SeAccessCheck failed, returning %08x\n", Status);
-            free_fcb(fcb);
+        
+            ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+            free_fileref(fileref);
+            ExReleaseResourceLite(&Vcb->fcb_lock);
             goto exit;
         }
         
-        if (fcb->open_count > 0) {
-            Status = IoCheckShareAccess(access, Stack->Parameters.Create.ShareAccess, FileObject, &fcb->share_access, TRUE);
+        if (fileref->fcb->open_count > 0) {
+            Status = IoCheckShareAccess(access, Stack->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access, TRUE);
             
             if (!NT_SUCCESS(Status)) {
                 WARN("IoCheckShareAccess failed, returning %08x\n", Status);
-                free_fcb(fcb);
+                
+                ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+                free_fileref(fileref);
+                ExReleaseResourceLite(&Vcb->fcb_lock);
                 goto exit;
             }
         } else {
-            IoSetShareAccess(access, Stack->Parameters.Create.ShareAccess, FileObject, &fcb->share_access);
+            IoSetShareAccess(access, Stack->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access);
         }
 
         if (access & FILE_WRITE_DATA || options & FILE_DELETE_ON_CLOSE) {
-            if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForWrite)) {
+            if (!MmFlushImageSection(&fileref->fcb->nonpaged->segment_object, MmFlushForWrite)) {
                 Status = (options & FILE_DELETE_ON_CLOSE) ? STATUS_CANNOT_DELETE : STATUS_SHARING_VIOLATION;
-                free_fcb(fcb);
+                
+                ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+                free_fileref(fileref);
+                ExReleaseResourceLite(&Vcb->fcb_lock);
                 goto exit;
             }
         }
         
         if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) {
             ULONG defda;
-            LIST_ENTRY changed_sector_list;
             
-            if ((RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF) && fcb->atts & FILE_ATTRIBUTE_READONLY) {
+            if ((RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF) && fileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {
                 WARN("cannot overwrite readonly file\n");
                 Status = STATUS_ACCESS_DENIED;
-                free_fcb(fcb);
+                free_fileref(fileref);
                 goto exit;
             }
     
@@ -2073,137 +2557,100 @@ static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_
 //                 free_fcb(fcb);
 //                 goto exit;
 //             }
-            InitializeListHead(&changed_sector_list);
             
             // FIXME - make sure not ADS!
-            Status = truncate_file(fcb, fcb->inode_item.st_size, rollback);
+            Status = truncate_file(fileref->fcb, fileref->fcb->inode_item.st_size, rollback);
             if (!NT_SUCCESS(Status)) {
                 ERR("truncate_file returned %08x\n", Status);
-                free_fcb(fcb);
+                free_fileref(fileref);
                 goto exit;
             }
             
-            Status = update_inode_item(Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, rollback);
+            if (Irp->Overlay.AllocationSize.QuadPart > 0) {
+                Status = extend_file(fileref->fcb, fileref, Irp->Overlay.AllocationSize.QuadPart, TRUE, rollback);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("extend_file returned %08x\n", Status);
+                    free_fileref(fileref);
+                    goto exit;
+                }
+            }
+            
+            Status = update_inode_item(Vcb, fileref->fcb->subvol, fileref->fcb->inode, &fileref->fcb->inode_item, rollback);
             if (!NT_SUCCESS(Status)) {
                 ERR("update_inode_item returned %08x\n", Status);
-                free_fcb(fcb);
+                free_fileref(fileref);
                 goto exit;
             }
             
-            defda = get_file_attributes(Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, fcb->filepart.Length > 0 && fcb->filepart.Buffer[0] == '.', TRUE);
+            defda = get_file_attributes(Vcb, &fileref->fcb->inode_item, fileref->fcb->subvol, fileref->fcb->inode, fileref->fcb->type,
+                                        fileref->filepart.Length > 0 && fileref->filepart.Buffer[0] == '.', TRUE);
             
             if (RequestedDisposition == FILE_SUPERSEDE)
-                fcb->atts = Stack->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
+                fileref->fcb->atts = Stack->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
             else
-                fcb->atts |= Stack->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
+                fileref->fcb->atts |= Stack->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
             
             if (Stack->Parameters.Create.FileAttributes != defda) {
                 char val[64];
             
                 sprintf(val, "0x%x", Stack->Parameters.Create.FileAttributes);
             
-                Status = set_xattr(Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), rollback);
+                Status = set_xattr(Vcb, fileref->fcb->subvol, fileref->fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), rollback);
                 if (!NT_SUCCESS(Status)) {
                     ERR("set_xattr returned %08x\n", Status);
-                    free_fcb(fcb);
+                    free_fileref(fileref);
                     goto exit;
                 }
             } else
-                delete_xattr(Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, rollback);
+                delete_xattr(Vcb, fileref->fcb->subvol, fileref->fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, rollback);
             
             // FIXME - truncate streams
             // FIXME - do we need to alter parent directory's times?
             // FIXME - send notifications
             
-            Status = consider_write(fcb->Vcb);
+            Status = consider_write(Vcb);
             if (!NT_SUCCESS(Status)) {
                 ERR("consider_write returned %08x\n", Status);
-                free_fcb(fcb);
+                free_fileref(fileref);
                 goto exit;
             }
         }
     
-//         fcb->Header.IsFastIoPossible = TRUE;
-        
-        if (fcb->ads) {
-            fcb->Header.AllocationSize.QuadPart = fcb->adssize;
-            fcb->Header.FileSize.QuadPart = fcb->adssize;
-            fcb->Header.ValidDataLength.QuadPart = fcb->adssize;
-        } else if (fcb->inode_item.st_size == 0 || (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK)) {
-            fcb->Header.AllocationSize.QuadPart = 0;
-            fcb->Header.FileSize.QuadPart = 0;
-            fcb->Header.ValidDataLength.QuadPart = 0;
-        } else {
-            KEY searchkey;
-            traverse_ptr tp;
-            EXTENT_DATA* ed;
-            
-            searchkey.obj_id = fcb->inode;
-            searchkey.obj_type = TYPE_EXTENT_DATA;
-            searchkey.offset = 0xffffffffffffffff;
-            
-            Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
-            if (!NT_SUCCESS(Status)) {
-                ERR("error - find_item returned %08x\n", Status);
-                free_fcb(fcb);
-                goto exit;
-            }
-            
-            if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
-                ERR("error - could not find EXTENT_DATA items for inode %llx in subvol %llx\n", fcb->inode, fcb->subvol->id);
-                free_traverse_ptr(&tp);
-                free_fcb(fcb);
-                Status = STATUS_INTERNAL_ERROR;
-                goto exit;
-            }
-            
-            if (tp.item->size < sizeof(EXTENT_DATA)) {
-                ERR("(%llx,%x,%llx) was %llx bytes, expected at least %llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
-                    tp.item->size, sizeof(EXTENT_DATA));
-                free_traverse_ptr(&tp);
-                free_fcb(fcb);
-                Status = STATUS_INTERNAL_ERROR;
-                goto exit;
-            }
+        if (options & FILE_NON_DIRECTORY_FILE && fileref->fcb->type == BTRFS_TYPE_DIRECTORY) {
+            ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+            free_fileref(fileref);
+            ExReleaseResourceLite(&Vcb->fcb_lock);
             
-            ed = (EXTENT_DATA*)tp.item->data;
-            
-            if (ed->type == EXTENT_TYPE_INLINE)
-                fcb->Header.AllocationSize.QuadPart = fcb->inode_item.st_size;
-            else
-                fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
-            
-            fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;
-            fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;
-            
-            free_traverse_ptr(&tp);
-        }
-    
-        if (options & FILE_NON_DIRECTORY_FILE && fcb->type == BTRFS_TYPE_DIRECTORY) {
-            free_fcb(fcb);
             Status = STATUS_FILE_IS_A_DIRECTORY;
             goto exit;
-        } else if (options & FILE_DIRECTORY_FILE && fcb->type != BTRFS_TYPE_DIRECTORY) {
-            TRACE("returning STATUS_NOT_A_DIRECTORY (type = %u, path = %.*S)\n", fcb->type, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
-            free_fcb(fcb);
+        } else if (options & FILE_DIRECTORY_FILE && fileref->fcb->type != BTRFS_TYPE_DIRECTORY) {
+            TRACE("returning STATUS_NOT_A_DIRECTORY (type = %u, %S)\n", fileref->fcb->type, file_desc_fileref(fileref));
+            
+            ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+            free_fileref(fileref);
+            ExReleaseResourceLite(&Vcb->fcb_lock);
+            
             Status = STATUS_NOT_A_DIRECTORY;
             goto exit;
         }
     
-        Status = attach_fcb_to_fileobject(Vcb, fcb, FileObject);
-        
-        if (options & FILE_DELETE_ON_CLOSE)
-            fcb->delete_on_close = TRUE;
+        FileObject->FsContext = fileref->fcb;
         
         ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);
         if (!ccb) {
             ERR("out of memory\n");
-            free_fcb(fcb);
+            
+            ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+            free_fileref(fileref);
+            ExReleaseResourceLite(&Vcb->fcb_lock);
+
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto exit;
         }
         
         RtlZeroMemory(ccb, sizeof(*ccb));
+        
         ccb->NodeType = BTRFS_NODE_TYPE_CCB;
         ccb->NodeSize = sizeof(ccb);
         ccb->disposition = RequestedDisposition;
@@ -2212,10 +2659,13 @@ static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_
         RtlInitUnicodeString(&ccb->query_string, NULL);
         ccb->has_wildcard = FALSE;
         ccb->specific_file = FALSE;
+        ccb->access = access;
+        
+        ccb->fileref = fileref;
         
         FileObject->FsContext2 = ccb;
         
-        FileObject->SectionObjectPointer = &fcb->nonpaged->segment_object;
+        FileObject->SectionObjectPointer = &fileref->fcb->nonpaged->segment_object;
         
         if (NT_SUCCESS(Status)) {
             switch (RequestedDisposition) {
@@ -2235,16 +2685,15 @@ static NTSTATUS STDCALL create_file(PDEVICE_OBJECT DeviceObject, PIRP Irp, LIST_
             }
         }
         
-        oc = InterlockedIncrement(&fcb->open_count);
 #ifdef DEBUG_FCB_REFCOUNTS
-        ERR("fcb %p: open_count now %i\n", fcb, oc);
+        oc = InterlockedIncrement(&fileref->fcb->open_count);
+        ERR("fcb %p: open_count now %i\n", fileref->fcb, oc);
+#else
+        InterlockedIncrement(&fileref->fcb->open_count);
 #endif
     } else {
         Status = file_create(Irp, DeviceObject->DeviceExtension, FileObject, &FileObject->FileName, RequestedDisposition, options, rollback);
         Irp->IoStatus.Information = NT_SUCCESS(Status) ? FILE_CREATED : 0;
-        
-//         if (!NT_SUCCESS(Status))
-//             free_fcb(fcb);
     }
     
     if (NT_SUCCESS(Status) && !(options & FILE_NO_INTERMEDIATE_BUFFERING))
@@ -2337,7 +2786,9 @@ NTSTATUS STDCALL drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
     if (IrpSp->FileObject->FileName.Length == 0 && !IrpSp->FileObject->RelatedFileObject) {
         ULONG RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff);
         ULONG RequestedOptions = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;
+#ifdef DEBUG_FCB_REFCOUNTS
         LONG rc, oc;
+#endif
         
         TRACE("open operation for volume\n");
 
@@ -2354,44 +2805,45 @@ NTSTATUS STDCALL drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
             goto exit;
         }
 
+#ifdef DEBUG_FCB_REFCOUNTS
         rc = InterlockedIncrement(&Vcb->volume_fcb->refcount);
         oc = InterlockedIncrement(&Vcb->volume_fcb->open_count);
-#ifdef DEBUG_FCB_REFCOUNTS
         WARN("fcb %p: refcount now %i (volume)\n", Vcb->volume_fcb, rc);
         WARN("fcb %p: open_count now %i (volume)\n", Vcb->volume_fcb, oc);
+#else
+        InterlockedIncrement(&Vcb->volume_fcb->refcount);
+        InterlockedIncrement(&Vcb->volume_fcb->open_count);
 #endif
-        attach_fcb_to_fileobject(Vcb, Vcb->volume_fcb, IrpSp->FileObject);
-// //         NtfsAttachFCBToFileObject(DeviceExt, DeviceExt->VolumeFcb, FileObject);
-// //         DeviceExt->VolumeFcb->RefCount++;
+        IrpSp->FileObject->FsContext = Vcb->volume_fcb;
         
         IrpSp->FileObject->SectionObjectPointer = &Vcb->volume_fcb->nonpaged->segment_object;
 
         Irp->IoStatus.Information = FILE_OPENED;
         Status = STATUS_SUCCESS;
     } else {
-        BOOL exclusive;
+        BOOL exclusive, skip_lock;
         ULONG disposition;
         
         TRACE("file name: %.*S\n", IrpSp->FileObject->FileName.Length / sizeof(WCHAR), IrpSp->FileObject->FileName.Buffer);
         
-        if (IrpSp->FileObject->RelatedFileObject) {
-            fcb* relfcb = IrpSp->FileObject->RelatedFileObject->FsContext;
-            
-            if (relfcb)
-                TRACE("related file name = %.*S\n", relfcb->full_filename.Length / sizeof(WCHAR), relfcb->full_filename.Buffer);
-        }
+        if (IrpSp->FileObject->RelatedFileObject)
+            TRACE("related file = %S\n", file_desc(IrpSp->FileObject->RelatedFileObject));
         
         disposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff);
         
         // We acquire the lock exclusively if there's the possibility we might be writing
         exclusive = disposition != FILE_OPEN;
+        
+        // Don't lock again if we're being called from within CcCopyRead etc.
+        skip_lock = ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock);
 
-        acquire_tree_lock(Vcb, exclusive); 
+        if (!skip_lock)
+            acquire_tree_lock(Vcb, exclusive); 
         
 //         ExAcquireResourceExclusiveLite(&Vpb->DirResource, TRUE);
     //     Status = NtfsCreateFile(DeviceObject,
     //                             Irp);
-        Status = create_file(DeviceObject, Irp, &rollback);
+        Status = open_file(DeviceObject, Irp, &rollback);
 //         ExReleaseResourceLite(&Vpb->DirResource);
         
         if (exclusive && !NT_SUCCESS(Status))
@@ -2399,7 +2851,8 @@ NTSTATUS STDCALL drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
         else
             clear_rollback(&rollback);
         
-        release_tree_lock(Vcb, exclusive);
+        if (!skip_lock)
+            release_tree_lock(Vcb, exclusive);
         
 //         Status = STATUS_ACCESS_DENIED;
     }
index 9f062a0..30bca36 100644 (file)
@@ -31,7 +31,59 @@ typedef struct {
     enum DirEntryType dir_entry_type;
 } dir_entry;
 
-static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp, dir_entry* de, root* r) {
+ULONG STDCALL get_reparse_tag(device_extension* Vcb, root* subvol, UINT64 inode, UINT8 type) {
+    ULONG att, tag, br;
+    NTSTATUS Status;
+    
+    // FIXME - will this slow things down?
+    
+    if (type == BTRFS_TYPE_SYMLINK)
+        return IO_REPARSE_TAG_SYMLINK;
+    
+    if (type != BTRFS_TYPE_FILE && type != BTRFS_TYPE_DIRECTORY)
+        return 0;
+    
+    att = get_file_attributes(Vcb, NULL, subvol, inode, type, FALSE, FALSE);
+    
+    if (!(att & FILE_ATTRIBUTE_REPARSE_POINT))
+        return 0;
+    
+    if (type == BTRFS_TYPE_DIRECTORY) {
+        UINT8* data;
+        UINT16 datalen;
+        
+        if (!get_xattr(Vcb, subvol, inode, EA_REPARSE, EA_REPARSE_HASH, &data, &datalen))
+            return 0;
+        
+        if (!data)
+            return 0;
+        
+        if (datalen < sizeof(ULONG)) {
+            ExFreePool(data);
+            return 0;
+        }
+        
+        RtlCopyMemory(&tag, data, sizeof(ULONG));
+        
+        ExFreePool(data);
+    } else {
+        // FIXME - see if file loaded and cached, and do CcCopyRead if it is
+
+        Status = read_file(Vcb, subvol, inode, (UINT8*)&tag, 0, sizeof(ULONG), &br);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("read_file returned %08x\n", Status);
+            return 0;
+        }
+        
+        if (br < sizeof(ULONG))
+            return 0;
+    }
+    
+    return tag;
+}
+
+static NTSTATUS STDCALL query_dir_item(fcb* fcb, file_ref* fileref, void* buf, LONG* len, PIRP Irp, dir_entry* de, root* r) {
     PIO_STACK_LOCATION IrpSp;
     UINT32 needed;
     UINT64 inode;
@@ -43,9 +95,21 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
     IrpSp = IoGetCurrentIrpStackLocation(Irp);
     
     if (de->key.obj_type == TYPE_ROOT_ITEM) { // subvol
-        r = fcb->Vcb->roots;
-        while (r && r->id != de->key.obj_id)
-            r = r->next;
+        LIST_ENTRY* le;
+        
+        r = NULL;
+        
+        le = fcb->Vcb->roots.Flink;
+        while (le != &fcb->Vcb->roots) {
+            root* subvol = CONTAINING_RECORD(le, root, list_entry);
+            
+            if (subvol->id == de->key.obj_id) {
+                r = subvol;
+                break;
+            }
+            
+            le = le->Flink;
+        }
         
         if (!r) {
             ERR("could not find root %llx\n", de->key.obj_id);
@@ -64,17 +128,23 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
                 LIST_ENTRY* le;
                 BOOL found = FALSE;
                 
-                le = fcb->children.Flink;
-                while (le != &fcb->children) {
-                    struct _fcb* c = CONTAINING_RECORD(le, struct _fcb, list_entry);
+                if (fileref) {
+                    ExAcquireResourceSharedLite(&fcb->Vcb->fcb_lock, TRUE);
                     
-                    if (c->subvol == r && c->inode == inode) {
-                        ii = c->inode_item;
-                        found = TRUE;
-                        break;
+                    le = fileref->children.Flink;
+                    while (le != &fileref->children) {
+                        file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);
+                        
+                        if (c->fcb->subvol == r && c->fcb->inode == inode && !c->fcb->ads) {
+                            ii = c->fcb->inode_item;
+                            found = TRUE;
+                            break;
+                        }
+                        
+                        le = le->Flink;
                     }
                     
-                    le = le->Flink;
+                    ExReleaseResourceLite(&fcb->Vcb->fcb_lock);
                 }
                 
                 if (!found) {
@@ -93,7 +163,6 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
                     
                     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
                         ERR("could not find inode item for inode %llx in root %llx\n", inode, r->id);
-                        free_traverse_ptr(&tp);
                         return STATUS_INTERNAL_ERROR;
                     }
                     
@@ -101,8 +170,6 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
                     
                     if (tp.item->size > 0)
                         RtlCopyMemory(&ii, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
-                    
-                    free_traverse_ptr(&tp);
                 }
                 
                 break;
@@ -115,9 +182,14 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
                 break;
                 
             case DirEntryType_Parent:
-                ii = fcb->par->inode_item;
-                r = fcb->par->subvol;
-                inode = fcb->par->inode;
+                if (fileref && fileref->parent) {
+                    ii = fileref->parent->fcb->inode_item;
+                    r = fileref->parent->fcb->subvol;
+                    inode = fileref->parent->fcb->inode;
+                } else {
+                    ERR("no fileref\n");
+                    return STATUS_INTERNAL_ERROR;
+                }
                 break;
         }
     }
@@ -162,7 +234,7 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
             fbdi->AllocationSize.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_blocks;
             fbdi->FileAttributes = get_file_attributes(fcb->Vcb, &ii, r, inode, de->type, dotfile, FALSE);
             fbdi->FileNameLength = stringlen;
-            fbdi->EaSize = de->type == BTRFS_TYPE_SYMLINK ? IO_REPARSE_TAG_SYMLINK : 0;
+            fbdi->EaSize = get_reparse_tag(fcb->Vcb, r, inode, de->type);
             fbdi->ShortNameLength = 0;
 //             fibdi->ShortName[12];
             
@@ -237,7 +309,7 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
             ffdi->AllocationSize.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_blocks;
             ffdi->FileAttributes = get_file_attributes(fcb->Vcb, &ii, r, inode, de->type, dotfile, FALSE);
             ffdi->FileNameLength = stringlen;
-            ffdi->EaSize = de->type == BTRFS_TYPE_SYMLINK ? IO_REPARSE_TAG_SYMLINK : 0;
+            ffdi->EaSize = get_reparse_tag(fcb->Vcb, r, inode, de->type);
             
             Status = RtlUTF8ToUnicodeN(ffdi->FileName, stringlen, &stringlen, de->name, de->namelen);
 
@@ -277,7 +349,7 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
             fibdi->AllocationSize.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_blocks;
             fibdi->FileAttributes = get_file_attributes(fcb->Vcb, &ii, r, inode, de->type, dotfile, FALSE);
             fibdi->FileNameLength = stringlen;
-            fibdi->EaSize = de->type == BTRFS_TYPE_SYMLINK ? IO_REPARSE_TAG_SYMLINK : 0;
+            fibdi->EaSize = get_reparse_tag(fcb->Vcb, r, inode, de->type);
             fibdi->ShortNameLength = 0;
 //             fibdi->ShortName[12];
             fibdi->FileId.QuadPart = inode;
@@ -347,13 +419,13 @@ static NTSTATUS STDCALL query_dir_item(fcb* fcb, void* buf, LONG* len, PIRP Irp,
     return STATUS_NO_MORE_FILES;
 }
 
-static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de, traverse_ptr* tp) {
+static NTSTATUS STDCALL next_dir_entry(fcb* fcb, file_ref* fileref, UINT64* offset, dir_entry* de, traverse_ptr* tp) {
     KEY searchkey;
     traverse_ptr next_tp;
     DIR_ITEM* di;
     NTSTATUS Status;
     
-    if (fcb->par) { // don't return . and .. if root directory
+    if (fileref && fileref->parent) { // don't return . and .. if root directory
         if (*offset == 0) {
             de->key.obj_id = fcb->inode;
             de->key.obj_type = TYPE_INODE_ITEM;
@@ -367,7 +439,7 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
             
             return STATUS_SUCCESS;
         } else if (*offset == 1) {
-            de->key.obj_id = fcb->par->inode;
+            de->key.obj_id = fileref->parent->fcb->inode;
             de->key.obj_type = TYPE_INODE_ITEM;
             de->key.offset = 0;
             de->dir_entry_type = DirEntryType_Parent;
@@ -389,7 +461,6 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
         Status = find_item(fcb->Vcb, fcb->subvol, tp, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
-            free_traverse_ptr(tp);
             tp->tree = NULL;
             return Status;
         }
@@ -398,7 +469,6 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
         
         if (keycmp(&tp->item->key, &searchkey) == -1) {
             if (find_next_item(fcb->Vcb, tp, &next_tp, FALSE)) {
-                free_traverse_ptr(tp);
                 *tp = next_tp;
                 
                 TRACE("moving on to %llx,%x,%llx\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);
@@ -406,7 +476,6 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
         }
         
         if (tp->item->key.obj_id != searchkey.obj_id || tp->item->key.obj_type != searchkey.obj_type || tp->item->key.offset < *offset) {
-            free_traverse_ptr(tp);
             tp->tree = NULL;
             return STATUS_NO_MORE_FILES;
         }
@@ -418,7 +487,6 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
         if (tp->item->size < sizeof(DIR_ITEM) || tp->item->size < sizeof(DIR_ITEM) - 1 + di->m + di->n) {
             ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->size, sizeof(DIR_ITEM));
             
-            free_traverse_ptr(tp);
             tp->tree = NULL;
             return STATUS_INTERNAL_ERROR;
         }
@@ -433,7 +501,6 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
     } else {
         if (find_next_item(fcb->Vcb, tp, &next_tp, FALSE)) {
             if (next_tp.item->key.obj_type == TYPE_DIR_INDEX && next_tp.item->key.obj_id == tp->item->key.obj_id) {
-                free_traverse_ptr(tp);
                 *tp = next_tp;
                 
                 *offset = tp->item->key.offset + 1;
@@ -442,8 +509,6 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
                 
                 if (tp->item->size < sizeof(DIR_ITEM) || tp->item->size < sizeof(DIR_ITEM) - 1 + di->m + di->n) {
                     ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->size, sizeof(DIR_ITEM));
-                    
-                    free_traverse_ptr(&next_tp);
                     return STATUS_INTERNAL_ERROR;
                 }
         
@@ -454,10 +519,8 @@ static NTSTATUS STDCALL next_dir_entry(fcb* fcb, UINT64* offset, dir_entry* de,
                 de->dir_entry_type = DirEntryType_File;
                 
                 return STATUS_SUCCESS;
-            } else {
-                free_traverse_ptr(&next_tp);
+            } else
                 return STATUS_NO_MORE_FILES;
-            }
         } else
             return STATUS_NO_MORE_FILES;
     }
@@ -468,11 +531,12 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     NTSTATUS Status, status2;
     fcb* fcb;
     ccb* ccb;
+    file_ref* fileref;
     void* buf;
     UINT8 *curitem, *lastitem;
     LONG length;
     ULONG count;
-    BOOL has_wildcard = FALSE, specific_file = FALSE;
+    BOOL has_wildcard = FALSE, specific_file = FALSE, initial;
 //     UINT64 num_reads_orig;
     traverse_ptr tp;
     dir_entry de;
@@ -486,10 +550,11 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     IrpSp = IoGetCurrentIrpStackLocation(Irp);
     fcb = IrpSp->FileObject->FsContext;
     ccb = IrpSp->FileObject->FsContext2;
+    fileref = ccb ? ccb->fileref : NULL;
     
     acquire_tree_lock(fcb->Vcb, FALSE);
     
-    TRACE("%.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    TRACE("%S\n", file_desc(IrpSp->FileObject));
     
     if (IrpSp->Flags == 0) {
         TRACE("QD flags: (none)\n");
@@ -517,6 +582,8 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
             TRACE("    unknown flags: %u\n", flags);
     }
     
+    initial = !ccb->query_string.Buffer;
+    
     if (IrpSp->Flags & SL_RESTART_SCAN) {
         ccb->query_dir_offset = 0;
         
@@ -567,6 +634,9 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     } else {
         has_wildcard = ccb->has_wildcard;
         specific_file = ccb->specific_file;
+        
+        if (!(IrpSp->Flags & SL_RESTART_SCAN))
+            initial = FALSE;
     }
     
     if (ccb->query_string.Buffer) {
@@ -574,10 +644,10 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     }
     
     tp.tree = NULL;
-    Status = next_dir_entry(fcb, &ccb->query_dir_offset, &de, &tp);
+    Status = next_dir_entry(fcb, fileref, &ccb->query_dir_offset, &de, &tp);
     
     if (!NT_SUCCESS(Status)) {
-        if (Status == STATUS_NO_MORE_FILES && IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)
+        if (Status == STATUS_NO_MORE_FILES && initial)
             Status = STATUS_NO_SUCH_FILE;
         goto end;
     }
@@ -604,7 +674,6 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     if (Irp->MdlAddress && !buf) {
         ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
         Status = STATUS_INSUFFICIENT_RESOURCES;
-        if (tp.tree) free_traverse_ptr(&tp);
         goto end;
     }
     
@@ -619,14 +688,12 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, de.name, de.namelen);
         if (!NT_SUCCESS(Status)) {
             ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
-            if (tp.tree) free_traverse_ptr(&tp);
             goto end;
         }
         
         uni_fn = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
         if (!uni_fn) {
             ERR("out of memory\n");
-            if (tp.tree) free_traverse_ptr(&tp);
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto end;
         }
@@ -635,7 +702,6 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         
         if (!NT_SUCCESS(Status)) {
             ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
-            if (tp.tree) free_traverse_ptr(&tp);
             goto end;
         }
         
@@ -643,21 +709,19 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         di_uni_fn.Buffer = uni_fn;
         
         while (!FsRtlIsNameInExpression(&ccb->query_string, &di_uni_fn, TRUE, NULL)) {
-            Status = next_dir_entry(fcb, &ccb->query_dir_offset, &de, &tp);
+            Status = next_dir_entry(fcb, fileref, &ccb->query_dir_offset, &de, &tp);
             
             ExFreePool(uni_fn);
             if (NT_SUCCESS(Status)) {
                 Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, de.name, de.namelen);
                 if (!NT_SUCCESS(Status)) {
                     ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
-                    if (tp.tree) free_traverse_ptr(&tp);
                     goto end;
                 }
                 
                 uni_fn = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
                 if (!uni_fn) {
                     ERR("out of memory\n");
-                    if (tp.tree) free_traverse_ptr(&tp);
                     Status = STATUS_INSUFFICIENT_RESOURCES;
                     goto end;
                 }
@@ -667,16 +731,13 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                 if (!NT_SUCCESS(Status)) {
                     ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
                     ExFreePool(uni_fn);
-                    if (tp.tree) free_traverse_ptr(&tp);
                     goto end;
                 }
                 
                 di_uni_fn.Length = di_uni_fn.MaximumLength = stringlen;
                 di_uni_fn.Buffer = uni_fn;
             } else {
-                if (tp.tree) free_traverse_ptr(&tp);
-
-                if (Status == STATUS_NO_MORE_FILES && IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)
+                if (Status == STATUS_NO_MORE_FILES && initial)
                     Status = STATUS_NO_SUCH_FILE;
                 
                 goto end;
@@ -689,7 +750,7 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     TRACE("file(0) = %.*s\n", de.namelen, de.name);
     TRACE("offset = %u\n", ccb->query_dir_offset - 1);
 
-    Status = query_dir_item(fcb, buf, &length, Irp, &de, fcb->subvol);
+    Status = query_dir_item(fcb, fileref, buf, &length, Irp, &de, fcb->subvol);
     
     count = 0;
     if (NT_SUCCESS(Status) && !(IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) && !specific_file) {
@@ -717,7 +778,7 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                 WCHAR* uni_fn = NULL;
                 UNICODE_STRING di_uni_fn;
                 
-                Status = next_dir_entry(fcb, &ccb->query_dir_offset, &de, &tp);
+                Status = next_dir_entry(fcb, fileref, &ccb->query_dir_offset, &de, &tp);
                 if (NT_SUCCESS(Status)) {
                     if (has_wildcard) {
                         ULONG stringlen;
@@ -725,14 +786,12 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                         Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, de.name, de.namelen);
                         if (!NT_SUCCESS(Status)) {
                             ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
-                            if (tp.tree) free_traverse_ptr(&tp);
                             goto end;
                         }
                         
                         uni_fn = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
                         if (!uni_fn) {
                             ERR("out of memory\n");
-                            if (tp.tree) free_traverse_ptr(&tp);
                             Status = STATUS_INSUFFICIENT_RESOURCES;
                             goto end;
                         }
@@ -742,7 +801,6 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                         if (!NT_SUCCESS(Status)) {
                             ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
                             ExFreePool(uni_fn);
-                            if (tp.tree) free_traverse_ptr(&tp);
                             goto end;
                         }
                         
@@ -757,7 +815,7 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                         TRACE("file(%u) %u = %.*s\n", count, curitem - (UINT8*)buf, de.namelen, de.name);
                         TRACE("offset = %u\n", ccb->query_dir_offset - 1);
                         
-                        status2 = query_dir_item(fcb, curitem, &length, Irp, &de, fcb->subvol);
+                        status2 = query_dir_item(fcb, fileref, curitem, &length, Irp, &de, fcb->subvol);
                         
                         if (NT_SUCCESS(status2)) {
                             ULONG* lastoffset = (ULONG*)lastitem;
@@ -789,8 +847,6 @@ static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     
     Irp->IoStatus.Information = IrpSp->Parameters.QueryDirectory.Length - length;
     
-    if (tp.tree) free_traverse_ptr(&tp);
-    
 end:
     release_tree_lock(fcb->Vcb, FALSE);
     
@@ -804,11 +860,18 @@ static NTSTATUS STDCALL notify_change_directory(device_extension* Vcb, PIRP Irp)
     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
     PFILE_OBJECT FileObject = IrpSp->FileObject;
     fcb* fcb = FileObject->FsContext;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb->fileref;
     NTSTATUS Status;
 //     WCHAR fn[MAX_PATH];
     
     TRACE("IRP_MN_NOTIFY_CHANGE_DIRECTORY\n");
     
+    if (!fileref) {
+        ERR("no fileref\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
     acquire_tree_lock(fcb->Vcb, FALSE);
     
     if (fcb->type != BTRFS_TYPE_DIRECTORY) {
@@ -818,9 +881,9 @@ static NTSTATUS STDCALL notify_change_directory(device_extension* Vcb, PIRP Irp)
     
     // FIXME - raise exception if FCB marked for deletion?
     
-    TRACE("%.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    TRACE("%S\n", file_desc(FileObject));
     
-    FsRtlNotifyFullChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext2, (PSTRING)&fcb->full_filename,
+    FsRtlNotifyFullChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext2, (PSTRING)&fileref->full_filename,
         IrpSp->Flags & SL_WATCH_TREE, FALSE, IrpSp->Parameters.NotifyDirectory.CompletionFilter, Irp, NULL, NULL);
     
     Status = STATUS_PENDING;
@@ -867,6 +930,10 @@ NTSTATUS STDCALL drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP I
 
     if (func != IRP_MN_NOTIFY_CHANGE_DIRECTORY || Status != STATUS_PENDING) {
         Irp->IoStatus.Status = Status;
+        
+        if (Irp->UserIosb)
+            *Irp->UserIosb = Irp->IoStatus;
+        
         IoCompleteRequest( Irp, IO_DISK_INCREMENT );
     }
     
diff --git a/reactos/drivers/filesystems/btrfs/extent-tree.c b/reactos/drivers/filesystems/btrfs/extent-tree.c
new file mode 100644 (file)
index 0000000..2699301
--- /dev/null
@@ -0,0 +1,1239 @@
+/* Copyright (c) Mark Harmstone 2016
+ * 
+ * This file is part of WinBtrfs.
+ * 
+ * WinBtrfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public Licence as published by
+ * the Free Software Foundation, either version 3 of the Licence, or
+ * (at your option) any later version.
+ * 
+ * WinBtrfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public Licence for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public Licence
+ * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include "btrfs_drv.h"
+
+static __inline ULONG get_extent_data_len(UINT8 type) {
+    switch (type) {
+        case TYPE_TREE_BLOCK_REF:
+            return sizeof(TREE_BLOCK_REF);
+            
+        case TYPE_EXTENT_DATA_REF:
+            return sizeof(EXTENT_DATA_REF);
+            
+        // FIXME - TYPE_EXTENT_REF_V0
+        // FIXME - TYPE_SHARED_BLOCK_REF
+            
+        case TYPE_SHARED_DATA_REF:
+            return sizeof(SHARED_DATA_REF);
+            
+        default:
+            return 0;
+    }
+}
+
+static __inline UINT64 get_extent_data_refcount(UINT8 type, void* data) {
+    switch (type) {
+        case TYPE_TREE_BLOCK_REF:
+            return 1;
+            
+        case TYPE_EXTENT_DATA_REF:
+        {
+            EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
+            return edr->count;
+        }
+        
+        // FIXME - TYPE_EXTENT_REF_V0
+        // FIXME - TYPE_SHARED_BLOCK_REF
+        
+        case TYPE_SHARED_DATA_REF:
+        {
+            SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;
+            return sdr->count;
+        }
+            
+        default:
+            return 0;
+    }
+}
+
+static UINT64 get_extent_data_ref_hash(EXTENT_DATA_REF* edr) {
+    UINT32 high_crc = 0xffffffff, low_crc = 0xffffffff;
+
+    high_crc = calc_crc32c(high_crc, (UINT8*)&edr->root, sizeof(UINT64));
+    low_crc = calc_crc32c(low_crc, (UINT8*)&edr->objid, sizeof(UINT64));
+    low_crc = calc_crc32c(low_crc, (UINT8*)&edr->offset, sizeof(UINT64));
+    
+    return ((UINT64)high_crc << 31) ^ (UINT64)low_crc;
+}
+
+static UINT64 get_extent_hash(UINT8 type, void* data) {
+    if (type == TYPE_EXTENT_DATA_REF) {
+        return get_extent_data_ref_hash((EXTENT_DATA_REF*)data);
+    } else {
+        ERR("unhandled extent type %x\n", type);
+        return 0;
+    }
+}
+
+static NTSTATUS increase_extent_refcount(device_extension* Vcb, UINT64 address, UINT64 size, UINT8 type, void* data, KEY* firstitem, UINT8 level, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp;
+    ULONG datalen = get_extent_data_len(type), len, max_extent_item_size;
+    EXTENT_ITEM* ei;
+    UINT8* ptr;
+    UINT64 inline_rc, offset;
+    UINT8* data2;
+    EXTENT_ITEM* newei;
+    
+    // FIXME - handle A9s
+    
+    if (datalen == 0) {
+        ERR("unrecognized extent type %x\n", type);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    // If entry doesn't exist yet, create new inline extent item
+    
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+        ULONG eisize;
+        EXTENT_ITEM* ei;
+        BOOL is_tree = type == TYPE_TREE_BLOCK_REF;
+        UINT8* ptr;
+        
+        eisize = sizeof(EXTENT_ITEM);
+        if (is_tree) eisize += sizeof(EXTENT_ITEM2);
+        eisize += sizeof(UINT8);
+        eisize += datalen;
+        
+        ei = ExAllocatePoolWithTag(PagedPool, eisize, ALLOC_TAG);
+        if (!ei) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        ei->refcount = get_extent_data_refcount(type, data);
+        ei->generation = Vcb->superblock.generation;
+        ei->flags = is_tree ? EXTENT_ITEM_TREE_BLOCK : EXTENT_ITEM_DATA;
+        ptr = (UINT8*)&ei[1];
+        
+        if (is_tree) {
+            EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)ptr;
+            ei2->firstitem = *firstitem;
+            ei2->level = level;
+            ptr = (UINT8*)&ei2[1];
+        }
+        
+        *ptr = type;
+        RtlCopyMemory(ptr + 1, data, datalen);
+        
+        if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, eisize, NULL, rollback)) {
+            ERR("insert_tree_item failed\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        // FIXME - add to space list?
+
+        return STATUS_SUCCESS;
+    } else if (tp.item->key.offset != size) {
+        ERR("extent %llx exists, but with size %llx rather than %llx expected\n", tp.item->key.obj_id, tp.item->key.offset, size);
+        return STATUS_INTERNAL_ERROR;
+    } else if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {
+        TRACE("converting old-style extent at (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        
+        Status = convert_old_data_extent(Vcb, address, size, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("convert_old_data_extent returned %08x\n", Status);
+            return Status;
+        }
+        
+        return increase_extent_refcount(Vcb, address, size, type, data, firstitem, level, rollback);
+    } else if (tp.item->size < sizeof(EXTENT_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    ei = (EXTENT_ITEM*)tp.item->data;
+    
+    len = tp.item->size - sizeof(EXTENT_ITEM);
+    ptr = (UINT8*)&ei[1];
+    
+    if (ei->flags & EXTENT_ITEM_TREE_BLOCK) {
+        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        len -= sizeof(EXTENT_ITEM2);
+        ptr += sizeof(EXTENT_ITEM2);
+    }
+    
+    inline_rc = 0;
+    
+    // Loop through existing inline extent entries
+    
+    while (len > 0) {
+        UINT8 secttype = *ptr;
+        ULONG sectlen = get_extent_data_len(secttype);
+        UINT64 sectcount = get_extent_data_refcount(secttype, ptr + sizeof(UINT8));
+        
+        len--;
+        
+        if (sectlen > len) {
+            ERR("(%llx,%x,%llx): %x bytes left, expecting at least %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
+            return STATUS_INTERNAL_ERROR;
+        }
+
+        if (sectlen == 0) {
+            ERR("(%llx,%x,%llx): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (secttype == TYPE_SHARED_DATA_REF) {
+            TRACE("found shared data extent at %llx, converting\n", tp.item->key.obj_id);
+            
+            Status = convert_shared_data_extent(Vcb, address, size, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("convert_shared_data_extent returned %08x\n", Status);
+                return Status;
+            }
+            
+            return increase_extent_refcount(Vcb, address, size, type, data, firstitem, level, rollback);
+        }
+        
+        // If inline extent already present, increase refcount and return
+        
+        if (secttype == type) {
+            if (type == TYPE_EXTENT_DATA_REF) {
+                EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(UINT8));
+                EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
+                
+                if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {
+                    UINT32 rc = get_extent_data_refcount(type, data);
+                    EXTENT_DATA_REF* sectedr2;
+                    
+                    newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                    if (!newei) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    RtlCopyMemory(newei, tp.item->data, tp.item->size);
+                    
+                    newei->generation = Vcb->superblock.generation;
+                    newei->refcount += rc;
+                    
+                    sectedr2 = (EXTENT_DATA_REF*)((UINT8*)newei + ((UINT8*)sectedr - tp.item->data));
+                    sectedr2->count += rc;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    if (!insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, rollback)) {
+                        ERR("insert_tree_item failed\n");
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                    
+                    return STATUS_SUCCESS;
+                }
+            } else if (type == TYPE_TREE_BLOCK_REF) {
+                ERR("trying to increase refcount of tree extent\n");
+                return STATUS_INTERNAL_ERROR;
+            } else {
+                ERR("unhandled extent type %x\n", type);
+                return STATUS_INTERNAL_ERROR;
+            }
+        }
+        
+        len -= sectlen;
+        ptr += sizeof(UINT8) + sectlen;
+        inline_rc += sectcount;
+    }
+    
+    offset = get_extent_hash(type, data);
+    
+    max_extent_item_size = (Vcb->superblock.node_size >> 4) - sizeof(leaf_node);
+    
+    // If we can, add entry as inline extent item
+    
+    if (inline_rc == ei->refcount && tp.item->size + sizeof(UINT8) + datalen < max_extent_item_size) {
+        len = tp.item->size - sizeof(EXTENT_ITEM);
+        ptr = (UINT8*)&ei[1];
+        
+        if (ei->flags & EXTENT_ITEM_TREE_BLOCK) {
+            len -= sizeof(EXTENT_ITEM2);
+            ptr += sizeof(EXTENT_ITEM2);
+        }
+
+        while (len > 0) {
+            UINT8 secttype = *ptr;
+            ULONG sectlen = get_extent_data_len(secttype);
+            
+            if (secttype > type)
+                break;
+            
+            len--;
+            
+            if (secttype == type) {
+                UINT64 sectoff = get_extent_hash(secttype, ptr + 1);
+                
+                if (sectoff > offset)
+                    break;
+            }
+            
+            len -= sectlen;
+            ptr += sizeof(UINT8) + sectlen;
+        }
+        
+        newei = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(UINT8) + datalen, ALLOC_TAG);
+        RtlCopyMemory(newei, tp.item->data, ptr - tp.item->data);
+        
+        newei->generation = Vcb->superblock.generation;
+        newei->refcount += get_extent_data_refcount(type, data);
+        
+        if (len > 0)
+            RtlCopyMemory((UINT8*)newei + (ptr - tp.item->data) + sizeof(UINT8) + datalen, ptr, len + 1);
+        
+        ptr = (ptr - tp.item->data) + (UINT8*)newei;
+        
+        *ptr = type;
+        RtlCopyMemory(ptr + 1, data, datalen);
+        
+        delete_tree_item(Vcb, &tp, rollback);
+        
+        if (!insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size + sizeof(UINT8) + datalen, NULL, rollback)) {
+            ERR("insert_tree_item failed\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        return STATUS_SUCCESS;
+    }
+    
+    // Look for existing non-inline entry, and increase refcount if found
+    
+    if (inline_rc != ei->refcount) {
+        traverse_ptr tp2;
+        
+        searchkey.obj_id = address;
+        searchkey.obj_type = type;
+        searchkey.offset = offset;
+        
+        Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        if (!keycmp(&tp.item->key, &searchkey)) {
+            if (tp.item->size < datalen) {
+                ERR("(%llx,%x,%llx) was %x bytes, expecting %x\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp.item->size, datalen);
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            data2 = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);
+            RtlCopyMemory(data2, tp2.item->data, tp2.item->size);
+            
+            if (type == TYPE_EXTENT_DATA_REF) {
+                EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data2;
+                
+                edr->count += get_extent_data_refcount(type, data);
+            } else if (type == TYPE_TREE_BLOCK_REF) {
+                ERR("trying to increase refcount of tree extent\n");
+                return STATUS_INTERNAL_ERROR;
+            } else {
+                ERR("unhandled extent type %x\n", type);
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            delete_tree_item(Vcb, &tp2, rollback);
+            
+            if (!insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, data2, tp2.item->size, NULL, rollback)) {
+                ERR("insert_tree_item failed\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+            RtlCopyMemory(newei, tp.item->data, tp.item->size);
+            
+            newei->generation = Vcb->superblock.generation;
+            newei->refcount += get_extent_data_refcount(type, data);
+            
+            delete_tree_item(Vcb, &tp, rollback);
+            
+            if (!insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, rollback)) {
+                ERR("insert_tree_item failed\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            return STATUS_SUCCESS;
+        }
+    }
+    
+    // Otherwise, add new non-inline entry
+    
+    data2 = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);
+    RtlCopyMemory(data2, data, datalen);
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, address, type, offset, data2, datalen, NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+    RtlCopyMemory(newei, tp.item->data, tp.item->size);
+    
+    newei->generation = Vcb->superblock.generation;
+    newei->refcount += get_extent_data_refcount(type, data);
+    
+    delete_tree_item(Vcb, &tp, rollback);
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS increase_extent_refcount_data(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, UINT32 refcount, LIST_ENTRY* rollback) {
+    EXTENT_DATA_REF edr;
+    
+    edr.root = subvol->id;
+    edr.objid = inode;
+    edr.offset = offset;
+    edr.count = refcount;
+    
+    return increase_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, rollback);
+}
+
+void decrease_chunk_usage(chunk* c, UINT64 delta) {
+    c->used -= delta;
+    
+    TRACE("decreasing size of chunk %llx by %llx\n", c->offset, delta);
+}
+
+static NTSTATUS remove_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* changed_sector_list) {
+    chunk* c;
+    LIST_ENTRY* le;
+    
+    if (changed_sector_list) {
+        changed_sector* sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
+        if (!sc) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        sc->ol.key = address;
+        sc->checksums = NULL;
+        sc->length = size / Vcb->superblock.sector_size;
+
+        sc->deleted = TRUE;
+        
+        insert_into_ordered_list(changed_sector_list, &sc->ol);
+    }
+    
+    c = NULL;
+    le = Vcb->chunks.Flink;
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (address >= c->offset && address + size < c->offset + c->chunk_item->size)
+            break;
+        
+        le = le->Flink;
+    }
+    if (le == &Vcb->chunks) c = NULL;
+    
+    if (c) {
+        decrease_chunk_usage(c, size);
+        
+        add_to_space_list(c, address, size, SPACE_TYPE_DELETING);
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS decrease_extent_refcount(device_extension* Vcb, UINT64 address, UINT64 size, UINT8 type, void* data, KEY* firstitem,
+                                         UINT8 level, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    NTSTATUS Status;
+    traverse_ptr tp, tp2;
+    EXTENT_ITEM* ei;
+    ULONG len;
+    UINT64 inline_rc, offset;
+    UINT8* ptr;
+    UINT32 rc = get_extent_data_refcount(type, data);
+    ULONG datalen = get_extent_data_len(type);
+    
+    // FIXME - handle trees
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+        ERR("could not find EXTENT_ITEM for address %llx\n", address);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp.item->key.offset != size) {
+        ERR("extent %llx had length %llx, not %llx as expected\n", address, tp.item->key.offset, size);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {
+        TRACE("converting old-style extent at (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        
+        Status = convert_old_data_extent(Vcb, address, size, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("convert_old_data_extent returned %08x\n", Status);
+            return Status;
+        }
+        
+        return decrease_extent_refcount(Vcb, address, size, type, data, firstitem, level, changed_sector_list, rollback);
+    }
+    
+    if (tp.item->size < sizeof(EXTENT_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    ei = (EXTENT_ITEM*)tp.item->data;
+    
+    len = tp.item->size - sizeof(EXTENT_ITEM);
+    ptr = (UINT8*)&ei[1];
+    
+    if (ei->flags & EXTENT_ITEM_TREE_BLOCK) {
+        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        len -= sizeof(EXTENT_ITEM2);
+        ptr += sizeof(EXTENT_ITEM2);
+    }
+    
+    if (ei->refcount < rc) {
+        ERR("error - extent has refcount %llx, trying to reduce by %x\n", ei->refcount, rc);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    inline_rc = 0;
+    
+    // Loop through inline extent entries
+    
+    while (len > 0) {
+        UINT8 secttype = *ptr;
+        ULONG sectlen = get_extent_data_len(secttype);
+        UINT64 sectcount = get_extent_data_refcount(secttype, ptr + sizeof(UINT8));
+        
+        len--;
+        
+        if (sectlen > len) {
+            ERR("(%llx,%x,%llx): %x bytes left, expecting at least %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
+            return STATUS_INTERNAL_ERROR;
+        }
+
+        if (sectlen == 0) {
+            ERR("(%llx,%x,%llx): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (secttype == TYPE_SHARED_DATA_REF) {
+            TRACE("found shared data extent at %llx, converting\n", tp.item->key.obj_id);
+            
+            Status = convert_shared_data_extent(Vcb, address, size, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("convert_shared_data_extent returned %08x\n", Status);
+                return Status;
+            }
+            
+            return decrease_extent_refcount(Vcb, address, size, type, data, firstitem, level, changed_sector_list, rollback);
+        }
+        
+        if (secttype == type) {
+            if (type == TYPE_EXTENT_DATA_REF) {
+                EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(UINT8));
+                EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
+                ULONG neweilen;
+                EXTENT_ITEM* newei;
+                
+                if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {
+                    if (ei->refcount == edr->count) {
+                        Status = remove_extent(Vcb, address, size, changed_sector_list);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("remove_extent returned %08x\n", Status);
+                            return Status;
+                        }
+                        
+                        delete_tree_item(Vcb, &tp, rollback);
+                        return STATUS_SUCCESS;
+                    }
+                    
+                    if (sectedr->count < edr->count) {
+                        ERR("error - extent section has refcount %x, trying to reduce by %x\n", sectedr->count, edr->count);
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                    
+                    if (sectedr->count > edr->count)    // reduce section refcount
+                        neweilen = tp.item->size;
+                    else                                // remove section entirely
+                        neweilen = tp.item->size - sizeof(UINT8) - sectlen;
+                    
+                    newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);
+                    if (!newei) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    if (sectedr->count > edr->count) {
+                        EXTENT_DATA_REF* newedr = (EXTENT_DATA_REF*)((UINT8*)newei + ((UINT8*)sectedr - tp.item->data));
+                        
+                        RtlCopyMemory(newei, ei, neweilen);
+                        
+                        newedr->count -= rc;
+                    } else {
+                        RtlCopyMemory(newei, ei, ptr - tp.item->data);
+                        
+                        if (len > sectlen)
+                            RtlCopyMemory((UINT8*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(UINT8), len - sectlen);
+                    }
+                    
+                    newei->generation = Vcb->superblock.generation;
+                    newei->refcount -= rc;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    if (!insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, rollback)) {
+                        ERR("insert_tree_item failed\n");
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                    
+                    return STATUS_SUCCESS;
+                }
+//             } else if (type == TYPE_TREE_BLOCK_REF) {
+//                 ERR("trying to increase refcount of tree extent\n");
+//                 return STATUS_INTERNAL_ERROR;
+            } else {
+                ERR("unhandled extent type %x\n", type);
+                return STATUS_INTERNAL_ERROR;
+            }
+        }
+        
+        len -= sectlen;
+        ptr += sizeof(UINT8) + sectlen;
+        inline_rc += sectcount;
+    }
+    
+    if (inline_rc == ei->refcount) {
+        ERR("entry not found in inline extent item for address %llx\n", address);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    offset = get_extent_hash(type, data);
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = type;
+    searchkey.offset = offset;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&tp2.item->key, &searchkey)) {
+        ERR("(%llx,%x,%llx) not found\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp2.item->size < datalen) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, datalen);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (type == TYPE_EXTENT_DATA_REF) {
+        EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)tp2.item->data;
+        EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
+        EXTENT_ITEM* newei;
+        
+        if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {
+            if (ei->refcount == edr->count) {
+                Status = remove_extent(Vcb, address, size, changed_sector_list);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("remove_extent returned %08x\n", Status);
+                    return Status;
+                }
+                
+                delete_tree_item(Vcb, &tp, rollback);
+                delete_tree_item(Vcb, &tp2, rollback);
+                return STATUS_SUCCESS;
+            }
+            
+            if (sectedr->count < edr->count) {
+                ERR("error - extent section has refcount %x, trying to reduce by %x\n", sectedr->count, edr->count);
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            delete_tree_item(Vcb, &tp2, rollback);
+            
+            if (sectedr->count > edr->count) {
+                EXTENT_DATA_REF* newedr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);
+                
+                if (!newedr) {
+                    ERR("out of memory\n");
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                RtlCopyMemory(newedr, sectedr, tp2.item->size);
+                
+                newedr->count -= edr->count;
+                
+                if (!insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, newedr, tp2.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    return STATUS_INTERNAL_ERROR;
+                }
+            }
+            
+            newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+            if (!newei) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            RtlCopyMemory(newei, tp.item->data, tp.item->size);
+
+            newei->generation = Vcb->superblock.generation;
+            newei->refcount -= rc;
+            
+            delete_tree_item(Vcb, &tp, rollback);
+            
+            if (!insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, rollback)) {
+                ERR("insert_tree_item failed\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            return STATUS_SUCCESS;
+        } else {
+            ERR("error - hash collision?\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+//     } else if (type == TYPE_TREE_BLOCK_REF) {
+//         ERR("trying to increase refcount of tree extent\n");
+//         return STATUS_INTERNAL_ERROR;
+    } else {
+        ERR("unhandled extent type %x\n", type);
+        return STATUS_INTERNAL_ERROR;
+    }
+}
+
+NTSTATUS decrease_extent_refcount_data(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode,
+                                       UINT64 offset, UINT32 refcount, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    EXTENT_DATA_REF edr;
+    
+    edr.root = subvol->id;
+    edr.objid = inode;
+    edr.offset = offset;
+    edr.count = refcount;
+    
+    return decrease_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, changed_sector_list, rollback);
+}
+
+typedef struct {
+    UINT8 type;
+    void* data;
+    BOOL allocated;
+    UINT64 hash;
+    LIST_ENTRY list_entry;
+} extent_ref;
+
+static void free_extent_refs(LIST_ENTRY* extent_refs) {
+    while (!IsListEmpty(extent_refs)) {
+        LIST_ENTRY* le = RemoveHeadList(extent_refs);
+        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+        
+        if (er->allocated)
+            ExFreePool(er->data);
+        
+        ExFreePool(er);
+    }
+}
+
+static NTSTATUS add_data_extent_ref(LIST_ENTRY* extent_refs, UINT64 tree_id, UINT64 obj_id, UINT64 offset) {
+    extent_ref* er2;
+    EXTENT_DATA_REF* edr;
+    LIST_ENTRY* le;
+    
+    if (!IsListEmpty(extent_refs)) {
+        le = extent_refs->Flink;
+        
+        while (le != extent_refs) {
+            extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+            
+            if (er->type == TYPE_EXTENT_DATA_REF) {
+                edr = (EXTENT_DATA_REF*)er->data;
+                
+                if (edr->root == tree_id && edr->objid == obj_id && edr->offset == offset) {
+                    edr->count++;
+                    return STATUS_SUCCESS;
+                }
+            }
+            
+            le = le->Flink;
+        }
+    }
+    
+    er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
+    if (!er2) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    edr = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA_REF), ALLOC_TAG);
+    if (!edr) {
+        ERR("out of memory\n");
+        ExFreePool(er2);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    edr->root = tree_id;
+    edr->objid = obj_id;
+    edr->offset = offset;
+    edr->count = 1; // FIXME - not necessarily
+    
+    er2->type = TYPE_EXTENT_DATA_REF;
+    er2->data = edr;
+    er2->allocated = TRUE;
+    
+    InsertTailList(extent_refs, &er2->list_entry);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS construct_extent_item(device_extension* Vcb, UINT64 address, UINT64 size, UINT64 flags, LIST_ENTRY* extent_refs, LIST_ENTRY* rollback) {
+    LIST_ENTRY *le, *next_le;
+    UINT64 refcount;
+    ULONG inline_len;
+    BOOL all_inline = TRUE;
+    extent_ref* first_noninline;
+    EXTENT_ITEM* ei;
+    UINT8* siptr;
+    
+    if (IsListEmpty(extent_refs)) {
+        WARN("no extent refs found\n");
+        return STATUS_SUCCESS;
+    }
+    
+    refcount = 0;
+    inline_len = sizeof(EXTENT_ITEM);
+    
+    le = extent_refs->Flink;
+    while (le != extent_refs) {
+        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+        UINT64 rc;
+        
+        next_le = le->Flink;
+        
+        rc = get_extent_data_refcount(er->type, er->data);
+        
+        if (rc == 0) {
+            if (er->allocated)
+                ExFreePool(er->data);
+            
+            RemoveEntryList(&er->list_entry);
+            
+            ExFreePool(er);
+        } else {
+            ULONG extlen = get_extent_data_len(er->type);
+            
+            refcount += rc;
+            
+            if (er->type == TYPE_EXTENT_DATA_REF)
+                er->hash = get_extent_data_ref_hash(er->data);
+            else
+                er->hash = 0;
+            
+            if (all_inline) {
+                if (inline_len + 1 + extlen > Vcb->superblock.node_size / 4) {
+                    all_inline = FALSE;
+                    first_noninline = er;
+                } else
+                    inline_len += extlen + 1;
+            }
+        }
+        
+        le = next_le;
+    }
+    
+    ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG);
+    if (!ei) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    ei->refcount = refcount;
+    ei->generation = Vcb->superblock.generation;
+    ei->flags = flags;
+    
+    // Do we need to sort the inline extent refs? The Linux driver doesn't seem to bother.
+    
+    siptr = (UINT8*)&ei[1];
+    le = extent_refs->Flink;
+    while (le != extent_refs) {
+        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+        ULONG extlen = get_extent_data_len(er->type);
+        
+        if (!all_inline && er == first_noninline)
+            break;
+        
+        *siptr = er->type;
+        siptr++;
+        
+        if (extlen > 0) {
+            RtlCopyMemory(siptr, er->data, extlen);
+            siptr += extlen;
+        }
+         
+        le = le->Flink;
+    }
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, inline_len, NULL, rollback)) {
+        ERR("error - failed to insert item\n");
+        ExFreePool(ei);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (!all_inline) {
+        le = &first_noninline->list_entry;
+        
+        while (le != extent_refs) {
+            extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+            
+            if (!insert_tree_item(Vcb, Vcb->extent_root, address, er->type, er->hash, er->data, get_extent_data_len(er->type), NULL, rollback)) {
+                ERR("error - failed to insert item\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            er->allocated = FALSE;
+            
+            le = le->Flink;
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS populate_extent_refs_from_tree(device_extension* Vcb, UINT64 tree_address, UINT64 extent_address, LIST_ENTRY* extent_refs) {
+    UINT8* buf;
+    tree_header* th;
+    NTSTATUS Status;
+    
+    buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);
+    if (!buf) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    Status = read_tree(Vcb, tree_address, buf);
+    if (!NT_SUCCESS(Status)) {
+        ERR("read_tree returned %08x\n", Status);
+        ExFreePool(buf);
+        return Status;
+    }
+    
+    th = (tree_header*)buf;
+
+    if (th->level == 0) {
+        UINT32 i;
+        leaf_node* ln = (leaf_node*)&th[1];
+        
+        for (i = 0; i < th->num_items; i++) {
+            if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) && ln[i].offset + ln[i].size <= Vcb->superblock.node_size - sizeof(tree_header)) {
+                EXTENT_DATA* ed = (EXTENT_DATA*)(((UINT8*)&th[1]) + ln[i].offset);
+                
+                if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0];
+                    
+                    if (ed2->address == extent_address) {
+                        Status = add_data_extent_ref(extent_refs, th->tree_id, ln[i].key.obj_id, ln[i].key.offset);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("add_data_extent_ref returned %08x\n", Status);
+                            ExFreePool(buf);
+                            return Status;
+                        }
+                    }
+                }
+            }
+        }
+    } else
+        WARN("shared data ref pointed to tree of level %x\n", th->level);
+    
+    ExFreePool(buf);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS convert_shared_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    LIST_ENTRY extent_refs;
+    LIST_ENTRY *le, *next_le;
+    EXTENT_ITEM* ei;
+    UINT64 eiflags, inline_rc;
+    UINT8* siptr;
+    ULONG len;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = size;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&tp.item->key, &searchkey)) {
+        WARN("extent item not found for address %llx, size %llx\n", address, size);
+        return STATUS_SUCCESS;
+    }
+    
+    if (tp.item->size < sizeof(EXTENT_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    ei = (EXTENT_ITEM*)tp.item->data;
+    len = tp.item->size - sizeof(EXTENT_ITEM);
+    eiflags = ei->flags;
+    
+    InitializeListHead(&extent_refs);
+    
+    inline_rc = 0;
+    siptr = (UINT8*)&ei[1];
+    
+    do {
+        extent_ref* er;
+        ULONG extlen;
+        
+        extlen = get_extent_data_len(*siptr);
+        
+        if (extlen == 0) {
+            ERR("unrecognized extent subitem %x\n", *siptr);
+            free_extent_refs(&extent_refs);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (extlen > len - 1) {
+            ERR("extent %llx was truncated\n", address);
+            free_extent_refs(&extent_refs);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        er = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
+        if (!er) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        er->type = *siptr;
+        
+        er->data = ExAllocatePoolWithTag(PagedPool, extlen, ALLOC_TAG);
+        if (!er->data) {
+            ERR("out of memory\n");
+            ExFreePool(er);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+
+        RtlCopyMemory(er->data, siptr+1, extlen);
+        er->allocated = TRUE;
+        
+        InsertTailList(&extent_refs, &er->list_entry);
+        
+        siptr += extlen;
+        len -= extlen + 1;
+        
+        inline_rc += get_extent_data_refcount(er->type, er->data);
+    } while (len > 0);
+    
+    delete_tree_item(Vcb, &tp, rollback);
+    
+    if (inline_rc < ei->refcount) {
+        BOOL b;
+        traverse_ptr next_tp;
+        
+        do {
+            b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+            
+            if (tp.item->key.obj_id == address) {
+                ULONG extlen;
+                
+                extlen = get_extent_data_len(tp.item->key.obj_type);
+                
+                if (extlen != 0 && tp.item->size >= extlen) {
+                    extent_ref* er = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
+                    if (!er) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    er->type = tp.item->key.obj_type;
+                    
+                    er->data = ExAllocatePoolWithTag(PagedPool, extlen, ALLOC_TAG);
+                    if (!er->data) {
+                        ERR("out of memory\n");
+                        ExFreePool(er);
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+
+                    RtlCopyMemory(er->data, siptr+1, extlen);
+                    er->allocated = TRUE;
+                    
+                    InsertTailList(&extent_refs, &er->list_entry);
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                }
+            }
+            
+            if (b) {
+                tp = next_tp;
+                
+                if (tp.item->key.obj_id > address)
+                    break;
+            }
+        } while (b);
+    }
+    
+    le = extent_refs.Flink;
+    while (le != &extent_refs) {
+        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+        next_le = le->Flink;
+        
+        if (er->type == TYPE_SHARED_DATA_REF) {
+            SHARED_DATA_REF* sdr = er->data;
+            
+            Status = populate_extent_refs_from_tree(Vcb, sdr->offset, address, &extent_refs);
+            if (!NT_SUCCESS(Status)) {
+                ERR("populate_extent_refs_from_tree returned %08x\n", Status);
+                free_extent_refs(&extent_refs);
+                return Status;
+            }
+
+            RemoveEntryList(&er->list_entry);
+            
+            if (er->allocated)
+                ExFreePool(er->data);
+            
+            ExFreePool(er);
+        }
+        // FIXME - also do for SHARED_BLOCK_REF?
+
+        le = next_le;
+    }
+    
+    Status = construct_extent_item(Vcb, address, size, eiflags, &extent_refs, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("construct_extent_item returned %08x\n", Status);
+        free_extent_refs(&extent_refs);
+        return Status;
+    }
+    
+    free_extent_refs(&extent_refs);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS convert_old_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    LIST_ENTRY extent_refs;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = size;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&tp.item->key, &searchkey)) {
+        WARN("extent item not found for address %llx, size %llx\n", address, size);
+        return STATUS_SUCCESS;
+    }
+    
+    if (tp.item->size != sizeof(EXTENT_ITEM_V0)) {
+        TRACE("extent does not appear to be old - returning STATUS_SUCCESS\n");
+        return STATUS_SUCCESS;
+    }
+    
+    delete_tree_item(Vcb, &tp, rollback);
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_REF_V0;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    InitializeListHead(&extent_refs);
+    
+    do {
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+        
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
+            Status = populate_extent_refs_from_tree(Vcb, tp.item->key.offset, address, &extent_refs);
+            if (!NT_SUCCESS(Status)) {
+                ERR("populate_extent_refs_from_tree returned %08x\n", Status);
+                return Status;
+            }
+            
+            delete_tree_item(Vcb, &tp, rollback);
+        }
+        
+        if (b) {
+            tp = next_tp;
+            
+            if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
+                break;
+        }
+    } while (b);
+    
+    Status = construct_extent_item(Vcb, address, size, EXTENT_ITEM_DATA, &extent_refs, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("construct_extent_item returned %08x\n", Status);
+        free_extent_refs(&extent_refs);
+        return Status;
+    }
+    
+    free_extent_refs(&extent_refs);
+    
+    return STATUS_SUCCESS;
+}
index e21c88b..5f6ee5e 100644 (file)
 
 #include "btrfs_drv.h"
 
-static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, UINT32 oldcrc32, BTRFS_TIME* now, BOOL ReplaceIfExists, LIST_ENTRY* rollback);
+static NTSTATUS STDCALL move_subvol(device_extension* Vcb, file_ref* fileref, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32,
+                                    UINT32 oldcrc32, BTRFS_TIME* now, BOOL ReplaceIfExists, LIST_ENTRY* rollback);
 
 static NTSTATUS STDCALL set_basic_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
     FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;
     fcb* fcb = FileObject->FsContext;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
     ULONG defda;
     BOOL inode_item_changed = FALSE;
     NTSTATUS Status;
     
-    if (fcb->ads)
-        fcb = fcb->par;
+    if (fcb->ads) {
+        if (fileref && fileref->parent)
+            fcb = fileref->parent->fcb;
+        else {
+            ERR("stream did not have fileref\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+    }
     
-    TRACE("file = %.*S, attributes = %x\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fbi->FileAttributes);
+    TRACE("file = %S, attributes = %x\n", file_desc(FileObject), fbi->FileAttributes);
     
     if (fbi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY && fcb->type != BTRFS_TYPE_DIRECTORY) {
         WARN("attempted to set FILE_ATTRIBUTE_DIRECTORY on non-directory\n");
@@ -47,7 +56,7 @@ static NTSTATUS STDCALL set_basic_information(device_extension* Vcb, PIRP Irp, P
         LARGE_INTEGER time;
         BTRFS_TIME now;
         
-        defda = get_file_attributes(Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, fcb->filepart.Length > 0 && fcb->filepart.Buffer[0] == '.', TRUE);
+        defda = get_file_attributes(Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, fileref->filepart.Length > 0 && fileref->filepart.Buffer[0] == '.', TRUE);
         
         if (fcb->type == BTRFS_TYPE_DIRECTORY)
             fbi->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
@@ -109,8 +118,6 @@ static NTSTATUS STDCALL set_basic_information(device_extension* Vcb, PIRP Irp, P
         else
             WARN("couldn't find old INODE_ITEM\n");
         
-        free_traverse_ptr(&tp);
-        
         ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
         if (!ii) {
             ERR("out of memory\n");
@@ -132,16 +139,31 @@ static NTSTATUS STDCALL set_basic_information(device_extension* Vcb, PIRP Irp, P
 static NTSTATUS STDCALL set_disposition_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
     FILE_DISPOSITION_INFORMATION* fdi = Irp->AssociatedIrp.SystemBuffer;
     fcb* fcb = FileObject->FsContext;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
     ULONG atts;
     
-    TRACE("changing delete_on_close to %s for %.*S (fcb %p)\n", fdi->DeleteFile ? "TRUE" : "FALSE", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb);
+    if (!fileref)
+        return STATUS_INVALID_PARAMETER;
+    
+    TRACE("changing delete_on_close to %s for %S (fcb %p)\n", fdi->DeleteFile ? "TRUE" : "FALSE", file_desc(FileObject), fcb);
+    
+    if (fcb->ads) {
+        if (fileref->parent)
+            atts = fileref->parent->fcb->atts;
+        else {
+            ERR("no fileref for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+    } else
+        atts = fcb->atts;
     
-    atts = fcb->ads ? fcb->par->atts : fcb->atts;
     TRACE("atts = %x\n", atts);
     
     if (atts & FILE_ATTRIBUTE_READONLY)
         return STATUS_CANNOT_DELETE;
     
+    // FIXME - can we skip this bit for subvols?
     if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0)
         return STATUS_DIRECTORY_NOT_EMPTY;
     
@@ -150,13 +172,8 @@ static NTSTATUS STDCALL set_disposition_information(device_extension* Vcb, PIRP
         return STATUS_CANNOT_DELETE;
     }
     
-    if (fcb->inode == SUBVOL_ROOT_INODE) {
-        FIXME("FIXME - subvol deletion not yet supported\n");
-        return STATUS_INTERNAL_ERROR;
-    }
+    ccb->fileref->delete_on_close = fdi->DeleteFile;
     
-    fcb->delete_on_close = fdi->DeleteFile;
-    // FIXME - should this fail if file opened with FILE_DELETE_ON_CLOSE?
     FileObject->DeletePending = fdi->DeleteFile;
     
     return STATUS_SUCCESS;
@@ -185,14 +202,12 @@ static NTSTATUS add_inode_extref(device_extension* Vcb, root* subvol, UINT64 ino
         
         if (iersize > maxlen) {
             ERR("item would be too long (%u > %u)\n", iersize, maxlen);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
         
         ier2 = ExAllocatePoolWithTag(PagedPool, iersize, ALLOC_TAG);
         if (!ier2) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
@@ -209,14 +224,12 @@ static NTSTATUS add_inode_extref(device_extension* Vcb, root* subvol, UINT64 ino
         
         if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ier2, iersize, NULL, rollback)) {
             ERR("error - failed to insert item\n");
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     } else {
         ier = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_EXTREF) - 1 + utf8->Length, ALLOC_TAG);
         if (!ier) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
 
@@ -227,13 +240,10 @@ static NTSTATUS add_inode_extref(device_extension* Vcb, root* subvol, UINT64 ino
     
         if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ier, sizeof(INODE_EXTREF) - 1 + utf8->Length, NULL, rollback)) {
             ERR("error - failed to insert item\n");
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
@@ -261,11 +271,9 @@ NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64
         if (irsize > maxlen) {
             if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
                 TRACE("INODE_REF too long, creating INODE_EXTREF\n");
-                free_traverse_ptr(&tp);
                 return add_inode_extref(Vcb, subvol, inode, parinode, index, utf8, rollback);
             } else {
                 ERR("item would be too long (%u > %u)\n", irsize, maxlen);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             }
         }
@@ -273,7 +281,6 @@ NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64
         ir2 = ExAllocatePoolWithTag(PagedPool, irsize, ALLOC_TAG);
         if (!ir2) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
@@ -289,14 +296,12 @@ NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64
         
         if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ir2, irsize, NULL, rollback)) {
             ERR("error - failed to insert item\n");
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     } else {
         ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + utf8->Length, ALLOC_TAG);
         if (!ir) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
 
@@ -306,20 +311,17 @@ NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64
     
         if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ir, sizeof(INODE_REF) - 1 + ir->n, NULL, rollback)) {
             ERR("error - failed to insert item\n");
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS get_fcb_from_dir_item(device_extension* Vcb, fcb** pfcb, fcb* parent, root* subvol, DIR_ITEM* di) {
+static NTSTATUS get_fileref_from_dir_item(device_extension* Vcb, file_ref** pfr, file_ref* parent, root* subvol, DIR_ITEM* di) {
     LIST_ENTRY* le;
+    file_ref* fileref;
     fcb* sf2;
-    struct _fcb* c;
     KEY searchkey;
     traverse_ptr tp;
     NTSTATUS Status;
@@ -327,48 +329,67 @@ static NTSTATUS get_fcb_from_dir_item(device_extension* Vcb, fcb** pfcb, fcb* pa
     le = parent->children.Flink;
     
     while (le != &parent->children) {
-        c = CONTAINING_RECORD(le, struct _fcb, list_entry);
+        file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);
         
-        if (c->refcount > 0 && c->inode == di->key.obj_id && c->subvol == subvol) {
-            c->refcount++;
+        if (c->fcb->inode == di->key.obj_id && c->fcb->subvol == subvol) {
 #ifdef DEBUG_FCB_REFCOUNTS
-            WARN("fcb %p: refcount now %i (%.*S)\n", c, c->refcount, c->full_filename.Length / sizeof(WCHAR), c->full_filename.Buffer);
+            LONG rc = InterlockedIncrement(&c->refcount);
+            WARN("fileref %p: refcount now %i (%S)\n", c, rc, file_desc_fileref(c));
+#else
+            InterlockedIncrement(&c->refcount);
 #endif
-            *pfcb = c;
+            *pfr = c;
             return STATUS_SUCCESS;
         }
         
         le = le->Flink;
     }
     
+    fileref = create_fileref();
+    if (!fileref) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
     sf2 = create_fcb();
     if (!sf2) {
         ERR("out of memory\n");
+        free_fileref(fileref);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
+    fileref->fcb = sf2;
     sf2->Vcb = Vcb;
 
-    sf2->utf8.Length = sf2->utf8.MaximumLength = di->n;
-    sf2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, di->n, ALLOC_TAG);
-    if (!sf2->utf8.Buffer) {
+    fileref->utf8.Length = fileref->utf8.MaximumLength = di->n;
+    fileref->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, di->n, ALLOC_TAG);
+    if (!fileref->utf8.Buffer) {
         ERR("out of memory\n");
+        free_fileref(fileref);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(sf2->utf8.Buffer, di->name, di->n);
+    RtlCopyMemory(fileref->utf8.Buffer, di->name, di->n);
 
-    sf2->par = parent;
-    
     parent->refcount++;
 #ifdef DEBUG_FCB_REFCOUNTS
-    WARN("fcb %p: refcount now %i (%.*S)\n", parent, parent->refcount, parent->full_filename.Length / sizeof(WCHAR), parent->full_filename.Buffer);
+    WARN("fileref %p: refcount now %i (%S)\n", parent, parent->refcount, file_desc_fileref(parent));
 #endif
     
     if (di->key.obj_type == TYPE_ROOT_ITEM) {
-        root* fcbroot = Vcb->roots;
-        while (fcbroot && fcbroot->id != di->key.obj_id)
-            fcbroot = fcbroot->next;
+        root* fcbroot = NULL;
+        
+        le = Vcb->roots.Flink;
+        while (le != &Vcb->roots) {
+            root* r = CONTAINING_RECORD(le, root, list_entry);
+            
+            if (r->id == di->key.obj_id) {
+                fcbroot = r;
+                break;
+            }
+            
+            le = le->Flink;
+        }
         
         sf2->subvol = fcbroot;
         sf2->inode = SUBVOL_ROOT_INODE;
@@ -379,18 +400,12 @@ static NTSTATUS get_fcb_from_dir_item(device_extension* Vcb, fcb** pfcb, fcb* pa
     
     sf2->type = di->type;
     
-    if (Vcb->fcbs)
-        Vcb->fcbs->prev = sf2;
-    
-    sf2->next = Vcb->fcbs;
-    Vcb->fcbs = sf2;
-    
-    sf2->name_offset = parent->full_filename.Length / sizeof(WCHAR);
+    fileref->name_offset = parent->full_filename.Length / sizeof(WCHAR);
    
-    if (parent != Vcb->root_fcb)
-        sf2->name_offset++;
+    if (parent != Vcb->root_fileref)
+        fileref->name_offset++;
     
-    InsertTailList(&parent->children, &sf2->list_entry);
+    InsertTailList(&parent->children, &fileref->list_entry);
     
     searchkey.obj_id = sf2->inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
@@ -399,26 +414,26 @@ static NTSTATUS get_fcb_from_dir_item(device_extension* Vcb, fcb** pfcb, fcb* pa
     Status = find_item(Vcb, sf2->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        free_fcb(sf2);
+        free_fileref(fileref);
         return Status;
     }
     
     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
         ERR("couldn't find INODE_ITEM for inode %llx in subvol %llx\n", sf2->inode, sf2->subvol->id);
-        free_traverse_ptr(&tp);
-        free_fcb(sf2);
+        free_fileref(fileref);
         return STATUS_INTERNAL_ERROR;
     }
     
     if (tp.item->size > 0)
         RtlCopyMemory(&sf2->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
     
-    free_traverse_ptr(&tp);
-    
     // This is just a quick function for the sake of move_across_subvols. As such, we don't bother
     // with sf2->atts, sf2->sd, or sf2->full_filename.
     
-    *pfcb = sf2;
+    fileref->parent = (struct _file_ref*)parent;
+    InsertTailList(&parent->children, &fileref->list_entry);
+    
+    *pfr = fileref;
 
     return STATUS_SUCCESS;
 }
@@ -454,7 +469,8 @@ static NTSTATUS get_fcb_from_dir_item(device_extension* Vcb, fcb** pfcb, fcb* pa
 //     return rc;
 // }
 
-static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, UINT64 inode, UINT64 oldparinode, PANSI_STRING utf8, UINT32 crc32, BTRFS_TIME* now, LIST_ENTRY* rollback) {
+static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, file_ref* fileref, root* destsubvol, UINT64 destinode, UINT64 inode,
+                                                  UINT64 oldparinode, PANSI_STRING utf8, UINT32 crc32, BTRFS_TIME* now, LIST_ENTRY* rollback) {
     UINT64 oldindex, index;
     UINT32 oldcrc32;
     INODE_ITEM* ii;
@@ -467,15 +483,15 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
     
     // move INODE_ITEM
     
-    fcb->inode_item.transid = Vcb->superblock.generation;
-    fcb->inode_item.sequence++;
-    fcb->inode_item.st_ctime = *now;    
+    fileref->fcb->inode_item.transid = Vcb->superblock.generation;
+    fileref->fcb->inode_item.sequence++;
+    fileref->fcb->inode_item.st_ctime = *now;    
     
-    searchkey.obj_id = fcb->inode;
+    searchkey.obj_id = fileref->fcb->inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
     searchkey.offset = 0;
     
-    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, fileref->fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         return Status;
@@ -484,21 +500,19 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
     if (!keycmp(&searchkey, &tp.item->key)) {
         delete_tree_item(Vcb, &tp, rollback);
         
-        if (fcb->inode_item.st_nlink > 1) {
-            fcb->inode_item.st_nlink--;
+        if (fileref->fcb->inode_item.st_nlink > 1) {
+            fileref->fcb->inode_item.st_nlink--;
             
             ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
             if (!ii) {
                 ERR("out of memory\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INSUFFICIENT_RESOURCES;
             }
             
-            RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+            RtlCopyMemory(ii, &fileref->fcb->inode_item, sizeof(INODE_ITEM));
             
-            if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
+            if (!insert_tree_item(Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
                 ERR("error - failed to insert item\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             }
             
@@ -508,9 +522,7 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         WARN("couldn't find old INODE_ITEM\n");
     }
     
-    free_traverse_ptr(&tp);
-    
-    fcb->inode_item.st_nlink = 1;
+    fileref->fcb->inode_item.st_nlink = 1;
     
     ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!ii) {
@@ -518,18 +530,18 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+    RtlCopyMemory(ii, &fileref->fcb->inode_item, sizeof(INODE_ITEM));
     
     if (!insert_tree_item(Vcb, destsubvol, inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
         ERR("error - failed to insert item\n");
         return STATUS_INTERNAL_ERROR;
     }
     
-    oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fcb->utf8.Buffer, (ULONG)fcb->utf8.Length);
+    oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fileref->utf8.Buffer, (ULONG)fileref->utf8.Length);
     
     // delete old DIR_ITEM
     
-    Status = delete_dir_item(Vcb, fcb->subvol, oldparinode, oldcrc32, &fcb->utf8, rollback);
+    Status = delete_dir_item(Vcb, fileref->fcb->subvol, oldparinode, oldcrc32, &fileref->utf8, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("delete_dir_item returned %08x\n", Status);
         return Status;
@@ -549,7 +561,7 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
     di->transid = Vcb->superblock.generation;
     di->m = 0;
     di->n = utf8->Length;
-    di->type = fcb->type;
+    di->type = fileref->fcb->type;
     RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
     
     Status = add_dir_item(Vcb, destsubvol, destinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8->Length, rollback);
@@ -558,7 +570,7 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         return Status;
     }
     
-    Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, oldparinode, &fcb->utf8, &oldindex, rollback);
+    Status = delete_inode_ref(Vcb, fileref->fcb->subvol, fileref->fcb->inode, oldparinode, &fileref->utf8, &oldindex, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("delete_inode_ref returned %08x\n", Status);
         return Status;
@@ -573,7 +585,7 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         searchkey.obj_type = TYPE_DIR_INDEX;
         searchkey.offset = oldindex;
         
-        Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+        Status = find_item(Vcb, fileref->fcb->subvol, &tp, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
             return Status;
@@ -583,8 +595,6 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
             delete_tree_item(Vcb, &tp, rollback);
         else
             WARN("couldn't find old DIR_INDEX\n");
-        
-        free_traverse_ptr(&tp);
     }
     
     // get new index
@@ -601,7 +611,6 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         
     if (!keycmp(&searchkey, &tp.item->key)) {
         if (find_prev_item(Vcb, &tp, &next_tp, FALSE)) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
                 
             TRACE("moving back to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
@@ -613,8 +622,6 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
     } else
         index = 2;
         
-    free_traverse_ptr(&tp);
-    
     // create INODE_REF
     
     Status = add_inode_ref(Vcb, destsubvol, inode, destinode, index, utf8, rollback);
@@ -637,7 +644,7 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
     di->transid = Vcb->superblock.generation;
     di->m = 0;
     di->n = utf8->Length;
-    di->type = fcb->type;
+    di->type = fileref->fcb->type;
     RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
     
     if (!insert_tree_item(Vcb, destsubvol, destinode, TYPE_DIR_INDEX, index, di, sizeof(DIR_ITEM) - 1 + utf8->Length, NULL, rollback)) {
@@ -647,23 +654,22 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
     
     // move XATTR_ITEMs
     
-    searchkey.obj_id = fcb->inode;
+    searchkey.obj_id = fileref->fcb->inode;
     searchkey.obj_type = TYPE_XATTR_ITEM;
     searchkey.offset = 0;
     
-    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, fileref->fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         return Status;
     }
     
     do {
-        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM && tp.item->size > 0) {
+        if (tp.item->key.obj_id == fileref->fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM && tp.item->size > 0) {
             di = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
             
             if (!di) {
                 ERR("out of memory\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INSUFFICIENT_RESOURCES;
             }
             
@@ -671,7 +677,6 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
             
             if (!insert_tree_item(Vcb, destsubvol, inode, TYPE_XATTR_ITEM, tp.item->key.offset, di, tp.item->size, NULL, rollback)) {
                 ERR("error - failed to insert item\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             }
             
@@ -681,30 +686,27 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
-            if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
+            if (next_tp.item->key.obj_id > fileref->fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
                 break;
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-
     // do extents
     
-    searchkey.obj_id = fcb->inode;
+    searchkey.obj_id = fileref->fcb->inode;
     searchkey.obj_type = TYPE_EXTENT_DATA;
     searchkey.offset = 0;
     
-    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, fileref->fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         return Status;
     }
     
     do {
-        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_EXTENT_DATA) {
+        if (tp.item->key.obj_id == fileref->fcb->inode && tp.item->key.obj_type == TYPE_EXTENT_DATA) {
             if (tp.item->size < sizeof(EXTENT_DATA)) {
                 ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
             } else {
@@ -712,7 +714,6 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
                 
                 if (!ed) {
                     ERR("out of memory\n");
-                    free_traverse_ptr(&tp);
                     return STATUS_INSUFFICIENT_RESOURCES;
                 }
                 
@@ -722,7 +723,6 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
                         
                 if (!insert_tree_item(Vcb, destsubvol, inode, TYPE_EXTENT_DATA, tp.item->key.offset, ed, tp.item->size, NULL, rollback)) {
                     ERR("error - failed to insert item\n");
-                    free_traverse_ptr(&tp);
                     return STATUS_INTERNAL_ERROR;
                 }
             
@@ -730,20 +730,18 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
                     EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
                     
                     if (ed2->address != 0) {
-                        Status = add_extent_ref(Vcb, ed2->address, ed2->size, destsubvol, inode, tp.item->key.offset, rollback);
-                        
+                        Status = increase_extent_refcount_data(Vcb, ed2->address, ed2->size, destsubvol, inode, tp.item->key.offset - ed2->offset, 1, rollback);
                         if (!NT_SUCCESS(Status)) {
-                            ERR("add_extent_ref returned %08x\n", Status);
-                            free_traverse_ptr(&tp);
+                            ERR("increase_extent_refcount_data returned %08x\n", Status);
                             return Status;
                         }
                         
                         if (!has_hardlink) {
-                            Status = remove_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset, NULL, rollback);
+                            Status = decrease_extent_refcount_data(Vcb, ed2->address, ed2->size, fileref->fcb->subvol, fileref->fcb->inode,
+                                                                   tp.item->key.offset - ed2->offset, 1, NULL, rollback);
                         
                             if (!NT_SUCCESS(Status)) {
-                                ERR("remove_extent_ref returned %08x\n", Status);
-                                free_traverse_ptr(&tp);
+                                ERR("decrease_extent_refcount_data returned %08x\n", Status);
                                 return Status;
                             }
                         }
@@ -757,21 +755,18 @@ static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fc
         
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
-            if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_EXTENT_DATA)
+            if (next_tp.item->key.obj_id > fileref->fcb->inode || next_tp.item->key.obj_type > TYPE_EXTENT_DATA)
                 break;
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
 typedef struct {
-    fcb* fcb;
+    file_ref* fileref;
     UINT8 level;
     UINT32 crc32;
     UINT64 newinode;
@@ -781,7 +776,7 @@ typedef struct {
     LIST_ENTRY list_entry;
 } dir_list;
 
-static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 newparinode, BOOL* empty) {
+static NTSTATUS add_to_dir_list(file_ref* fileref, UINT8 level, LIST_ENTRY* dl, UINT64 newparinode, BOOL* empty) {
     KEY searchkey;
     traverse_ptr tp, next_tp;
     BOOL b;
@@ -789,23 +784,23 @@ static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 ne
     
     *empty = TRUE;
     
-    searchkey.obj_id = fcb->inode;
+    searchkey.obj_id = fileref->fcb->inode;
     searchkey.obj_type = TYPE_DIR_INDEX;
     searchkey.offset = 2;
     
-    Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(fileref->fcb->Vcb, fileref->fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         return Status;
     }
     
     do {
-        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_DIR_INDEX) {
+        if (tp.item->key.obj_id == fileref->fcb->inode && tp.item->key.obj_type == TYPE_DIR_INDEX) {
             if (tp.item->size < sizeof(DIR_ITEM)) {
                 ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
             } else {
                 DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
-                struct _fcb* child;
+                file_ref* child;
                 dir_list* dl2;
                 
                 if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->n + di->m) {
@@ -819,21 +814,19 @@ static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 ne
                         
                         *empty = FALSE;
                         
-                        Status = get_fcb_from_dir_item(fcb->Vcb, &child, fcb, fcb->subvol, di);
+                        Status = get_fileref_from_dir_item(fileref->fcb->Vcb, &child, fileref, fileref->fcb->subvol, di);
                         if (!NT_SUCCESS(Status)) {
-                            ERR("get_fcb_from_dir_item returned %08x\n", Status);
-                            free_traverse_ptr(&tp);
+                            ERR("get_fileref_from_dir_item returned %08x\n", Status);
                             return Status;
                         }
                         
                         dl2 = ExAllocatePoolWithTag(PagedPool, sizeof(dir_list), ALLOC_TAG);
                         if (!dl2) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             return STATUS_INSUFFICIENT_RESOURCES;
                         }
                         
-                        dl2->fcb = child;
+                        dl2->fileref = child;
                         dl2->level = level;
                         dl2->newparinode = newparinode;
                         dl2->subvol = di->key.obj_type == TYPE_ROOT_ITEM;
@@ -842,7 +835,6 @@ static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 ne
                         dl2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, dl2->utf8.MaximumLength, ALLOC_TAG);
                         if (!dl2->utf8.Buffer) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             return STATUS_INSUFFICIENT_RESOURCES;
                         }
                         
@@ -855,9 +847,8 @@ static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 ne
             }
         }
         
-        b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
+        b = find_next_item(fileref->fcb->Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
@@ -865,38 +856,42 @@ static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 ne
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL move_across_subvols(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, BTRFS_TIME* now, LIST_ENTRY* rollback) {
+static NTSTATUS STDCALL move_across_subvols(device_extension* Vcb, file_ref* fileref, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, BTRFS_TIME* now, LIST_ENTRY* rollback) {
     UINT64 inode, oldparinode;
     NTSTATUS Status;
     LIST_ENTRY dl;
     
+    if (fileref->fcb->inode_item.st_nlink > 1 && fileref->fcb->open_count > 1) {
+        WARN("not moving hard-linked inode across subvols when open more than once\n");
+        // FIXME - don't do this if only one fileref?
+        return STATUS_ACCESS_DENIED;
+    }
+    
     if (destsubvol->lastinode == 0)
         get_last_inode(Vcb, destsubvol);
     
     inode = destsubvol->lastinode + 1;
     destsubvol->lastinode++;
     
-    oldparinode = fcb->subvol == fcb->par->subvol ? fcb->par->inode : SUBVOL_ROOT_INODE;
+    oldparinode = fileref->fcb->subvol == fileref->parent->fcb->subvol ? fileref->parent->fcb->inode : SUBVOL_ROOT_INODE;
     
-    Status = move_inode_across_subvols(Vcb, fcb, destsubvol, destinode, inode, oldparinode, utf8, crc32, now, rollback);
+    Status = move_inode_across_subvols(Vcb, fileref, destsubvol, destinode, inode, oldparinode, utf8, crc32, now, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("move_inode_across_subvols returned %08x\n", Status);
         return Status;
     }
     
-    if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) {
+    if (fileref->fcb->type == BTRFS_TYPE_DIRECTORY && fileref->fcb->inode_item.st_size > 0) {
         BOOL b, empty;
         UINT8 level, max_level;
         LIST_ENTRY* le;
         
         InitializeListHead(&dl);
         
-        add_to_dir_list(fcb, 0, &dl, inode, &b);
+        add_to_dir_list(fileref, 0, &dl, inode, &b);
         
         level = 0;
         do {
@@ -912,8 +907,8 @@ static NTSTATUS STDCALL move_across_subvols(device_extension* Vcb, fcb* fcb, roo
                     
                     dl2->newinode = inode;
                     
-                    if (dl2->fcb->type == BTRFS_TYPE_DIRECTORY) {
-                        add_to_dir_list(dl2->fcb, level+1, &dl, dl2->newinode, &b);
+                    if (dl2->fileref->fcb->type == BTRFS_TYPE_DIRECTORY) {
+                        add_to_dir_list(dl2->fileref, level+1, &dl, dl2->newinode, &b);
                         if (!b) empty = FALSE;
                     }
                 }
@@ -935,17 +930,17 @@ static NTSTATUS STDCALL move_across_subvols(device_extension* Vcb, fcb* fcb, roo
                 
                 if (dl2->level == level) {
                     if (dl2->subvol) {
-                        TRACE("subvol %llx\n", dl2->fcb->subvol->id);
+                        TRACE("subvol %llx\n", dl2->fileref->fcb->subvol->id);
                         
-                        Status = move_subvol(Vcb, dl2->fcb, destsubvol, dl2->newparinode, &dl2->utf8, dl2->crc32, dl2->crc32, now, FALSE, rollback);
+                        Status = move_subvol(Vcb, dl2->fileref, destsubvol, dl2->newparinode, &dl2->utf8, dl2->crc32, dl2->crc32, now, FALSE, rollback);
                         if (!NT_SUCCESS(Status)) {
                             ERR("move_subvol returned %08x\n", Status);
                             return Status;
                         }
                     } else {
-                        TRACE("inode %llx\n", dl2->fcb->inode);
+                        TRACE("inode %llx\n", dl2->fileref->fcb->inode);
 
-                        Status = move_inode_across_subvols(Vcb, dl2->fcb, destsubvol, dl2->newparinode, dl2->newinode, dl2->fcb->par->inode, &dl2->utf8, dl2->crc32, now, rollback);
+                        Status = move_inode_across_subvols(Vcb, dl2->fileref, destsubvol, dl2->newparinode, dl2->newinode, dl2->fileref->parent->fcb->inode, &dl2->utf8, dl2->crc32, now, rollback);
                         if (!NT_SUCCESS(Status)) {
                             ERR("move_inode_across_subvols returned %08x\n", Status);
                             return Status;
@@ -964,22 +959,30 @@ static NTSTATUS STDCALL move_across_subvols(device_extension* Vcb, fcb* fcb, roo
             dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
             
             ExFreePool(dl2->utf8.Buffer);
-            free_fcb(dl2->fcb);
+            free_fileref(dl2->fileref);
             
             ExFreePool(dl2);
         }
     }
     
-    fcb->inode = inode;
-    fcb->subvol = destsubvol;
+    fileref->fcb->inode = inode;
+    fileref->fcb->subvol = destsubvol;
       
-    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
-    fcb->subvol->root_item.ctime = *now;
+    fileref->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fileref->fcb->subvol->root_item.ctime = *now;
+    
+    RemoveEntryList(&fileref->fcb->list_entry);
+    InsertTailList(&fileref->fcb->subvol->fcbs, &fileref->fcb->list_entry);
+    
+    if (fileref->fcb->debug_desc) {
+        ExFreePool(fileref->fcb->debug_desc);
+        fileref->fcb->debug_desc = NULL;
+    }
     
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS delete_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, UINT64 parinode, PANSI_STRING utf8, UINT64* index, LIST_ENTRY* rollback) {
+NTSTATUS delete_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, UINT64 parinode, PANSI_STRING utf8, UINT64* index, LIST_ENTRY* rollback) {
     KEY searchkey;
     traverse_ptr tp;
     NTSTATUS Status;
@@ -1026,7 +1029,6 @@ static NTSTATUS delete_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 p
                         
                         if (!newrr) {
                             ERR("out of memory\n");
-                            free_traverse_ptr(&tp);
                             return STATUS_INSUFFICIENT_RESOURCES;
                         }
                         
@@ -1060,10 +1062,9 @@ static NTSTATUS delete_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 p
         }
     } else {
         WARN("could not find ROOT_REF entry for subvol %llx in %llx\n", searchkey.offset, searchkey.obj_id);
+        return STATUS_NOT_FOUND;
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
@@ -1103,24 +1104,20 @@ static NTSTATUS add_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 pars
         if (!insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr2, rrsize, NULL, rollback)) {
             ERR("error - failed to insert item\n");
             ExFreePool(rr2);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     } else {
         if (!insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr, sizeof(ROOT_REF) - 1 + rr->n, NULL, rollback)) {
             ERR("error - failed to insert item\n");
             ExFreePool(rr);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, LIST_ENTRY* rollback) {
+NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, LIST_ENTRY* rollback) {
     KEY searchkey;
     traverse_ptr tp;
     UINT8* data;
@@ -1151,8 +1148,6 @@ static NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvol
         datalen = 0;
     }
     
-    free_traverse_ptr(&tp);
-    
     searchkey.obj_id = subvolid;
     searchkey.obj_type = TYPE_ROOT_BACKREF;
     searchkey.offset = parsubvolid;
@@ -1166,8 +1161,6 @@ static NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvol
     if (!keycmp(&tp.item->key, &searchkey))
         delete_tree_item(Vcb, &tp, rollback);
     
-    free_traverse_ptr(&tp);
-    
     if (datalen > 0) {
         if (!insert_tree_item(Vcb, Vcb->root_root, subvolid, TYPE_ROOT_BACKREF, parsubvolid, data, datalen, NULL, rollback)) {
             ERR("error - failed to insert item\n");
@@ -1179,7 +1172,8 @@ static NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvol
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, UINT32 oldcrc32, BTRFS_TIME* now, BOOL ReplaceIfExists, LIST_ENTRY* rollback) {
+static NTSTATUS STDCALL move_subvol(device_extension* Vcb, file_ref* fileref, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32,
+                                    UINT32 oldcrc32, BTRFS_TIME* now, BOOL ReplaceIfExists, LIST_ENTRY* rollback) {
     DIR_ITEM* di;
     NTSTATUS Status;
     KEY searchkey;
@@ -1189,7 +1183,7 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
     
     // delete old DIR_ITEM
     
-    Status = delete_dir_item(Vcb, fcb->par->subvol, fcb->par->inode, oldcrc32, &fcb->utf8, rollback);
+    Status = delete_dir_item(Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, oldcrc32, &fileref->utf8, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("delete_dir_item returned %08x\n", Status);
         return Status;
@@ -1203,13 +1197,13 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
         return STATUS_INSUFFICIENT_RESOURCES;
     }
         
-    di->key.obj_id = fcb->subvol->id;
+    di->key.obj_id = fileref->fcb->subvol->id;
     di->key.obj_type = TYPE_ROOT_ITEM;
     di->key.offset = 0;
     di->transid = Vcb->superblock.generation;
     di->m = 0;
     di->n = utf8->Length;
-    di->type = fcb->type;
+    di->type = fileref->fcb->type;
     RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
     
     Status = add_dir_item(Vcb, destsubvol, destinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8->Length, rollback);
@@ -1222,7 +1216,7 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
     
     oldindex = 0;
     
-    Status = delete_root_ref(Vcb, fcb->subvol->id, fcb->par->subvol->id, fcb->par->inode, &fcb->utf8, &oldindex, rollback);
+    Status = delete_root_ref(Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, &fileref->utf8, &oldindex, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("delete_root_ref returned %08x\n", Status);
         return Status;
@@ -1233,11 +1227,11 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
     // delete old DIR_INDEX
     
     if (oldindex != 0) {
-        searchkey.obj_id = fcb->par->inode;
+        searchkey.obj_id = fileref->parent->fcb->inode;
         searchkey.obj_type = TYPE_DIR_INDEX;
         searchkey.offset = oldindex;
         
-        Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
+        Status = find_item(Vcb, fileref->parent->fcb->subvol, &tp, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
             return Status;
@@ -1250,13 +1244,11 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
         } else {
             WARN("could not find old DIR_INDEX entry\n");
         }
-        
-        free_traverse_ptr(&tp);
     }
     
     // create new DIR_INDEX
     
-    if (fcb->par->subvol == destsubvol && fcb->par->inode == destinode) {
+    if (fileref->parent->fcb->subvol == destsubvol && fileref->parent->fcb->inode == destinode) {
         index = oldindex;
     } else {
         index = find_next_dir_index(Vcb, destsubvol, destinode);
@@ -1268,13 +1260,13 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
         return STATUS_INSUFFICIENT_RESOURCES;
     }
         
-    di->key.obj_id = fcb->subvol->id;
+    di->key.obj_id = fileref->fcb->subvol->id;
     di->key.obj_type = TYPE_ROOT_ITEM;
     di->key.offset = 0;
     di->transid = Vcb->superblock.generation;
     di->m = 0;
     di->n = utf8->Length;
-    di->type = fcb->type;
+    di->type = fileref->fcb->type;
     RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
     
     if (!insert_tree_item(Vcb, destsubvol, destinode, TYPE_DIR_INDEX, index, di, sizeof(DIR_ITEM) - 1 + utf8->Length, NULL, rollback)) {
@@ -1295,27 +1287,27 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
     rr->n = utf8->Length;
     RtlCopyMemory(rr->name, utf8->Buffer, utf8->Length);
     
-    Status = add_root_ref(Vcb, fcb->subvol->id, destsubvol->id, rr, rollback);
+    Status = add_root_ref(Vcb, fileref->fcb->subvol->id, destsubvol->id, rr, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("add_root_ref returned %08x\n", Status);
         return Status;
     }
     
-    Status = update_root_backref(Vcb, fcb->subvol->id, fcb->par->subvol->id, rollback);
+    Status = update_root_backref(Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("update_root_backref 1 returned %08x\n", Status);
         return Status;
     }
     
-    if (fcb->par->subvol != destsubvol) {
-        Status = update_root_backref(Vcb, fcb->subvol->id, destsubvol->id, rollback);
+    if (fileref->parent->fcb->subvol != destsubvol) {
+        Status = update_root_backref(Vcb, fileref->fcb->subvol->id, destsubvol->id, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("update_root_backref 1 returned %08x\n", Status);
             return Status;
         }
         
-        fcb->par->subvol->root_item.ctransid = Vcb->superblock.generation;
-        fcb->par->subvol->root_item.ctime = *now;
+        fileref->parent->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+        fileref->parent->fcb->subvol->root_item.ctime = *now;
     }
     
     destsubvol->root_item.ctransid = Vcb->superblock.generation;
@@ -1324,21 +1316,21 @@ static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* dests
     return STATUS_SUCCESS;
 }
 
-static BOOL has_open_children(fcb* fcb) {
-    LIST_ENTRY* le = fcb->children.Flink;
-    struct _fcb* c;
+BOOL has_open_children(file_ref* fileref) {
+    LIST_ENTRY* le = fileref->children.Flink;
     
-    while (le != &fcb->children) {
-        c = CONTAINING_RECORD(le, struct _fcb, list_entry);
+    if (IsListEmpty(&fileref->children))
+        return FALSE;
         
-        if (c->refcount > 0) {
-            if (c->open_count > 0)
-                return TRUE;
-            
-            if (has_open_children(c))
-                return TRUE;
-        }
+    while (le != &fileref->children) {
+        file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);
         
+        if (c->fcb->open_count > 0)
+            return TRUE;
+        
+        if (has_open_children(c))
+            return TRUE;
+
         le = le->Flink;
     }
     
@@ -1347,7 +1339,9 @@ static BOOL has_open_children(fcb* fcb) {
 
 static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, BOOL ReplaceIfExists, LIST_ENTRY* rollback) {
     FILE_RENAME_INFORMATION* fri = Irp->AssociatedIrp.SystemBuffer;
-    fcb *fcb = FileObject->FsContext, *tfofcb, *oldparfcb, *oldfcb;
+    fcb *fcb = FileObject->FsContext, *tfofcb/*, *oldfcb*/;
+    file_ref *fileref, *oldfileref = NULL, *related;
+    ccb* ccb = FileObject->FsContext2;
     root* parsubvol;
     UINT64 parinode, dirpos;
     WCHAR* fn;
@@ -1363,20 +1357,30 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
     BTRFS_TIME now;
     BOOL across_directories;
     INODE_ITEM* ii;
+    LONG i;
     
     // FIXME - MSDN says we should be able to rename streams here, but I can't get it to work.
     
+    // FIXME - don't ignore fri->RootDirectory
     TRACE("    tfo = %p\n", tfo);
     TRACE("    ReplaceIfExists = %u\n", ReplaceIfExists);
     TRACE("    RootDirectory = %p\n", fri->RootDirectory);
     TRACE("    FileName = %.*S\n", fri->FileNameLength / sizeof(WCHAR), fri->FileName);
     
+    if (!ccb->fileref) {
+        ERR("tried to rename file with no fileref\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    fileref = ccb->fileref;
+    
     KeQuerySystemTime(&time);
     win_time_to_unix(time, &now);
     
     utf8.Buffer = NULL;
     
-    if (!fcb->par) {
+    if (!fileref->parent) {
         ERR("error - tried to rename file with no parent\n");
         Status = STATUS_ACCESS_DENIED;
         goto end;
@@ -1386,14 +1390,12 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
     fnlen = fri->FileNameLength / sizeof(WCHAR);
     
     if (!tfo) {
-        parsubvol = fcb->par->subvol;
-        parinode = fcb->par->inode;
+        parsubvol = fileref->parent->fcb->subvol;
+        parinode = fileref->parent->fcb->inode;
         tfofcb = NULL;
         
         across_directories = FALSE;
     } else {
-        LONG i;
-        
         tfofcb = tfo->FsContext;
         parsubvol = tfofcb->subvol;
         parinode = tfofcb->inode;
@@ -1406,7 +1408,7 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
             }
         }
         
-        across_directories = parsubvol != fcb->par->subvol || parinode != fcb->par->inode;
+        across_directories = parsubvol != fileref->parent->fcb->subvol || parinode != fileref->parent->fcb->inode;
     }
     
     fnus.Buffer = fn;
@@ -1414,6 +1416,9 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
     
     TRACE("fnus = %.*S\n", fnus.Length / sizeof(WCHAR), fnus.Buffer);
     
+    if (!is_file_name_valid(&fnus))
+        return STATUS_OBJECT_NAME_INVALID;
+    
     Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
     if (!NT_SUCCESS(Status))
         goto end;
@@ -1433,28 +1438,34 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
     crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8.Buffer, (ULONG)utf8.Length);
     
     // FIXME - set to crc32 if utf8 and oldutf8 are identical
-    oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fcb->utf8.Buffer, (ULONG)fcb->utf8.Length);
+    oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fileref->utf8.Buffer, (ULONG)fileref->utf8.Length);
     
 //     TRACE("utf8 fn = %s (%08x), old utf8 fn = %s (%08x)\n", utf8, crc32, oldutf8, oldcrc32);
+    
+    if (tfo && tfo->FsContext2) {
+        struct _ccb* relatedccb = tfo->FsContext2;
+        
+        related = relatedccb->fileref;
+    } else
+        related = NULL;
 
-    oldfcb = NULL;
-
-    Status = get_fcb(Vcb, &oldfcb, &fnus, tfo ? tfo->FsContext : NULL, FALSE);
+//     Status = get_fcb(Vcb, &oldfcb, &fnus, tfo ? tfo->FsContext : NULL, FALSE, NULL);
+    Status = open_fileref(Vcb, &oldfileref, &fnus, related, FALSE, NULL);
 
     if (NT_SUCCESS(Status)) {
-        WARN("destination file %.*S already exists\n", oldfcb->full_filename.Length / sizeof(WCHAR), oldfcb->full_filename.Buffer);
+        WARN("destination file %S already exists\n", file_desc_fileref(oldfileref));
         
-        if (fcb != oldfcb && !(oldfcb->open_count == 0 && oldfcb->deleted)) {
+        if (fileref != oldfileref && !(oldfileref->fcb->open_count == 0 && oldfileref->deleted)) {
             if (!ReplaceIfExists) {
                 Status = STATUS_OBJECT_NAME_COLLISION;
                 goto end;
-            } else if (oldfcb->open_count >= 1 && !oldfcb->deleted) {
+            } else if (oldfileref->fcb->open_count >= 1 && !oldfileref->deleted) {
                 WARN("trying to overwrite open file\n");
                 Status = STATUS_ACCESS_DENIED;
                 goto end;
             }
             
-            if (oldfcb->type == BTRFS_TYPE_DIRECTORY) {
+            if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) {
                 WARN("trying to overwrite directory\n");
                 Status = STATUS_ACCESS_DENIED;
                 goto end;
@@ -1462,39 +1473,30 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         }
     }
     
-    if (has_open_children(fcb)) {
+    if (has_open_children(fileref)) {
         WARN("trying to rename file with open children\n");
         Status = STATUS_ACCESS_DENIED;
         goto end;
     }
     
-    if (oldfcb) {
-        Status = delete_fcb(oldfcb, NULL, rollback);
+    if (oldfileref) {
+        // FIXME - check we have permission to delete oldfileref
+        Status = delete_fileref(oldfileref, NULL, rollback);
         if (!NT_SUCCESS(Status)) {
-            ERR("delete_fcb returned %08x\n", Status);
+            ERR("delete_fileref returned %08x\n", Status);
             goto end;
         }
     }
     
     if (fcb->inode == SUBVOL_ROOT_INODE) {
-        UNICODE_STRING filename;
-        
-        filename.Buffer = fn;
-        filename.MaximumLength = filename.Length = fnlen * sizeof(WCHAR);
-        
-        Status = move_subvol(Vcb, fcb, tfofcb->subvol, tfofcb->inode, &utf8, crc32, oldcrc32, &now, ReplaceIfExists, rollback);
+        Status = move_subvol(Vcb, fileref, tfofcb->subvol, tfofcb->inode, &utf8, crc32, oldcrc32, &now, ReplaceIfExists, rollback);
         
         if (!NT_SUCCESS(Status)) {
             ERR("move_subvol returned %08x\n", Status);
             goto end;
         }
     } else if (parsubvol != fcb->subvol) {
-        UNICODE_STRING filename;
-        
-        filename.Buffer = fn;
-        filename.MaximumLength = filename.Length = fnlen * sizeof(WCHAR);
-        
-        Status = move_across_subvols(Vcb, fcb, tfofcb->subvol, tfofcb->inode, &utf8, crc32, &now, rollback);
+        Status = move_across_subvols(Vcb, fileref, tfofcb->subvol, tfofcb->inode, &utf8, crc32, &now, rollback);
         
         if (!NT_SUCCESS(Status)) {
             ERR("move_across_subvols returned %08x\n", Status);
@@ -1506,7 +1508,7 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         
         // delete old DIR_ITEM entry
         
-        Status = delete_dir_item(Vcb, fcb->subvol, fcb->par->inode, oldcrc32, &fcb->utf8, rollback);
+        Status = delete_dir_item(Vcb, fcb->subvol, fileref->parent->fcb->inode, oldcrc32, &fileref->utf8, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("delete_dir_item returned %08x\n", Status);
             return Status;
@@ -1539,7 +1541,7 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         
         oldindex = 0;
         
-        Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, fcb->par->inode, &fcb->utf8, &oldindex, rollback);
+        Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, fileref->parent->fcb->inode, &fileref->utf8, &oldindex, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("delete_inode_ref returned %08x\n", Status);
             return Status;
@@ -1548,11 +1550,11 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         // delete old DIR_INDEX entry
         
         if (oldindex != 0) {
-            searchkey.obj_id = fcb->par->inode;
+            searchkey.obj_id = fileref->parent->fcb->inode;
             searchkey.obj_type = TYPE_DIR_INDEX;
             searchkey.offset = oldindex;
             
-            Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
+            Status = find_item(Vcb, fileref->parent->fcb->subvol, &tp, &searchkey, FALSE);
             if (!NT_SUCCESS(Status)) {
                 ERR("error - find_item returned %08x\n", Status);
                 goto end;
@@ -1563,15 +1565,13 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
             else {
                 WARN("couldn't find DIR_INDEX\n");
             }
-                
-            free_traverse_ptr(&tp);
         } else {
             WARN("couldn't get index from INODE_REF\n");
         }
         
         // create new DIR_INDEX entry
         
-        if (parsubvol != fcb->par->subvol || parinode != fcb->par->inode) {
+        if (parsubvol != fileref->parent->fcb->subvol || parinode != fileref->parent->fcb->inode) {
             searchkey.obj_id = parinode;
             searchkey.obj_type = TYPE_DIR_INDEX + 1;
             searchkey.offset = 0;
@@ -1593,13 +1593,10 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
                 }
                 
                 if (find_prev_item(Vcb, &tp, &next_tp, FALSE)) {
-                    free_traverse_ptr(&tp);
                     tp = next_tp;
                 } else
                     break;
             } while (tp.item->key.obj_id >= parinode && tp.item->key.obj_type >= TYPE_DIR_INDEX);
-
-            free_traverse_ptr(&tp);
         } else
             dirpos = oldindex;
         
@@ -1647,8 +1644,6 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
             delete_tree_item(Vcb, &tp, rollback);
         
-        free_traverse_ptr(&tp);
-        
         ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
         if (!ii) {
             ERR("out of memory\n");
@@ -1664,16 +1659,16 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
     
     // update directory INODE_ITEMs
     
-    fcb->par->inode_item.transid = Vcb->superblock.generation;
-    fcb->par->inode_item.sequence++;
-    fcb->par->inode_item.st_ctime = now;
-    fcb->par->inode_item.st_mtime = now;
+    fileref->parent->fcb->inode_item.transid = Vcb->superblock.generation;
+    fileref->parent->fcb->inode_item.sequence++;
+    fileref->parent->fcb->inode_item.st_ctime = now;
+    fileref->parent->fcb->inode_item.st_mtime = now;
     
-    TRACE("fcb->par->inode_item.st_size was %llx\n", fcb->par->inode_item.st_size);
-    if (!tfofcb || (fcb->par->inode == tfofcb->inode && fcb->par->subvol == tfofcb->subvol)) {
-        fcb->par->inode_item.st_size += 2 * (utf8.Length - fcb->utf8.Length);
+    TRACE("fileref->parent->fcb->inode_item.st_size was %llx\n", fileref->parent->fcb->inode_item.st_size);
+    if (!tfofcb || (fileref->parent->fcb->inode == tfofcb->inode && fileref->parent->fcb->subvol == tfofcb->subvol)) {
+        fileref->parent->fcb->inode_item.st_size += 2 * (utf8.Length - fileref->utf8.Length);
     } else {
-        fcb->par->inode_item.st_size -= 2 * fcb->utf8.Length;
+        fileref->parent->fcb->inode_item.st_size -= 2 * fileref->utf8.Length;
         TRACE("tfofcb->inode_item.st_size was %llx\n", tfofcb->inode_item.st_size);
         tfofcb->inode_item.st_size += 2 * utf8.Length;
         TRACE("tfofcb->inode_item.st_size now %llx\n", tfofcb->inode_item.st_size);
@@ -1682,19 +1677,19 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         tfofcb->inode_item.st_ctime = now;
         tfofcb->inode_item.st_mtime = now;
     }
-    TRACE("fcb->par->inode_item.st_size now %llx\n", fcb->par->inode_item.st_size);
+    TRACE("fileref->parent->fcb->inode_item.st_size now %llx\n", fileref->parent->fcb->inode_item.st_size);
     
-    if (oldfcb && oldfcb->par != fcb->par) {
-        TRACE("oldfcb->par->inode_item.st_size was %llx\n", oldfcb->par->inode_item.st_size);
-        oldfcb->par->inode_item.st_size -= 2 * oldfcb->utf8.Length;
-        TRACE("oldfcb->par->inode_item.st_size now %llx\n", oldfcb->par->inode_item.st_size);
+    if (oldfileref && oldfileref->fcb && oldfileref->parent->fcb != fileref->parent->fcb) {
+        TRACE("oldfileref->parent->fcb->inode_item.st_size was %llx\n", oldfileref->parent->fcb->inode_item.st_size);
+        oldfileref->parent->fcb->inode_item.st_size -= 2 * oldfileref->utf8.Length;
+        TRACE("oldfileref->parent->fcb->inode_item.st_size now %llx\n", oldfileref->parent->fcb->inode_item.st_size);
     }
     
-    searchkey.obj_id = fcb->par->inode;
+    searchkey.obj_id = fileref->parent->fcb->inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
     searchkey.offset = 0xffffffffffffffff;
     
-    Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, fileref->parent->fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         return Status;
@@ -1706,18 +1701,15 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
     ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!ii) {
         ERR("out of memory\n");
-        free_traverse_ptr(&tp);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(ii, &fcb->par->inode_item, sizeof(INODE_ITEM));
+    RtlCopyMemory(ii, &fileref->parent->fcb->inode_item, sizeof(INODE_ITEM));
     
-    if (!insert_tree_item(Vcb, fcb->par->subvol, fcb->par->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback))
+    if (!insert_tree_item(Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback))
         WARN("insert_tree_item failed\n");
     
-    free_traverse_ptr(&tp);
-    
-    if (tfofcb && (fcb->par->inode != tfofcb->inode || fcb->par->subvol != tfofcb->subvol)) {
+    if (tfofcb && (fileref->parent->fcb->inode != tfofcb->inode || fileref->parent->fcb->subvol != tfofcb->subvol)) {
         searchkey.obj_id = tfofcb->inode;
         searchkey.obj_type = TYPE_INODE_ITEM;
         searchkey.offset = 0xffffffffffffffff;
@@ -1734,7 +1726,6 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
         if (!ii) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
@@ -1742,82 +1733,89 @@ static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp,
         
         if (!insert_tree_item(Vcb, tfofcb->subvol, tfofcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback))
             WARN("insert_tree_item failed\n");
-        
-        free_traverse_ptr(&tp);
     }
     
     fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
     fcb->subvol->root_item.ctime = now;
     
     // FIXME - handle overwrite by rename here
-    FsRtlNotifyFullReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fcb->full_filename, fcb->name_offset * sizeof(WCHAR), NULL, NULL,
-                                fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
-                                across_directories ? FILE_ACTION_REMOVED : FILE_ACTION_RENAMED_OLD_NAME, NULL);
+    send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
+                              across_directories ? FILE_ACTION_REMOVED : FILE_ACTION_RENAMED_OLD_NAME);
 
     // FIXME - change full_filename and name_offset of open children
     
-    if (fnlen != fcb->filepart.Length / sizeof(WCHAR) || RtlCompareMemory(fn, fcb->filepart.Buffer, fcb->filepart.Length) != fcb->filepart.Length) {
-        RtlFreeUnicodeString(&fcb->filepart);
-        fcb->filepart.Length = fcb->filepart.MaximumLength = (USHORT)(fnlen * sizeof(WCHAR));
-        fcb->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->filepart.Length, ALLOC_TAG);
+    if (fnlen != fileref->filepart.Length / sizeof(WCHAR) || RtlCompareMemory(fn, fileref->filepart.Buffer, fileref->filepart.Length) != fileref->filepart.Length) {
+        ExFreePool(fileref->filepart.Buffer);
+        fileref->filepart.Length = fileref->filepart.MaximumLength = (USHORT)(fnlen * sizeof(WCHAR));
+        fileref->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->filepart.MaximumLength, ALLOC_TAG);
         
-        if (!fcb->filepart.Buffer) {
+        if (!fileref->filepart.Buffer) {
             ERR("out of memory\n");
             
             Status = STATUS_INSUFFICIENT_RESOURCES;
             goto end;
         }
         
-        RtlCopyMemory(fcb->filepart.Buffer, fn, fcb->filepart.Length);
+        RtlCopyMemory(fileref->filepart.Buffer, fn, fileref->filepart.Length);
     }
     
-    if (tfo && tfofcb != fcb->par) {
-        oldparfcb = fcb->par;
-        fcb->par = tfofcb;
-        
-        fcb->par->refcount++;
-        
-        RemoveEntryList(&fcb->list_entry);
-        InsertTailList(&fcb->par->children, &fcb->list_entry);
+    if (related && related != (file_ref*)fileref->parent) {
+        file_ref* oldpar = (file_ref*)fileref->parent;
+#ifdef DEBUG_FCB_REFCOUNTS
+        LONG rc;
+#endif
         
+        RemoveEntryList(&fileref->list_entry);
+      
+        fileref->parent = (struct _file_ref*)related;
 #ifdef DEBUG_FCB_REFCOUNTS
-        WARN("fcb %p: refcount now %i (%.*S)\n", fcb->par, fcb->par->refcount, fcb->par->full_filename.Length / sizeof(WCHAR), fcb->par->full_filename.Buffer);
+        rc = InterlockedIncrement(&related->refcount);
+        WARN("fileref %p: refcount now %i (%S)\n", fileref->parent, rc, file_desc_fileref((file_ref*)fileref->parent));
+#else
+        InterlockedIncrement(&related->refcount);
 #endif
-        free_fcb(oldparfcb);
+        
+        InsertTailList(&related->children, &fileref->list_entry);
+        
+        free_fileref(oldpar);
     }
     
-    ExFreePool(fcb->utf8.Buffer);
-    fcb->utf8 = utf8;
+    ExFreePool(fileref->utf8.Buffer);
+    fileref->utf8 = utf8;
     utf8.Buffer = NULL;
     
-    // change fcb->full_filename
+    // change fileref->full_filename
     
-    fcb->full_filename.MaximumLength = fcb->par->full_filename.Length + fcb->filepart.Length;
-    if (fcb->par->par) fcb->full_filename.MaximumLength += sizeof(WCHAR);
-    ExFreePool(fcb->full_filename.Buffer);
+    fileref->full_filename.MaximumLength = fileref->parent->full_filename.Length + fileref->filepart.Length;
+    if (fileref->parent->parent) fileref->full_filename.MaximumLength += sizeof(WCHAR);
+    ExFreePool(fileref->full_filename.Buffer);
     
-    fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->full_filename.MaximumLength, ALLOC_TAG);
-    if (!fcb->full_filename.Buffer) {
+    fileref->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->full_filename.MaximumLength, ALLOC_TAG);
+    if (!fileref->full_filename.Buffer) {
         ERR("out of memory\n");
         
         Status = STATUS_INSUFFICIENT_RESOURCES;
         goto end;
     }
     
-    RtlCopyMemory(fcb->full_filename.Buffer, fcb->par->full_filename.Buffer, fcb->par->full_filename.Length);
-    fcb->full_filename.Length = fcb->par->full_filename.Length;
+    RtlCopyMemory(fileref->full_filename.Buffer, fileref->parent->full_filename.Buffer, fileref->parent->full_filename.Length);
+    fileref->full_filename.Length = fileref->parent->full_filename.Length;
     
-    if (fcb->par->par) {
-        fcb->full_filename.Buffer[fcb->full_filename.Length / sizeof(WCHAR)] = '\\';
-        fcb->full_filename.Length += sizeof(WCHAR);
+    if (fileref->parent->parent) {
+        fileref->full_filename.Buffer[fileref->full_filename.Length / sizeof(WCHAR)] = '\\';
+        fileref->full_filename.Length += sizeof(WCHAR);
     }
-    fcb->name_offset = fcb->full_filename.Length / sizeof(WCHAR);
+    fileref->name_offset = fileref->full_filename.Length / sizeof(WCHAR);
+    
+    RtlAppendUnicodeStringToString(&fileref->full_filename, &fileref->filepart);
     
-    RtlAppendUnicodeStringToString(&fcb->full_filename, &fcb->filepart);
+    if (fileref->debug_desc) {
+        ExFreePool(fileref->debug_desc);
+        fileref->debug_desc = NULL;
+    }
     
-    FsRtlNotifyFullReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fcb->full_filename, fcb->name_offset * sizeof(WCHAR), NULL, NULL,
-                                fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
-                                across_directories ? FILE_ACTION_ADDED : FILE_ACTION_RENAMED_NEW_NAME, NULL);
+    send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
+                              across_directories ? FILE_ACTION_ADDED : FILE_ACTION_RENAMED_NEW_NAME);
     
     Status = STATUS_SUCCESS;
     
@@ -1825,15 +1823,13 @@ end:
     if (utf8.Buffer)
         ExFreePool(utf8.Buffer);
     
-    if (oldfcb)
-        free_fcb(oldfcb);
+    if (oldfileref)
+        free_fileref(oldfileref);
     
     return Status;
 }
 
-static NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, BOOL advance_only, LIST_ENTRY* rollback) {
-    FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer;
-    fcb* fcb = FileObject->FsContext;
+NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb, UINT64 end, fcb* fcb, file_ref* fileref, PFILE_OBJECT FileObject, BOOL advance_only, LIST_ENTRY* rollback) {
     LARGE_INTEGER time;
     BTRFS_TIME now;
     KEY searchkey;
@@ -1844,36 +1840,41 @@ static NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb
     UINT16 datalen;
     NTSTATUS Status;
     
-    TRACE("setting new end to %llx bytes (currently %x)\n", feofi->EndOfFile.QuadPart, fcb->adssize);
+    TRACE("setting new end to %llx bytes (currently %x)\n", end, fcb->adssize);
+    
+    if (!fileref || !fileref->parent) {
+        ERR("no fileref for stream\n");
+        return STATUS_INTERNAL_ERROR;
+    }
     
-    if (feofi->EndOfFile.QuadPart < fcb->adssize) {
+    if (end < fcb->adssize) {
         if (advance_only)
             return STATUS_SUCCESS;
         
-        TRACE("truncating stream to %llx bytes\n", feofi->EndOfFile.QuadPart);
+        TRACE("truncating stream to %llx bytes\n", end);
         
-        if (feofi->EndOfFile.QuadPart > 0) {
+        if (end > 0) {
             if (!get_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &data, &datalen)) {
                 ERR("get_xattr failed\n");
                 return STATUS_INTERNAL_ERROR;
             }
         }
         
-        Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data, feofi->EndOfFile.QuadPart, rollback);
+        Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data, end, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("set_xattr returned %08x\n", Status);
             return Status;
         }
         
-        fcb->adssize = feofi->EndOfFile.QuadPart;
+        fcb->adssize = end;
         
         if (data)
             ExFreePool(data);
-    } else if (feofi->EndOfFile.QuadPart > fcb->adssize) {
+    } else if (end > fcb->adssize) {
         UINT16 maxlen;
         UINT8* data2;
         
-        TRACE("extending stream to %llx bytes\n", feofi->EndOfFile.QuadPart);
+        TRACE("extending stream to %llx bytes\n", end);
         
         // find maximum length of xattr
         maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
@@ -1890,71 +1891,60 @@ static NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb
         
         if (keycmp(&tp.item->key, &searchkey)) {
             ERR("error - could not find key for xattr\n");
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
-
-#ifdef __REACTOS__
-        // BUGBUG: FIXME!!
-        ERR("BUGBUG: datalen is uninitialized! Set it to zero temporarily...\n");
-        datalen = 0;
-#endif
-
-        if (tp.item->size < datalen) {
-            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, datalen);
-            free_traverse_ptr(&tp);
+        
+        if (!get_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &data, &datalen)) {
+            ERR("get_xattr failed\n");
             return STATUS_INTERNAL_ERROR;
         }
         
         maxlen -= tp.item->size - datalen; // subtract XATTR_ITEM overhead
         
-        free_traverse_ptr(&tp);
-        
-        if (feofi->EndOfFile.QuadPart > maxlen) {
-            ERR("error - xattr too long (%llu > %u)\n", feofi->EndOfFile.QuadPart, maxlen);
+        if (end > maxlen) {
+            ERR("error - xattr too long (%llu > %u)\n", end, maxlen);
             return STATUS_DISK_FULL;
         }
-        
-        if (!get_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &data, &datalen)) {
-            ERR("get_xattr failed\n");
-            return STATUS_INTERNAL_ERROR;
-        }
 
-        data2 = ExAllocatePoolWithTag(PagedPool, feofi->EndOfFile.QuadPart, ALLOC_TAG);
+        data2 = ExAllocatePoolWithTag(PagedPool, end, ALLOC_TAG);
         if (!data2) {
             ERR("out of memory\n");
             ExFreePool(data);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
-        RtlCopyMemory(data2, data, datalen);
-        ExFreePool(data);
+        if (data) {
+            RtlCopyMemory(data2, data, datalen);
+            ExFreePool(data);
+        }
         
-        RtlZeroMemory(&data2[datalen], feofi->EndOfFile.QuadPart - datalen);
+        RtlZeroMemory(&data2[datalen], end - datalen);
         
-        Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data2, feofi->EndOfFile.QuadPart, rollback);
+        Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data2, end, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("set_xattr returned %08x\n", Status);
             return Status;
         }
         
-        fcb->adssize = feofi->EndOfFile.QuadPart;
+        fcb->adssize = end;
         
         ExFreePool(data2);
     }
 
-    ccfs.AllocationSize = fcb->Header.AllocationSize;
-    ccfs.FileSize = fcb->Header.FileSize;
-    ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+    if (FileObject) {
+        ccfs.AllocationSize = fcb->Header.AllocationSize;
+        ccfs.FileSize = fcb->Header.FileSize;
+        ccfs.ValidDataLength = fcb->Header.ValidDataLength;
 
-    CcSetFileSizes(FileObject, &ccfs);
+        CcSetFileSizes(FileObject, &ccfs);
+    }
     
     KeQuerySystemTime(&time);
     win_time_to_unix(time, &now);
     
-    fcb->par->inode_item.transid = Vcb->superblock.generation;
-    fcb->par->inode_item.sequence++;
-    fcb->par->inode_item.st_ctime = now;
+    fileref->parent->fcb->inode_item.transid = Vcb->superblock.generation;
+    fileref->parent->fcb->inode_item.sequence++;
+    fileref->parent->fcb->inode_item.st_ctime = now;
     
     searchkey.obj_id = fcb->inode;
     searchkey.obj_type = TYPE_INODE_ITEM;
@@ -1974,17 +1964,14 @@ static NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb
     ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!ii) {
         ERR("out of memory\n");
-        free_traverse_ptr(&tp);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
     
-    RtlCopyMemory(ii, &fcb->par->inode_item, sizeof(INODE_ITEM));
+    RtlCopyMemory(ii, &fileref->parent->fcb->inode_item, sizeof(INODE_ITEM));
     insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
     
-    free_traverse_ptr(&tp);
-    
-    fcb->par->subvol->root_item.ctransid = Vcb->superblock.generation;
-    fcb->par->subvol->root_item.ctime = now;
+    fileref->parent->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fileref->parent->fcb->subvol->root_item.ctime = now;
 
     return STATUS_SUCCESS;
 }
@@ -1992,6 +1979,8 @@ static NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb
 static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, BOOL advance_only, LIST_ENTRY* rollback) {
     FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer;
     fcb* fcb = FileObject->FsContext;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
     NTSTATUS Status;
     LARGE_INTEGER time;
     KEY searchkey;
@@ -1999,13 +1988,13 @@ static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP
     INODE_ITEM* ii;
     CC_FILE_SIZES ccfs;
     
-    if (fcb->deleted)
+    if (fileref ? fileref->deleted : fcb->deleted)
         return STATUS_FILE_CLOSED;
     
     if (fcb->ads)
-        return stream_set_end_of_file_information(Vcb, Irp, FileObject, advance_only, rollback);
+        return stream_set_end_of_file_information(Vcb, feofi->EndOfFile.QuadPart, fcb, fileref, FileObject, advance_only, rollback);
     
-    TRACE("filename %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    TRACE("file: %S\n", file_desc(FileObject));
     TRACE("paging IO: %s\n", Irp->Flags & IRP_PAGING_IO ? "TRUE" : "FALSE");
     TRACE("FileObject: AllocationSize = %llx, FileSize = %llx, ValidDataLength = %llx\n",
         fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
@@ -2035,8 +2024,7 @@ static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP
         
         TRACE("extending file to %llx bytes\n", feofi->EndOfFile.QuadPart);
         
-        // FIXME - pass flag to say that new extents should be prealloc rather than sparse
-        Status = extend_file(fcb, feofi->EndOfFile.QuadPart, rollback);
+        Status = extend_file(fcb, fileref, feofi->EndOfFile.QuadPart, TRUE, rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("error - extend_file failed\n");
             return Status;
@@ -2048,7 +2036,7 @@ static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP
     ccfs.ValidDataLength = fcb->Header.ValidDataLength;
 
     CcSetFileSizes(FileObject, &ccfs);
-    TRACE("setting FileSize for %.*S to %llx\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, ccfs.FileSize);
+    TRACE("setting FileSize for %S to %llx\n", file_desc(FileObject), ccfs.FileSize);
     
     KeQuerySystemTime(&time);
     
@@ -2078,8 +2066,6 @@ static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP
     RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
     insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
     
-    free_traverse_ptr(&tp);
-
     return STATUS_SUCCESS;
 }
 
@@ -2096,11 +2082,8 @@ static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP
 
 static NTSTATUS STDCALL set_position_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
     FILE_POSITION_INFORMATION* fpi = (FILE_POSITION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
-#ifdef DEBUG_LONG_MESSAGES
-    fcb* fcb = FileObject->FsContext;
     
-    TRACE("setting the position on %.*S to %llx\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fpi->CurrentByteOffset.QuadPart);
-#endif
+    TRACE("setting the position on %S to %llx\n", file_desc(FileObject), fpi->CurrentByteOffset.QuadPart);
     
     // FIXME - make sure aligned for FO_NO_INTERMEDIATE_BUFFERING
     
@@ -2109,84 +2092,401 @@ static NTSTATUS STDCALL set_position_information(device_extension* Vcb, PIRP Irp
     return STATUS_SUCCESS;
 }
 
-NTSTATUS STDCALL drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+static NTSTATUS STDCALL set_link_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, LIST_ENTRY* rollback) {
+    FILE_LINK_INFORMATION* fli = Irp->AssociatedIrp.SystemBuffer;
+    fcb *fcb = FileObject->FsContext, *tfofcb;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related;
+    root* parsubvol;
+    UINT64 parinode, dirpos;
+    WCHAR* fn;
+    ULONG fnlen, utf8len, disize;
+    UNICODE_STRING fnus;
+    ANSI_STRING utf8;
     NTSTATUS Status;
-    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
-    device_extension* Vcb = DeviceObject->DeviceExtension;
-    fcb* fcb = IrpSp->FileObject->FsContext;
-    BOOL top_level;
-    LIST_ENTRY rollback;
+    UINT32 crc32;
+    DIR_ITEM *di, *di2;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    KEY searchkey;
+    traverse_ptr tp;
+    INODE_ITEM *ii, *fcbii;
     
-    InitializeListHead(&rollback);
+    // FIXME - check fli length
+    // FIXME - don't ignore fli->RootDirectory
     
-    FsRtlEnterFileSystem();
+    TRACE("ReplaceIfExists = %x\n", fli->ReplaceIfExists);
+    TRACE("RootDirectory = %p\n", fli->RootDirectory);
+    TRACE("FileNameLength = %x\n", fli->FileNameLength);
+    TRACE("FileName = %.*S\n", fli->FileNameLength / sizeof(WCHAR), fli->FileName);
     
-    top_level = is_top_level(Irp);
+    fn = fli->FileName;
+    fnlen = fli->FileNameLength / sizeof(WCHAR);
     
-    if (Vcb->readonly) {
-        Status = STATUS_MEDIA_WRITE_PROTECTED;
+    if (!tfo) {
+        if (!fileref || !fileref->parent) {
+            ERR("no fileref set and no directory given\n");
+            return STATUS_INVALID_PARAMETER;
+        }
+        
+        parsubvol = fileref->parent->fcb->subvol;
+        parinode = fileref->parent->fcb->inode;
+        tfofcb = NULL;
+    } else {
+        LONG i;
+        
+        tfofcb = tfo->FsContext;
+        parsubvol = tfofcb->subvol;
+        parinode = tfofcb->inode;
+        
+        for (i = fnlen - 1; i >= 0; i--) {
+            if (fli->FileName[i] == '\\' || fli->FileName[i] == '/') {
+                fn = &fli->FileName[i+1];
+                fnlen = (fli->FileNameLength / sizeof(WCHAR)) - i - 1;
+                break;
+            }
+        }
+    }
+    
+    utf8.Buffer = NULL;
+    
+    if (fcb->type == BTRFS_TYPE_DIRECTORY) {
+        WARN("tried to create hard link on directory\n");
+        Status = STATUS_FILE_IS_A_DIRECTORY;
         goto end;
     }
     
-    if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
-        Status = STATUS_ACCESS_DENIED;
+    if (fcb->ads) {
+        WARN("tried to create hard link on stream\n");
+        Status = STATUS_INVALID_PARAMETER;
         goto end;
     }
-
-    Irp->IoStatus.Information = 0;
     
-    Status = STATUS_NOT_IMPLEMENTED;
-
-    TRACE("set information\n");
+    fnus.Buffer = fn;
+    fnus.Length = fnus.MaximumLength = fnlen * sizeof(WCHAR);
     
-    acquire_tree_lock(Vcb, TRUE);
-
-    switch (IrpSp->Parameters.SetFile.FileInformationClass) {
-        case FileAllocationInformation:
-            TRACE("FileAllocationInformation\n");
-            Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, FALSE, &rollback);
-            break;
-
-        case FileBasicInformation:
-            TRACE("FileBasicInformation\n");
-            Status = set_basic_information(Vcb, Irp, IrpSp->FileObject, &rollback);
-            break;
-
-        case FileDispositionInformation:
-            TRACE("FileDispositionInformation\n");
-            Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject);
-            break;
-
-        case FileEndOfFileInformation:
-            TRACE("FileEndOfFileInformation\n");
-            Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.AdvanceOnly, &rollback);
-            break;
-
-        case FileLinkInformation:
-            FIXME("STUB: FileLinkInformation\n");
-            break;
-
-        case FilePositionInformation:
-            TRACE("FilePositionInformation\n");
-            Status = set_position_information(Vcb, Irp, IrpSp->FileObject);
-            break;
+    TRACE("fnus = %.*S\n", fnus.Length / sizeof(WCHAR), fnus.Buffer);
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
+    if (!NT_SUCCESS(Status))
+        goto end;
+    
+    utf8.MaximumLength = utf8.Length = utf8len;
+    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);
+    if (!utf8.Buffer) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    Status = RtlUnicodeToUTF8N(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
+    if (!NT_SUCCESS(Status))
+        goto end;
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8.Buffer, (ULONG)utf8.Length);
+    
+    if (tfo && tfo->FsContext2) {
+        struct _ccb* relatedccb = tfo->FsContext2;
+        
+        related = relatedccb->fileref;
+    } else
+        related = NULL;
 
-        case FileRenameInformation:
-            TRACE("FileRenameInformation\n");
-            // FIXME - make this work with streams
-            Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, IrpSp->Parameters.SetFile.ReplaceIfExists, &rollback);
-            break;
+    Status = open_fileref(Vcb, &oldfileref, &fnus, related, FALSE, NULL);
 
-        case FileValidDataLengthInformation:
-            FIXME("STUB: FileValidDataLengthInformation\n");
-            break;
-            
-#if (NTDDI_VERSION >= NTDDI_VISTA)
-        case FileNormalizedNameInformation:
-            FIXME("STUB: FileNormalizedNameInformation\n");
-            break;
-#endif
+    if (NT_SUCCESS(Status)) {
+        WARN("destination file %S already exists\n", file_desc_fileref(oldfileref));
+        
+        if (fileref != oldfileref && !(oldfileref->fcb->open_count == 0 && oldfileref->deleted)) {
+            if (!fli->ReplaceIfExists) {
+                Status = STATUS_OBJECT_NAME_COLLISION;
+                goto end;
+            } else if (oldfileref->fcb->open_count >= 1 && !oldfileref->deleted) {
+                WARN("trying to overwrite open file\n");
+                Status = STATUS_ACCESS_DENIED;
+                goto end;
+            }
+            
+            if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) {
+                WARN("trying to overwrite directory\n");
+                Status = STATUS_ACCESS_DENIED;
+                goto end;
+            }
+        }
+    }
+    
+    if (fcb->subvol != parsubvol) {
+        WARN("can't create hard link over subvolume boundary\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    if (oldfileref) {
+        // FIXME - check we have permissions for this
+        
+        Status = delete_fileref(oldfileref, NULL, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("delete_fcb returned %08x\n", Status);
+            goto end;
+        }
+    }
+    
+    // add DIR_ITEM
+    
+    disize = sizeof(DIR_ITEM) - 1 + utf8len;
+    
+    di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di2) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        ExFreePool(di);
+        goto end;
+    }
+    
+    di->key.obj_id = fcb->inode;
+    di->key.obj_type = TYPE_INODE_ITEM;
+    di->key.offset = 0;
+    di->transid = Vcb->superblock.generation;
+    di->m = 0;
+    di->n = utf8len;
+    di->type = fcb->type;
+    RtlCopyMemory(di->name, utf8.Buffer, di->n);
+    RtlCopyMemory(di2, di, disize);
+    
+    Status = add_dir_item(Vcb, fcb->subvol, parinode, crc32, di, disize, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_dir_item returned %08x\n", Status);
+        ExFreePool(di);
+        ExFreePool(di2);
+        goto end;
+    }
+    
+    // add DIR_INDEX
+    
+    dirpos = find_next_dir_index(Vcb, fcb->subvol, parinode);
+    if (dirpos == 0) {
+        ERR("find_next_dir_index failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        ExFreePool(di2);
+        goto end;
+    }
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, parinode, TYPE_DIR_INDEX, dirpos, di2, disize, NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        ExFreePool(di2);
+        goto end;
+    }
+    
+    // add INODE_REF
+    
+    Status = add_inode_ref(Vcb, fcb->subvol, fcb->inode, parinode, dirpos, &utf8, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_inode_ref returned %08x\n", Status);
+        goto end;
+    }
+    
+    // update inode's INODE_ITEM
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+    fcb->inode_item.transid = Vcb->superblock.generation;
+    fcb->inode_item.sequence++;
+    fcb->inode_item.st_nlink++;
+    fcb->inode_item.st_ctime = now;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
+        delete_tree_item(Vcb, &tp, rollback);
+        searchkey.offset = tp.item->key.offset;
+    } else
+        searchkey.offset = 0;
+    
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ii, sizeof(INODE_ITEM), NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // update parent's INODE_ITEM
+    
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
+        delete_tree_item(Vcb, &tp, rollback);
+        searchkey.offset = tp.item->key.offset;
+    } else {
+        ERR("could not find INODE_ITEM for inode %llx in subvol %llx\n", parinode, fcb->subvol->id);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    if (tfofcb)
+        fcbii = &tfofcb->inode_item;
+    else {
+        if (tp.item->size < sizeof(INODE_ITEM)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_ITEM));
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+        
+        fcbii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+        if (!fcbii) {
+            ERR("out of memory\n");
+            goto end;
+        }
+        
+        RtlCopyMemory(fcbii, tp.item->data, sizeof(INODE_ITEM));
+    }
+    
+    fcbii->transid = Vcb->superblock.generation;
+    fcbii->st_size += 2 * utf8len;
+    fcbii->sequence++;
+    fcbii->st_ctime = now;
+    
+    if (tfofcb) {
+        ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+        if (!ii) {
+            ERR("out of memory\n");
+            goto end;
+        }
+        
+        RtlCopyMemory(ii, fcbii, sizeof(INODE_ITEM));
+    } else
+        ii = fcbii;
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ii, sizeof(INODE_ITEM), NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        ExFreePool(ii);
+        goto end;
+    }
+    
+    // FIXME - notification
+
+    Status = STATUS_SUCCESS;
+    
+end:
+    if (utf8.Buffer)
+        ExFreePool(utf8.Buffer);
+    
+    if (oldfileref)
+        free_fileref(oldfileref);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    fcb* fcb = IrpSp->FileObject->FsContext;
+    BOOL top_level;
+    LIST_ENTRY rollback;
+    
+    InitializeListHead(&rollback);
+    
+    FsRtlEnterFileSystem();
+    
+    top_level = is_top_level(Irp);
+    
+    if (Vcb->readonly) {
+        Status = STATUS_MEDIA_WRITE_PROTECTED;
+        goto end;
+    }
+    
+    if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+
+    Irp->IoStatus.Information = 0;
+    
+    Status = STATUS_NOT_IMPLEMENTED;
+
+    TRACE("set information\n");
+    
+    acquire_tree_lock(Vcb, TRUE);
+
+    switch (IrpSp->Parameters.SetFile.FileInformationClass) {
+        case FileAllocationInformation:
+            TRACE("FileAllocationInformation\n");
+            Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, FALSE, &rollback);
+            break;
+
+        case FileBasicInformation:
+            TRACE("FileBasicInformation\n");
+            Status = set_basic_information(Vcb, Irp, IrpSp->FileObject, &rollback);
+            break;
+
+        case FileDispositionInformation:
+            TRACE("FileDispositionInformation\n");
+            Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject);
+            break;
+
+        case FileEndOfFileInformation:
+            TRACE("FileEndOfFileInformation\n");
+            Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.AdvanceOnly, &rollback);
+            break;
+
+        case FileLinkInformation:
+            TRACE("FileLinkInformation\n");
+            Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, &rollback);
+            break;
+
+        case FilePositionInformation:
+            TRACE("FilePositionInformation\n");
+            Status = set_position_information(Vcb, Irp, IrpSp->FileObject);
+            break;
+
+        case FileRenameInformation:
+            TRACE("FileRenameInformation\n");
+            // FIXME - make this work with streams
+            Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, IrpSp->Parameters.SetFile.ReplaceIfExists, &rollback);
+            break;
 
+        case FileValidDataLengthInformation:
+            FIXME("STUB: FileValidDataLengthInformation\n");
+            break;
+            
+#if (NTDDI_VERSION >= NTDDI_VISTA)
+        case FileNormalizedNameInformation:
+            FIXME("STUB: FileNormalizedNameInformation\n");
+            break;
+#endif
             
 #if (NTDDI_VERSION >= NTDDI_WIN7)
         case FileStandardLinkInformation:
@@ -2225,7 +2525,7 @@ end:
     return Status;
 }
 
-static NTSTATUS STDCALL fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb) {
+static NTSTATUS STDCALL fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb, file_ref* fileref) {
     RtlZeroMemory(fbi, sizeof(FILE_BASIC_INFORMATION));
     
     *length -= sizeof(FILE_BASIC_INFORMATION);
@@ -2234,12 +2534,20 @@ static NTSTATUS STDCALL fill_in_file_basic_information(FILE_BASIC_INFORMATION* f
     fbi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
     fbi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
     fbi->ChangeTime.QuadPart = 0;
-    fbi->FileAttributes = fcb->ads ? fcb->par->atts : fcb->atts;
+    
+    if (fcb->ads) {
+        if (!fileref || !fileref->parent) {
+            ERR("no fileref for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        } else
+            fbi->FileAttributes = fileref->parent->fcb->atts;
+    } else
+        fbi->FileAttributes = fcb->atts;
     
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, LONG* length) {
+static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, file_ref* fileref, LONG* length) {
     INODE_ITEM* ii;
     
     if (*length < sizeof(FILE_NETWORK_OPEN_INFORMATION)) {
@@ -2251,12 +2559,16 @@ static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_
     
     *length -= sizeof(FILE_NETWORK_OPEN_INFORMATION);
     
-    if (fcb->ads)
-        ii = &fcb->par->inode_item;
-    else
+    if (fcb->ads) {
+        if (!fileref || !fileref->parent) {
+            ERR("no fileref for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        ii = &fileref->parent->fcb->inode_item;
+    } else
         ii = &fcb->inode_item;
     
-    
     fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
     fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
     fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
@@ -2264,7 +2576,7 @@ static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_
     
     if (fcb->ads) {
         fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adssize;
-        fnoi->FileAttributes = fcb->par->atts;
+        fnoi->FileAttributes = fileref->parent->fcb->atts;
     } else {
         fnoi->AllocationSize.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
         fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
@@ -2274,15 +2586,20 @@ static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, LONG* length) {
+static NTSTATUS STDCALL fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, file_ref* fileref, LONG* length) {
     RtlZeroMemory(fsi, sizeof(FILE_STANDARD_INFORMATION));
     
     *length -= sizeof(FILE_STANDARD_INFORMATION);
     
     if (fcb->ads) {
+        if (!fileref || !fileref->parent) {
+            ERR("no fileref for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
         fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adssize;
-        fsi->NumberOfLinks = fcb->par->inode_item.st_nlink;
-        fsi->Directory = S_ISDIR(fcb->par->inode_item.st_mode);
+        fsi->NumberOfLinks = fileref->parent->fcb->inode_item.st_nlink;
+        fsi->Directory = S_ISDIR(fileref->parent->fcb->inode_item.st_mode);
     } else {
         fsi->AllocationSize.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
         fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
@@ -2292,7 +2609,7 @@ static NTSTATUS STDCALL fill_in_file_standard_information(FILE_STANDARD_INFORMAT
     
     TRACE("length = %llu\n", fsi->EndOfFile.QuadPart);
     
-    fsi->DeletePending = fcb->delete_on_close;
+    fsi->DeletePending = fileref ? fileref->delete_on_close : FALSE;
     
     return STATUS_SUCCESS;
 }
@@ -2308,6 +2625,7 @@ static NTSTATUS STDCALL fill_in_file_internal_information(FILE_INTERNAL_INFORMAT
 static NTSTATUS STDCALL fill_in_file_ea_information(FILE_EA_INFORMATION* eai, LONG* length) {
     *length -= sizeof(FILE_EA_INFORMATION);
     
+    // FIXME - should this be the reparse tag for symlinks?
     eai->EaSize = 0;
     
     return STATUS_SUCCESS;
@@ -2367,13 +2685,18 @@ static NTSTATUS STDCALL fill_in_file_alignment_information(FILE_ALIGNMENT_INFORM
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, LONG* length) {
+static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, file_ref* fileref, LONG* length) {
 #ifdef _DEBUG
     ULONG retlen = 0;
 #endif
     static WCHAR datasuf[] = {':','$','D','A','T','A',0};
     ULONG datasuflen = wcslen(datasuf) * sizeof(WCHAR);
     
+    if (!fileref) {
+        ERR("called without fileref\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
     RtlZeroMemory(fni, sizeof(FILE_NAME_INFORMATION));
     
     *length -= (LONG)offsetof(FILE_NAME_INFORMATION, FileName[0]);
@@ -2383,15 +2706,15 @@ static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni
     
     fni->FileName[0] = 0;
     
-    if (*length >= (LONG)fcb->full_filename.Length) {
-        RtlCopyMemory(fni->FileName, fcb->full_filename.Buffer, fcb->full_filename.Length);
+    if (*length >= (LONG)fileref->full_filename.Length) {
+        RtlCopyMemory(fni->FileName, fileref->full_filename.Buffer, fileref->full_filename.Length);
 #ifdef _DEBUG
-        retlen = fcb->full_filename.Length;
+        retlen = fileref->full_filename.Length;
 #endif
-        *length -= fcb->full_filename.Length;
+        *length -= fileref->full_filename.Length;
     } else {
         if (*length > 0) {
-            RtlCopyMemory(fni->FileName, fcb->full_filename.Buffer, *length);
+            RtlCopyMemory(fni->FileName, fileref->full_filename.Buffer, *length);
 #ifdef _DEBUG
             retlen = *length;
 #endif
@@ -2399,18 +2722,18 @@ static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni
         *length = -1;
     }
     
-    fni->FileNameLength = fcb->full_filename.Length;
+    fni->FileNameLength = fileref->full_filename.Length;
     
     if (fcb->ads) {
         if (*length >= (LONG)datasuflen) {
-            RtlCopyMemory(&fni->FileName[fcb->full_filename.Length / sizeof(WCHAR)], datasuf, datasuflen);
+            RtlCopyMemory(&fni->FileName[fileref->full_filename.Length / sizeof(WCHAR)], datasuf, datasuflen);
 #ifdef _DEBUG
             retlen += datasuflen;
 #endif
             *length -= datasuflen;
         } else {
             if (*length > 0) {
-                RtlCopyMemory(&fni->FileName[fcb->full_filename.Length / sizeof(WCHAR)], datasuf, *length);
+                RtlCopyMemory(&fni->FileName[fileref->full_filename.Length / sizeof(WCHAR)], datasuf, *length);
 #ifdef _DEBUG
                 retlen += *length;
 #endif
@@ -2424,11 +2747,20 @@ static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni
     return STATUS_SUCCESS;
 }
 
-static NTSTATUS STDCALL fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, LONG* length) {
+static NTSTATUS STDCALL fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, file_ref* fileref, LONG* length) {
     *length -= sizeof(FILE_ATTRIBUTE_TAG_INFORMATION);
     
-    ati->FileAttributes = fcb->ads ? fcb->par->atts : fcb->atts;
-    ati->ReparseTag = 0;
+    if (fcb->ads) {
+        if (!fileref || !fileref->parent) {
+            ERR("no fileref for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        ati->FileAttributes = fileref->parent->fcb->atts;
+    } else
+        ati->FileAttributes = fcb->atts;
+    
+    ati->ReparseTag = 0; // FIXME
     
     return STATUS_SUCCESS;
 }
@@ -2495,7 +2827,6 @@ static NTSTATUS STDCALL fill_in_file_stream_information(FILE_STREAM_INFORMATION*
         
         b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
@@ -2503,8 +2834,6 @@ static NTSTATUS STDCALL fill_in_file_stream_information(FILE_STREAM_INFORMATION*
         }
     } while (b);
 
-    free_traverse_ptr(&tp);
-    
     Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
@@ -2600,7 +2929,6 @@ static NTSTATUS STDCALL fill_in_file_stream_information(FILE_STREAM_INFORMATION*
         
         b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
@@ -2608,8 +2936,6 @@ static NTSTATUS STDCALL fill_in_file_stream_information(FILE_STREAM_INFORMATION*
         }
     } while (b);    
     
-    free_traverse_ptr(&tp);
-    
     TRACE("length = %i, reqsize = %u\n", *length, reqsize);
     
     if (reqsize > *length) {
@@ -2658,14 +2984,14 @@ end:
     return Status;
 }
 
-static NTSTATUS STDCALL fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, LONG* length) {
+static NTSTATUS STDCALL fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, file_ref* fileref, LONG* length) {
     TRACE("FileStandardLinkInformation\n");
     
     // FIXME - NumberOfAccessibleLinks should subtract open links which have been marked as delete_on_close
     
     fsli->NumberOfAccessibleLinks = fcb->inode_item.st_nlink;
     fsli->TotalNumberOfLinks = fcb->inode_item.st_nlink;
-    fsli->DeletePending = fcb->delete_on_close;
+    fsli->DeletePending = fileref ? fileref->delete_on_close : FALSE;
     fsli->Directory = fcb->type == BTRFS_TYPE_DIRECTORY ? TRUE : FALSE;
     
     *length -= sizeof(FILE_STANDARD_LINK_INFORMATION);
@@ -2673,11 +2999,479 @@ static NTSTATUS STDCALL fill_in_file_standard_link_information(FILE_STANDARD_LIN
     return STATUS_SUCCESS;
 }
 
+typedef struct {
+    UNICODE_STRING name;
+    UINT64 inode;
+    LIST_ENTRY list_entry;
+} name_bit;
+
+static NTSTATUS get_inode_dir_path(device_extension* Vcb, root* subvol, UINT64 inode, PUNICODE_STRING us);
+
+static NTSTATUS get_subvol_path(device_extension* Vcb, root* subvol) {
+    KEY searchkey;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    LIST_ENTRY* le;
+    root* parsubvol;
+    UNICODE_STRING dirpath;
+    ROOT_REF* rr;
+    ULONG namelen;
+    
+    // FIXME - add subvol->parent field
+    
+    if (subvol == Vcb->root_fileref->fcb->subvol) {
+        subvol->path.Length = subvol->path.MaximumLength = sizeof(WCHAR);
+        subvol->path.Buffer = ExAllocatePoolWithTag(PagedPool, subvol->path.Length, ALLOC_TAG);
+        subvol->path.Buffer[0] = '\\';
+        return STATUS_SUCCESS;
+    }
+    
+    searchkey.obj_id = subvol->id;
+    searchkey.obj_type = TYPE_ROOT_BACKREF;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { // top subvol
+        subvol->path.Length = subvol->path.MaximumLength = sizeof(WCHAR);
+        subvol->path.Buffer = ExAllocatePoolWithTag(PagedPool, subvol->path.Length, ALLOC_TAG);
+        subvol->path.Buffer[0] = '\\';
+        return STATUS_SUCCESS;
+    }
+    
+    if (tp.item->size < sizeof(ROOT_REF)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    rr = (ROOT_REF*)tp.item->data;
+    
+    if (tp.item->size < sizeof(ROOT_REF) - 1 + rr->n) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF) - 1 + rr->n);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    le = Vcb->roots.Flink;
+
+    parsubvol = NULL;
+
+    while (le != &Vcb->roots) {
+        root* r2 = CONTAINING_RECORD(le, root, list_entry);
+        
+        if (r2->id == tp.item->key.offset) {
+            parsubvol = r2;
+            break;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (!parsubvol) {
+        ERR("unable to find subvol %llx\n", tp.item->key.offset);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    // FIXME - recursion
+
+    Status = get_inode_dir_path(Vcb, parsubvol, rr->dir, &dirpath);
+    if (!NT_SUCCESS(Status)) {
+        ERR("get_inode_dir_path returned %08x\n", Status);
+        return Status;
+    }
+    
+    Status = RtlUTF8ToUnicodeN(NULL, 0, &namelen, rr->name, rr->n);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (namelen == 0) {
+        ERR("length was 0\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    subvol->path.Length = subvol->path.MaximumLength = dirpath.Length + namelen;
+    subvol->path.Buffer = ExAllocatePoolWithTag(PagedPool, subvol->path.Length, ALLOC_TAG);
+    
+    if (!subvol->path.Buffer) {  
+        ERR("out of memory\n");
+        
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlCopyMemory(subvol->path.Buffer, dirpath.Buffer, dirpath.Length);
+        
+    Status = RtlUTF8ToUnicodeN(&subvol->path.Buffer[dirpath.Length / sizeof(WCHAR)], namelen, &namelen, rr->name, rr->n);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+        goto end;
+    }
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    if (dirpath.Buffer)
+        ExFreePool(dirpath.Buffer);
+    
+    if (!NT_SUCCESS(Status) && subvol->path.Buffer) {
+        ExFreePool(subvol->path.Buffer);
+        subvol->path.Buffer = NULL;
+    }
+    
+    return Status;
+}
+
+static NTSTATUS get_inode_dir_path(device_extension* Vcb, root* subvol, UINT64 inode, PUNICODE_STRING us) {
+    KEY searchkey;
+    NTSTATUS Status;
+    UINT64 in;
+    traverse_ptr tp;
+    LIST_ENTRY name_trail, *le;
+    UINT16 levels = 0;
+    UINT32 namelen = 0;
+    WCHAR* usbuf;
+    
+    InitializeListHead(&name_trail);
+    
+    in = inode;
+    
+    // FIXME - start with subvol prefix
+    if (!subvol->path.Buffer) {
+        Status = get_subvol_path(Vcb, subvol);
+        if (!NT_SUCCESS(Status)) {
+            ERR("get_subvol_path returned %08x\n", Status);
+            return Status;
+        }
+    }
+    
+    while (in != subvol->root_item.objid) {
+        searchkey.obj_id = in;
+        searchkey.obj_type = TYPE_INODE_EXTREF;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            goto end;
+        }
+        
+        if (tp.item->key.obj_id != searchkey.obj_id) {
+            ERR("could not find INODE_REF for inode %llx in subvol %llx\n", searchkey.obj_id, subvol->id);
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+        
+        if (tp.item->key.obj_type == TYPE_INODE_REF) {
+            INODE_REF* ir = (INODE_REF*)tp.item->data;
+            name_bit* nb;
+            ULONG len;
+            
+            if (tp.item->size < sizeof(INODE_REF)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_REF));
+                Status = STATUS_INTERNAL_ERROR;
+                goto end;
+            }
+            
+            if (tp.item->size < sizeof(INODE_REF) - 1 + ir->n) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_REF) - 1 + ir->n);
+                Status = STATUS_INTERNAL_ERROR;
+                goto end;
+            }
+            
+            nb = ExAllocatePoolWithTag(PagedPool, sizeof(name_bit), ALLOC_TAG);
+            if (!nb) {
+                ERR("out of memory\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+            
+            nb->name.Buffer = NULL;
+            
+            InsertTailList(&name_trail, &nb->list_entry);
+            levels++;
+            
+            Status = RtlUTF8ToUnicodeN(NULL, 0, &len, ir->name, ir->n);
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                goto end;
+            }
+            
+            if (len == 0) {
+                ERR("length was 0\n");
+                Status = STATUS_INTERNAL_ERROR;
+                goto end;
+            }
+            
+            nb->name.Length = nb->name.MaximumLength = len;
+            
+            nb->name.Buffer = ExAllocatePoolWithTag(PagedPool, nb->name.Length, ALLOC_TAG);
+            if (!nb->name.Buffer) {  
+                ERR("out of memory\n");
+                
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+                
+            Status = RtlUTF8ToUnicodeN(nb->name.Buffer, len, &len, ir->name, ir->n);
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                goto end;
+            }
+            
+            in = tp.item->key.offset;
+            namelen += nb->name.Length;
+            
+//         } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
+//             // FIXME
+        } else {
+            ERR("could not find INODE_REF for inode %llx in subvol %llx\n", searchkey.obj_id, subvol->id);
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+    }
+    
+    namelen += (levels + 1) * sizeof(WCHAR);
+    
+    us->Length = us->MaximumLength = namelen;
+    us->Buffer = ExAllocatePoolWithTag(PagedPool, us->Length, ALLOC_TAG);
+    
+    if (!us->Buffer) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    us->Buffer[0] = '\\';
+    usbuf = &us->Buffer[1];
+    
+    le = name_trail.Blink;
+    while (le != &name_trail) {
+        name_bit* nb = CONTAINING_RECORD(le, name_bit, list_entry);
+        
+        RtlCopyMemory(usbuf, nb->name.Buffer, nb->name.Length);
+        usbuf += nb->name.Length / sizeof(WCHAR);
+        
+        usbuf[0] = '\\';
+        usbuf++;
+        
+        le = le->Blink;
+    }
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    while (!IsListEmpty(&name_trail)) {
+        name_bit* nb = CONTAINING_RECORD(name_trail.Flink, name_bit, list_entry);
+        
+        if (nb->name.Buffer)
+            ExFreePool(nb->name.Buffer);
+        
+        RemoveEntryList(&nb->list_entry);
+        
+        ExFreePool(nb);
+    }
+
+    return Status;
+}
+
+static NTSTATUS STDCALL fill_in_hard_link_information(FILE_LINKS_INFORMATION* fli, fcb* fcb, LONG* length) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    NTSTATUS Status;
+    BOOL b;
+    LIST_ENTRY hardlinks, *le;
+    ULONG bytes_needed, num_entries = 0;
+    FILE_LINK_ENTRY_INFORMATION* feli;
+    
+    if (fcb->ads)
+        return STATUS_INVALID_PARAMETER;
+    
+    if (*length < offsetof(FILE_LINKS_INFORMATION, Entry))
+        return STATUS_INVALID_PARAMETER;
+    
+    RtlZeroMemory(fli, *length);
+    
+    InitializeListHead(&hardlinks);
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_REF;
+    searchkey.offset = 0;
+    
+    Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    bytes_needed = offsetof(FILE_LINKS_INFORMATION, Entry);
+    
+    do {
+        if (tp.item->key.obj_id == fcb->inode) {
+            if (tp.item->key.obj_type == TYPE_INODE_REF) {
+                ULONG len = tp.item->size;
+                INODE_REF* ir = (INODE_REF*)tp.item->data;
+                
+                if (tp.item->size >= sizeof(INODE_REF)) {
+                    UNICODE_STRING dirpath;
+                    
+                    Status = get_inode_dir_path(fcb->Vcb, fcb->subvol, tp.item->key.offset, &dirpath);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("get_inode_dir_path returned %08x\n", Status);
+                        goto end;
+                    }
+                    
+                    if (fcb->inode == fcb->subvol->root_item.objid) {
+                        name_bit* nb;
+                        
+                        nb = ExAllocatePoolWithTag(PagedPool, sizeof(name_bit), ALLOC_TAG);
+                        if (!nb) {
+                            ERR("out of memory\n");
+                            Status = STATUS_INSUFFICIENT_RESOURCES;
+                            goto end;
+                        }
+                        
+                        nb->inode = tp.item->key.offset;
+                        nb->name = dirpath;
+                        
+                        InsertTailList(&hardlinks, &nb->list_entry);
+                    } else {
+                        while (len >= sizeof(INODE_REF) && len >= sizeof(INODE_REF) - 1 + ir->n) {
+                            name_bit* nb;
+                            ULONG namelen;
+                            
+                            Status = RtlUTF8ToUnicodeN(NULL, 0, &namelen, ir->name, ir->n);
+                            if (!NT_SUCCESS(Status)) {
+                                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                                goto end;
+                            }
+                            
+                            if (namelen == 0) {
+                                ERR("length was 0\n");
+                                Status = STATUS_INTERNAL_ERROR;
+                                goto end;
+                            }
+                            
+                            nb = ExAllocatePoolWithTag(PagedPool, sizeof(name_bit), ALLOC_TAG);
+                            if (!nb) {
+                                ERR("out of memory\n");
+                                Status = STATUS_INSUFFICIENT_RESOURCES;
+                                goto end;
+                            }
+                            
+                            nb->inode = tp.item->key.offset;
+                            nb->name.Buffer = NULL;
+                            
+                            InsertTailList(&hardlinks, &nb->list_entry);
+                            
+                            nb->name.Length = nb->name.MaximumLength = namelen + dirpath.Length;
+                            
+                            nb->name.Buffer = ExAllocatePoolWithTag(PagedPool, nb->name.Length, ALLOC_TAG);
+                            if (!nb->name.Buffer) {  
+                                ERR("out of memory\n");
+                                
+                                Status = STATUS_INSUFFICIENT_RESOURCES;
+                                goto end;
+                            }
+                            
+                            RtlCopyMemory(nb->name.Buffer, dirpath.Buffer, dirpath.Length);
+
+                            Status = RtlUTF8ToUnicodeN(&nb->name.Buffer[dirpath.Length / sizeof(WCHAR)], namelen, &namelen, ir->name, ir->n);
+                            if (!NT_SUCCESS(Status)) {
+                                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                                goto end;
+                            }
+                            
+                            ERR("nb->name = %.*S\n", nb->name.Length / sizeof(WCHAR), nb->name.Buffer);
+                            
+                            if (num_entries > 0)
+                                bytes_needed = sector_align(bytes_needed, 8);
+                            
+                            num_entries++;
+                            bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) - sizeof(WCHAR) + max(sizeof(WCHAR), nb->name.Length);
+                            
+                            len -= sizeof(INODE_REF) - 1 + ir->n;
+                            ir = (INODE_REF*)&ir->name[ir->n];
+                        }
+                        
+                        ExFreePool(dirpath.Buffer);
+                    }
+                }
+            } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
+                // FIXME
+            }
+        }
+        
+        b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
+        if (b) {
+            tp = next_tp;
+            
+            if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_INODE_EXTREF)
+                break;
+        }
+    } while (b);
+    
+    bytes_needed = sector_align(bytes_needed, 8);
+    
+    fli->BytesNeeded = bytes_needed;
+    fli->EntriesReturned = 0;
+    
+    *length -= offsetof(FILE_LINKS_INFORMATION, Entry);
+    feli = NULL;
+    
+    le = hardlinks.Flink;
+    while (le != &hardlinks) {
+        name_bit* nb = CONTAINING_RECORD(le, name_bit, list_entry);
+        
+        if (sector_align(sizeof(FILE_LINK_ENTRY_INFORMATION) - sizeof(WCHAR) + max(sizeof(WCHAR), nb->name.Length), 8) > *length) {
+            Status = STATUS_BUFFER_OVERFLOW;
+            goto end;
+        }
+        
+        if (feli) {
+            feli->NextEntryOffset = sector_align(sizeof(FILE_LINK_ENTRY_INFORMATION) + ((feli->FileNameLength - 1) * sizeof(WCHAR)), 8);
+            feli = (FILE_LINK_ENTRY_INFORMATION*)((UINT8*)feli + feli->NextEntryOffset);
+        } else
+            feli = &fli->Entry;
+        
+        feli->NextEntryOffset = 0;
+        feli->ParentFileId = nb->inode;
+        feli->FileNameLength = nb->name.Length / sizeof(WCHAR);
+        RtlCopyMemory(feli->FileName, nb->name.Buffer, nb->name.Length);
+        
+        *length -= sector_align(sizeof(FILE_LINK_ENTRY_INFORMATION) - sizeof(WCHAR) + max(sizeof(WCHAR), nb->name.Length), 8);
+        fli->EntriesReturned++;
+        
+        le = le->Flink;
+    }
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    while (!IsListEmpty(&hardlinks)) {
+        name_bit* nb = CONTAINING_RECORD(hardlinks.Flink, name_bit, list_entry);
+        
+        if (nb->name.Buffer)
+            ExFreePool(nb->name.Buffer);
+        
+        RemoveEntryList(&nb->list_entry);
+        
+        ExFreePool(nb);
+    }
+    return Status;
+}
+
 static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) {
     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
     LONG length = IrpSp->Parameters.QueryFile.Length;
     fcb* fcb = FileObject->FsContext;
     ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
     NTSTATUS Status;
     
     TRACE("(%p, %p, %p)\n", Vcb, FileObject, Irp);
@@ -2694,16 +3488,22 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
             
             TRACE("FileAllInformation\n");
             
-            if (fcb->ads)
-                ii = &fcb->par->inode_item;
-            else
+            if (fcb->ads) {
+                if (!fileref || !fileref->parent) {
+                    ERR("no fileref for stream\n");
+                    Status = STATUS_INTERNAL_ERROR;
+                    goto exit;
+                }
+                
+                ii = &fileref->parent->fcb->inode_item;
+            } else
                 ii = &fcb->inode_item;
             
             if (length > 0)
-                fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb);
+                fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb, fileref);
             
             if (length > 0)
-                fill_in_file_standard_information(&fai->StandardInformation, fcb, &length);
+                fill_in_file_standard_information(&fai->StandardInformation, fcb, fileref, &length);
             
             if (length > 0)
                 fill_in_file_internal_information(&fai->InternalInformation, fcb->inode, &length);
@@ -2724,7 +3524,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
                 fill_in_file_alignment_information(&fai->AlignmentInformation, Vcb, &length);
             
             if (length > 0)
-                fill_in_file_name_information(&fai->NameInformation, fcb, &length);
+                fill_in_file_name_information(&fai->NameInformation, fcb, fileref, &length);
             
             Status = STATUS_SUCCESS;
 
@@ -2737,7 +3537,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
             
             TRACE("FileAttributeTagInformation\n");
             
-            Status = fill_in_file_attribute_information(ati, fcb, &length);
+            Status = fill_in_file_attribute_information(ati, fcb, fileref, &length);
             
             break;
         }
@@ -2755,12 +3555,18 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
                 goto exit;
             }
             
-            if (fcb->ads)
-                ii = &fcb->par->inode_item;
-            else
+            if (fcb->ads) {
+                if (!fileref || !fileref->parent) {
+                    ERR("no fileref for stream\n");
+                    Status = STATUS_INTERNAL_ERROR;
+                    goto exit;
+                }
+                
+                ii = &fileref->parent->fcb->inode_item;
+            } else
                 ii = &fcb->inode_item;
             
-            Status = fill_in_file_basic_information(fbi, ii, &length, fcb);
+            Status = fill_in_file_basic_information(fbi, ii, &length, fcb, fileref);
             break;
         }
 
@@ -2797,7 +3603,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
             
             TRACE("FileNameInformation\n");
             
-            Status = fill_in_file_name_information(fni, fcb, &length);
+            Status = fill_in_file_name_information(fni, fcb, fileref, &length);
             
             break;
         }
@@ -2808,7 +3614,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
             
             TRACE("FileNetworkOpenInformation\n");
             
-            Status = fill_in_file_network_open_information(fnoi, fcb, &length);
+            Status = fill_in_file_network_open_information(fnoi, fcb, fileref, &length);
 
             break;
         }
@@ -2836,7 +3642,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
                 goto exit;
             }
             
-            Status = fill_in_file_standard_information(fsi, fcb, &length);
+            Status = fill_in_file_standard_information(fsi, fcb, ccb->fileref, &length);
             
             break;
         }
@@ -2854,9 +3660,15 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
 
 #if (NTDDI_VERSION >= NTDDI_VISTA)
         case FileHardLinkInformation:
-            FIXME("STUB: FileHardLinkInformation\n");
-            Status = STATUS_INVALID_PARAMETER;
-            goto exit;
+        {
+            FILE_LINKS_INFORMATION* fli = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileHardLinkInformation\n");
+            
+            Status = fill_in_hard_link_information(fli, fcb, &length);
+            
+            break;
+        }
             
         case FileNormalizedNameInformation:
         {
@@ -2864,7 +3676,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
             
             TRACE("FileNormalizedNameInformation\n");
             
-            Status = fill_in_file_name_information(fni, fcb, &length);
+            Status = fill_in_file_name_information(fni, fcb, fileref, &length);
             
             break;
         }
@@ -2878,7 +3690,7 @@ static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObjec
             
             TRACE("FileStandardLinkInformation\n");
             
-            Status = fill_in_file_standard_link_information(fsli, fcb, &length);
+            Status = fill_in_file_standard_link_information(fsli, fcb, ccb->fileref, &length);
             
             break;
         }
index c9708cd..5688ee9 100644 (file)
@@ -31,7 +31,7 @@ static void do_flush(device_extension* Vcb) {
     if (Vcb->write_trees > 0)
         do_write(Vcb, &rollback);
     
-    free_tree_cache(&Vcb->tree_cache);
+    free_trees(Vcb);
     
     clear_rollback(&rollback);
 
@@ -41,23 +41,31 @@ static void do_flush(device_extension* Vcb) {
 }
 
 void STDCALL flush_thread(void* context) {
-    device_extension* Vcb = context;
+    DEVICE_OBJECT* devobj = context;
+    device_extension* Vcb = devobj->DeviceExtension;
     LARGE_INTEGER due_time;
+    KTIMER flush_thread_timer;
     
-    KeInitializeTimer(&Vcb->flush_thread_timer);
+    ObReferenceObject(devobj);
+    
+    KeInitializeTimer(&flush_thread_timer);
     
     due_time.QuadPart = -INTERVAL * 10000;
     
-    KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);
+    KeSetTimer(&flush_thread_timer, due_time, NULL);
     
     while (TRUE) {
-        KeWaitForSingleObject(&Vcb->flush_thread_timer, Executive, KernelMode, FALSE, NULL);        
+        KeWaitForSingleObject(&flush_thread_timer, Executive, KernelMode, FALSE, NULL);
 
+        if (!(devobj->Vpb->Flags & VPB_MOUNTED))
+            break;
+            
         do_flush(Vcb);
         
-        KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);
+        KeSetTimer(&flush_thread_timer, due_time, NULL);
     }
     
-    KeCancelTimer(&Vcb->flush_thread_timer);
+    ObDereferenceObject(devobj);
+    KeCancelTimer(&flush_thread_timer);
     PsTerminateSystemThread(STATUS_SUCCESS);
 }
diff --git a/reactos/drivers/filesystems/btrfs/free-space.c b/reactos/drivers/filesystems/btrfs/free-space.c
new file mode 100644 (file)
index 0000000..30812e3
--- /dev/null
@@ -0,0 +1,1089 @@
+/* Copyright (c) Mark Harmstone 2016
+ * 
+ * This file is part of WinBtrfs.
+ * 
+ * WinBtrfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public Licence as published by
+ * the Free Software Foundation, either version 3 of the Licence, or
+ * (at your option) any later version.
+ * 
+ * WinBtrfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public Licence for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public Licence
+ * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include "btrfs_drv.h"
+
+// Number of increments in the size of each cache inode, in sectors. Should
+// this be a constant number of sectors, a constant 256 KB, or what?
+#define CACHE_INCREMENTS    64
+
+static NTSTATUS remove_free_space_inode(device_extension* Vcb, KEY* key, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    traverse_ptr tp;
+    INODE_ITEM* ii;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, key, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(key, &tp.item->key)) {
+        ERR("could not find (%llx,%x,%llx) in root_root\n", key->obj_id, key->obj_type, key->offset);
+        return STATUS_NOT_FOUND;
+    }
+    
+    if (tp.item->size < offsetof(INODE_ITEM, st_blocks)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(INODE_ITEM, st_blocks));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    ii = (INODE_ITEM*)tp.item->data;
+    
+    Status = excise_extents_inode(Vcb, Vcb->root_root, key->obj_id, NULL, 0, ii->st_size, NULL, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("excise_extents returned %08x\n", Status);
+        return Status;
+    }
+    
+    delete_tree_item(Vcb, &tp, rollback);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS clear_free_space_cache(device_extension* Vcb) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    NTSTATUS Status;
+    BOOL b;
+    LIST_ENTRY rollback;
+    
+    InitializeListHead(&rollback);
+    
+    searchkey.obj_id = FREE_SPACE_CACHE_ID;
+    searchkey.obj_type = 0;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    do {
+        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))
+            break;
+        
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
+            delete_tree_item(Vcb, &tp, &rollback);
+            
+            if (tp.item->size >= sizeof(FREE_SPACE_ITEM)) {
+                FREE_SPACE_ITEM* fsi = (FREE_SPACE_ITEM*)tp.item->data;
+                
+                if (fsi->key.obj_type != TYPE_INODE_ITEM)
+                    WARN("key (%llx,%x,%llx) does not point to an INODE_ITEM\n", fsi->key.obj_id, fsi->key.obj_type, fsi->key.offset);
+                else {
+                    Status = remove_free_space_inode(Vcb, &fsi->key, &rollback);
+                    
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("remove_free_space_inode for (%llx,%x,%llx) returned %08x\n", fsi->key.obj_id, fsi->key.obj_type, fsi->key.offset, Status);
+                        goto end;
+                    }
+                }
+            } else
+                WARN("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));
+        }
+        
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+        if (b)
+            tp = next_tp;
+    } while (b);
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    if (NT_SUCCESS(Status))
+        clear_rollback(&rollback);
+    else
+        do_rollback(Vcb, &rollback);
+    
+    return Status;
+}
+
+static NTSTATUS add_space_entry(chunk* c, UINT64 offset, UINT64 size) {
+    space* s;
+    
+    s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+
+    if (!s) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    s->offset = offset;
+    s->size = size;
+    s->type = SPACE_TYPE_FREE;
+    
+    if (IsListEmpty(&c->space))
+        InsertTailList(&c->space, &s->list_entry);
+    else {
+        space* s2 = CONTAINING_RECORD(c->space.Blink, space, list_entry);
+        
+        if (s2->offset < offset)
+            InsertTailList(&c->space, &s->list_entry);
+        else {
+            LIST_ENTRY* le;
+            
+            le = c->space.Flink;
+            while (le != &c->space) {
+                s2 = CONTAINING_RECORD(le, space, list_entry);
+                
+                if (s2->offset > offset) {
+                    InsertTailList(le, &s->list_entry);
+                    return STATUS_SUCCESS;
+                }
+                
+                le = le->Flink;
+            }
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static void load_free_space_bitmap(device_extension* Vcb, chunk* c, UINT64 offset, void* data) {
+    RTL_BITMAP bmph;
+    UINT32 i, *dwords = data;
+    ULONG runlength, index;
+    
+    // flip bits
+    for (i = 0; i < Vcb->superblock.sector_size / sizeof(UINT32); i++) {
+        dwords[i] = ~dwords[i];
+    }
+
+    RtlInitializeBitMap(&bmph, data, Vcb->superblock.sector_size * 8);
+
+    index = 0;
+    runlength = RtlFindFirstRunClear(&bmph, &index);
+            
+    while (runlength != 0) {
+        UINT64 addr, length;
+        
+        addr = offset + (index * Vcb->superblock.sector_size);
+        length = Vcb->superblock.sector_size * runlength;
+        
+        add_space_entry(c, addr, length);
+        index += runlength;
+       
+        runlength = RtlFindNextForwardRunClear(&bmph, index, &index);
+    }
+}
+
+static NTSTATUS load_stored_free_space_cache(device_extension* Vcb, chunk* c) {
+    KEY searchkey;
+    traverse_ptr tp, tp2;
+    FREE_SPACE_ITEM* fsi;
+    UINT64 inode, num_sectors, num_valid_sectors, i, *generation;
+    INODE_ITEM* ii;
+    UINT8* data;
+    NTSTATUS Status;
+    UINT32 *checksums, crc32;
+    FREE_SPACE_ENTRY* fse;
+    UINT64 size, num_entries, num_bitmaps, extent_length, bmpnum, off;
+    LIST_ENTRY* le;
+    
+    // FIXME - does this break if Vcb->superblock.sector_size is not 4096?
+    
+    TRACE("(%p, %llx)\n", Vcb, c->offset);
+    
+    searchkey.obj_id = FREE_SPACE_CACHE_ID;
+    searchkey.obj_type = 0;
+    searchkey.offset = c->offset;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&tp.item->key, &searchkey)) {
+        TRACE("(%llx,%x,%llx) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+        return STATUS_NOT_FOUND;
+    }
+    
+    if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {
+        WARN("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));
+        return STATUS_NOT_FOUND;
+    }
+    
+    fsi = (FREE_SPACE_ITEM*)tp.item->data;
+    
+    if (fsi->key.obj_type != TYPE_INODE_ITEM) {
+        WARN("cache pointed to something other than an INODE_ITEM\n");
+        return STATUS_NOT_FOUND;
+    }
+    
+    inode = fsi->key.obj_id;
+    
+    searchkey = fsi->key;
+
+    num_entries = fsi->num_entries;
+    num_bitmaps = fsi->num_bitmaps;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp2, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&tp2.item->key, &searchkey)) {
+        WARN("(%llx,%x,%llx) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+        return STATUS_NOT_FOUND;
+    }
+    
+    if (tp2.item->size < sizeof(INODE_ITEM)) {
+        WARN("(%llx,%x,%llx) was %u bytes, expected %u\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, sizeof(INODE_ITEM));
+        return STATUS_NOT_FOUND;
+    }
+    
+    ii = (INODE_ITEM*)tp2.item->data;
+    
+    if (ii->st_size == 0) {
+        ERR("inode %llx had a length of 0\n", inode);
+        return STATUS_NOT_FOUND;
+    }
+    
+    c->cache_size = ii->st_size;
+    c->cache_inode = fsi->key.obj_id;
+    
+    size = sector_align(ii->st_size, Vcb->superblock.sector_size);
+    
+    data = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);
+    
+    if (!data) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    Status = read_file(Vcb, Vcb->root_root, inode, data, 0, ii->st_size, NULL);
+    if (!NT_SUCCESS(Status)) {
+        ERR("read_file returned %08x\n", Status);
+        ExFreePool(data);
+        return Status;
+    }
+    
+    if (size > ii->st_size)
+        RtlZeroMemory(&data[ii->st_size], size - ii->st_size);
+    
+    num_sectors = size / Vcb->superblock.sector_size;
+    
+    generation = (UINT64*)(data + (num_sectors * sizeof(UINT32)));
+    
+    if (*generation != fsi->generation) {
+        WARN("free space cache generation for %llx was %llx, expected %llx\n", c->offset, generation, fsi->generation);
+        ExFreePool(data);
+        return STATUS_NOT_FOUND;
+    }
+    
+    extent_length = (num_sectors * sizeof(UINT32)) + sizeof(UINT64) + (num_entries * sizeof(FREE_SPACE_ENTRY));
+    
+    num_valid_sectors = (sector_align(extent_length, Vcb->superblock.sector_size) / Vcb->superblock.sector_size) + num_bitmaps;
+    
+    if (num_valid_sectors > num_sectors) {
+        ERR("free space cache for %llx was %llx sectors, expected at least %llx\n", c->offset, num_sectors, num_valid_sectors);
+        ExFreePool(data);
+        return STATUS_NOT_FOUND;
+    }
+    
+    checksums = (UINT32*)data;
+    
+    for (i = 0; i < num_valid_sectors; i++) {
+        if (i * Vcb->superblock.sector_size > sizeof(UINT32) * num_sectors)
+            crc32 = ~calc_crc32c(0xffffffff, &data[i * Vcb->superblock.sector_size], Vcb->superblock.sector_size);
+        else if ((i + 1) * Vcb->superblock.sector_size < sizeof(UINT32) * num_sectors)
+            crc32 = 0; // FIXME - test this
+        else
+            crc32 = ~calc_crc32c(0xffffffff, &data[sizeof(UINT32) * num_sectors], ((i + 1) * Vcb->superblock.sector_size) - (sizeof(UINT32) * num_sectors));
+        
+        if (crc32 != checksums[i]) {
+            WARN("checksum %llu was %08x, expected %08x\n", i, crc32, checksums[i]);
+            ExFreePool(data);
+            return STATUS_NOT_FOUND;
+        }
+    }
+    
+    off = (sizeof(UINT32) * num_sectors) + sizeof(UINT64);
+
+    bmpnum = 0;
+    for (i = 0; i < num_entries; i++) {
+        if ((off + sizeof(FREE_SPACE_ENTRY)) / Vcb->superblock.sector_size != off / Vcb->superblock.sector_size)
+            off = sector_align(off, Vcb->superblock.sector_size);
+        
+        fse = (FREE_SPACE_ENTRY*)&data[off];
+        
+        if (fse->type == FREE_SPACE_EXTENT) {
+            Status = add_space_entry(c, fse->offset, fse->size);
+            if (!NT_SUCCESS(Status)) {
+                ERR("add_space_entry returned %08x\n", Status);
+                ExFreePool(data);
+                return Status;
+            }
+        } else if (fse->type != FREE_SPACE_BITMAP) {
+            ERR("unknown free-space type %x\n", fse->type);
+        }
+                
+        off += sizeof(FREE_SPACE_ENTRY);
+    }
+    
+    if (num_bitmaps > 0) {
+        bmpnum = sector_align(off, Vcb->superblock.sector_size) / Vcb->superblock.sector_size;
+        off = (sizeof(UINT32) * num_sectors) + sizeof(UINT64);
+        
+        for (i = 0; i < num_entries; i++) {
+            if ((off + sizeof(FREE_SPACE_ENTRY)) / Vcb->superblock.sector_size != off / Vcb->superblock.sector_size)
+                off = sector_align(off, Vcb->superblock.sector_size);
+            
+            fse = (FREE_SPACE_ENTRY*)&data[off];
+            
+            if (fse->type == FREE_SPACE_BITMAP) {
+                // FIXME - make sure we don't overflow the buffer here
+                load_free_space_bitmap(Vcb, c, fse->offset, &data[bmpnum * Vcb->superblock.sector_size]);
+                bmpnum++;
+            }
+            
+            off += sizeof(FREE_SPACE_ENTRY);
+        }
+    }
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        space* s = CONTAINING_RECORD(le, space, list_entry);
+        LIST_ENTRY* le2 = le->Flink;
+        
+        if (le2 != &c->space) {
+            space* s2 = CONTAINING_RECORD(le2, space, list_entry);
+            
+            if (s2->offset == s->offset + s->size) {
+                s->size += s2->size;
+                
+                RemoveEntryList(&s2->list_entry);
+                ExFreePool(s2);
+                
+                le2 = le;
+            }
+        }
+        
+        le = le2;
+    }
+    
+    ExFreePool(data);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS load_free_space_cache(device_extension* Vcb, chunk* c) {
+    traverse_ptr tp, next_tp;
+    KEY searchkey;
+    UINT64 lastaddr;
+    BOOL b;
+    space *s, *s2;
+    LIST_ENTRY* le;
+    NTSTATUS Status;
+    
+    if (Vcb->superblock.generation - 1 == Vcb->superblock.cache_generation) {
+        Status = load_stored_free_space_cache(Vcb, c);
+        
+        if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {
+            ERR("load_stored_free_space_cache returned %08x\n", Status);
+            return Status;
+        }
+    } else
+        Status = STATUS_NOT_FOUND;
+     
+    if (Status == STATUS_NOT_FOUND) {
+        TRACE("generating free space cache for chunk %llx\n", c->offset);
+        
+        searchkey.obj_id = c->offset;
+        searchkey.obj_type = TYPE_EXTENT_ITEM;
+        searchkey.offset = 0;
+        
+        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        lastaddr = c->offset;
+        
+        do {
+            if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)
+                break;
+            
+            if (tp.item->key.obj_id >= c->offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) {
+                if (tp.item->key.obj_id > lastaddr) {
+                    s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+                    
+                    if (!s) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    s->offset = lastaddr;
+                    s->size = tp.item->key.obj_id - lastaddr;
+                    s->type = SPACE_TYPE_FREE;
+                    InsertTailList(&c->space, &s->list_entry);
+                    
+                    TRACE("(%llx,%llx)\n", s->offset, s->size);
+                }
+                
+                if (tp.item->key.obj_type == TYPE_METADATA_ITEM)
+                    lastaddr = tp.item->key.obj_id + Vcb->superblock.node_size;
+                else
+                    lastaddr = tp.item->key.obj_id + tp.item->key.offset;
+            }
+            
+            b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+            if (b)
+                tp = next_tp;
+        } while (b);
+        
+        if (lastaddr < c->offset + c->chunk_item->size) {
+            s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+            
+            if (!s) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            s->offset = lastaddr;
+            s->size = c->offset + c->chunk_item->size - lastaddr;
+            s->type = SPACE_TYPE_FREE;
+            InsertTailList(&c->space, &s->list_entry);
+            
+            TRACE("(%llx,%llx)\n", s->offset, s->size);
+        }
+    }
+    
+    // add allocated space
+    
+    lastaddr = c->offset;
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        if (s->offset > lastaddr) {
+            s2 = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+            
+            if (!s2) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            s2->offset = lastaddr;
+            s2->size = s->offset - lastaddr;
+            s2->type = SPACE_TYPE_USED;
+            
+            InsertTailList(&s->list_entry, &s2->list_entry);
+        }
+        
+        lastaddr = s->offset + s->size;
+        
+        le = le->Flink;
+    }
+    
+    if (lastaddr < c->offset + c->chunk_item->size) {
+        s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+        
+        if (!s) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        s->offset = lastaddr;
+        s->size = c->offset + c->chunk_item->size - lastaddr;
+        s->type = SPACE_TYPE_USED;
+        InsertTailList(&c->space, &s->list_entry);
+    }
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        TRACE("%llx,%llx,%u\n", s->offset, s->size, s->type);
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS insert_cache_extent(device_extension* Vcb, UINT64 inode, UINT64 start, UINT64 length, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = Vcb->chunks.Flink;
+    chunk* c;
+    UINT64 flags;
+    
+    // FIXME - how do we know which RAID level to put this to?
+    flags = BLOCK_FLAG_DATA; // SINGLE
+    
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+            if (insert_extent_chunk_inode(Vcb, Vcb->root_root, inode, NULL, c, start, length, FALSE, NULL, NULL, rollback))
+                return STATUS_SUCCESS;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if ((c = alloc_chunk(Vcb, flags, rollback))) {
+        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+            if (insert_extent_chunk_inode(Vcb, Vcb->root_root, inode, NULL, c, start, length, FALSE, NULL, NULL, rollback))
+                return STATUS_SUCCESS;
+        }
+    }
+    
+    WARN("couldn't find any data chunks with %llx bytes free\n", length);
+
+    return STATUS_DISK_FULL;
+}
+
+static NTSTATUS allocate_cache_chunk(device_extension* Vcb, chunk* c, BOOL* changed, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le;
+    NTSTATUS Status;
+    UINT64 num_entries, new_cache_size, i;
+    UINT64 lastused = c->offset;
+    UINT32 num_sectors;
+    
+    // FIXME - also do bitmaps
+    // FIXME - make sure this works when sector_size is not 4096
+    
+    *changed = FALSE;
+    
+    num_entries = 0;
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        space* s = CONTAINING_RECORD(le, space, list_entry);
+
+        if (s->type == SPACE_TYPE_USED || s->type == SPACE_TYPE_WRITING) {
+            if (s->offset > lastused) {
+//                 TRACE("free: (%llx,%llx)\n", lastused, s->offset - lastused);
+                num_entries++;
+            }
+            
+            lastused = s->offset + s->size;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (c->offset + c->chunk_item->size > lastused) {
+//         TRACE("free: (%llx,%llx)\n", lastused, c->offset + c->chunk_item->size - lastused);
+        num_entries++;
+    }
+    
+    new_cache_size = sizeof(UINT64) + (num_entries * sizeof(FREE_SPACE_ENTRY));
+    
+    num_sectors = sector_align(new_cache_size, Vcb->superblock.sector_size) / Vcb->superblock.sector_size;
+    num_sectors = sector_align(num_sectors, CACHE_INCREMENTS);
+    
+    // adjust for padding
+    // FIXME - there must be a more efficient way of doing this
+    new_cache_size = sizeof(UINT64) + (sizeof(UINT32) * num_sectors);
+    for (i = 0; i < num_entries; i++) {
+        if ((new_cache_size / Vcb->superblock.sector_size) != ((new_cache_size + sizeof(FREE_SPACE_ENTRY)) / Vcb->superblock.sector_size))
+            new_cache_size = sector_align(new_cache_size, Vcb->superblock.sector_size);
+        
+        new_cache_size += sizeof(FREE_SPACE_ENTRY);
+    }
+    
+    new_cache_size = sector_align(new_cache_size, CACHE_INCREMENTS * Vcb->superblock.sector_size);
+    
+    TRACE("chunk %llx: cache_size = %llx, new_cache_size = %llx\n", c->offset, c->cache_size, new_cache_size);
+    
+    if (new_cache_size > c->cache_size) {
+        if (c->cache_size == 0) {
+            INODE_ITEM* ii;
+            FREE_SPACE_ITEM* fsi;
+            UINT64 inode;
+            
+            // create new inode
+            
+            ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+            if (!ii) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            RtlZeroMemory(ii, sizeof(INODE_ITEM));
+            ii->st_size = new_cache_size;
+            ii->st_blocks = new_cache_size;
+            ii->st_nlink = 1;
+            ii->st_mode = S_IRUSR | S_IWUSR | __S_IFREG;
+            ii->flags = BTRFS_INODE_NODATASUM | BTRFS_INODE_NODATACOW | BTRFS_INODE_NOCOMPRESS | BTRFS_INODE_PREALLOC;
+            
+            if (Vcb->root_root->lastinode == 0)
+                get_last_inode(Vcb, Vcb->root_root);
+            
+            inode = Vcb->root_root->lastinode > 0x100 ? (Vcb->root_root->lastinode + 1) : 0x101;
+
+            if (!insert_tree_item(Vcb, Vcb->root_root, inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
+                ERR("insert_tree_item failed\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            // create new free space entry
+            
+            fsi = ExAllocatePoolWithTag(PagedPool, sizeof(FREE_SPACE_ITEM), ALLOC_TAG);
+            if (!fsi) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            fsi->key.obj_id = inode;
+            fsi->key.obj_type = TYPE_INODE_ITEM;
+            fsi->key.offset = 0;
+            
+            if (!insert_tree_item(Vcb, Vcb->root_root, FREE_SPACE_CACHE_ID, 0, c->offset, fsi, sizeof(FREE_SPACE_ITEM), NULL, rollback)) {
+                ERR("insert_tree_item failed\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            // allocate space
+            
+            Status = insert_cache_extent(Vcb, inode, 0, new_cache_size, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("insert_cache_extent returned %08x\n", Status);
+                return Status;
+            }
+            
+            Vcb->root_root->lastinode = inode;
+            c->cache_inode = inode;
+        } else {
+            KEY searchkey;
+            traverse_ptr tp;
+            INODE_ITEM* ii;
+            
+            ERR("extending existing inode\n");
+            
+            // FIXME - try to extend existing extent first of all
+            // Or ditch all existing extents and replace with one new one?
+            
+            // add INODE_ITEM to tree cache
+            
+            searchkey.obj_id = c->cache_inode;
+            searchkey.obj_type = TYPE_INODE_ITEM;
+            searchkey.offset = 0;
+            
+            Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                return Status;
+            }
+            
+            if (keycmp(&searchkey, &tp.item->key)) {
+                ERR("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            if (tp.item->size < sizeof(INODE_ITEM)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_ITEM));
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            ii = (INODE_ITEM*)tp.item->data;
+            
+            if (!tp.tree->write) {
+                tp.tree->write = TRUE;
+                Vcb->write_trees++;
+            }
+
+            // add free_space entry to tree cache
+            
+            searchkey.obj_id = FREE_SPACE_CACHE_ID;
+            searchkey.obj_type = 0;
+            searchkey.offset = c->offset;
+            
+            Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                return Status;
+            }
+            
+            if (keycmp(&searchkey, &tp.item->key)) {
+                ERR("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            if (!tp.tree->write) {
+                tp.tree->write = TRUE;
+                Vcb->write_trees++;
+            }
+
+            // add new extent
+            
+            Status = insert_cache_extent(Vcb, c->cache_inode, c->cache_size, new_cache_size - c->cache_size, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("insert_cache_extent returned %08x\n", Status);
+                return Status;
+            }
+            
+            // modify INODE_ITEM
+            
+            ii->st_size = new_cache_size;
+            ii->st_blocks = new_cache_size;
+        }
+        
+        c->cache_size = new_cache_size;
+        *changed = TRUE;
+    } else {
+        KEY searchkey;
+        traverse_ptr tp;
+        
+        // add INODE_ITEM and free_space entry to tree cache, for writing later
+        
+        searchkey.obj_id = c->cache_inode;
+        searchkey.obj_type = TYPE_INODE_ITEM;
+        searchkey.offset = 0;
+        
+        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        if (keycmp(&searchkey, &tp.item->key)) {
+            ERR("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (tp.item->size < sizeof(INODE_ITEM)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_ITEM));
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (!tp.tree->write) {
+            tp.tree->write = TRUE;
+            Vcb->write_trees++;
+        }
+
+        searchkey.obj_id = FREE_SPACE_CACHE_ID;
+        searchkey.obj_type = 0;
+        searchkey.offset = c->offset;
+        
+        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        if (keycmp(&searchkey, &tp.item->key)) {
+            ERR("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        if (!tp.tree->write) {
+            tp.tree->write = TRUE;
+            Vcb->write_trees++;
+        }
+    }
+    
+    // FIXME - reduce inode allocation if cache is shrinking. Make sure to avoid infinite write loops
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS allocate_cache(device_extension* Vcb, BOOL* changed, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = Vcb->chunks.Flink;
+    NTSTATUS Status;
+    chunk* c;
+
+    *changed = FALSE;
+    
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (c->space_changed) {
+            BOOL b;
+            
+            Status = allocate_cache_chunk(Vcb, c, &b, rollback);
+            
+            if (b)
+                *changed = TRUE;
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("allocate_cache_chunk(%llx) returned %08x\n", c->offset, Status);
+                return Status;
+            }
+        }
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS update_chunk_cache(device_extension* Vcb, chunk* c, BTRFS_TIME* now, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp;
+    FREE_SPACE_ITEM* fsi;
+    INODE_ITEM* ii;
+    void* data;
+    FREE_SPACE_ENTRY* fse;
+    UINT64 num_entries, num_sectors, lastused, *cachegen, i, off;
+    UINT32* checksums;
+    LIST_ENTRY* le;
+    BOOL b;
+    
+    data = ExAllocatePoolWithTag(NonPagedPool, c->cache_size, ALLOC_TAG);
+    if (!data) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(data, c->cache_size);
+    
+    num_entries = 0;
+    num_sectors = c->cache_size / Vcb->superblock.sector_size;
+    off = (sizeof(UINT32) * num_sectors) + sizeof(UINT64);
+    
+    lastused = c->offset;
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        space* s = CONTAINING_RECORD(le, space, list_entry);
+
+        if (s->type == SPACE_TYPE_USED || s->type == SPACE_TYPE_WRITING) {
+            if (s->offset > lastused) {
+                if ((off + sizeof(FREE_SPACE_ENTRY)) / Vcb->superblock.sector_size != off / Vcb->superblock.sector_size)
+                    off = sector_align(off, Vcb->superblock.sector_size);
+                
+                fse = (FREE_SPACE_ENTRY*)((UINT8*)data + off);
+                
+                fse->offset = lastused;
+                fse->size = s->offset - lastused;
+                fse->type = FREE_SPACE_EXTENT;
+                num_entries++;
+                
+                off += sizeof(FREE_SPACE_ENTRY);
+            }
+            
+            lastused = s->offset + s->size;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (c->offset + c->chunk_item->size > lastused) {
+        if ((off + sizeof(FREE_SPACE_ENTRY)) / Vcb->superblock.sector_size != off / Vcb->superblock.sector_size)
+            off = sector_align(off, Vcb->superblock.sector_size);
+        
+        fse = (FREE_SPACE_ENTRY*)((UINT8*)data + off);
+        
+        fse->offset = lastused;
+        fse->size = c->offset + c->chunk_item->size - lastused;
+        fse->type = FREE_SPACE_EXTENT;
+        num_entries++;
+    }
+
+    // update INODE_ITEM
+    
+    searchkey.obj_id = c->cache_inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&searchkey, &tp.item->key)) {
+        ERR("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp.item->size < sizeof(INODE_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_ITEM));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    ii = (INODE_ITEM*)tp.item->data;
+    
+    ii->generation = Vcb->superblock.generation;
+    ii->transid = Vcb->superblock.generation;
+    ii->sequence++;
+    ii->st_ctime = *now;
+    
+    // update free_space item
+    
+    searchkey.obj_id = FREE_SPACE_CACHE_ID;
+    searchkey.obj_type = 0;
+    searchkey.offset = c->offset;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (keycmp(&searchkey, &tp.item->key)) {
+        ERR("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    fsi = (FREE_SPACE_ITEM*)tp.item->data;
+    
+    fsi->generation = Vcb->superblock.generation;
+    fsi->num_entries = num_entries;
+    fsi->num_bitmaps = 0;
+    
+    // set cache generation
+    
+    cachegen = (UINT64*)((UINT8*)data + (sizeof(UINT32) * num_sectors));
+    *cachegen = Vcb->superblock.generation;
+    
+    // calculate cache checksums
+    
+    checksums = (UINT32*)data;
+    
+    // FIXME - if we know sector is fully zeroed, use cached checksum
+    
+    for (i = 0; i < num_sectors; i++) {
+        if (i * Vcb->superblock.sector_size > sizeof(UINT32) * num_sectors)
+            checksums[i] = ~calc_crc32c(0xffffffff, (UINT8*)data + (i * Vcb->superblock.sector_size), Vcb->superblock.sector_size);
+        else if ((i + 1) * Vcb->superblock.sector_size < sizeof(UINT32) * num_sectors)
+            checksums[i] = 0; // FIXME - test this
+        else
+            checksums[i] = ~calc_crc32c(0xffffffff, (UINT8*)data + (sizeof(UINT32) * num_sectors), ((i + 1) * Vcb->superblock.sector_size) - (sizeof(UINT32) * num_sectors));
+    }
+    
+    // write cache
+    
+    searchkey.obj_id = c->cache_inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    do {
+        traverse_ptr next_tp;
+        
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->key.offset < c->cache_size) {
+            EXTENT_DATA* ed;
+            EXTENT_DATA2* eds;
+            
+            if (tp.item->size < sizeof(EXTENT_DATA)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            ed = (EXTENT_DATA*)tp.item->data;
+            
+            if (ed->type != EXTENT_TYPE_REGULAR) {
+                ERR("cache EXTENT_DATA type not regular\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+
+            if (tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            eds = (EXTENT_DATA2*)&ed->data[0];
+
+            if (ed->compression != BTRFS_COMPRESSION_NONE) {
+                ERR("not writing compressed cache\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+
+            if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
+                WARN("encryption not supported\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+
+            if (ed->encoding != BTRFS_ENCODING_NONE) {
+                WARN("other encodings not supported\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            if (eds->address == 0) {
+                ERR("not writing cache to sparse extent\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            Status = write_data(Vcb, eds->address + eds->offset, (UINT8*)data + tp.item->key.offset, min(c->cache_size - tp.item->key.offset, eds->num_bytes));
+            if (!NT_SUCCESS(Status)) {
+                ERR("write_data returned %08x\n", Status);
+                return Status;
+            }
+        }
+        
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+        if (b) {
+            tp = next_tp;
+            
+            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))
+                break;
+        }
+    } while (b);
+    
+    ExFreePool(data);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS update_chunk_caches(device_extension* Vcb, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = Vcb->chunks.Flink;
+    NTSTATUS Status;
+    chunk* c;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (c->space_changed) {
+            Status = update_chunk_cache(Vcb, c, &now, rollback);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("update_chunk_cache(%llx) returned %08x\n", c->offset, Status);
+                return Status;
+            }
+            
+        }
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
index 9afacee..1390318 100644 (file)
 
 #include "btrfs_drv.h"
 #include "btrfsioctl.h"
+#ifndef __REACTOS__
+#include <winioctl.h>
+#endif
 
 #ifndef FSCTL_CSV_CONTROL
 #define FSCTL_CSV_CONTROL CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 181, METHOD_BUFFERED, FILE_ANY_ACCESS)
 #endif
 
+#define DOTDOT ".."
+
 static NTSTATUS get_file_ids(PFILE_OBJECT FileObject, void* data, ULONG length) {
     btrfs_get_file_ids* bgfi;
     fcb* fcb;
@@ -41,7 +46,1143 @@ static NTSTATUS get_file_ids(PFILE_OBJECT FileObject, void* data, ULONG length)
     
     bgfi->subvol = fcb->subvol->id;
     bgfi->inode = fcb->inode;
-    bgfi->top = fcb->Vcb->root_fcb == fcb ? TRUE : FALSE;
+    bgfi->top = fcb->Vcb->root_fileref->fcb == fcb ? TRUE : FALSE;
+    
+    return STATUS_SUCCESS;
+}
+
+static void get_uuid(BTRFS_UUID* uuid) {
+    LARGE_INTEGER seed;
+    UINT8 i;
+    
+    seed = KeQueryPerformanceCounter(NULL);
+
+    for (i = 0; i < 16; i+=2) {
+        ULONG rand = RtlRandomEx(&seed.LowPart);
+        
+        uuid->uuid[i] = (rand & 0xff00) >> 8;
+        uuid->uuid[i+1] = rand & 0xff;
+    }
+}
+
+static NTSTATUS snapshot_tree_copy(device_extension* Vcb, UINT64 addr, root* subvol, UINT64 dupflags, UINT64* newaddr, LIST_ENTRY* rollback) {
+    UINT8* buf;
+    NTSTATUS Status;
+    write_tree_context* wtc;
+    LIST_ENTRY* le;
+    tree t;
+    tree_header* th;
+    chunk* c;
+    
+    buf = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);
+    if (!buf) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    wtc = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_tree_context), ALLOC_TAG);
+    if (!wtc) {
+        ERR("out of memory\n");
+        ExFreePool(buf);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    Status = read_tree(Vcb, addr, buf);
+    if (!NT_SUCCESS(Status)) {
+        ERR("read_tree returned %08x\n", Status);
+        goto end;
+    }
+    
+    th = (tree_header*)buf;
+    
+    RtlZeroMemory(&t, sizeof(tree));
+    t.flags = dupflags;
+    t.root = subvol;
+    t.header.level = th->level;
+    t.header.tree_id = t.root->id;
+    
+    Status = get_tree_new_address(Vcb, &t, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("get_tree_new_address returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (!t.has_new_address) {
+        ERR("tree new address not set\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    c = get_chunk_from_address(Vcb, t.new_address);
+            
+    if (c) {
+        increase_chunk_usage(c, Vcb->superblock.node_size);
+    } else {
+        ERR("could not find chunk for address %llx\n", t.new_address);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    th->address = t.new_address;
+    th->tree_id = subvol->id;
+    th->generation = Vcb->superblock.generation;
+    
+    if (th->level == 0) {
+        UINT32 i;
+        leaf_node* ln = (leaf_node*)&th[1];
+        
+        for (i = 0; i < th->num_items; i++) {
+            if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) && ln[i].offset + ln[i].size <= Vcb->superblock.node_size - sizeof(tree_header)) {
+                EXTENT_DATA* ed = (EXTENT_DATA*)(((UINT8*)&th[1]) + ln[i].offset);
+                
+                // FIXME - what are we supposed to do with prealloc here? Replace it with sparse extents, or do new preallocation?
+                if (ed->type == EXTENT_TYPE_REGULAR && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0];
+                    
+                    if (ed2->size != 0) { // not sparse
+                        Status = increase_extent_refcount_data(Vcb, ed2->address, ed2->size, subvol, ln[i].key.obj_id, ln[i].key.offset - ed2->offset, 1, rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("increase_extent_refcount_data returned %08x\n", Status);
+                            goto end;
+                        }
+                    }
+                }
+            }
+        }
+    } else {
+        UINT32 i;
+        UINT64 newaddr;
+        internal_node* in = (internal_node*)&th[1];
+        
+        for (i = 0; i < th->num_items; i++) {
+            Status = snapshot_tree_copy(Vcb, in[i].address, subvol, dupflags, &newaddr, rollback);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("snapshot_tree_copy returned %08x\n", Status);
+                goto end;
+            }
+            
+            in[i].generation = Vcb->superblock.generation;
+            in[i].address = newaddr;
+        }
+    }
+    
+    *((UINT32*)buf) = ~calc_crc32c(0xffffffff, (UINT8*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));
+    
+    KeInitializeEvent(&wtc->Event, NotificationEvent, FALSE);
+    InitializeListHead(&wtc->stripes);
+    
+    Status = write_tree(Vcb, t.new_address, buf, wtc);
+    if (!NT_SUCCESS(Status)) {
+        ERR("write_tree returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (wtc->stripes.Flink != &wtc->stripes) {
+        // launch writes and wait
+        le = wtc->stripes.Flink;
+        while (le != &wtc->stripes) {
+            write_tree_stripe* stripe = CONTAINING_RECORD(le, write_tree_stripe, list_entry);
+            
+            IoCallDriver(stripe->device->devobj, stripe->Irp);
+            
+            le = le->Flink;
+        }
+        
+        KeWaitForSingleObject(&wtc->Event, Executive, KernelMode, FALSE, NULL);
+        
+        le = wtc->stripes.Flink;
+        while (le != &wtc->stripes) {
+            write_tree_stripe* stripe = CONTAINING_RECORD(le, write_tree_stripe, list_entry);
+            
+            if (!NT_SUCCESS(stripe->iosb.Status)) {
+                Status = stripe->iosb.Status;
+                break;
+            }
+            
+            le = le->Flink;
+        }
+        
+        free_write_tree_stripes(wtc);
+        buf = NULL;
+    }
+    
+    if (NT_SUCCESS(Status))
+        *newaddr = t.new_address;
+    
+end:
+    ExFreePool(wtc);
+    
+    if (buf)
+        ExFreePool(buf);
+    
+    return Status;
+}
+
+static void flush_subvol_fcbs(root* subvol) {
+    LIST_ENTRY* le = subvol->fcbs.Flink;
+    
+    if (IsListEmpty(&subvol->fcbs))
+        return;
+    
+    while (le != &subvol->fcbs) {
+        struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry);
+        IO_STATUS_BLOCK iosb;
+        
+        if (fcb->type != BTRFS_TYPE_DIRECTORY && !fcb->deleted)
+            CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &iosb);
+        
+        le = le->Flink;
+    }
+}
+
+static NTSTATUS do_create_snapshot(device_extension* Vcb, PFILE_OBJECT parent, fcb* subvol_fcb, UINT32 crc32, PANSI_STRING utf8) {
+    LIST_ENTRY rollback;
+    UINT64 id;
+    NTSTATUS Status;
+    root *r, *subvol = subvol_fcb->subvol;
+    KEY searchkey;
+    traverse_ptr tp;
+    UINT64 address, dirpos, *root_num;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    fcb* fcb = parent->FsContext;
+    ULONG disize, rrsize;
+    DIR_ITEM *di, *di2;
+    ROOT_REF *rr, *rr2;
+    INODE_ITEM* ii;
+    
+    InitializeListHead(&rollback);
+    
+    acquire_tree_lock(Vcb, TRUE);
+    
+    // flush open files on this subvol
+    
+    flush_subvol_fcbs(subvol);
+
+    // flush metadata
+    
+    if (Vcb->write_trees > 0)
+        do_write(Vcb, &rollback);
+    
+    free_trees(Vcb);
+    
+    clear_rollback(&rollback);
+    
+    InitializeListHead(&rollback);
+    
+    // create new root
+    
+    if (Vcb->root_root->lastinode == 0)
+        get_last_inode(Vcb, Vcb->root_root);
+    
+    id = Vcb->root_root->lastinode > 0x100 ? (Vcb->root_root->lastinode + 1) : 0x101;
+    Status = create_root(Vcb, id, &r, TRUE, Vcb->superblock.generation, &rollback);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("create_root returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (!Vcb->uuid_root) {
+        root* uuid_root;
+        
+        TRACE("uuid root doesn't exist, creating it\n");
+        
+        Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, FALSE, 0, &rollback);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("create_root returned %08x\n", Status);
+            goto end;
+        }
+        
+        Vcb->uuid_root = uuid_root;
+    }
+    
+    root_num = ExAllocatePoolWithTag(PagedPool, sizeof(UINT64), ALLOC_TAG);
+    if (!root_num) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    tp.tree = NULL;
+    
+    do {
+        get_uuid(&r->root_item.uuid);
+        
+        RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid, sizeof(UINT64));
+        searchkey.obj_type = TYPE_SUBVOL_UUID;
+        RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(UINT64)], sizeof(UINT64));
+        
+        Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, FALSE);
+    } while (NT_SUCCESS(Status) && !keycmp(&searchkey, &tp.item->key));
+    
+    *root_num = r->id;
+    
+    if (!insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(UINT64), NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(root_num);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    searchkey.obj_id = r->id;
+    searchkey.obj_type = TYPE_ROOT_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    Status = snapshot_tree_copy(Vcb, subvol->root_item.block_number, r, tp.tree->flags, &address, &rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("snapshot_tree_copy returned %08x\n", Status);
+        goto end;
+    }
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+    r->root_item.inode.generation = 1;
+    r->root_item.inode.st_size = 3;
+    r->root_item.inode.st_blocks = subvol->root_item.inode.st_blocks;
+    r->root_item.inode.st_nlink = 1;
+    r->root_item.inode.st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755
+    r->root_item.inode.flags = 0xffffffff80000000; // FIXME - find out what these mean
+    r->root_item.generation = Vcb->superblock.generation;
+    r->root_item.objid = subvol->root_item.objid;
+    r->root_item.block_number = address;
+    r->root_item.bytes_used = subvol->root_item.bytes_used;
+    r->root_item.last_snapshot_generation = Vcb->superblock.generation;
+    r->root_item.root_level = subvol->root_item.root_level;
+    r->root_item.generation2 = Vcb->superblock.generation;
+    r->root_item.parent_uuid = subvol->root_item.uuid;
+    r->root_item.ctransid = subvol->root_item.ctransid;
+    r->root_item.otransid = Vcb->superblock.generation;
+    r->root_item.ctime = subvol->root_item.ctime;
+    r->root_item.otime = now;
+    
+    r->treeholder.address = address;
+    
+    // FIXME - do we need to copy over the send and receive fields too?
+    
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+        ERR("error - could not find ROOT_ITEM for subvol %llx\n", r->id);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    RtlCopyMemory(tp.item->data, &r->root_item, sizeof(ROOT_ITEM));
+    Vcb->root_root->lastinode = r->id;
+    
+    // update ROOT_ITEM of original subvol
+    
+    subvol->root_item.last_snapshot_generation = Vcb->superblock.generation;
+    
+    // We also rewrite the top of the old subvolume tree, for some reason
+    searchkey.obj_id = 0;
+    searchkey.obj_type = 0;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (!subvol->treeholder.tree->write) {
+        subvol->treeholder.tree->write = TRUE;
+        Vcb->write_trees++;
+    }
+    
+    // add DIR_ITEM
+    
+    dirpos = find_next_dir_index(Vcb, fcb->subvol, fcb->inode);
+    if (dirpos == 0) {
+        ERR("find_next_dir_index failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    disize = sizeof(DIR_ITEM) - 1 + utf8->Length;
+    di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di2) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        ExFreePool(di);
+        goto end;
+    }
+    
+    di->key.obj_id = id;
+    di->key.obj_type = TYPE_ROOT_ITEM;
+    di->key.offset = 0xffffffffffffffff;
+    di->transid = Vcb->superblock.generation;
+    di->m = 0;
+    di->n = utf8->Length;
+    di->type = BTRFS_TYPE_DIRECTORY;
+    RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
+    
+    RtlCopyMemory(di2, di, disize);
+    
+    Status = add_dir_item(Vcb, fcb->subvol, fcb->inode, crc32, di, disize, &rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_dir_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    // add DIR_INDEX
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_DIR_INDEX, dirpos, di2, disize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // add ROOT_REF
+    
+    rrsize = sizeof(ROOT_REF) - 1 + utf8->Length;
+    rr = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
+    if (!rr) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    rr->dir = fcb->inode;
+    rr->index = dirpos;
+    rr->n = utf8->Length;
+    RtlCopyMemory(rr->name, utf8->Buffer, utf8->Length);
+    
+    if (!insert_tree_item(Vcb, Vcb->root_root, fcb->subvol->id, TYPE_ROOT_REF, r->id, rr, rrsize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // add ROOT_BACKREF
+    
+    rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
+    if (!rr2) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlCopyMemory(rr2, rr, rrsize);
+    
+    if (!insert_tree_item(Vcb, Vcb->root_root, r->id, TYPE_ROOT_BACKREF, fcb->subvol->id, rr2, rrsize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // change fcb's INODE_ITEM
+    
+    // unlike when we create a file normally, the seq of the parent doesn't appear to change
+    fcb->inode_item.transid = Vcb->superblock.generation;
+    fcb->inode_item.st_size += utf8->Length * 2;
+    fcb->inode_item.st_ctime = now;
+    fcb->inode_item.st_mtime = now;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (keycmp(&searchkey, &tp.item->key)) {
+        ERR("error - could not find INODE_ITEM for directory %llx in subvol %llx\n", fcb->inode, fcb->subvol->id);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+    delete_tree_item(Vcb, &tp, &rollback);
+    
+    insert_tree_item(Vcb, fcb->subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ii, sizeof(INODE_ITEM), NULL, &rollback);
+    
+    fcb->subvol->root_item.ctime = now;
+    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    
+    if (Vcb->write_trees > 0)
+        do_write(Vcb, &rollback);
+    
+    free_trees(Vcb);
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    if (NT_SUCCESS(Status))
+        clear_rollback(&rollback);
+    else
+        do_rollback(Vcb, &rollback);
+
+    release_tree_lock(Vcb, TRUE);
+    
+    return Status;
+}
+
+static NTSTATUS create_snapshot(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length) {
+    PFILE_OBJECT subvol_obj;
+    NTSTATUS Status;
+    btrfs_create_snapshot* bcs = data;
+    fcb* subvol_fcb;
+    ANSI_STRING utf8;
+    UNICODE_STRING nameus;
+    ULONG len;
+    UINT32 crc32;
+    fcb* fcb;
+    ccb* ccb;
+    file_ref* fileref;
+    
+    if (length < offsetof(btrfs_create_snapshot, name))
+        return STATUS_INVALID_PARAMETER;
+    
+    if (length < offsetof(btrfs_create_snapshot, name) + bcs->namelen)
+        return STATUS_INVALID_PARAMETER;
+    
+    if (!bcs->subvol)
+        return STATUS_INVALID_PARAMETER;
+    
+    if (!FileObject || !FileObject->FsContext)
+        return STATUS_INVALID_PARAMETER;
+    
+    fcb = FileObject->FsContext;
+    ccb = FileObject->FsContext2;
+    
+    if (!fcb || !ccb || fcb->type != BTRFS_TYPE_DIRECTORY)
+        return STATUS_INVALID_PARAMETER;
+    
+    fileref = ccb->fileref;
+    
+    if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) {
+        WARN("insufficient privileges\n");
+        return STATUS_ACCESS_DENIED;
+    }
+    
+    nameus.Buffer = bcs->name;
+    nameus.Length = nameus.MaximumLength = bcs->namelen;
+    
+    if (!is_file_name_valid(&nameus))
+        return STATUS_OBJECT_NAME_INVALID;
+    
+    utf8.Buffer = NULL;
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &len, bcs->name, bcs->namelen);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N failed with error %08x\n", Status);
+        return Status;
+    }
+    
+    if (len == 0) {
+        ERR("RtlUnicodeToUTF8N returned a length of 0\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    utf8.MaximumLength = utf8.Length = len;
+    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);
+    
+    if (!utf8.Buffer) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    Status = RtlUnicodeToUTF8N(utf8.Buffer, len, &len, bcs->name, bcs->namelen);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N failed with error %08x\n", Status);
+        goto end2;
+    }
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8.Buffer, utf8.Length);
+    
+    if (find_file_in_dir_with_crc32(Vcb, &nameus, crc32, fcb->subvol, fcb->inode, NULL, NULL, NULL, NULL)) {
+        WARN("file already exists\n");
+        Status = STATUS_OBJECT_NAME_COLLISION;
+        goto end2;
+    }
+    
+    Status = ObReferenceObjectByHandle(bcs->subvol, 0, *IoFileObjectType, UserMode, (void**)&subvol_obj, NULL);
+    if (!NT_SUCCESS(Status)) {
+        ERR("ObReferenceObjectByHandle returned %08x\n", Status);
+        goto end2;
+    }
+    
+    subvol_fcb = subvol_obj->FsContext;
+    if (!subvol_fcb) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    if (subvol_fcb->inode != subvol_fcb->subvol->root_item.objid) {
+        WARN("handle inode was %llx, expected %llx\n", subvol_fcb->inode, subvol_fcb->subvol->root_item.objid);
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    ccb = subvol_obj->FsContext2;
+    
+    if (!ccb) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    if (!(ccb->access & FILE_TRAVERSE)) {
+        WARN("insufficient privileges\n");
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
+    Status = do_create_snapshot(Vcb, FileObject, subvol_fcb, crc32, &utf8);
+    
+    if (NT_SUCCESS(Status)) {
+        UNICODE_STRING ffn;
+        
+        ffn.Length = fileref->full_filename.Length + bcs->namelen;
+        if (fcb != fcb->Vcb->root_fileref->fcb)
+            ffn.Length += sizeof(WCHAR);
+        
+        ffn.MaximumLength = ffn.Length;
+        ffn.Buffer = ExAllocatePoolWithTag(PagedPool, ffn.Length, ALLOC_TAG);
+        
+        if (ffn.Buffer) {
+            ULONG i;
+            
+            RtlCopyMemory(ffn.Buffer, fileref->full_filename.Buffer, fileref->full_filename.Length);
+            i = fileref->full_filename.Length;
+            
+            if (fcb != fcb->Vcb->root_fileref->fcb) {
+                ffn.Buffer[i / sizeof(WCHAR)] = '\\';
+                i += sizeof(WCHAR);
+            }
+            
+            RtlCopyMemory(&ffn.Buffer[i / sizeof(WCHAR)], bcs->name, bcs->namelen);
+            
+            TRACE("full filename = %.*S\n", ffn.Length / sizeof(WCHAR), ffn.Buffer);
+            
+            FsRtlNotifyFullReportChange(Vcb->NotifySync, &Vcb->DirNotifyList, (PSTRING)&ffn, i, NULL, NULL,
+                                        FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL);
+            
+            ExFreePool(ffn.Buffer);
+        } else
+            ERR("out of memory\n");
+    }
+    
+end:
+    ObDereferenceObject(subvol_obj);
+    
+end2:
+    ExFreePool(utf8.Buffer);
+    
+    return Status;
+}
+
+static NTSTATUS create_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, WCHAR* name, ULONG length) {
+    fcb* fcb;
+    ccb* ccb;
+    file_ref* fileref;
+    NTSTATUS Status;
+    LIST_ENTRY rollback;
+    UINT64 id;
+    root* r;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    ULONG len, disize, rrsize, irsize;
+    UNICODE_STRING nameus;
+    ANSI_STRING utf8;
+    UINT64 dirpos;
+    DIR_ITEM *di, *di2;
+    UINT32 crc32;
+    ROOT_REF *rr, *rr2;
+    INODE_ITEM* ii;
+    INODE_REF* ir;
+    KEY searchkey;
+    traverse_ptr tp;
+    SECURITY_DESCRIPTOR* sd = NULL;
+    SECURITY_SUBJECT_CONTEXT subjcont;
+    PSID owner;
+    BOOLEAN defaulted;
+    UINT64* root_num;
+    
+    fcb = FileObject->FsContext;
+    if (!fcb) {
+        ERR("error - fcb was NULL\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    ccb = FileObject->FsContext2;
+    if (!ccb) {
+        ERR("error - ccb was NULL\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    fileref = ccb->fileref;
+    
+    if (fcb->type != BTRFS_TYPE_DIRECTORY) {
+        ERR("parent FCB was not a directory\n");
+        return STATUS_NOT_A_DIRECTORY;
+    }
+    
+    if (fileref->deleted || fcb->deleted) {
+        ERR("parent has been deleted\n");
+        return STATUS_FILE_DELETED;
+    }
+    
+    if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) {
+        WARN("insufficient privileges\n");
+        return STATUS_ACCESS_DENIED;
+    }
+    
+    nameus.Length = nameus.MaximumLength = length;
+    nameus.Buffer = name;
+    
+    if (!is_file_name_valid(&nameus))
+        return STATUS_OBJECT_NAME_INVALID;
+    
+    utf8.Buffer = NULL;
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &len, name, length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N failed with error %08x\n", Status);
+        return Status;
+    }
+    
+    if (len == 0) {
+        ERR("RtlUnicodeToUTF8N returned a length of 0\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    utf8.MaximumLength = utf8.Length = len;
+    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);
+    
+    if (!utf8.Buffer) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    Status = RtlUnicodeToUTF8N(utf8.Buffer, len, &len, name, length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N failed with error %08x\n", Status);
+        goto end2;
+    }
+    
+    acquire_tree_lock(Vcb, TRUE);
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+    InitializeListHead(&rollback);
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8.Buffer, utf8.Length);
+    
+    if (find_file_in_dir_with_crc32(fcb->Vcb, &nameus, crc32, fcb->subvol, fcb->inode, NULL, NULL, NULL, NULL)) {
+        WARN("file already exists\n");
+        Status = STATUS_OBJECT_NAME_COLLISION;
+        goto end;
+    }
+    
+    if (Vcb->root_root->lastinode == 0)
+        get_last_inode(Vcb, Vcb->root_root);
+    
+    // FIXME - make sure rollback removes new roots from internal structures
+    
+    id = Vcb->root_root->lastinode > 0x100 ? (Vcb->root_root->lastinode + 1) : 0x101;
+    Status = create_root(Vcb, id, &r, FALSE, 0, &rollback);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("create_root returned %08x\n", Status);
+        goto end;
+    }
+    
+    TRACE("created root %llx\n", id);
+    
+    if (!Vcb->uuid_root) {
+        root* uuid_root;
+        
+        TRACE("uuid root doesn't exist, creating it\n");
+        
+        Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, FALSE, 0, &rollback);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("create_root returned %08x\n", Status);
+            goto end;
+        }
+        
+        Vcb->uuid_root = uuid_root;
+    }
+    
+    root_num = ExAllocatePoolWithTag(PagedPool, sizeof(UINT64), ALLOC_TAG);
+    if (!root_num) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    tp.tree = NULL;
+    
+    do {
+        get_uuid(&r->root_item.uuid);
+        
+        RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid, sizeof(UINT64));
+        searchkey.obj_type = TYPE_SUBVOL_UUID;
+        RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(UINT64)], sizeof(UINT64));
+        
+        Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, FALSE);
+    } while (NT_SUCCESS(Status) && !keycmp(&searchkey, &tp.item->key));
+    
+    *root_num = r->id;
+    
+    if (!insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(UINT64), NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    r->root_item.inode.generation = 1;
+    r->root_item.inode.st_size = 3;
+    r->root_item.inode.st_blocks = Vcb->superblock.node_size;
+    r->root_item.inode.st_nlink = 1;
+    r->root_item.inode.st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755
+    r->root_item.inode.flags = 0xffffffff80000000; // FIXME - find out what these mean
+    
+    r->root_item.objid = SUBVOL_ROOT_INODE;
+    r->root_item.bytes_used = Vcb->superblock.node_size;
+    r->root_item.ctransid = Vcb->superblock.generation;
+    r->root_item.otransid = Vcb->superblock.generation;
+    r->root_item.ctime = now;
+    r->root_item.otime = now;
+    
+    // add .. inode to new subvol
+    
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlZeroMemory(ii, sizeof(INODE_ITEM));
+    ii->generation = Vcb->superblock.generation;
+    ii->transid = Vcb->superblock.generation;
+    ii->st_nlink = 1;
+    ii->st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755
+    ii->st_atime = ii->st_ctime = ii->st_mtime = ii->otime = now;
+    ii->st_gid = GID_NOBODY; // FIXME?
+       
+    SeCaptureSubjectContext(&subjcont);
+    
+    Status = SeAssignSecurity(fcb->sd, NULL, (void**)&sd, TRUE, &subjcont, IoGetFileObjectGenericMapping(), PagedPool);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("SeAssignSecurity returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (!sd) {
+        ERR("SeAssignSecurity returned NULL security descriptor\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    Status = RtlGetOwnerSecurityDescriptor(sd, &owner, &defaulted);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlGetOwnerSecurityDescriptor returned %08x\n", Status);
+        ii->st_uid = UID_NOBODY;
+    } else {
+        ii->st_uid = sid_to_uid(&owner);
+    }
+
+    if (!insert_tree_item(Vcb, r, r->root_item.objid, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // add security.NTACL xattr
+    
+    Status = set_xattr(Vcb, r, r->root_item.objid, EA_NTACL, EA_NTACL_HASH, (UINT8*)sd, RtlLengthSecurityDescriptor(fcb->sd), &rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("set_xattr returned %08x\n", Status);
+        goto end;
+    }
+    
+    ExFreePool(sd);
+    
+    // add INODE_REF
+    
+    irsize = sizeof(INODE_REF) - 1 + strlen(DOTDOT);
+    ir = ExAllocatePoolWithTag(PagedPool, irsize, ALLOC_TAG);
+    if (!ir) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    ir->index = 0;
+    ir->n = strlen(DOTDOT);
+    RtlCopyMemory(ir->name, DOTDOT, ir->n);
+
+    if (!insert_tree_item(Vcb, r, r->root_item.objid, TYPE_INODE_REF, r->root_item.objid, ir, irsize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // add DIR_ITEM
+    
+    dirpos = find_next_dir_index(Vcb, fcb->subvol, fcb->inode);
+    if (dirpos == 0) {
+        ERR("find_next_dir_index failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    disize = sizeof(DIR_ITEM) - 1 + utf8.Length;
+    di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di2) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        ExFreePool(di);
+        goto end;
+    }
+    
+    di->key.obj_id = id;
+    di->key.obj_type = TYPE_ROOT_ITEM;
+    di->key.offset = 0;
+    di->transid = Vcb->superblock.generation;
+    di->m = 0;
+    di->n = utf8.Length;
+    di->type = BTRFS_TYPE_DIRECTORY;
+    RtlCopyMemory(di->name, utf8.Buffer, utf8.Length);
+    
+    RtlCopyMemory(di2, di, disize);
+    
+    Status = add_dir_item(Vcb, fcb->subvol, fcb->inode, crc32, di, disize, &rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_dir_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    // add DIR_INDEX
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_DIR_INDEX, dirpos, di2, disize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // add ROOT_REF
+    
+    rrsize = sizeof(ROOT_REF) - 1 + utf8.Length;
+    rr = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
+    if (!rr) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    rr->dir = fcb->inode;
+    rr->index = dirpos;
+    rr->n = utf8.Length;
+    RtlCopyMemory(rr->name, utf8.Buffer, utf8.Length);
+    
+    if (!insert_tree_item(Vcb, Vcb->root_root, fcb->subvol->id, TYPE_ROOT_REF, r->id, rr, rrsize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // add ROOT_BACKREF
+    
+    rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
+    if (!rr2) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlCopyMemory(rr2, rr, rrsize);
+    
+    if (!insert_tree_item(Vcb, Vcb->root_root, r->id, TYPE_ROOT_BACKREF, fcb->subvol->id, rr2, rrsize, NULL, &rollback)) {
+        ERR("insert_tree_item failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // change fcb->subvol's ROOT_ITEM
+    
+    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = now;
+    
+    // change fcb's INODE_ITEM
+    
+    // unlike when we create a file normally, the times and seq of the parent don't appear to change
+    fcb->inode_item.transid = Vcb->superblock.generation;
+    fcb->inode_item.st_size += utf8.Length * 2;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (keycmp(&searchkey, &tp.item->key)) {
+        ERR("error - could not find INODE_ITEM for directory %llx in subvol %llx\n", fcb->inode, fcb->subvol->id);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+    delete_tree_item(Vcb, &tp, &rollback);
+    
+    insert_tree_item(Vcb, fcb->subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ii, sizeof(INODE_ITEM), NULL, &rollback);
+    
+    Vcb->root_root->lastinode = id;
+
+    Status = STATUS_SUCCESS;    
+    
+end:
+    if (NT_SUCCESS(Status))
+        Status = consider_write(Vcb);
+    
+    if (!NT_SUCCESS(Status))
+        do_rollback(Vcb, &rollback);
+    else
+        clear_rollback(&rollback);
+    
+    release_tree_lock(Vcb, TRUE);
+    
+    if (NT_SUCCESS(Status)) {
+        UNICODE_STRING ffn;
+        
+        ffn.Length = fileref->full_filename.Length + length;
+        if (fcb != fcb->Vcb->root_fileref->fcb)
+            ffn.Length += sizeof(WCHAR);
+        
+        ffn.MaximumLength = ffn.Length;
+        ffn.Buffer = ExAllocatePoolWithTag(PagedPool, ffn.Length, ALLOC_TAG);
+        
+        if (ffn.Buffer) {
+            ULONG i;
+            
+            RtlCopyMemory(ffn.Buffer, fileref->full_filename.Buffer, fileref->full_filename.Length);
+            i = fileref->full_filename.Length;
+            
+            if (fcb != fcb->Vcb->root_fileref->fcb) {
+                ffn.Buffer[i / sizeof(WCHAR)] = '\\';
+                i += sizeof(WCHAR);
+            }
+            
+            RtlCopyMemory(&ffn.Buffer[i / sizeof(WCHAR)], name, length);
+            
+            TRACE("full filename = %.*S\n", ffn.Length / sizeof(WCHAR), ffn.Buffer);
+            
+            FsRtlNotifyFullReportChange(Vcb->NotifySync, &Vcb->DirNotifyList, (PSTRING)&ffn, i, NULL, NULL,
+                                        FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL);
+            
+            ExFreePool(ffn.Buffer);
+        } else
+            ERR("out of memory\n");
+    }
+    
+end2:
+    if (utf8.Buffer)
+        ExFreePool(utf8.Buffer);
+    
+    return Status;
+}
+
+static NTSTATUS is_volume_mounted(device_extension* Vcb, PIRP Irp) {
+    UINT64 i, num_devices;
+    NTSTATUS Status;
+    ULONG cc;
+    IO_STATUS_BLOCK iosb;
+    BOOL verify = FALSE;
+    
+    num_devices = Vcb->superblock.num_devices;
+    for (i = 0; i < num_devices; i++) {
+        if (Vcb->devices[i].devobj && Vcb->devices[i].removable) {
+            Status = dev_ioctl(Vcb->devices[i].devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), FALSE, &iosb);
+            
+            if (iosb.Information != sizeof(ULONG))
+                cc = 0;
+            
+            if (Status == STATUS_VERIFY_REQUIRED || (NT_SUCCESS(Status) && cc != Vcb->devices[i].change_count)) {
+                Vcb->devices[i].devobj->Flags |= DO_VERIFY_VOLUME;
+                verify = TRUE;
+            }
+            
+            if (NT_SUCCESS(Status) && iosb.Information == sizeof(ULONG))
+                Vcb->devices[i].change_count = cc;
+            
+            if (!NT_SUCCESS(Status) || verify) {
+                IoSetHardErrorOrVerifyDevice(Irp, Vcb->devices[i].devobj);
+                
+                return verify ? STATUS_VERIFY_REQUIRED : Status;
+            }
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS fs_get_statistics(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject, void* buffer, DWORD buflen, DWORD* retlen) {
+    FILESYSTEM_STATISTICS* fss;
+    
+    WARN("STUB: FSCTL_FILESYSTEM_GET_STATISTICS\n");
+    
+    // This is hideously wrong, but at least it stops SMB from breaking
+    
+    if (buflen < sizeof(FILESYSTEM_STATISTICS))
+        return STATUS_BUFFER_TOO_SMALL;
+    
+    fss = buffer;
+    RtlZeroMemory(fss, sizeof(FILESYSTEM_STATISTICS));
+    
+    fss->Version = 1;
+    fss->FileSystemType = FILESYSTEM_STATISTICS_TYPE_NTFS;
+    fss->SizeOfCompleteStructure = sizeof(FILESYSTEM_STATISTICS);
+    
+    *retlen = sizeof(FILESYSTEM_STATISTICS);
     
     return STATUS_SUCCESS;
 }
@@ -97,8 +1238,7 @@ NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL
             break;
 
         case FSCTL_IS_VOLUME_MOUNTED:
-            WARN("STUB: FSCTL_IS_VOLUME_MOUNTED\n");
-            Status = STATUS_NOT_IMPLEMENTED;
+            Status = is_volume_mounted(DeviceObject->DeviceExtension, Irp);
             break;
 
         case FSCTL_IS_PATHNAME_VALID:
@@ -152,8 +1292,8 @@ NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL
             break;
 
         case FSCTL_FILESYSTEM_GET_STATISTICS:
-            WARN("STUB: FSCTL_FILESYSTEM_GET_STATISTICS\n");
-            Status = STATUS_NOT_IMPLEMENTED;
+            Status = fs_get_statistics(DeviceObject, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,
+                                       IrpSp->Parameters.DeviceIoControl.OutputBufferLength, &Irp->IoStatus.Information);
             break;
 
         case FSCTL_GET_NTFS_VOLUME_DATA:
@@ -221,8 +1361,7 @@ NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL
             break;
 
         case FSCTL_DELETE_REPARSE_POINT:
-            WARN("STUB: FSCTL_DELETE_REPARSE_POINT\n");
-            Status = STATUS_NOT_IMPLEMENTED;
+            Status = delete_reparse_point(DeviceObject, Irp);
             break;
 
         case FSCTL_ENUM_USN_DATA:
@@ -524,9 +1663,17 @@ NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL
         case FSCTL_BTRFS_GET_FILE_IDS:
             Status = get_file_ids(IrpSp->FileObject, map_user_buffer(Irp), IrpSp->Parameters.DeviceIoControl.OutputBufferLength);
             break;
+            
+        case FSCTL_BTRFS_CREATE_SUBVOL:
+            Status = create_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, map_user_buffer(Irp), IrpSp->Parameters.DeviceIoControl.OutputBufferLength);
+            break;
+            
+        case FSCTL_BTRFS_CREATE_SNAPSHOT:
+            Status = create_snapshot(DeviceObject->DeviceExtension, IrpSp->FileObject, map_user_buffer(Irp), IrpSp->Parameters.DeviceIoControl.OutputBufferLength);
+            break;
 
         default:
-            WARN("unknown control code %x (DeviceType = %x, Access = %x, Function = %x, Method = %x)\n",
+            TRACE("unknown control code %x (DeviceType = %x, Access = %x, Function = %x, Method = %x)\n",
                           IrpSp->Parameters.FileSystemControl.FsControlCode, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0xff0000) >> 16,
                           (IrpSp->Parameters.FileSystemControl.FsControlCode & 0xc000) >> 14, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0x3ffc) >> 2,
                           IrpSp->Parameters.FileSystemControl.FsControlCode & 0x3);
diff --git a/reactos/drivers/filesystems/btrfs/loader.c b/reactos/drivers/filesystems/btrfs/loader.c
deleted file mode 100644 (file)
index b4014d9..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-/* 
- * Adapted from code at http://support.fccps.cz/download/adv/frr/win32_ddk_mingw/win32_ddk_mingw.html - thanks!
- * 
-   File created by Frank Rysanek <rysanek@fccps.cz>
-   Source code taken almost verbatim from "Driver Development, Part 1"
-   published by Toby Opferman at CodeProject.com 
-*/
-
-#include <stdio.h>
-#include <windows.h>
-/*#include <string.h>*/
-#include <unistd.h>     /* getcwd() */
-
-#define MY_DRIVER_NAME "btrfs"
-#define MY_DEVICE_NAME     "\\Btrfs"
-#define MY_DOSDEVICE_NAME  "\\DosDevices\\"  MY_DRIVER_NAME  /* AKA symlink name */
-/* for the loader and app */
-#define MY_SERVICE_NAME_LONG  "Driver Test2"
-#define MY_SERVICE_NAME_SHORT MY_DRIVER_NAME
-#define MY_DRIVER_FILENAME    MY_DRIVER_NAME ".sys"
-
-#define MAX_CWD_LEN 1024
-static char cwd[MAX_CWD_LEN+3];  /* the XXXXX.sys filename will get appended to this as well */
-
-/* geterrstr() taken verbatim from some code snippet at www.mingw.org by Dan Osborne. */
-/* Apparently, it's a way to get a classic null-terminated string containing "last error". */
-static char errbuffer[256];
-
-static const char *geterrstr(DWORD errcode)
-{
- size_t skip = 0;
- DWORD chars;
-
-   chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
-                         NULL, errcode, 0, errbuffer, sizeof(errbuffer)-1, 0);
-   errbuffer[sizeof(errbuffer)-1] = 0;
-
-   if (chars) 
-   {
-      while (errbuffer[chars-1] == '\r' || errbuffer[chars-1] == '\n') 
-      {
-         errbuffer[--chars] = 0;
-      }
-   }
-
-   if (chars && errbuffer[chars-1] == '.') errbuffer[--chars] = 0;
-
-   if (chars >= 2 && errbuffer[0] == '%' 
-       && errbuffer[1] >= '0' && errbuffer[1] <= '9')
-   {
-      skip = 2;
-
-      while (chars > skip && errbuffer[skip] == ' ') ++skip;
-
-      if (chars >= skip+2 && errbuffer[skip] == 'i' && errbuffer[skip+1] == 's')
-      {
-         skip += 2;
-         while (chars > skip && errbuffer[skip] == ' ') ++skip;
-      }
-   }
-
-   if (chars > skip && errbuffer[skip] >= 'A' && errbuffer[skip] <= 'Z') 
-   {
-      errbuffer[skip] += 'a' - 'A';
-   }
-
-   return errbuffer+skip;
-}
-
-void process_error(void)
-{
-   DWORD err = GetLastError();
-   printf("Error: %lu = \"%s\"\n", (unsigned long)err, geterrstr(err));
-}
-
-int main(void)
-{
- HANDLE hSCManager;
- HANDLE hService;
- SERVICE_STATUS ss;
- int retval = 0;
- BOOL amd64;
-
-   /* First of all, maybe concatenate the current working directory
-      with the desired driver file name - before we start messing with
-      the service manager etc. */
-   if (getcwd(cwd, MAX_CWD_LEN) == NULL)  /* error */
-   {
-      printf("Failed to learn the current working directory!\n");
-      retval = -8;
-      goto err_out1;
-   } /* else got CWD just fine */
-
-   if (strlen(cwd) + strlen(MY_DRIVER_FILENAME) + 1 > MAX_CWD_LEN)
-   {
-      printf("Current working dir + driver filename > longer than %d ?!?\n", MAX_CWD_LEN);
-      retval = -9;
-      goto err_out1;
-   } /* else our buffer is long enough :-) */
-
-   strcat(cwd, "\\");
-      
-   IsWow64Process(GetCurrentProcess(),&amd64);
-   strcat(cwd, amd64 ? "x64" : "x86");
-   
-   strcat(cwd, "\\");
-   strcat(cwd, MY_DRIVER_FILENAME); 
-   printf("Driver path+name: %s\n", cwd);
-
-
-   printf("Going to open the service manager... ");
-
-   hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
-   if (! hSCManager)
-   {
-      printf("Uh oh:\n");
-      process_error();
-      retval = -1;
-      goto err_out1;
-   }
-
-   printf("okay.\n");
-   printf("Going to create the service... ");
-
-   hService = CreateService(hSCManager, MY_SERVICE_NAME_SHORT, 
-                            MY_SERVICE_NAME_LONG, 
-                            SERVICE_START | DELETE | SERVICE_STOP, 
-                            SERVICE_KERNEL_DRIVER,
-                            SERVICE_DEMAND_START, 
-                            SERVICE_ERROR_IGNORE, 
-                            cwd, 
-                            NULL, NULL, NULL, NULL, NULL);
-
-   if(!hService)
-   {
-      process_error();
-      printf("\n already exists? Trying to open it... ");
-      hService = OpenService(hSCManager, MY_SERVICE_NAME_SHORT, 
-                             SERVICE_START | DELETE | SERVICE_STOP);
-   }
-
-   if(!hService)
-   {
-      printf("FAILED!\n");
-      process_error();
-      retval = -2;
-      goto err_out2;
-   }
-
-   printf("okay.\n");
-   printf("Going to start the service... ");
-
-   if (StartService(hService, 0, NULL) == 0)  /* error */
-   {
-      printf("Uh oh:\n");
-      process_error();
-      retval = -3;
-      goto err_out3;
-   }
-
-   printf("okay.\n");
-   
-//    TCHAR VolumeName[] = _T("Z:");
-// TCHAR DeviceName[] = _T("\\Device\\VDisk1");
-//    printf("Mounting volume... ");
-//     if (!DefineDosDeviceA(DDD_RAW_TARGET_PATH, "T:", "\\Device\\HarddiskVolume3"))
-//     {
-//         printf("Uh oh:\n");
-//         process_error();
-//     } else {
-//         printf("okay.\n");
-//     }
-   
-//     if (!SetVolumeMountPointA("T:\\", "\\\\?\\Volume{9bd714c3-4379-11e5-b26c-806e6f6e6963}\\")) {
-//         printf("Uh oh:\n");
-//         process_error();
-//     } else {
-//         printf("okay.\n");
-//     }
-   
-   printf("\n >>> Press Enter to unload the driver! <<<\n");
-   getchar();
-   
-//    printf("Unmounting volume... ");
-//     if (!DefineDosDeviceA(DDD_REMOVE_DEFINITION, "T:", NULL))
-//     {
-//         printf("Uh oh:\n");
-//         process_error();
-//     } else {
-//         printf("okay.\n");
-//     }
-
-   printf("Going to stop the service... ");
-   if (ControlService(hService, SERVICE_CONTROL_STOP, &ss) == 0) /* error */
-   {
-      printf("Uh oh:\n");
-      process_error();
-      retval = -4;
-   }
-   else printf("okay.\n");
-
-err_out3:
-
-   printf("Going to close the service handle... ");
-   if (CloseServiceHandle(hService) == 0) /* error */
-   {
-      printf("Uh oh:\n");
-      process_error();
-      retval = -6;
-   }
-   else printf("okay.\n");
-
-err_out2:
-
-   printf("Going to close the service manager... ");
-   if (CloseServiceHandle(hSCManager) == 0) /* error */
-   {
-      printf("Uh oh:\n");
-      process_error();
-      retval = -7;
-   }
-   else printf("okay.\n");
-
-err_out1:
-
-   printf("Finished! :-b\n");
-
-   return(retval);
-}
-
diff --git a/reactos/drivers/filesystems/btrfs/pnp.c b/reactos/drivers/filesystems/btrfs/pnp.c
new file mode 100644 (file)
index 0000000..e3f84f8
--- /dev/null
@@ -0,0 +1,283 @@
+#include "btrfs_drv.h"
+
+struct pnp_context;
+
+typedef struct {
+    struct pnp_context* context;
+    PIRP Irp;
+    IO_STATUS_BLOCK iosb;
+    NTSTATUS Status;
+} pnp_stripe;
+
+typedef struct {
+    KEVENT Event;
+    NTSTATUS Status;
+    LONG left;
+    pnp_stripe* stripes;
+} pnp_context;
+
+static NTSTATUS STDCALL pnp_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
+    pnp_stripe* stripe = conptr;
+    pnp_context* context = (pnp_context*)stripe->context;
+    
+    stripe->Status = Irp->IoStatus.Status;
+    
+    InterlockedDecrement(&context->left);
+    
+    if (context->left == 0)
+        KeSetEvent(&context->Event, 0, FALSE);
+
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS send_disks_pnp_message(device_extension* Vcb, UCHAR minor) {
+    pnp_context* context;
+    UINT64 num_devices, i;
+    NTSTATUS Status;
+    
+    context = ExAllocatePoolWithTag(NonPagedPool, sizeof(pnp_context), ALLOC_TAG);
+    if (!context) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context, sizeof(pnp_context));
+    KeInitializeEvent(&context->Event, NotificationEvent, FALSE);
+    
+    num_devices = Vcb->superblock.num_devices;
+    
+    context->stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(pnp_stripe) * num_devices, ALLOC_TAG);
+    if (!context->stripes) {
+        ERR("out of memory\n");
+        ExFreePool(context);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context->stripes, sizeof(pnp_stripe) * num_devices);
+    
+    for (i = 0; i < num_devices; i++) {
+        PIO_STACK_LOCATION IrpSp;
+        
+        if (Vcb->devices[i].devobj) {
+            context->stripes[i].context = (struct pnp_context*)context;
+
+            context->stripes[i].Irp = IoAllocateIrp(Vcb->devices[i].devobj->StackSize, FALSE);
+            
+            if (!context->stripes[i].Irp) {
+                UINT64 j;
+                
+                ERR("IoAllocateIrp failed\n");
+                
+                for (j = 0; j < i; j++) {
+                    if (Vcb->devices[j].devobj) {
+                        IoFreeIrp(context->stripes[j].Irp);
+                    }
+                }
+                ExFreePool(context->stripes);
+                ExFreePool(context);
+                
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            IrpSp = IoGetNextIrpStackLocation(context->stripes[i].Irp);
+            IrpSp->MajorFunction = IRP_MJ_PNP;
+            IrpSp->MinorFunction = minor;
+
+            context->stripes[i].Irp->UserIosb = &context->stripes[i].iosb;
+            
+            IoSetCompletionRoutine(context->stripes[i].Irp, pnp_completion, &context->stripes[i], TRUE, TRUE, TRUE);
+            
+            context->stripes[i].Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
+            
+            context->left++;
+        }
+    }
+    
+    if (context->left == 0) {
+        Status = STATUS_SUCCESS;
+        goto end;
+    }
+    
+    for (i = 0; i < num_devices; i++) {
+        if (context->stripes[i].Irp) {
+            IoCallDriver(Vcb->devices[i].devobj, context->stripes[i].Irp);
+        }
+    }
+    
+    KeWaitForSingleObject(&context->Event, Executive, KernelMode, FALSE, NULL);
+    
+    Status = STATUS_SUCCESS;
+    
+    for (i = 0; i < num_devices; i++) {
+        if (context->stripes[i].Irp) {
+            if (context->stripes[i].Status != STATUS_SUCCESS)
+                Status = context->stripes[i].Status;
+        }
+    }
+    
+end:
+    for (i = 0; i < num_devices; i++) {
+        if (context->stripes[i].Irp) {
+            IoFreeIrp(context->stripes[i].Irp);
+        }
+    }
+
+    ExFreePool(context->stripes);
+    ExFreePool(context);
+
+    return Status;
+}
+
+static NTSTATUS pnp_cancel_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+device_extension* Vcb = DeviceObject->DeviceExtension;
+    NTSTATUS Status;
+    
+    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+
+    if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->fcb->open_count > 0 || has_open_children(Vcb->root_fileref))) {
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
+    Status = send_disks_pnp_message(Vcb, IRP_MN_CANCEL_REMOVE_DEVICE);
+    if (!NT_SUCCESS(Status)) {
+        WARN("send_disks_pnp_message returned %08x\n", Status);
+        goto end;
+    }
+
+    Vcb->removing = FALSE;
+end:
+    ExReleaseResourceLite(&Vcb->fcb_lock);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS pnp_query_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    NTSTATUS Status;
+    LIST_ENTRY rollback;
+    
+    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+
+    if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->fcb->open_count > 0 || has_open_children(Vcb->root_fileref))) {
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
+    Status = send_disks_pnp_message(Vcb, IRP_MN_QUERY_REMOVE_DEVICE);
+    if (!NT_SUCCESS(Status)) {
+        WARN("send_disks_pnp_message returned %08x\n", Status);
+        goto end;
+    }
+
+    Vcb->removing = TRUE;
+    
+    InitializeListHead(&rollback);
+    
+    acquire_tree_lock(Vcb, TRUE);
+
+    if (Vcb->write_trees > 0)
+        do_write(Vcb, &rollback);
+    
+    clear_rollback(&rollback);
+
+    release_tree_lock(Vcb, TRUE);
+
+    Status = STATUS_SUCCESS;
+end:
+    ExReleaseResourceLite(&Vcb->fcb_lock);
+    
+    return Status;
+}
+
+static NTSTATUS pnp_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    NTSTATUS Status;
+    
+    Status = send_disks_pnp_message(Vcb, IRP_MN_REMOVE_DEVICE);
+    if (!NT_SUCCESS(Status)) {
+        WARN("send_disks_pnp_message returned %08x\n", Status);
+    }
+    
+    if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {
+        uninit(Vcb, FALSE);
+        DeviceObject->Vpb->Flags &= ~VPB_MOUNTED;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS pnp_start_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    FIXME("STUB\n");
+
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pnp_surprise_removal(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    FIXME("STUB\n");
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS STDCALL drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    NTSTATUS Status;
+    BOOL top_level;
+
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_NOT_IMPLEMENTED;
+    
+    switch (IrpSp->MinorFunction) {
+        case IRP_MN_CANCEL_REMOVE_DEVICE:
+            Status = pnp_cancel_remove_device(DeviceObject, Irp);
+            break;
+
+        case IRP_MN_QUERY_REMOVE_DEVICE:
+            Status = pnp_query_remove_device(DeviceObject, Irp);
+            break;
+
+        case IRP_MN_REMOVE_DEVICE:
+            Status = pnp_remove_device(DeviceObject, Irp);
+            break;
+
+        case IRP_MN_START_DEVICE:
+            Status = pnp_start_device(DeviceObject, Irp);
+            break;
+
+        case IRP_MN_SURPRISE_REMOVAL:
+            Status = pnp_surprise_removal(DeviceObject, Irp);
+            break;
+
+        default:
+            TRACE("passing minor function 0x%x on\n", IrpSp->MinorFunction);
+            
+            IoSkipCurrentIrpStackLocation(Irp);
+            Status = IoCallDriver(Vcb->devices[0].devobj, Irp);
+            goto end;
+    }
+
+// //     Irp->IoStatus.Status = Status;
+// //     Irp->IoStatus.Information = 0;
+// 
+//     IoSkipCurrentIrpStackLocation(Irp);
+//     
+//     Status = IoCallDriver(Vcb->devices[0].devobj, Irp);
+// 
+// //     IoCompleteRequest(Irp, IO_NO_INCREMENT);
+
+    Irp->IoStatus.Status = Status;
+
+    IoCompleteRequest(Irp, IO_NO_INCREMENT);
+    
+end:
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
index 16e045f..a0a12d9 100644 (file)
@@ -331,7 +331,6 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
 
     if (tp.item->key.obj_id < searchkey.obj_id || tp.item->key.obj_type < searchkey.obj_type) {
         if (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             TRACE("moving on to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
@@ -339,7 +338,6 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
     }
     
     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
-        free_traverse_ptr(&tp);
         ERR("couldn't find EXTENT_DATA for inode %llx in subvol %llx\n", searchkey.obj_id, subvol->id);
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
@@ -347,7 +345,6 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
     
     if (tp.item->key.offset > start) {
         ERR("first EXTENT_DATA was after offset\n");
-        free_traverse_ptr(&tp);
         Status = STATUS_INTERNAL_ERROR;
         goto exit;
     }
@@ -375,14 +372,12 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
         
         if (tp.item->size < sizeof(EXTENT_DATA)) {
             ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
-            free_traverse_ptr(&tp);
             Status = STATUS_INTERNAL_ERROR;
             goto exit;
         }
         
         if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
             ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
-            free_traverse_ptr(&tp);
             Status = STATUS_INTERNAL_ERROR;
             goto exit;
         }
@@ -393,28 +388,24 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
         
         if (tp.item->key.offset + len < start) {
             ERR("Tried to read beyond end of file\n");
-            free_traverse_ptr(&tp);
             Status = STATUS_END_OF_FILE;
             goto exit;
         }
         
         if (ed->compression != BTRFS_COMPRESSION_NONE) {
             FIXME("FIXME - compression not yet supported\n");
-            free_traverse_ptr(&tp);
             Status = STATUS_NOT_IMPLEMENTED;
             goto exit;
         }
         
         if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
             WARN("Encryption not supported\n");
-            free_traverse_ptr(&tp);
             Status = STATUS_NOT_IMPLEMENTED;
             goto exit;
         }
         
         if (ed->encoding != BTRFS_ENCODING_NONE) {
             WARN("Other encodings not supported\n");
-            free_traverse_ptr(&tp);
             Status = STATUS_NOT_IMPLEMENTED;
             goto exit;
         }
@@ -452,7 +443,6 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
                     
                     if (!buf) {
                         ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
                         Status = STATUS_INSUFFICIENT_RESOURCES;
                         goto exit;
                     }
@@ -463,7 +453,6 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
                     if (!NT_SUCCESS(Status)) {
                         ERR("read_data returned %08x\n", Status);
                         ExFreePool(buf);
-                        free_traverse_ptr(&tp);
                         goto exit;
                     }
                     
@@ -495,7 +484,6 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
                 
             default:
                 WARN("Unsupported extent data type %u\n", ed->type);
-                free_traverse_ptr(&tp);
                 Status = STATUS_NOT_IMPLEMENTED;
                 goto exit;
         }
@@ -509,20 +497,16 @@ NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UI
                 next_tp.item->key.obj_type != TYPE_EXTENT_DATA ||
                 next_tp.item->key.offset != tp.item->key.offset + len
             ) {
-                free_traverse_ptr(&next_tp);
                 break;
             } else {
                 TRACE("found next key (%llx,%x,%llx)\n", next_tp.item->key.obj_id, next_tp.item->key.obj_type, next_tp.item->key.offset);
                 
-                free_traverse_ptr(&tp);
                 tp = next_tp;
             }
         } else
             break;
     } while (TRUE);
     
-    free_traverse_ptr(&tp);
-    
     Status = STATUS_SUCCESS;
     if (pbr)
         *pbr = bytes_read;
@@ -547,53 +531,18 @@ NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     
     TRACE("read\n");
     
-    switch (IrpSp->MinorFunction) {
-        case IRP_MN_COMPLETE:
-            FIXME("unsupported - IRP_MN_COMPLETE\n");
-            break;
-
-        case IRP_MN_COMPLETE_MDL:
-            FIXME("unsupported - IRP_MN_COMPLETE_MDL\n");
-            break;
-
-        case IRP_MN_COMPLETE_MDL_DPC:
-            FIXME("unsupported - IRP_MN_COMPLETE_MDL_DPC\n");
-            break;
-
-        case IRP_MN_COMPRESSED:
-            FIXME("unsupported - IRP_MN_COMPRESSED\n");
-            break;
-
-        case IRP_MN_DPC:
-            FIXME("unsupported - IRP_MN_DPC\n");
-            break;
-
-        case IRP_MN_MDL:
-            FIXME("unsupported - IRP_MN_MDL\n");
-            break;
-
-        case IRP_MN_MDL_DPC:
-            FIXME("unsupported - IRP_MN_MDL_DPC\n");
-            break;
-
-        case IRP_MN_NORMAL:
-            TRACE("IRP_MN_NORMAL\n");
-            break;
-        
-        default:
-            WARN("unknown minor %u\n", IrpSp->MinorFunction);
-    }
-    
-    data = map_user_buffer(Irp);
+    Irp->IoStatus.Information = 0;
     
-    if (Irp->MdlAddress && !data) {
-        ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
-        Status = STATUS_INSUFFICIENT_RESOURCES;
+    if (IrpSp->MinorFunction & IRP_MN_COMPLETE) {
+        CcMdlReadComplete(IrpSp->FileObject, Irp->MdlAddress);
+        
+        Irp->MdlAddress = NULL;
+        Status = STATUS_SUCCESS;
+        bytes_read = 0;
+        
         goto exit;
     }
     
-    Irp->IoStatus.Information = 0;
-    
     start = IrpSp->Parameters.Read.ByteOffset.QuadPart;
     length = IrpSp->Parameters.Read.Length;
     bytes_read = 0;
@@ -603,11 +552,14 @@ NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         goto exit;
     }
     
-    TRACE("file = %.*S (fcb = %p)\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb);
+    TRACE("file = %S (fcb = %p)\n", file_desc(FileObject), fcb);
     TRACE("offset = %llx, length = %x\n", start, length);
     TRACE("paging_io = %s, no cache = %s\n", Irp->Flags & IRP_PAGING_IO ? "TRUE" : "FALSE", Irp->Flags & IRP_NOCACHE ? "TRUE" : "FALSE");
 
-    // FIXME - shouldn't be able to read from a directory
+    if (fcb->type == BTRFS_TYPE_DIRECTORY) {
+        Status = STATUS_INVALID_DEVICE_REQUEST;
+        goto exit;
+    }
     
     if (!(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForReadAccess(&fcb->lock, Irp)) {
         WARN("tried to read locked region\n");
@@ -630,9 +582,19 @@ NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     TRACE("FileObject %p fcb %p FileSize = %llx st_size = %llx (%p)\n", FileObject, fcb, fcb->Header.FileSize.QuadPart, fcb->inode_item.st_size, &fcb->inode_item.st_size);
 //     int3;
     
-    if (length + start > fcb->Header.ValidDataLength.QuadPart) {
-        RtlZeroMemory(data + (fcb->Header.ValidDataLength.QuadPart - start), length - (fcb->Header.ValidDataLength.QuadPart - start));
-        length = fcb->Header.ValidDataLength.QuadPart - start;
+    if (Irp->Flags & IRP_NOCACHE || !(IrpSp->MinorFunction & IRP_MN_MDL)) {
+        data = map_user_buffer(Irp);
+        
+        if (Irp->MdlAddress && !data) {
+            ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto exit;
+        }
+        
+        if (length + start > fcb->Header.ValidDataLength.QuadPart) {
+            RtlZeroMemory(data + (fcb->Header.ValidDataLength.QuadPart - start), length - (fcb->Header.ValidDataLength.QuadPart - start));
+            length = fcb->Header.ValidDataLength.QuadPart - start;
+        }
     }
         
     if (!(Irp->Flags & IRP_NOCACHE)) {
@@ -640,7 +602,7 @@ NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         
         Status = STATUS_SUCCESS;
         
-//         try {
+        try {
             if (!FileObject->PrivateCacheMap) {
                 CC_FILE_SIZES ccfs;
                 
@@ -659,19 +621,23 @@ NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     //         wait = IoIsOperationSynchronous(Irp) ? TRUE : FALSE;
             wait = TRUE;
             
-            TRACE("CcCopyRead(%p, %llx, %x, %u, %p, %p)\n", FileObject, IrpSp->Parameters.Read.ByteOffset.QuadPart, length, wait, data, &Irp->IoStatus);
-            TRACE("sizes = %llx, %llx, %llx\n", fcb->Header.AllocationSize, fcb->Header.FileSize, fcb->Header.ValidDataLength);
-            if (!CcCopyRead(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, wait, data, &Irp->IoStatus)) {
-                TRACE("CcCopyRead failed\n");
-                
-                IoMarkIrpPending(Irp);
-                Status = STATUS_PENDING;
-                goto exit;
+            if (IrpSp->MinorFunction & IRP_MN_MDL) {
+                CcMdlRead(FileObject,&IrpSp->Parameters.Read.ByteOffset, length, &Irp->MdlAddress, &Irp->IoStatus);
+            } else {
+                TRACE("CcCopyRead(%p, %llx, %x, %u, %p, %p)\n", FileObject, IrpSp->Parameters.Read.ByteOffset.QuadPart, length, wait, data, &Irp->IoStatus);
+                TRACE("sizes = %llx, %llx, %llx\n", fcb->Header.AllocationSize, fcb->Header.FileSize, fcb->Header.ValidDataLength);
+                if (!CcCopyRead(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, wait, data, &Irp->IoStatus)) {
+                    TRACE("CcCopyRead failed\n");
+                    
+                    IoMarkIrpPending(Irp);
+                    Status = STATUS_PENDING;
+                    goto exit;
+                }
+                TRACE("CcCopyRead finished\n");
             }
-            TRACE("CcCopyRead finished\n");
-//         } except (EXCEPTION_EXECUTE_HANDLER) {
-//             Status = GetExceptionCode();
-//         }
+        } except (EXCEPTION_EXECUTE_HANDLER) {
+            Status = GetExceptionCode();
+        }
         
         if (NT_SUCCESS(Status)) {
             Status = Irp->IoStatus.Status;
@@ -711,6 +677,10 @@ exit:
     if (FileObject->Flags & FO_SYNCHRONOUS_IO && !(Irp->Flags & IRP_PAGING_IO))
         FileObject->CurrentByteOffset.QuadPart = start + (NT_SUCCESS(Status) ? bytes_read : 0);
     
+    // fastfat doesn't do this, but the Wine ntdll file test seems to think we ought to
+    if (Irp->UserIosb)
+        *Irp->UserIosb = Irp->IoStatus;
+    
     TRACE("Irp->IoStatus.Status = %08x\n", Irp->IoStatus.Status);
     TRACE("Irp->IoStatus.Information = %lu\n", Irp->IoStatus.Information);
     TRACE("returning %08x\n", Status);
index 95078f2..15724b0 100644 (file)
 
 #include "btrfs_drv.h"
 
-BOOL follow_symlink(fcb* fcb, PFILE_OBJECT FileObject) {
+NTSTATUS get_reparse_point(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject, void* buffer, DWORD buflen, DWORD* retlen) {
+    USHORT subnamelen, printnamelen, i;
+    ULONG stringlen;
+    DWORD reqlen;
+    REPARSE_DATA_BUFFER* rdb = buffer;
+    fcb* fcb = FileObject->FsContext;
+    char* data;
     NTSTATUS Status;
-    ULONG len, stringlen;
-    USHORT newlen;
-    OBJECT_NAME_INFORMATION* oni;
-    UINT8* data;
-    UINT32 i;
-    BOOL success = FALSE;
-    WCHAR* utf16;
-    UNICODE_STRING abspath;
-    
-    if (fcb->inode_item.st_size == 0) {
-        WARN("can't follow symlink with no data\n");
-        return FALSE;
-    }
     
-    Status = ObQueryNameString(FileObject->DeviceObject, NULL, 0, &len);
+    // FIXME - check permissions
     
-    if (Status != STATUS_INFO_LENGTH_MISMATCH) {
-        ERR("ObQueryNameString 1 returned %08x\n", Status);
-        return FALSE;
-    }
-    
-    oni = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
-    if (!oni) {
-        ERR("out of memory\n");
-        goto end;
-    }
-    
-    Status = ObQueryNameString(FileObject->DeviceObject, oni, len, &len);
-    
-    if (!NT_SUCCESS(Status)) {
-        ERR("ObQueryNameString 2 returned %08x\n", Status);
-        ExFreePool(oni);
-        return FALSE;
-    }
-       
-    data = ExAllocatePoolWithTag(PagedPool, fcb->inode_item.st_size, ALLOC_TAG);
-    if (!data) {
-        ERR("out of memory\n");
-        goto end;
-    }
-    
-    Status = read_file(fcb->Vcb, fcb->subvol, fcb->inode, data, 0, fcb->inode_item.st_size, NULL);
-    
-    if (!NT_SUCCESS(Status)) {
-        ERR("read_file returned %08x\n", Status);
-        goto end;
-    }
-    
-    for (i = 0; i < fcb->inode_item.st_size; i++) {
-        if (data[i] == '/')
-            data[i] = '\\';
-    }
-    
-    Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, (char*)data, fcb->inode_item.st_size);
-    if (!NT_SUCCESS(Status)) {
-        ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
-        goto end;
-    }
+    TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject, FileObject, buffer, buflen, retlen);
     
-    utf16 = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
-    if (!utf16) {
-        ERR("out of memory\n");
-        goto end;
-    }
-
-    Status = RtlUTF8ToUnicodeN(utf16, stringlen, &stringlen, (char*)data, fcb->inode_item.st_size);
-
-    if (!NT_SUCCESS(Status)) {
-        ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
-        ExFreePool(utf16);
-        goto end;
-    }
+    acquire_tree_lock(fcb->Vcb, FALSE);
     
-    if (utf16[0] == '\\') { // absolute path
-        abspath.MaximumLength = abspath.Length = stringlen;
-        abspath.Buffer = utf16;
-    } else { // relative path
-        abspath.MaximumLength = fcb->par->full_filename.Length + sizeof(WCHAR) + stringlen;
-        abspath.Buffer = ExAllocatePoolWithTag(PagedPool, abspath.MaximumLength, ALLOC_TAG);
-        if (!abspath.Buffer) {
+    if (fcb->type == BTRFS_TYPE_SYMLINK) {
+        data = ExAllocatePoolWithTag(PagedPool, fcb->inode_item.st_size, ALLOC_TAG);
+        if (!data) {
             ERR("out of memory\n");
-            ExFreePool(utf16);
+            Status = STATUS_INSUFFICIENT_RESOURCES;
             goto end;
         }
         
-        RtlCopyMemory(abspath.Buffer, fcb->par->full_filename.Buffer, fcb->par->full_filename.Length);
-        abspath.Length = fcb->par->full_filename.Length;
+        TRACE("data = %p, size = %x\n", data, fcb->inode_item.st_size);
+        Status = read_file(DeviceObject->DeviceExtension, fcb->subvol, fcb->inode, (UINT8*)data, 0, fcb->inode_item.st_size, NULL);
         
-        if (fcb->par->par) {
-            abspath.Buffer[abspath.Length / sizeof(WCHAR)] = '\\';
-            abspath.Length += sizeof(WCHAR);
+        if (!NT_SUCCESS(Status)) {
+            ERR("read_file returned %08x\n", Status);
+            ExFreePool(data);
+            goto end;
         }
         
-        RtlCopyMemory(&abspath.Buffer[abspath.Length / sizeof(WCHAR)], utf16, stringlen);
-        abspath.Length += stringlen;
+        Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, data, fcb->inode_item.st_size);
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+            ExFreePool(data);
+            goto end;
+        }
         
-        ExFreePool(utf16);
-    }
-
-    Status = FsRtlRemoveDotsFromPath(abspath.Buffer, abspath.Length, &newlen);
-    if (!NT_SUCCESS(Status)) {
-        ERR("FsRtlRemoveDotsFromPath returned %08x\n", Status);
-        ExFreePool(abspath.Buffer);
-        goto end;
-    }
-    
-    abspath.Length = newlen;
-    
-    TRACE("abspath = %.*S\n", abspath.Length / sizeof(WCHAR), abspath.Buffer);
-    
-    TRACE("filename was %.*S\n", FileObject->FileName.Length / sizeof(WCHAR), FileObject->FileName.Buffer);
-    
-    if (FileObject->FileName.MaximumLength < oni->Name.Length + abspath.Length) {
-        ExFreePool(FileObject->FileName.Buffer);
-        FileObject->FileName.MaximumLength = oni->Name.Length + abspath.Length;
-        FileObject->FileName.Buffer = ExAllocatePoolWithTag(PagedPool, FileObject->FileName.MaximumLength, ALLOC_TAG);
+        subnamelen = stringlen;
+        printnamelen = stringlen;
         
-        if (!FileObject->FileName.Buffer) {
-            ERR("out of memory\n");
-            ExFreePool(abspath.Buffer);
+        reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;
+        
+        if (buflen < reqlen) {
+            Status = STATUS_BUFFER_OVERFLOW;
             goto end;
         }
-    }
-    
-    RtlCopyMemory(FileObject->FileName.Buffer, oni->Name.Buffer, oni->Name.Length);
-    RtlCopyMemory(&FileObject->FileName.Buffer[oni->Name.Length / sizeof(WCHAR)], abspath.Buffer, abspath.Length);
-    FileObject->FileName.Length = oni->Name.Length + abspath.Length;
-    
-    TRACE("filename now %.*S\n", FileObject->FileName.Length / sizeof(WCHAR), FileObject->FileName.Buffer);
-    
-    ExFreePool(abspath.Buffer);
-    
-    success = TRUE;
-end:
-    ExFreePool(data);
-    ExFreePool(oni);
-    
-    return success;
-}
-
-NTSTATUS get_reparse_point(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject, void* buffer, DWORD buflen, DWORD* retlen) {
-    USHORT subnamelen, printnamelen, i;
-    ULONG stringlen;
-    DWORD reqlen;
-    REPARSE_DATA_BUFFER* rdb = buffer;
-    fcb* fcb = FileObject->FsContext;
-    char* data;
-    NTSTATUS Status;
-    
-    TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject, FileObject, buffer, buflen, retlen);
-    
-    acquire_tree_lock(fcb->Vcb, FALSE);
-    
-    if (fcb->type != BTRFS_TYPE_SYMLINK) {
-        Status = STATUS_NOT_A_REPARSE_POINT;
-        goto end;
-    }
-    
-    data = ExAllocatePoolWithTag(PagedPool, fcb->inode_item.st_size, ALLOC_TAG);
-    if (!data) {
-        ERR("out of memory\n");
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto end;
-    }
-    
-    TRACE("data = %p, size = %x\n", data, fcb->inode_item.st_size);
-    Status = read_file(DeviceObject->DeviceExtension, fcb->subvol, fcb->inode, (UINT8*)data, 0, fcb->inode_item.st_size, NULL);
-    
-    if (!NT_SUCCESS(Status)) {
-        ERR("read_file returned %08x\n", Status);
-        ExFreePool(data);
-        goto end;
-    }
-    
-    Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, data, fcb->inode_item.st_size);
-    if (!NT_SUCCESS(Status)) {
-        ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
-        ExFreePool(data);
-        goto end;
-    }
-    
-    subnamelen = stringlen;
-    printnamelen = stringlen;
-    
-    reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;
-    
-    if (buflen < reqlen) {
-        Status = STATUS_BUFFER_OVERFLOW;
-        goto end;
-    }
-    
-    rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
-    rdb->ReparseDataLength = reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer);
-    rdb->Reserved = 0;
-    
-    rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
-    rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;
-    rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;
-    rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;
-    rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
-    
-    Status = RtlUTF8ToUnicodeN(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
-                               stringlen, &stringlen, data, fcb->inode_item.st_size);
+        
+        rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
+        rdb->ReparseDataLength = reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer);
+        rdb->Reserved = 0;
+        
+        rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
+        rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;
+        rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;
+        rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;
+        rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
+        
+        Status = RtlUTF8ToUnicodeN(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
+                                stringlen, &stringlen, data, fcb->inode_item.st_size);
 
-    if (!NT_SUCCESS(Status)) {
-        ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+            ExFreePool(data);
+            goto end;
+        }
+        
+        for (i = 0; i < stringlen / sizeof(WCHAR); i++) {
+            if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')
+                rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\';
+        }
+        
+        RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],
+                    &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
+                    rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
+        
+        *retlen = reqlen;
+        
         ExFreePool(data);
-        goto end;
-    }
-      
-    for (i = 0; i < stringlen / sizeof(WCHAR); i++) {
-        if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')
-            rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\';
+        
+        Status = STATUS_SUCCESS;
+    } else if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {
+        if (fcb->type == BTRFS_TYPE_FILE) {
+            Status = read_file(DeviceObject->DeviceExtension, fcb->subvol, fcb->inode, buffer, 0, buflen, retlen);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("read_file returned %08x\n", Status);
+            }
+        } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
+            UINT8* data;
+            UINT16 datalen;
+            
+            if (!get_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_REPARSE, EA_REPARSE_HASH, &data, &datalen)) {
+                Status = STATUS_NOT_A_REPARSE_POINT;
+                goto end;
+            }
+            
+            if (!data) {
+                Status = STATUS_NOT_A_REPARSE_POINT;
+                goto end;
+            }
+            
+            if (datalen < sizeof(ULONG)) {
+                ExFreePool(data);
+                Status = STATUS_NOT_A_REPARSE_POINT;
+                goto end;
+            }
+            
+            if (buflen > 0) {
+                *retlen = min(buflen, datalen);
+                RtlCopyMemory(buffer, data, *retlen);
+            } else
+                *retlen = 0;
+            
+            ExFreePool(data);
+        } else
+            Status = STATUS_NOT_A_REPARSE_POINT;
+    } else {
+        Status = STATUS_NOT_A_REPARSE_POINT;
     }
     
-    RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],
-                  &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
-                  rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
-    
-    *retlen = reqlen;
-    
-    ExFreePool(data);
-    
-    Status = STATUS_SUCCESS;
-    
 end:
     release_tree_lock(fcb->Vcb, FALSE);
 
@@ -278,7 +173,6 @@ static NTSTATUS change_file_type(device_extension* Vcb, UINT64 inode, root* subv
             
             if (!di) {
                 ERR("out of memory\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INSUFFICIENT_RESOURCES;
             }
             
@@ -314,8 +208,6 @@ static NTSTATUS change_file_type(device_extension* Vcb, UINT64 inode, root* subv
         WARN("search for DIR_ITEM by crc32 failed\n");
     }
 
-    free_traverse_ptr(&tp);
-
     searchkey.obj_id = parinode;
     searchkey.obj_type = TYPE_DIR_INDEX;
     searchkey.offset = index;
@@ -334,7 +226,6 @@ static NTSTATUS change_file_type(device_extension* Vcb, UINT64 inode, root* subv
             DIR_ITEM* di2 = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
             if (!di2) {
                 ERR("out of memory\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INSUFFICIENT_RESOURCES;
             }
             
@@ -346,85 +237,24 @@ static NTSTATUS change_file_type(device_extension* Vcb, UINT64 inode, root* subv
         }
     }
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
-NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
-    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
-    PFILE_OBJECT FileObject = IrpSp->FileObject;
-    void* buffer = Irp->AssociatedIrp.SystemBuffer;
-    DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
-    NTSTATUS Status = STATUS_SUCCESS;
-    fcb* fcb;
-    ULONG tag, minlen;
+static NTSTATUS set_symlink(PIRP Irp, fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    ULONG minlen;
     UNICODE_STRING subname;
     ANSI_STRING target;
-    REPARSE_DATA_BUFFER* rdb = buffer;
     KEY searchkey;
     traverse_ptr tp, next_tp;
     BOOL b;
     LARGE_INTEGER offset;
     USHORT i;
-    LIST_ENTRY rollback;
-    
-    // FIXME - send notification if this succeeds? The attributes will have changed.
-    
-    TRACE("(%p, %p)\n", DeviceObject, Irp);
-    
-    InitializeListHead(&rollback);
-    
-    if (!FileObject) {
-        ERR("FileObject was NULL\n");
-        return STATUS_INVALID_PARAMETER;
-    }
-    
-    fcb = FileObject->FsContext;
-    
-    ERR("filename: %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
-    
-    acquire_tree_lock(fcb->Vcb, TRUE);
-    
-    if (fcb->type == BTRFS_TYPE_SYMLINK) {
-        WARN("tried to set a reparse point on an existing symlink\n");
-        Status = STATUS_INVALID_PARAMETER;
-        goto end;
-    }
-    
-    if (!fcb->utf8.Buffer) {
-        ERR("error - utf8 on FCB not set\n");
-        Status = STATUS_INTERNAL_ERROR;
-        goto end;
-    }
-    
-    // FIXME - die if not file
-    // FIXME - die if ADS
-    
-    if (buflen < sizeof(ULONG)) {
-        WARN("buffer was not long enough to hold tag\n");
-        Status = STATUS_INVALID_PARAMETER;
-        goto end;
-    }
-    
-    RtlCopyMemory(&tag, buffer, sizeof(ULONG));
-    
-    if (tag != IO_REPARSE_TAG_SYMLINK) {
-        FIXME("FIXME: handle arbitrary reparse tags (%08x)\n", tag);
-        Status = STATUS_NOT_IMPLEMENTED;
-        goto end;
-    }
     
     minlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + sizeof(WCHAR);
     if (buflen < minlen) {
         WARN("buffer was less than minimum length (%u < %u)\n", buflen, minlen);
-        Status = STATUS_INVALID_PARAMETER;
-        goto end;
-    }
-    
-    if (!(rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE)) {
-        FIXME("FIXME: support non-relative symlinks\n");
-        Status = STATUS_NOT_IMPLEMENTED;
+        return STATUS_INVALID_PARAMETER;
     }
     
     subname.Buffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)];
@@ -441,7 +271,7 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        goto end;
+        return Status;
     }
     
     do {
@@ -464,11 +294,11 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                     utf8.Buffer = ir->name;
                     utf8.Length = utf8.MaximumLength = ir->n;
                     
-                    Status = change_file_type(fcb->Vcb, fcb->inode, fcb->subvol, tp.item->key.offset, ir->index, &utf8, BTRFS_TYPE_SYMLINK, &rollback);
+                    Status = change_file_type(fcb->Vcb, fcb->inode, fcb->subvol, tp.item->key.offset, ir->index, &utf8, BTRFS_TYPE_SYMLINK, rollback);
                     
                     if (!NT_SUCCESS(Status)) {
                         ERR("error - change_file_type returned %08x\n", Status);
-                        goto end;
+                        return Status;
                     }
                     
                     if (size > sizeof(INODE_REF) - 1 + ir->n) {
@@ -483,15 +313,12 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         
         b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             b = tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_INODE_REF;
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     if (fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
         searchkey.obj_id = fcb->inode;
         searchkey.obj_type = TYPE_INODE_EXTREF;
@@ -500,7 +327,7 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
-            goto end;
+            return Status;
         }
         
         do {
@@ -523,11 +350,11 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
                         utf8.Buffer = ier->name;
                         utf8.Length = utf8.MaximumLength = ier->n;
                         
-                        Status = change_file_type(fcb->Vcb, fcb->inode, fcb->subvol, ier->dir, ier->index, &utf8, BTRFS_TYPE_SYMLINK, &rollback);
+                        Status = change_file_type(fcb->Vcb, fcb->inode, fcb->subvol, ier->dir, ier->index, &utf8, BTRFS_TYPE_SYMLINK, rollback);
                         
                         if (!NT_SUCCESS(Status)) {
                             ERR("error - change_file_type returned %08x\n", Status);
-                            goto end;
+                            return Status;
                         }
                         
                         if (size > sizeof(INODE_EXTREF) - 1 + ier->n) {
@@ -542,42 +369,39 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
             
             b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
             if (b) {
-                free_traverse_ptr(&tp);
                 tp = next_tp;
                 
                 b = tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_INODE_EXTREF;
             }
         } while (b);
-        
-        free_traverse_ptr(&tp);
     }
     
     fcb->inode_item.st_mode |= __S_IFLNK;
     
-    Status = truncate_file(fcb, 0, &rollback);
+    Status = truncate_file(fcb, 0, rollback);
     if (!NT_SUCCESS(Status)) {
         ERR("truncate_file returned %08x\n", Status);
-        goto end;
+        return Status;
     }
     
     Status = RtlUnicodeToUTF8N(NULL, 0, (PULONG)&target.Length, subname.Buffer, subname.Length);
     if (!NT_SUCCESS(Status)) {
         ERR("RtlUnicodeToUTF8N 3 failed with error %08x\n", Status);
-        goto end;
+        return Status;
     }
     
     target.MaximumLength = target.Length;
     target.Buffer = ExAllocatePoolWithTag(PagedPool, target.MaximumLength, ALLOC_TAG);
     if (!target.Buffer) {
         ERR("out of memory\n");
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto end;
+        return STATUS_INSUFFICIENT_RESOURCES;
     }
     
     Status = RtlUnicodeToUTF8N(target.Buffer, target.Length, (PULONG)&target.Length, subname.Buffer, subname.Length);
     if (!NT_SUCCESS(Status)) {
         ERR("RtlUnicodeToUTF8N 4 failed with error %08x\n", Status);
-        goto end;
+        ExFreePool(target.Buffer);
+        return Status;
     }
     
     for (i = 0; i < target.Length; i++) {
@@ -586,10 +410,427 @@ NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     }
     
     offset.QuadPart = 0;
-    Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, (ULONG*)&target.Length, Irp->Flags & IRP_PAGING_IO, Irp->Flags & IRP_NOCACHE, &rollback);
+    Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, (ULONG*)&target.Length, Irp->Flags & IRP_PAGING_IO, Irp->Flags & IRP_NOCACHE, rollback);
     
     ExFreePool(target.Buffer);
     
+    return Status;
+}
+
+static NTSTATUS delete_symlink(fcb* fcb, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_REF;
+    searchkey.offset = 0;
+    
+    Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    do {
+        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_INODE_REF) {
+            if (tp.item->size < sizeof(INODE_REF)) {
+                WARN("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_REF));
+            } else {
+                INODE_REF* ir;
+                ULONG size = tp.item->size;
+                ANSI_STRING utf8;
+                
+                ir = (INODE_REF*)tp.item->data;
+                
+                do {
+                    if (size < sizeof(INODE_REF) || size < sizeof(INODE_REF) - 1 + ir->n) {
+                        WARN("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                        break;
+                    }
+                    
+                    utf8.Buffer = ir->name;
+                    utf8.Length = utf8.MaximumLength = ir->n;
+                    
+                    Status = change_file_type(fcb->Vcb, fcb->inode, fcb->subvol, tp.item->key.offset, ir->index, &utf8, BTRFS_TYPE_FILE, rollback);
+                    
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("error - change_file_type returned %08x\n", Status);
+                        return Status;
+                    }
+                    
+                    if (size > sizeof(INODE_REF) - 1 + ir->n) {
+                        size -= sizeof(INODE_REF) - 1 + ir->n;
+                        
+                        ir = (INODE_REF*)&ir->name[ir->n];
+                    } else
+                        break;
+                } while (TRUE);
+            }
+        }
+        
+        b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
+        if (b) {
+            tp = next_tp;
+            
+            b = tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_INODE_REF;
+        }
+    } while (b);
+    
+    if (fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_INODE_EXTREF;
+        searchkey.offset = 0;
+        
+        Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        do {
+            if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_INODE_EXTREF) {
+                if (tp.item->size < sizeof(INODE_EXTREF)) {
+                    WARN("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_EXTREF));
+                } else {
+                    INODE_EXTREF* ier;
+                    ULONG size = tp.item->size;
+                    ANSI_STRING utf8;
+                    
+                    ier = (INODE_EXTREF*)tp.item->data;
+                    
+                    do {
+                        if (size < sizeof(INODE_EXTREF) || size < sizeof(INODE_EXTREF) - 1 + ier->n) {
+                            WARN("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                            break;
+                        }
+                        
+                        utf8.Buffer = ier->name;
+                        utf8.Length = utf8.MaximumLength = ier->n;
+                        
+                        Status = change_file_type(fcb->Vcb, fcb->inode, fcb->subvol, ier->dir, ier->index, &utf8, BTRFS_TYPE_FILE, rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("error - change_file_type returned %08x\n", Status);
+                            return Status;
+                        }
+                        
+                        if (size > sizeof(INODE_EXTREF) - 1 + ier->n) {
+                            size -= sizeof(INODE_EXTREF) - 1 + ier->n;
+                            
+                            ier = (INODE_EXTREF*)&ier->name[ier->n];
+                        } else
+                            break;
+                    } while (TRUE);
+                }
+            }
+            
+            b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
+            if (b) {
+                tp = next_tp;
+                
+                b = tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_INODE_EXTREF;
+            }
+        } while (b);
+    }   
+    
+    Status = truncate_file(fcb, 0, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("truncate_file returned %08x\n", Status);
+        return Status;
+    }
+    
+    KeQuerySystemTime(&time);
+    
+    win_time_to_unix(time, &now);
+    
+    fcb->type = BTRFS_TYPE_FILE;
+    fcb->inode_item.st_mode &= ~__S_IFLNK;
+    fcb->inode_item.st_mode |= __S_IFREG;
+    fcb->inode_item.transid = fcb->Vcb->superblock.generation;
+    fcb->inode_item.sequence++;
+    fcb->inode_item.st_ctime = now;
+    fcb->inode_item.st_mtime = now;
+
+    Status = update_inode_item(fcb->Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("update_inode_item returned %08x\n", Status);
+        return Status;
+    }
+
+    fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = now;
+    
+    return Status;
+}
+
+NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    void* buffer = Irp->AssociatedIrp.SystemBuffer;
+    REPARSE_DATA_BUFFER* rdb = buffer;
+    DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
+    NTSTATUS Status = STATUS_SUCCESS;
+    fcb* fcb;
+    ULONG tag;
+    LIST_ENTRY rollback;
+    
+    // FIXME - send notification if this succeeds? The attributes will have changed.
+    // FIXME - check permissions
+    
+    TRACE("(%p, %p)\n", DeviceObject, Irp);
+    
+    InitializeListHead(&rollback);
+    
+    if (!FileObject) {
+        ERR("FileObject was NULL\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
+    fcb = FileObject->FsContext;
+    
+    TRACE("%S\n", file_desc(FileObject));
+    
+    acquire_tree_lock(fcb->Vcb, TRUE);
+    
+    if (fcb->type == BTRFS_TYPE_SYMLINK) {
+        WARN("tried to set a reparse point on an existing symlink\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
+    
+    // FIXME - die if not file or directory
+    // FIXME - die if ADS
+    
+    if (buflen < sizeof(ULONG)) {
+        WARN("buffer was not long enough to hold tag\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    RtlCopyMemory(&tag, buffer, sizeof(ULONG));
+    
+    if (fcb->type == BTRFS_TYPE_FILE && tag == IO_REPARSE_TAG_SYMLINK && rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) {
+        Status = set_symlink(Irp, fcb, rdb, buflen, &rollback);
+    } else {
+        LARGE_INTEGER offset;
+        char val[64];
+        
+        if (fcb->type == BTRFS_TYPE_DIRECTORY) { // for directories, store as xattr
+            Status = set_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_REPARSE, EA_REPARSE_HASH, buffer, buflen, &rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("set_xattr returned %08x\n", Status);
+                goto end;
+            }
+        } else { // otherwise, store as file data
+            Status = truncate_file(fcb, 0, &rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("truncate_file returned %08x\n", Status);
+                goto end;
+            }
+            
+            offset.QuadPart = 0;
+            
+            Status = write_file2(fcb->Vcb, Irp, offset, buffer, &buflen, Irp->Flags & IRP_PAGING_IO, Irp->Flags & IRP_NOCACHE, &rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("write_file2 returned %08x\n", Status);
+                goto end;
+            }
+        }
+            
+        fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
+        
+        sprintf(val, "0x%lx", fcb->atts);
+    
+        Status = set_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), &rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("set_xattr returned %08x\n", Status);
+            goto end;
+        }
+    }
+    
+    if (NT_SUCCESS(Status))
+        Status = consider_write(fcb->Vcb);
+    
+end:
+    if (NT_SUCCESS(Status))
+        clear_rollback(&rollback);
+    else
+        do_rollback(fcb->Vcb, &rollback);
+
+    release_tree_lock(fcb->Vcb, TRUE);
+    
+    return Status;
+}
+
+NTSTATUS delete_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer;
+    DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
+    NTSTATUS Status;
+    fcb* fcb;
+    ccb* ccb;
+    file_ref* fileref;
+    LIST_ENTRY rollback;
+    
+    // FIXME - send notification if this succeeds? The attributes will have changed.
+    // FIXME - check permissions
+    
+    TRACE("(%p, %p)\n", DeviceObject, Irp);
+    
+    InitializeListHead(&rollback);
+    
+    if (!FileObject) {
+        ERR("FileObject was NULL\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
+    fcb = FileObject->FsContext;
+    ccb = FileObject->FsContext2;
+    fileref = ccb ? ccb->fileref : NULL;
+    
+    TRACE("%S\n", file_desc(FileObject));
+    
+    if (!fileref) {
+        ERR("fileref was NULL\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
+    if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) {
+        ERR("buffer was too short\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
+    if (rdb->ReparseDataLength > 0) {
+        WARN("rdb->ReparseDataLength was not zero\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
+    acquire_tree_lock(fcb->Vcb, TRUE);
+    
+    if (fcb->ads) {
+        WARN("tried to delete reparse point on ADS\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    if (fcb->type == BTRFS_TYPE_SYMLINK) {
+        if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
+            WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
+            Status = STATUS_INVALID_PARAMETER;
+            goto end;
+        }
+        
+        Status = delete_symlink(fcb, &rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("delete_symlink returned %08x\n", Status);
+            goto end;
+        }
+    } else if (fcb->type == BTRFS_TYPE_FILE) {
+        LARGE_INTEGER time;
+        BTRFS_TIME now;
+        ULONG defda;
+        
+        // FIXME - do we need to check that the reparse tags match?
+        
+        Status = truncate_file(fcb, 0, &rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("truncate_file returned %08x\n", Status);
+            goto end;
+        }
+        
+        fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
+        
+        defda = get_file_attributes(fcb->Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, fileref->filepart.Length > 0 && fileref->filepart.Buffer[0] == '.', TRUE);
+        
+        if (defda != fcb->atts) {
+            char val[64];
+            
+            sprintf(val, "0x%lx", fcb->atts);
+        
+            Status = set_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), &rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("set_xattr returned %08x\n", Status);
+                goto end;
+            }
+        } else
+            delete_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, &rollback);
+        
+        KeQuerySystemTime(&time);
+        
+        win_time_to_unix(time, &now);
+        
+        fcb->inode_item.transid = fcb->Vcb->superblock.generation;
+        fcb->inode_item.sequence++;
+        fcb->inode_item.st_ctime = now;
+        fcb->inode_item.st_mtime = now;
+
+        Status = update_inode_item(fcb->Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, &rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("update_inode_item returned %08x\n", Status);
+            goto end;
+        }
+
+        fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+        fcb->subvol->root_item.ctime = now;
+    } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
+        LARGE_INTEGER time;
+        BTRFS_TIME now;
+        ULONG defda;
+        
+        // FIXME - do we need to check that the reparse tags match?
+        
+        fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
+        
+        defda = get_file_attributes(fcb->Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, fileref->filepart.Length > 0 && fileref->filepart.Buffer[0] == '.', TRUE);
+        defda |= FILE_ATTRIBUTE_DIRECTORY;
+        
+        if (defda != fcb->atts) {
+            char val[64];
+            
+            sprintf(val, "0x%lx", fcb->atts);
+        
+            Status = set_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), &rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("set_xattr returned %08x\n", Status);
+                goto end;
+            }
+        } else
+            delete_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, &rollback);
+        
+        delete_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_REPARSE, EA_REPARSE_HASH, &rollback);
+        
+        KeQuerySystemTime(&time);
+        
+        win_time_to_unix(time, &now);
+        
+        fcb->inode_item.transid = fcb->Vcb->superblock.generation;
+        fcb->inode_item.sequence++;
+        fcb->inode_item.st_ctime = now;
+        fcb->inode_item.st_mtime = now;
+
+        Status = update_inode_item(fcb->Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, &rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("update_inode_item returned %08x\n", Status);
+            goto end;
+        }
+
+        fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+        fcb->subvol->root_item.ctime = now;
+    } else {
+        ERR("unsupported file type %u\n", fcb->type);
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    Status = STATUS_SUCCESS;
+    
     if (NT_SUCCESS(Status))
         Status = consider_write(fcb->Vcb);
     
index 0eb1c2a..009670c 100644 (file)
@@ -281,7 +281,7 @@ static void uid_to_sid(UINT32 uid, PSID* sid) {
     *sid = sh;
 }
 
-static UINT32 sid_to_uid(PSID sid) {
+UINT32 sid_to_uid(PSID sid) {
     LIST_ENTRY* le;
     uid_map* um;
     sid_header* sh = sid;
@@ -646,7 +646,7 @@ static BOOL get_sd_from_xattr(fcb* fcb) {
     return TRUE;
 }
 
-void fcb_get_sd(fcb* fcb) {
+void fcb_get_sd(fcb* fcb, struct _fcb* parent) {
     NTSTATUS Status;
     SECURITY_DESCRIPTOR sd;
     ULONG buflen;
@@ -694,10 +694,10 @@ void fcb_get_sd(fcb* fcb) {
         }
 //     }
     
-    if (!fcb->par)
+    if (!parent)
         acl = load_default_acl();
     else
-        acl = inherit_acl(fcb->par->sd, fcb->type != BTRFS_TYPE_DIRECTORY);
+        acl = inherit_acl(parent->sd, fcb->type != BTRFS_TYPE_DIRECTORY);
     
     if (!acl) {
         ERR("out of memory\n");
@@ -758,9 +758,17 @@ end:
 static NTSTATUS STDCALL get_file_security(device_extension* Vcb, PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* relsd, ULONG* buflen, SECURITY_INFORMATION flags) {
     NTSTATUS Status;
     fcb* fcb = FileObject->FsContext;
-    
-    if (fcb->ads)
-        fcb = fcb->par;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
+    
+    if (fcb->ads) {
+        if (fileref && fileref->parent)
+            fcb = fileref->parent->fcb;
+        else {
+            ERR("could not get parent fcb for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+    }
     
 //     TRACE("buflen = %u, fcb->sdlen = %u\n", *buflen, fcb->sdlen);
 
@@ -846,6 +854,8 @@ NTSTATUS STDCALL drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
 static NTSTATUS STDCALL set_file_security(device_extension* Vcb, PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* sd, SECURITY_INFORMATION flags) {
     NTSTATUS Status;
     fcb* fcb = FileObject->FsContext;
+    ccb* ccb = FileObject->FsContext2;
+    file_ref* fileref = ccb ? ccb->fileref : NULL;
     SECURITY_DESCRIPTOR* oldsd;
     INODE_ITEM* ii;
     KEY searchkey;
@@ -863,8 +873,14 @@ static NTSTATUS STDCALL set_file_security(device_extension* Vcb, PFILE_OBJECT Fi
     
     acquire_tree_lock(Vcb, TRUE);
     
-    if (fcb->ads)
-        fcb = fcb->par;
+    if (fcb->ads) {
+        if (fileref && fileref->parent)
+            fcb = fileref->parent->fcb;
+        else {
+            ERR("could not find parent fcb for stream\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+    }
     
     if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
         Status = STATUS_ACCESS_DENIED;
@@ -895,12 +911,10 @@ static NTSTATUS STDCALL set_file_security(device_extension* Vcb, PFILE_OBJECT Fi
     if (keycmp(&tp.item->key, &searchkey)) {
         ERR("error - could not find INODE_ITEM for inode %llx in subvol %llx\n", fcb->inode, fcb->subvol->id);
         Status = STATUS_INTERNAL_ERROR;
-        free_traverse_ptr(&tp);
         goto end;
     }
     
     delete_tree_item(Vcb, &tp, &rollback);
-    free_traverse_ptr(&tp);
     
     KeQuerySystemTime(&time);
     win_time_to_unix(time, &now);
@@ -1009,12 +1023,12 @@ NTSTATUS STDCALL drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
     return Status;
 }
 
-NTSTATUS fcb_get_new_sd(fcb* fcb, ACCESS_STATE* as) {
+NTSTATUS fcb_get_new_sd(fcb* fcb, file_ref* fileref, ACCESS_STATE* as) {
     NTSTATUS Status;
     PSID owner;
     BOOLEAN defaulted;
     
-    Status = SeAssignSecurity(fcb->par ? fcb->par->sd : NULL, as->SecurityDescriptor, (void**)&fcb->sd, fcb->type == BTRFS_TYPE_DIRECTORY,
+    Status = SeAssignSecurity((fileref && fileref->parent) ? fileref->parent->fcb->sd : NULL, as->SecurityDescriptor, (void**)&fcb->sd, fcb->type == BTRFS_TYPE_DIRECTORY,
                               &as->SubjectSecurityContext, IoGetFileObjectGenericMapping(), PagedPool);
     
     if (!NT_SUCCESS(Status)) {
index ecaf18a..8314887 100644 (file)
@@ -105,7 +105,7 @@ end:
     return STATUS_MORE_PROCESSING_REQUIRED;
 }
 
-static NTSTATUS STDCALL read_tree(device_extension* Vcb, UINT64 addr, UINT8* buf) {
+NTSTATUS STDCALL read_tree(device_extension* Vcb, UINT64 addr, UINT8* buf) {
     CHUNK_ITEM* ci;
     CHUNK_ITEM_STRIPE* cis;
     read_tree_context* context;
@@ -277,6 +277,17 @@ static NTSTATUS STDCALL read_tree(device_extension* Vcb, UINT64 addr, UINT8* buf
     
     // FIXME - if checksum error, write good data over bad
     
+    // check if any of the devices return a "user-induced" error
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status == ReadTreeStatus_Error && IoIsErrorUserInduced(context->stripes[i].iosb.Status)) {
+            IoSetHardErrorOrVerifyDevice(context->stripes[i].Irp, devices[i]->devobj);
+            
+            Status = context->stripes[i].iosb.Status;
+            goto exit;
+        }
+    }
+    
     // check if any of the stripes succeeded
     
     for (i = 0; i < ci->num_stripes; i++) {
@@ -385,7 +396,6 @@ NTSTATUS STDCALL _load_tree(device_extension* Vcb, UINT64 addr, root* r, tree**
     RtlCopyMemory(&t->header, th, sizeof(tree_header));
 //     t->address = addr;
 //     t->level = th->level;
-    t->refcount = 1;
     t->has_address = TRUE;
     t->Vcb = Vcb;
     t->parent = NULL;
@@ -395,13 +405,7 @@ NTSTATUS STDCALL _load_tree(device_extension* Vcb, UINT64 addr, root* r, tree**
     t->size = 0;
     t->new_address = 0;
     t->has_new_address = FALSE;
-#ifdef DEBUG_TREE_REFCOUNTS   
-#ifdef DEBUG_LONG_MESSAGES
-    _debug_message(func, file, line, "loaded tree %p (%llx)\n", t, addr);
-#else
-    _debug_message(func, "loaded tree %p (%llx)\n", t, addr);
-#endif
-#endif
+    t->write = FALSE;
     
     c = get_chunk_from_address(Vcb, addr);
     
@@ -494,85 +498,56 @@ NTSTATUS STDCALL _load_tree(device_extension* Vcb, UINT64 addr, root* r, tree**
 }
 
 static tree* free_tree2(tree* t, const char* func, const char* file, unsigned int line) {
-    LONG rc;
     LIST_ENTRY* le;
     tree_data* td;
     tree* par;
-    
-#ifdef DEBUG_TREE_REFCOUNTS
-    TRACE("(%p)\n", t);
-#endif
+    root* r = t->root;
     
     par = t->parent;
     
 //     if (par) ExAcquireResourceExclusiveLite(&par->nonpaged->load_tree_lock, TRUE);
     
-    rc = InterlockedDecrement(&t->refcount);
-    
-#ifdef DEBUG_TREE_REFCOUNTS
-#ifdef DEBUG_LONG_MESSAGES
-    _debug_message(func, file, line, "tree %p: refcount decreased to %i (free_tree2)\n", t, rc);
-#else
-    _debug_message(func, "tree %p: refcount decreased to %i (free_tree2)\n", t, rc);
-#endif
-#endif
-    
-    if (rc < 0) {
-        ERR("error - negative refcount (%i)\n", rc);
-        int3;
-    }
+    if (r && r->treeholder.tree != t)
+        r = NULL;
     
-    if (rc == 0) {
-        root* r = t->root;
-        if (r && r->treeholder.tree != t)
-            r = NULL;
-        
 //         if (r) {
 //             FsRtlEnterFileSystem();
 //             ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
 //         }
+    
+    if (par) {
+        if (t->paritem)
+            t->paritem->treeholder.tree = NULL;
         
-        if (par) {
-            if (t->paritem)
-                t->paritem->treeholder.tree = NULL;
-            
 //             ExReleaseResourceLite(&par->nonpaged->load_tree_lock);
-        }
-        
-        if (t->parent)
-            t->parent = free_tree2(t->parent, func, file, line);
-        
+    }
+    
 //         ExDeleteResourceLite(&t->nonpaged->load_tree_lock);
-        
+    
 //         ExFreePool(t->nonpaged);
+    
+    while (!IsListEmpty(&t->itemlist)) {
+        le = RemoveHeadList(&t->itemlist);
+        td = CONTAINING_RECORD(le, tree_data, list_entry);
         
-        while (!IsListEmpty(&t->itemlist)) {
-            le = RemoveHeadList(&t->itemlist);
-            td = CONTAINING_RECORD(le, tree_data, list_entry);
+        if (t->header.level == 0 && td->data)
+            ExFreePool(td->data);
             
-            if (t->header.level == 0 && td->data)
-                ExFreePool(td->data);
-             
-            ExFreePool(td);
-        }
-        
-        InterlockedDecrement(&t->Vcb->open_trees);
-        RemoveEntryList(&t->list_entry);
-        
-        if (r) {
-            r->treeholder.tree = NULL;
+        ExFreePool(td);
+    }
+    
+    InterlockedDecrement(&t->Vcb->open_trees);
+    RemoveEntryList(&t->list_entry);
+    
+    if (r) {
+        r->treeholder.tree = NULL;
 //             ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
 //             FsRtlExitFileSystem();
-        }
-        
-        ExFreePool(t);
-
-        return NULL;
-    } else {
-//         if (par) ExReleaseResourceLite(&par->nonpaged->load_tree_lock);
     }
     
-    return t;
+    ExFreePool(t);
+
+    return NULL;
 }
 
 NTSTATUS STDCALL _do_load_tree(device_extension* Vcb, tree_holder* th, root* r, tree* t, tree_data* td, BOOL* loaded, const char* func, const char* file, unsigned int line) {
@@ -623,11 +598,8 @@ NTSTATUS STDCALL _do_load_tree(device_extension* Vcb, tree_holder* th, root* r,
         th->tree->paritem = td;
         
         ret = TRUE;
-    } else {
-        _increase_tree_rc(th->tree, func, file, line);
-        
+    } else
         ret = FALSE;
-    }
     
 //     KeReleaseSpinLock(&thnp->spin_lock, irql);
     
@@ -726,13 +698,11 @@ static NTSTATUS STDCALL find_item_in_tree(device_extension* Vcb, tree* t, traver
             
             oldtp.tree = t;
             oldtp.item = td;
-            _increase_tree_rc(t, func, file, line);
             
             while (_find_prev_item(Vcb, &oldtp, tp, TRUE, func, file, line)) {
                 if (!tp->item->ignore)
                     return STATUS_SUCCESS;
                 
-                free_traverse_ptr(&oldtp);
                 oldtp = *tp;
             }
             
@@ -740,23 +710,18 @@ static NTSTATUS STDCALL find_item_in_tree(device_extension* Vcb, tree* t, traver
             
             oldtp.tree = t;
             oldtp.item = td;
-            _increase_tree_rc(t, func, file, line);
             
             while (_find_next_item(Vcb, &oldtp, tp, TRUE, func, file, line)) {
                 if (!tp->item->ignore)
                     return STATUS_SUCCESS;
                 
-                free_traverse_ptr(&oldtp);
                 oldtp = *tp;
             }
             
             return STATUS_INTERNAL_ERROR;
         } else {
             tp->tree = t;
-            _increase_tree_rc(t, func, file, line);
             tp->item = td;
-            
-            add_to_tree_cache(Vcb, t, FALSE);
         }
         
         return STATUS_SUCCESS;
@@ -780,14 +745,8 @@ static NTSTATUS STDCALL find_item_in_tree(device_extension* Vcb, tree* t, traver
             return Status;
         }
         
-        if (loaded)
-            _increase_tree_rc(t, func, file, line);
-        
         Status = find_item_in_tree(Vcb, td->treeholder.tree, tp, searchkey, ignore, func, file, line);
         
-        td->treeholder.tree = _free_tree(td->treeholder.tree, func, file, line);
-        TRACE("tree now %p\n", td->treeholder.tree);
-        
         return Status;
     }
 }
@@ -799,18 +758,18 @@ NTSTATUS STDCALL _find_item(device_extension* Vcb, root* r, traverse_ptr* tp, co
     
     TRACE("(%p, %p, %p, %p)\n", Vcb, r, tp, searchkey);
     
-    Status = _do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, &loaded, func, file, line);
-    if (!NT_SUCCESS(Status)) {
-        ERR("do_load_tree returned %08x\n", Status);
-        return Status;
+    if (!r->treeholder.tree) {
+        Status = _do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, &loaded, func, file, line);
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_load_tree returned %08x\n", Status);
+            return Status;
+        }
     }
 
     Status = find_item_in_tree(Vcb, r->treeholder.tree, tp, searchkey, ignore, func, file, line);
     if (!NT_SUCCESS(Status)) {
         ERR("find_item_in_tree returned %08x\n", Status);
     }
-
-    _free_tree(r->treeholder.tree, func, file, line);
     
 // #ifdef DEBUG_PARANOID
 //     if (b && !ignore && tp->item->ignore) {
@@ -822,12 +781,6 @@ NTSTATUS STDCALL _find_item(device_extension* Vcb, root* r, traverse_ptr* tp, co
     return Status;
 }
 
-void STDCALL _free_traverse_ptr(traverse_ptr* tp, const char* func, const char* file, unsigned int line) {
-    if (tp->tree) {
-        tp->tree = free_tree2(tp->tree, func, file, line);
-    }
-}
-
 BOOL STDCALL _find_next_item(device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* next_tp, BOOL ignore, const char* func, const char* file, unsigned int line) {
     tree* t;
     tree_data *td, *next;
@@ -843,7 +796,6 @@ BOOL STDCALL _find_next_item(device_extension* Vcb, const traverse_ptr* tp, trav
     
     if (next) {
         next_tp->tree = tp->tree;
-        _increase_tree_rc(next_tp->tree, func, file, line);
         next_tp->item = next;
         
 #ifdef DEBUG_PARANOID
@@ -879,9 +831,6 @@ BOOL STDCALL _find_next_item(device_extension* Vcb, const traverse_ptr* tp, trav
         return FALSE;
     }
     
-    if (loaded)
-        _increase_tree_rc(t->parent, func, file, line);
-    
     t = td->treeholder.tree;
     
     while (t->header.level != 0) {
@@ -906,21 +855,16 @@ BOOL STDCALL _find_next_item(device_extension* Vcb, const traverse_ptr* tp, trav
         BOOL b;
         
         while ((b = _find_next_item(Vcb, next_tp, &ntp2, TRUE, func, file, line))) {
-            _free_traverse_ptr(next_tp, func, file, line);
             *next_tp = ntp2;
             
             if (!next_tp->item->ignore)
                 break;
         }
         
-        if (!b) {
-            _free_traverse_ptr(next_tp, func, file, line);
+        if (!b)
             return FALSE;
-        }
     }
     
-    add_to_tree_cache(Vcb, t, FALSE);
-    
 #ifdef DEBUG_PARANOID
     if (!ignore && next_tp->item->ignore) {
         ERR("error - returning ignored item\n");
@@ -949,7 +893,6 @@ BOOL STDCALL _find_prev_item(device_extension* Vcb, const traverse_ptr* tp, trav
     // FIXME - support ignore flag
     if (prev_item(tp->tree, tp->item)) {
         prev_tp->tree = tp->tree;
-        _increase_tree_rc(prev_tp->tree, func, file, line);
         prev_tp->item = prev_item(tp->tree, tp->item);
 
         return TRUE;
@@ -974,9 +917,6 @@ BOOL STDCALL _find_prev_item(device_extension* Vcb, const traverse_ptr* tp, trav
         return FALSE;
     }
     
-    if (loaded)
-        _increase_tree_rc(t->parent, func, file, line);
-    
     t = td->treeholder.tree;
     
     while (t->header.level != 0) {
@@ -993,8 +933,6 @@ BOOL STDCALL _find_prev_item(device_extension* Vcb, const traverse_ptr* tp, trav
         t = li->treeholder.tree;
     }
     
-    add_to_tree_cache(Vcb, t, FALSE);
-    
     prev_tp->tree = t;
     prev_tp->item = last_item(t);
     
@@ -1013,74 +951,54 @@ BOOL STDCALL _find_prev_item(device_extension* Vcb, const traverse_ptr* tp, trav
 //     ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
 // }
 
-void STDCALL free_tree_cache(LIST_ENTRY* tc) {
+void free_trees_root(device_extension* Vcb, root* r) {
     LIST_ENTRY* le;
-    tree_cache* tc2;
-    root* r;
-
-    while (tc->Flink != tc) {
-        le = tc->Flink;
-        tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
-        r = tc2->tree->root;
+    UINT8 level;
+    
+    for (level = 0; level <= 255; level++) {
+        BOOL empty = TRUE;
         
-        ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
+        le = Vcb->trees.Flink;
         
-        while (le != tc) {
+        while (le != &Vcb->trees) {
             LIST_ENTRY* nextle = le->Flink;
-            tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+            tree* t = CONTAINING_RECORD(le, tree, list_entry);
             
-            if (tc2->tree->root == r) {
-                tree* nt;
-                BOOL top = !tc2->tree->paritem;
+            if (t->root == r && t->header.level == level) {
+                BOOL top = !t->paritem;
                 
-                nt = free_tree2(tc2->tree, funcname, __FILE__, __LINE__);
-                if (top && !nt && r->treeholder.tree == tc2->tree)
+                empty = FALSE;
+                
+                free_tree2(t, funcname, __FILE__, __LINE__);
+                if (top && r->treeholder.tree == t)
                     r->treeholder.tree = NULL;
                 
-                RemoveEntryList(&tc2->list_entry);
-                ExFreePool(tc2);
+                if (IsListEmpty(&Vcb->trees))
+                    return;
             }
             
             le = nextle;
         }
         
-        ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+        if (empty)
+            break;
     }
 }
 
-void STDCALL add_to_tree_cache(device_extension* Vcb, tree* t, BOOL write) {
-    LIST_ENTRY* le;
-    tree_cache* tc2;
-    
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+void STDCALL free_trees(device_extension* Vcb) {
+    tree* t;
+    root* r;
+
+    while (!IsListEmpty(&Vcb->trees)) {
+        t = CONTAINING_RECORD(Vcb->trees.Flink, tree, list_entry);
+        r = t->root;
         
-        if (tc2->tree == t) {
-            if (write && !tc2->write) {
-                Vcb->write_trees++;
-                tc2->write = TRUE;
-            }
-            return;
-        }
+        ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
         
-        le = le->Flink;
-    }
-    
-    tc2 = ExAllocatePoolWithTag(PagedPool, sizeof(tree_cache), ALLOC_TAG);
-    if (!tc2) {
-        ERR("out of memory\n");
-        return;
+        free_trees_root(Vcb, r);
+        
+        ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
     }
-    
-    TRACE("adding %p to tree cache\n", t);
-    
-    tc2->tree = t;
-    tc2->write = write;
-    increase_tree_rc(t);
-    InsertTailList(&Vcb->tree_cache, &tc2->list_entry);
-
-//     print_trees(tc);
 }
 
 static void add_rollback(LIST_ENTRY* rollback, enum rollback_type type, void* ptr) {
@@ -1152,7 +1070,6 @@ BOOL STDCALL insert_tree_item(device_extension* Vcb, root* r, UINT64 obj_id, UIN
         
         if (cmp == 0 && !tp.item->ignore) { // FIXME - look for all items of the same key to make sure none are non-ignored
             ERR("error: key (%llx,%x,%llx) already present\n", obj_id, obj_type, offset);
-            free_traverse_ptr(&tp);
             goto end;
         }
     } else
@@ -1161,7 +1078,6 @@ BOOL STDCALL insert_tree_item(device_extension* Vcb, root* r, UINT64 obj_id, UIN
     td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
     if (!td) {
         ERR("out of memory\n");
-        free_traverse_ptr(&tp);
         goto end;
     }
     
@@ -1205,11 +1121,12 @@ BOOL STDCALL insert_tree_item(device_extension* Vcb, root* r, UINT64 obj_id, UIN
 //     ERR("tree %p, num_items now %x\n", tp.tree, tp.tree->header.num_items);
 //     ERR("size now %x\n", tp.tree->size);
     
-    add_to_tree_cache(Vcb, tp.tree, TRUE);
+    if (!tp.tree->write) {
+        tp.tree->write = TRUE;
+        Vcb->write_trees++;
+    }
     
-    if (!ptp)
-        free_traverse_ptr(&tp);
-    else
+    if (ptp)
         *ptp = tp;
     
     t = tp.tree;
@@ -1276,7 +1193,10 @@ void STDCALL delete_tree_item(device_extension* Vcb, traverse_ptr* tp, LIST_ENTR
 
     tp->item->ignore = TRUE;
     
-    add_to_tree_cache(Vcb, tp->tree, TRUE);
+    if (!tp->tree->write) {
+        tp->tree->write = TRUE;
+        Vcb->write_trees++;
+    }
     
     tp->tree->header.num_items--;
     
index c4d2153..f723c63 100644 (file)
@@ -50,21 +50,8 @@ typedef struct {
     CHUNK_ITEM_STRIPE stripes[1];
 } CHUNK_ITEM2;
 
-typedef struct {
-    LIST_ENTRY list_entry;
-    UINT64 key;
-} ordered_list;
-
-typedef struct {
-    ordered_list ol;
-    ULONG length;
-    UINT32* checksums;
-    BOOL deleted;
-} changed_sector;
-
-static NTSTATUS convert_old_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback);
 static BOOL extent_item_is_shared(EXTENT_ITEM* ei, ULONG len);
-static NTSTATUS convert_shared_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback);
+static BOOL is_file_prealloc(fcb* fcb, UINT64 start_data, UINT64 end_data);
 
 static NTSTATUS STDCALL write_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
     write_context* context = conptr;
@@ -265,8 +252,8 @@ void add_to_space_list(chunk* c, UINT64 offset, UINT64 size, UINT8 type) {
         if (s->offset >= offset && s->offset + s->size <= offset + size) { // delete entirely
 #ifndef __REACTOS__
             RemoveEntryList(&s->list_entry);
-
 #endif
+            
             if (s->offset + s->size == offset + size) {
                 insbef = s->list_entry.Flink;
                 RemoveEntryList(&s->list_entry);
@@ -294,7 +281,7 @@ void add_to_space_list(chunk* c, UINT64 offset, UINT64 size, UINT8 type) {
         } else if (s->offset + s->size > offset && s->offset + s->size <= offset + size) { // truncate before
             s->size = offset - s->offset;
         } else if (s->offset < offset + size && s->offset + s->size > offset + size) { // truncate after
-            s->size -= s->offset - offset + size;
+            s->size -= offset + size - s->offset;
             s->offset = offset + size;
             
             insbef = le;
@@ -466,10 +453,8 @@ static UINT64 find_new_chunk_address(device_extension* Vcb, UINT64 size) {
             } else {
                 CHUNK_ITEM* ci = (CHUNK_ITEM*)tp.item->data;
                 
-                if (tp.item->key.offset >= lastaddr + size) {
-                    free_traverse_ptr(&tp);
+                if (tp.item->key.offset >= lastaddr + size)
                     return lastaddr;
-                }
                 
                 lastaddr = tp.item->key.offset + ci->size;
             }
@@ -477,7 +462,6 @@ static UINT64 find_new_chunk_address(device_extension* Vcb, UINT64 size) {
         
         b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
@@ -485,8 +469,6 @@ static UINT64 find_new_chunk_address(device_extension* Vcb, UINT64 size) {
         }
     } while (b);
     
-    free_traverse_ptr(&tp);
-    
     return lastaddr;
 }
 
@@ -508,14 +490,11 @@ static BOOL increase_dev_item_used(device_extension* Vcb, device* device, UINT64
     
     if (keycmp(&tp.item->key, &searchkey)) {
         ERR("error - could not find DEV_ITEM for device %llx\n", device->devitem.dev_id);
-        free_traverse_ptr(&tp);
         return FALSE;
     }
     
     delete_tree_item(Vcb, &tp, rollback);
     
-    free_traverse_ptr(&tp);
-    
     device->devitem.bytes_used += size;
     
     di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG);
@@ -663,7 +642,7 @@ static NTSTATUS add_to_bootstrap(device_extension* Vcb, UINT64 obj_id, UINT8 obj
     return STATUS_SUCCESS;
 }
 
-static chunk* alloc_chunk(device_extension* Vcb, UINT64 flags, LIST_ENTRY* rollback) {
+chunk* alloc_chunk(device_extension* Vcb, UINT64 flags, LIST_ENTRY* rollback) {
     UINT64 max_stripe_size, max_chunk_size, stripe_size;
     UINT64 total_size = 0, i, j, logaddr;
     int num_stripes;
@@ -851,6 +830,8 @@ static chunk* alloc_chunk(device_extension* Vcb, UINT64 flags, LIST_ENTRY* rollb
     c->offset = logaddr;
     c->used = c->oldused = 0;
     c->space_changed = FALSE;
+    c->cache_size = 0;
+    c->cache_inode = 0;
     InitializeListHead(&c->space);
     
     s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
@@ -949,19 +930,7 @@ end:
     return success ? c : NULL;
 }
 
-static void decrease_chunk_usage(chunk* c, UINT64 delta) {
-    c->used -= delta;
-    
-    TRACE("decreasing size of chunk %llx by %llx\n", c->offset, delta);
-}
-
-static void increase_chunk_usage(chunk* c, UINT64 delta) {
-    c->used += delta;
-    
-    TRACE("increasing size of chunk %llx by %llx\n", c->offset, delta);
-}
-
-static NTSTATUS STDCALL write_data(device_extension* Vcb, UINT64 address, void* data, UINT32 length) {
+NTSTATUS STDCALL write_data(device_extension* Vcb, UINT64 address, void* data, UINT32 length) {
     KEY searchkey;
     traverse_ptr tp;
     CHUNK_ITEM2* ci;
@@ -1014,7 +983,6 @@ static NTSTATUS STDCALL write_data(device_extension* Vcb, UINT64 address, void*
     }
     
 end:
-    free_traverse_ptr(&tp);
     
     return Status;
 }
@@ -1086,22 +1054,22 @@ static void clean_space_cache(device_extension* Vcb) {
     }
 }
 
-static BOOL trees_consistent(device_extension* Vcb) {
+static BOOL trees_consistent(device_extension* Vcb, LIST_ENTRY* rollback) {
     ULONG maxsize = Vcb->superblock.node_size - sizeof(tree_header);
     LIST_ENTRY* le;
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
         
-        if (tc2->write) {
-            if (tc2->tree->header.num_items == 0 && tc2->tree->parent)
+        if (t->write) {
+            if (t->header.num_items == 0 && t->parent)
                 return FALSE;
             
-            if (tc2->tree->size > maxsize)
+            if (t->size > maxsize)
                 return FALSE;
             
-            if (!tc2->tree->has_new_address)
+            if (!t->has_new_address)
                 return FALSE;
         }
         
@@ -1115,18 +1083,21 @@ static NTSTATUS add_parents(device_extension* Vcb, LIST_ENTRY* rollback) {
     LIST_ENTRY* le;
     NTSTATUS Status;
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
         
-        if (tc2->write) {
-            if (tc2->tree->parent)
-                add_to_tree_cache(Vcb, tc2->tree->parent, TRUE);
-            else if (tc2->tree->root != Vcb->chunk_root && tc2->tree->root != Vcb->root_root) {
+        if (t->write) {
+            if (t->parent) {
+                if (!t->parent->write) {
+                    t->parent->write = TRUE;
+                    Vcb->write_trees++;
+                }
+            } else if (t->root != Vcb->chunk_root && t->root != Vcb->root_root) {
                 KEY searchkey;
                 traverse_ptr tp;
                 
-                searchkey.obj_id = tc2->tree->root->id;
+                searchkey.obj_id = t->root->id;
                 searchkey.obj_type = TYPE_ROOT_ITEM;
                 searchkey.offset = 0xffffffffffffffff;
                 
@@ -1138,7 +1109,6 @@ static NTSTATUS add_parents(device_extension* Vcb, LIST_ENTRY* rollback) {
                 
                 if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
                     ERR("could not find ROOT_ITEM for tree %llx\n", searchkey.obj_id);
-                    free_traverse_ptr(&tp);
                     return STATUS_INTERNAL_ERROR;
                 }
                 
@@ -1156,15 +1126,16 @@ static NTSTATUS add_parents(device_extension* Vcb, LIST_ENTRY* rollback) {
                     
                     delete_tree_item(Vcb, &tp, rollback);
                     
-                    if (!insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, 0, ri, sizeof(ROOT_ITEM), NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         return STATUS_INTERNAL_ERROR;
                     }
                 } else {
-                    add_to_tree_cache(Vcb, tp.tree, TRUE);
+                    if (!tp.tree->write) {
+                        tp.tree->write = TRUE;
+                        Vcb->write_trees++;
+                    }
                 }
-                
-                free_traverse_ptr(&tp);
             }
         }
         
@@ -1174,32 +1145,6 @@ static NTSTATUS add_parents(device_extension* Vcb, LIST_ENTRY* rollback) {
     return STATUS_SUCCESS;
 }
 
-void print_trees(LIST_ENTRY* tc) {
-    LIST_ENTRY *le, *le2;
-    
-    le = tc->Flink;
-    while (le != tc) {
-        KEY firstitem = {0xcccccccccccccccc,0xcc,0xcccccccccccccccc};
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
-        UINT32 num_items = 0;
-        
-        le2 = tc2->tree->itemlist.Flink;
-        while (le2 != &tc2->tree->itemlist) {
-            tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
-            if (!td->ignore) {
-                firstitem = td->key;
-                num_items++;
-            }
-            le2 = le2->Flink;
-        }
-        
-        ERR("tree: root %llx, first key %llx,%x,%llx, level %x, num_items %x / %x\n",
-            tc2->tree->header.tree_id, firstitem.obj_id, firstitem.obj_type, firstitem.offset, tc2->tree->header.level, num_items, tc2->tree->header.num_items);
-        
-        le = le->Flink;
-    }
-}
-
 static void add_parents_to_cache(device_extension* Vcb, tree* t) {
     KEY searchkey;
     traverse_ptr tp;
@@ -1208,7 +1153,10 @@ static void add_parents_to_cache(device_extension* Vcb, tree* t) {
     while (t->parent) {
         t = t->parent;
         
-        add_to_tree_cache(Vcb, t, TRUE);
+        if (!t->write) {
+            t->write = TRUE;
+            Vcb->write_trees++;
+        }
     }
     
     if (t->root == Vcb->root_root || t->root == Vcb->chunk_root)
@@ -1226,16 +1174,16 @@ static void add_parents_to_cache(device_extension* Vcb, tree* t) {
     
     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
         ERR("could not find ROOT_ITEM for tree %llx\n", searchkey.obj_id);
-        free_traverse_ptr(&tp);
         return;
     }
     
-    add_to_tree_cache(Vcb, tp.tree, TRUE);
-    
-    free_traverse_ptr(&tp);
+    if (!tp.tree->write) {
+        tp.tree->write = TRUE;
+        Vcb->write_trees++;
+    }
 }
 
-static BOOL insert_tree_extent_skinny(device_extension* Vcb, tree* t, chunk* c, UINT64 address, LIST_ENTRY* rollback) {
+static BOOL insert_tree_extent_skinny(device_extension* Vcb, UINT8 level, UINT64 root_id, chunk* c, UINT64 address, LIST_ENTRY* rollback) {
     EXTENT_ITEM_SKINNY_METADATA* eism;
     traverse_ptr insert_tp;
     
@@ -1249,9 +1197,9 @@ static BOOL insert_tree_extent_skinny(device_extension* Vcb, tree* t, chunk* c,
     eism->ei.generation = Vcb->superblock.generation;
     eism->ei.flags = EXTENT_ITEM_TREE_BLOCK;
     eism->type = TYPE_TREE_BLOCK_REF;
-    eism->tbr.offset = t->header.tree_id;
+    eism->tbr.offset = root_id;
     
-    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, t->header.level, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, rollback)) {
+    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, rollback)) {
         ERR("insert_tree_item failed\n");
         ExFreePool(eism);
         return FALSE;
@@ -1259,29 +1207,29 @@ static BOOL insert_tree_extent_skinny(device_extension* Vcb, tree* t, chunk* c,
     
     add_to_space_list(c, address, Vcb->superblock.node_size, SPACE_TYPE_WRITING);
 
-//     add_to_tree_cache(tc, insert_tp.tree, TRUE);
     add_parents_to_cache(Vcb, insert_tp.tree);
     
-    free_traverse_ptr(&insert_tp);
-    
-    t->new_address = address;
-    t->has_new_address = TRUE;
-    
     return TRUE;
 }
 
-static BOOL insert_tree_extent(device_extension* Vcb, tree* t, chunk* c, LIST_ENTRY* rollback) {
+static BOOL insert_tree_extent(device_extension* Vcb, UINT8 level, UINT64 root_id, chunk* c, UINT64* new_address, LIST_ENTRY* rollback) {
     UINT64 address;
     EXTENT_ITEM_TREE2* eit2;
     traverse_ptr insert_tp;
     
-    TRACE("(%p, %p, %p, %p)\n", Vcb, t, c, rollback);
+    TRACE("(%p, %x, %llx, %p, %p, %p, %p)\n", Vcb, level, root_id, c, new_address, rollback);
     
     if (!find_address_in_chunk(Vcb, c, Vcb->superblock.node_size, &address))
         return FALSE;
     
-    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)
-        return insert_tree_extent_skinny(Vcb, t, c, address, rollback);
+    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {
+        BOOL b = insert_tree_extent_skinny(Vcb, level, root_id, c, address, rollback);
+        
+        if (b)
+            *new_address = address;
+        
+        return b;
+    }
     
     eit2 = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_TREE2), ALLOC_TAG);
     if (!eit2) {
@@ -1293,9 +1241,9 @@ static BOOL insert_tree_extent(device_extension* Vcb, tree* t, chunk* c, LIST_EN
     eit2->eit.extent_item.generation = Vcb->superblock.generation;
     eit2->eit.extent_item.flags = EXTENT_ITEM_TREE_BLOCK;
 //     eit2->eit.firstitem = wt->firstitem;
-    eit2->eit.level = t->header.level;
+    eit2->eit.level = level;
     eit2->type = TYPE_TREE_BLOCK_REF;
-    eit2->tbr.offset = t->header.tree_id;
+    eit2->tbr.offset = root_id;
     
 // #ifdef DEBUG_PARANOID
 //     if (wt->firstitem.obj_type == 0xcc) { // TESTING
@@ -1313,21 +1261,17 @@ static BOOL insert_tree_extent(device_extension* Vcb, tree* t, chunk* c, LIST_EN
     
     add_to_space_list(c, address, Vcb->superblock.node_size, SPACE_TYPE_WRITING);
 
-//     add_to_tree_cache(tc, insert_tp.tree, TRUE);
     add_parents_to_cache(Vcb, insert_tp.tree);
     
-    free_traverse_ptr(&insert_tp);
-    
-    t->new_address = address;
-    t->has_new_address = TRUE;
+    *new_address = address;
     
     return TRUE;
 }
 
-static NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, LIST_ENTRY* rollback) {
+NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, LIST_ENTRY* rollback) {
     chunk *origchunk = NULL, *c;
     LIST_ENTRY* le;
-    UINT64 flags = t->flags;
+    UINT64 flags = t->flags, addr;
     
     if (flags == 0)
         flags = (t->root->id == BTRFS_ROOT_CHUNK ? BLOCK_FLAG_SYSTEM : BLOCK_FLAG_METADATA) | BLOCK_FLAG_DUPLICATE;
@@ -1348,19 +1292,23 @@ static NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, LIST_ENTRY*
     if (t->has_address) {
         origchunk = get_chunk_from_address(Vcb, t->header.address);
         
-        if (insert_tree_extent(Vcb, t, origchunk, rollback))
+        if (insert_tree_extent(Vcb, t->header.level, t->header.tree_id, origchunk, &addr, rollback)) {
+            t->new_address = addr;
+            t->has_new_address = TRUE;
             return STATUS_SUCCESS;
+        }
     }
     
     le = Vcb->chunks.Flink;
     while (le != &Vcb->chunks) {
         c = CONTAINING_RECORD(le, chunk, list_entry);
         
-        // FIXME - make sure to avoid superblocks
-        
         if (c != origchunk && c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {
-            if (insert_tree_extent(Vcb, t, c, rollback))
+            if (insert_tree_extent(Vcb, t->header.level, t->header.tree_id, c, &addr, rollback)) {
+                t->new_address = addr;
+                t->has_new_address = TRUE;
                 return STATUS_SUCCESS;
+            }
         }
 
         le = le->Flink;
@@ -1369,8 +1317,11 @@ static NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, LIST_ENTRY*
     // allocate new chunk if necessary
     if ((c = alloc_chunk(Vcb, flags, rollback))) {
         if ((c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {
-            if (insert_tree_extent(Vcb, t, c, rollback))
+            if (insert_tree_extent(Vcb, t->header.level, t->header.tree_id, c, &addr, rollback)) {
+                t->new_address = addr;
+                t->has_new_address = TRUE;
                 return STATUS_SUCCESS;
+            }
         }
     }
     
@@ -1398,20 +1349,18 @@ static BOOL reduce_tree_extent_skinny(device_extension* Vcb, UINT64 address, tre
     
     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
         TRACE("could not find %llx,%x,%llx in extent_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
-        free_traverse_ptr(&tp);
         return FALSE;
     }
     
     if (tp.item->size < sizeof(EXTENT_ITEM_SKINNY_METADATA)) {
         ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_SKINNY_METADATA));
-        free_traverse_ptr(&tp);
         return FALSE;
     }
     
     delete_tree_item(Vcb, &tp, rollback);
     
     eism = (EXTENT_ITEM_SKINNY_METADATA*)tp.item->data;
-    if (t->header.level == 0 && eism->ei.flags & EXTENT_ITEM_SHARED_BACKREFS && eism->type == TYPE_TREE_BLOCK_REF) {
+    if (t && t->header.level == 0 && eism->ei.flags & EXTENT_ITEM_SHARED_BACKREFS && eism->type == TYPE_TREE_BLOCK_REF) {
         // convert shared data extents
         
         LIST_ENTRY* le = t->itemlist.Flink;
@@ -1450,8 +1399,6 @@ static BOOL reduce_tree_extent_skinny(device_extension* Vcb, UINT64 address, tre
     } else
         ERR("could not find chunk for address %llx\n", address);
     
-    free_traverse_ptr(&tp);
-    
     return TRUE;
 }
 
@@ -1508,7 +1455,6 @@ static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree*
     
     if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
         TRACE("could not find EXTENT_REF_V0 for %llx\n", searchkey.obj_id);
-        free_traverse_ptr(&tp);
         return;
     }
     
@@ -1519,21 +1465,16 @@ static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree*
     Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        free_traverse_ptr(&tp);
         return;
     }
     
     if (keycmp(&searchkey, &tp2.item->key)) {
         ERR("could not find %llx,%x,%llx\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
-        free_traverse_ptr(&tp2);
-        free_traverse_ptr(&tp);
         return;
     }
     
     if (tp.item->size < sizeof(EXTENT_REF_V0)) {
         ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_REF_V0));
-        free_traverse_ptr(&tp2);
-        free_traverse_ptr(&tp);
         return;
     }
     
@@ -1547,8 +1488,6 @@ static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree*
         
         if (!eism) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp2);
-            free_traverse_ptr(&tp);
             return;
         }
         
@@ -1560,8 +1499,6 @@ static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree*
         
         if (!insert_tree_item(Vcb, Vcb->extent_root, td->treeholder.address, TYPE_METADATA_ITEM, t->header.level -1, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, rollback)) {
             ERR("insert_tree_item failed\n");
-            free_traverse_ptr(&tp2);
-            free_traverse_ptr(&tp);
             return;
         }
     } else {
@@ -1569,8 +1506,6 @@ static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree*
         
         if (!eit2) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp2);
-            free_traverse_ptr(&tp);
             return;
         }
         
@@ -1584,21 +1519,13 @@ static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree*
 
         if (!insert_tree_item(Vcb, Vcb->extent_root, td->treeholder.address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, eit2, sizeof(EXTENT_ITEM_TREE2), &insert_tp, rollback)) {
             ERR("insert_tree_item failed\n");
-            free_traverse_ptr(&tp2);
-            free_traverse_ptr(&tp);
             return;
         }
     }
     
-//     add_to_tree_cache(tc, insert_tp.tree, TRUE);
     add_parents_to_cache(Vcb, insert_tp.tree);
     add_parents_to_cache(Vcb, tp.tree);
     add_parents_to_cache(Vcb, tp2.tree);
-    
-    free_traverse_ptr(&insert_tp);
-    
-    free_traverse_ptr(&tp2);
-    free_traverse_ptr(&tp);
 }
 
 static NTSTATUS reduce_tree_extent(device_extension* Vcb, UINT64 address, tree* t, LIST_ENTRY* rollback) {
@@ -1632,7 +1559,6 @@ static NTSTATUS reduce_tree_extent(device_extension* Vcb, UINT64 address, tree*
     if (keycmp(&tp.item->key, &searchkey)) {
         ERR("could not find %llx,%x,%llx in extent_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
         int3;
-        free_traverse_ptr(&tp);
         return STATUS_INTERNAL_ERROR;
     }
     
@@ -1641,13 +1567,11 @@ static NTSTATUS reduce_tree_extent(device_extension* Vcb, UINT64 address, tree*
         
         if (eiv0->refcount > 1) {
             FIXME("FIXME - cannot deal with refcounts larger than 1 at present (eiv0->refcount == %llx)\n", eiv0->refcount);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
     } else {
         if (tp.item->size < sizeof(EXTENT_ITEM)) {
             ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
         
@@ -1655,11 +1579,10 @@ static NTSTATUS reduce_tree_extent(device_extension* Vcb, UINT64 address, tree*
         
         if (ei->refcount > 1) {
             FIXME("FIXME - cannot deal with refcounts larger than 1 at present (ei->refcount == %llx)\n", ei->refcount);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
         
-        if (t->header.level == 0 && ei->flags & EXTENT_ITEM_SHARED_BACKREFS) {
+        if (t && t->header.level == 0 && ei->flags & EXTENT_ITEM_SHARED_BACKREFS) {
             // convert shared data extents
             
             LIST_ENTRY* le = t->itemlist.Flink;
@@ -1703,17 +1626,15 @@ static NTSTATUS reduce_tree_extent(device_extension* Vcb, UINT64 address, tree*
         Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
         if (!NT_SUCCESS(Status)) {
             ERR("error - find_item returned %08x\n", Status);
-            free_traverse_ptr(&tp);
             return Status;
         }
         
         if (tp2.item->key.obj_id == searchkey.obj_id && tp2.item->key.obj_type == searchkey.obj_type) {
             delete_tree_item(Vcb, &tp2, rollback);
         }
-        free_traverse_ptr(&tp2);
     }
      
-    if (!(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {
+    if (t && !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {
         LIST_ENTRY* le;
         
         // when writing old internal trees, convert related extents
@@ -1754,8 +1675,6 @@ static NTSTATUS reduce_tree_extent(device_extension* Vcb, UINT64 address, tree*
     } else
         ERR("could not find chunk for address %llx\n", address);
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_SUCCESS;
 }
 
@@ -1765,23 +1684,23 @@ static NTSTATUS allocate_tree_extents(device_extension* Vcb, LIST_ENTRY* rollbac
     
     TRACE("(%p)\n", Vcb);
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
         
-        if (tc2->write && !tc2->tree->has_new_address) {
+        if (t->write && !t->has_new_address) {
             chunk* c;
             
-            Status = get_tree_new_address(Vcb, tc2->tree, rollback);
+            Status = get_tree_new_address(Vcb, t, rollback);
             if (!NT_SUCCESS(Status)) {
                 ERR("get_tree_new_address returned %08x\n", Status);
                 return Status;
             }
             
-            TRACE("allocated extent %llx\n", tc2->tree->new_address);
+            TRACE("allocated extent %llx\n", t->new_address);
             
-            if (tc2->tree->has_address) {
-                Status = reduce_tree_extent(Vcb, tc2->tree->header.address, tc2->tree, rollback);
+            if (t->has_address) {
+                Status = reduce_tree_extent(Vcb, t->header.address, t, rollback);
                 
                 if (!NT_SUCCESS(Status)) {
                     ERR("reduce_tree_extent returned %08x\n", Status);
@@ -1789,12 +1708,12 @@ static NTSTATUS allocate_tree_extents(device_extension* Vcb, LIST_ENTRY* rollbac
                 }
             }
 
-            c = get_chunk_from_address(Vcb, tc2->tree->new_address);
+            c = get_chunk_from_address(Vcb, t->new_address);
             
             if (c) {
                 increase_chunk_usage(c, Vcb->superblock.node_size);
             } else {
-                ERR("could not find chunk for address %llx\n", tc2->tree->new_address);
+                ERR("could not find chunk for address %llx\n", t->new_address);
                 return STATUS_INTERNAL_ERROR;
             }
         }
@@ -1811,16 +1730,16 @@ static NTSTATUS update_root_root(device_extension* Vcb, LIST_ENTRY* rollback) {
     
     TRACE("(%p)\n", Vcb);
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
         
-        if (tc2->write && !tc2->tree->parent) {
-            if (tc2->tree->root != Vcb->root_root && tc2->tree->root != Vcb->chunk_root) {
+        if (t->write && !t->parent) {
+            if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) {
                 KEY searchkey;
                 traverse_ptr tp;
                 
-                searchkey.obj_id = tc2->tree->root->id;
+                searchkey.obj_id = t->root->id;
                 searchkey.obj_type = TYPE_ROOT_ITEM;
                 searchkey.offset = 0xffffffffffffffff;
                 
@@ -1832,16 +1751,15 @@ static NTSTATUS update_root_root(device_extension* Vcb, LIST_ENTRY* rollback) {
                 
                 if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
                     ERR("could not find ROOT_ITEM for tree %llx\n", searchkey.obj_id);
-                    free_traverse_ptr(&tp);
                     return STATUS_INTERNAL_ERROR;
                 }
                 
-                TRACE("updating the address for root %llx to %llx\n", searchkey.obj_id, tc2->tree->new_address);
+                TRACE("updating the address for root %llx to %llx\n", searchkey.obj_id, t->new_address);
                 
-                tc2->tree->root->root_item.block_number = tc2->tree->new_address;
-                tc2->tree->root->root_item.root_level = tc2->tree->header.level;
-                tc2->tree->root->root_item.generation = Vcb->superblock.generation;
-                tc2->tree->root->root_item.generation2 = Vcb->superblock.generation;
+                t->root->root_item.block_number = t->new_address;
+                t->root->root_item.root_level = t->header.level;
+                t->root->root_item.generation = Vcb->superblock.generation;
+                t->root->root_item.generation2 = Vcb->superblock.generation;
                 
                 if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, delete and create new entry
                     ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
@@ -1851,7 +1769,7 @@ static NTSTATUS update_root_root(device_extension* Vcb, LIST_ENTRY* rollback) {
                         return STATUS_INSUFFICIENT_RESOURCES;
                     }
                     
-                    RtlCopyMemory(ri, &tc2->tree->root->root_item, sizeof(ROOT_ITEM));
+                    RtlCopyMemory(ri, &t->root->root_item, sizeof(ROOT_ITEM));
                     
                     delete_tree_item(Vcb, &tp, rollback);
                     
@@ -1860,45 +1778,24 @@ static NTSTATUS update_root_root(device_extension* Vcb, LIST_ENTRY* rollback) {
                         return STATUS_INTERNAL_ERROR;
                     }
                 } else
-                    RtlCopyMemory(tp.item->data, &tc2->tree->root->root_item, sizeof(ROOT_ITEM));
-                
-                free_traverse_ptr(&tp);
+                    RtlCopyMemory(tp.item->data, &t->root->root_item, sizeof(ROOT_ITEM));
             }
             
-            tc2->tree->root->treeholder.address = tc2->tree->new_address;
+            t->root->treeholder.address = t->new_address;
         }
         
         le = le->Flink;
     }
     
+    Status = update_chunk_caches(Vcb, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("update_chunk_caches returned %08x\n", Status);
+        return Status;
+    }
+    
     return STATUS_SUCCESS;
 }
 
-enum write_tree_status {
-    WriteTreeStatus_Pending,
-    WriteTreeStatus_Success,
-    WriteTreeStatus_Error,
-    WriteTreeStatus_Cancelling,
-    WriteTreeStatus_Cancelled
-};
-
-struct write_tree_context;
-
-typedef struct {
-    struct write_tree_context* context;
-    UINT8* buf;
-    device* device;
-    PIRP Irp;
-    IO_STATUS_BLOCK iosb;
-    enum write_tree_status status;
-    LIST_ENTRY list_entry;
-} write_tree_stripe;
-
-typedef struct {
-    KEVENT Event;
-    LIST_ENTRY stripes;
-} write_tree_context;
-
 static NTSTATUS STDCALL write_tree_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
     write_tree_stripe* stripe = conptr;
     write_tree_context* context = (write_tree_context*)stripe->context;
@@ -1952,7 +1849,7 @@ end:
     return STATUS_MORE_PROCESSING_REQUIRED;
 }
 
-static NTSTATUS write_tree(device_extension* Vcb, UINT64 addr, UINT8* data, write_tree_context* wtc) {
+NTSTATUS write_tree(device_extension* Vcb, UINT64 addr, UINT8* data, write_tree_context* wtc) {
     chunk* c;
     CHUNK_ITEM_STRIPE* cis;
     write_tree_stripe* stripe;
@@ -2025,7 +1922,7 @@ static NTSTATUS write_tree(device_extension* Vcb, UINT64 addr, UINT8* data, writ
     return STATUS_SUCCESS;
 }
 
-static void free_stripes(write_tree_context* wtc) {
+void free_write_tree_stripes(write_tree_context* wtc) {
     LIST_ENTRY *le, *le2, *nextle;
     
     le = wtc->stripes.Flink;
@@ -2082,23 +1979,23 @@ static NTSTATUS write_trees(device_extension* Vcb) {
         
         TRACE("level = %u\n", level);
         
-        le = Vcb->tree_cache.Flink;
-        while (le != &Vcb->tree_cache) {
-            tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+        le = Vcb->trees.Flink;
+        while (le != &Vcb->trees) {
+            tree* t = CONTAINING_RECORD(le, tree, list_entry);
             
-            if (tc2->write && tc2->tree->header.level == level) {
+            if (t->write && t->header.level == level) {
                 KEY firstitem, searchkey;
                 LIST_ENTRY* le2;
                 traverse_ptr tp;
                 EXTENT_ITEM_TREE* eit;
                 
-                if (!tc2->tree->has_new_address) {
+                if (!t->has_new_address) {
                     ERR("error - tried to write tree with no new address\n");
                     int3;
                 }
                 
-                le2 = tc2->tree->itemlist.Flink;
-                while (le2 != &tc2->tree->itemlist) {
+                le2 = t->itemlist.Flink;
+                while (le2 != &t->itemlist) {
                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
                     if (!td->ignore) {
                         firstitem = td->key;
@@ -2107,14 +2004,14 @@ static NTSTATUS write_trees(device_extension* Vcb) {
                     le2 = le2->Flink;
                 }
                 
-                if (tc2->tree->parent) {
-                    tc2->tree->paritem->key = firstitem;
-                    tc2->tree->paritem->treeholder.address = tc2->tree->new_address;
-                    tc2->tree->paritem->treeholder.generation = Vcb->superblock.generation;
+                if (t->parent) {
+                    t->paritem->key = firstitem;
+                    t->paritem->treeholder.address = t->new_address;
+                    t->paritem->treeholder.generation = Vcb->superblock.generation;
                 }
                 
                 if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {
-                    searchkey.obj_id = tc2->tree->new_address;
+                    searchkey.obj_id = t->new_address;
                     searchkey.obj_type = TYPE_EXTENT_ITEM;
                     searchkey.offset = Vcb->superblock.node_size;
                     
@@ -2130,7 +2027,6 @@ static NTSTATUS write_trees(device_extension* Vcb) {
 //                         tree_data* paritem;
                         
                         ERR("could not find %llx,%x,%llx in extent_root (found %llx,%x,%llx instead)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
-                        free_traverse_ptr(&tp);
                         
 //                         searchkey.obj_id = 0;
 //                         searchkey.obj_type = 0;
@@ -2161,14 +2057,11 @@ static NTSTATUS write_trees(device_extension* Vcb) {
                     
                     if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) {
                         ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_TREE));
-                        free_traverse_ptr(&tp);
                         return STATUS_INTERNAL_ERROR;
                     }
                     
                     eit = (EXTENT_ITEM_TREE*)tp.item->data;
                     eit->firstitem = firstitem;
-                    
-                    free_traverse_ptr(&tp);
                 }
                 
                 nothing_found = FALSE;
@@ -2192,58 +2085,58 @@ static NTSTATUS write_trees(device_extension* Vcb) {
     KeInitializeEvent(&wtc->Event, NotificationEvent, FALSE);
     InitializeListHead(&wtc->stripes);
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
 #ifdef DEBUG_PARANOID
         UINT32 num_items = 0, size = 0;
         LIST_ENTRY* le2;
         BOOL crash = FALSE;
 #endif
 
-        if (tc2->write) {
+        if (t->write) {
 #ifdef DEBUG_PARANOID
-            le2 = tc2->tree->itemlist.Flink;
-            while (le2 != &tc2->tree->itemlist) {
+            le2 = t->itemlist.Flink;
+            while (le2 != &t->itemlist) {
                 tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
                 if (!td->ignore) {
                     num_items++;
                     
-                    if (tc2->tree->header.level == 0)
+                    if (t->header.level == 0)
                         size += td->size;
                 }
                 le2 = le2->Flink;
             }
             
-            if (tc2->tree->header.level == 0)
+            if (t->header.level == 0)
                 size += num_items * sizeof(leaf_node);
             else
                 size += num_items * sizeof(internal_node);
             
-            if (num_items != tc2->tree->header.num_items) {
-                ERR("tree %llx, level %x: num_items was %x, expected %x\n", tc2->tree->root->id, tc2->tree->header.level, num_items, tc2->tree->header.num_items);
+            if (num_items != t->header.num_items) {
+                ERR("tree %llx, level %x: num_items was %x, expected %x\n", t->root->id, t->header.level, num_items, t->header.num_items);
                 crash = TRUE;
             }
             
-            if (size != tc2->tree->size) {
-                ERR("tree %llx, level %x: size was %x, expected %x\n", tc2->tree->root->id, tc2->tree->header.level, size, tc2->tree->size);
+            if (size != t->size) {
+                ERR("tree %llx, level %x: size was %x, expected %x\n", t->root->id, t->header.level, size, t->size);
                 crash = TRUE;
             }
             
-            if (tc2->tree->header.num_items == 0 && tc2->tree->parent) {
-                ERR("tree %llx, level %x: tried to write empty tree with parent\n", tc2->tree->root->id, tc2->tree->header.level);
+            if (t->header.num_items == 0 && t->parent) {
+                ERR("tree %llx, level %x: tried to write empty tree with parent\n", t->root->id, t->header.level);
                 crash = TRUE;
             }
             
-            if (tc2->tree->size > Vcb->superblock.node_size - sizeof(tree_header)) {
-                ERR("tree %llx, level %x: tried to write overlarge tree (%x > %x)\n", tc2->tree->root->id, tc2->tree->header.level, tc2->tree->size, Vcb->superblock.node_size - sizeof(tree_header));
+            if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) {
+                ERR("tree %llx, level %x: tried to write overlarge tree (%x > %x)\n", t->root->id, t->header.level, t->size, Vcb->superblock.node_size - sizeof(tree_header));
                 crash = TRUE;
             }
             
             if (crash) {
-                ERR("tree %p\n", tc2->tree);
-                le2 = tc2->tree->itemlist.Flink;
-                while (le2 != &tc2->tree->itemlist) {
+                ERR("tree %p\n", t);
+                le2 = t->itemlist.Flink;
+                while (le2 != &t->itemlist) {
                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
                     if (!td->ignore) {
                         ERR("%llx,%x,%llx inserted=%u\n", td->key.obj_id, td->key.obj_type, td->key.offset, td->inserted);
@@ -2253,10 +2146,10 @@ static NTSTATUS write_trees(device_extension* Vcb) {
                 int3;
             }
 #endif
-            tc2->tree->header.address = tc2->tree->new_address;
-            tc2->tree->header.generation = Vcb->superblock.generation;
-            tc2->tree->header.flags |= HEADER_FLAG_MIXED_BACKREF;
-            tc2->tree->has_address = TRUE;
+            t->header.address = t->new_address;
+            t->header.generation = Vcb->superblock.generation;
+            t->header.flags |= HEADER_FLAG_MIXED_BACKREF;
+            t->has_address = TRUE;
             
             data = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);
             if (!data) {
@@ -2267,17 +2160,17 @@ static NTSTATUS write_trees(device_extension* Vcb) {
             
             body = data + sizeof(tree_header);
             
-            RtlCopyMemory(data, &tc2->tree->header, sizeof(tree_header));
+            RtlCopyMemory(data, &t->header, sizeof(tree_header));
             RtlZeroMemory(body, Vcb->superblock.node_size - sizeof(tree_header));
             
-            if (tc2->tree->header.level == 0) {
+            if (t->header.level == 0) {
                 leaf_node* itemptr = (leaf_node*)body;
                 int i = 0;
                 LIST_ENTRY* le2;
                 UINT8* dataptr = data + Vcb->superblock.node_size;
                 
-                le2 = tc2->tree->itemlist.Flink;
-                while (le2 != &tc2->tree->itemlist) {
+                le2 = t->itemlist.Flink;
+                while (le2 != &t->itemlist) {
                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
                     if (!td->ignore) {
                         dataptr = dataptr - td->size;
@@ -2298,8 +2191,8 @@ static NTSTATUS write_trees(device_extension* Vcb) {
                 int i = 0;
                 LIST_ENTRY* le2;
                 
-                le2 = tc2->tree->itemlist.Flink;
-                while (le2 != &tc2->tree->itemlist) {
+                le2 = t->itemlist.Flink;
+                while (le2 != &t->itemlist) {
                     tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
                     if (!td->ignore) {
                         itemptr[i].key = td->key;
@@ -2317,7 +2210,7 @@ static NTSTATUS write_trees(device_extension* Vcb) {
             *((UINT32*)data) = crc32;
             TRACE("setting crc32 to %08x\n", crc32);
             
-            Status = write_tree(Vcb, tc2->tree->new_address, data, wtc);
+            Status = write_tree(Vcb, t->new_address, data, wtc);
             if (!NT_SUCCESS(Status)) {
                 ERR("write_tree returned %08x\n", Status);
                 goto end;
@@ -2354,7 +2247,7 @@ static NTSTATUS write_trees(device_extension* Vcb) {
             le = le->Flink;
         }
         
-        free_stripes(wtc);
+        free_write_tree_stripes(wtc);
     }
     
 end:
@@ -2363,6 +2256,75 @@ end:
     return Status;
 }
 
+static void update_backup_superblock(device_extension* Vcb, superblock_backup* sb) {
+    KEY searchkey;
+    traverse_ptr tp;
+    
+    RtlZeroMemory(sb, sizeof(superblock_backup));
+    
+    sb->root_tree_addr = Vcb->superblock.root_tree_addr;
+    sb->root_tree_generation = Vcb->superblock.generation;
+    sb->root_level = Vcb->superblock.root_level;
+
+    sb->chunk_tree_addr = Vcb->superblock.chunk_tree_addr;
+    sb->chunk_tree_generation = Vcb->superblock.chunk_root_generation;
+    sb->chunk_root_level = Vcb->superblock.chunk_root_level;
+
+    searchkey.obj_id = BTRFS_ROOT_EXTENT;
+    searchkey.obj_type = TYPE_ROOT_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE))) {
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
+            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
+            
+            sb->extent_tree_addr = ri->block_number;
+            sb->extent_tree_generation = ri->generation;
+            sb->extent_root_level = ri->root_level;
+        }
+    }
+
+    searchkey.obj_id = BTRFS_ROOT_FSTREE;
+    
+    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE))) {
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
+            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
+            
+            sb->fs_tree_addr = ri->block_number;
+            sb->fs_tree_generation = ri->generation;
+            sb->fs_root_level = ri->root_level;
+        }
+    }
+    
+    searchkey.obj_id = BTRFS_ROOT_DEVTREE;
+    
+    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE))) {
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
+            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
+            
+            sb->dev_root_addr = ri->block_number;
+            sb->dev_root_generation = ri->generation;
+            sb->dev_root_level = ri->root_level;
+        }
+    }
+
+    searchkey.obj_id = BTRFS_ROOT_CHECKSUM;
+    
+    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE))) {
+        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {
+            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
+            
+            sb->csum_root_addr = ri->block_number;
+            sb->csum_root_generation = ri->generation;
+            sb->csum_root_level = ri->root_level;
+        }
+    }
+
+    sb->total_bytes = Vcb->superblock.total_bytes;
+    sb->bytes_used = Vcb->superblock.bytes_used;
+    sb->num_devices = Vcb->superblock.num_devices;
+}
+
 static NTSTATUS write_superblocks(device_extension* Vcb) {
     UINT64 i;
     NTSTATUS Status;
@@ -2370,24 +2332,30 @@ static NTSTATUS write_superblocks(device_extension* Vcb) {
     
     TRACE("(%p)\n", Vcb);
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
-        
-        if (tc2->write && !tc2->tree->parent) {
-            if (tc2->tree->root == Vcb->root_root) {
-                Vcb->superblock.root_tree_addr = tc2->tree->new_address;
-                Vcb->superblock.root_level = tc2->tree->header.level;
-            } else if (tc2->tree->root == Vcb->chunk_root) {
-                Vcb->superblock.chunk_tree_addr = tc2->tree->new_address;
-                Vcb->superblock.chunk_root_generation = tc2->tree->header.generation;
-                Vcb->superblock.chunk_root_level = tc2->tree->header.level;
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
+        
+        if (t->write && !t->parent) {
+            if (t->root == Vcb->root_root) {
+                Vcb->superblock.root_tree_addr = t->new_address;
+                Vcb->superblock.root_level = t->header.level;
+            } else if (t->root == Vcb->chunk_root) {
+                Vcb->superblock.chunk_tree_addr = t->new_address;
+                Vcb->superblock.chunk_root_generation = t->header.generation;
+                Vcb->superblock.chunk_root_level = t->header.level;
             }
         }
         
         le = le->Flink;
     }
     
+    for (i = 0; i < BTRFS_NUM_BACKUP_ROOTS - 1; i++) {
+        RtlCopyMemory(&Vcb->superblock.backup[i], &Vcb->superblock.backup[i+1], sizeof(superblock_backup));
+    }
+    
+    update_backup_superblock(Vcb, &Vcb->superblock.backup[BTRFS_NUM_BACKUP_ROOTS - 1]);
+    
     for (i = 0; i < Vcb->superblock.num_devices; i++) {
         if (Vcb->devices[i].devobj) {
             Status = write_superblock(Vcb, &Vcb->devices[i]);
@@ -2428,20 +2396,17 @@ static NTSTATUS update_chunk_usage(device_extension* Vcb, LIST_ENTRY* rollback)
             if (keycmp(&searchkey, &tp.item->key)) {
                 ERR("could not find (%llx,%x,%llx) in extent_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
                 int3;
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             }
             
             if (tp.item->size < sizeof(BLOCK_GROUP_ITEM)) {
                 ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(BLOCK_GROUP_ITEM));
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             }
             
             bgi = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
             if (!bgi) {
                 ERR("out of memory\n");
-                free_traverse_ptr(&tp);
                 return STATUS_INSUFFICIENT_RESOURCES;
             }
     
@@ -2464,37 +2429,30 @@ static NTSTATUS update_chunk_usage(device_extension* Vcb, LIST_ENTRY* rollback)
             if (c->chunk_item->type & BLOCK_FLAG_RAID0) {
                 FIXME("RAID0 not yet supported\n");
                 ExFreePool(bgi);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             } else if (c->chunk_item->type & BLOCK_FLAG_RAID1) {
                 FIXME("RAID1 not yet supported\n");
                 ExFreePool(bgi);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             } else if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE) {
                 Vcb->superblock.bytes_used = Vcb->superblock.bytes_used + (2 * (c->used - c->oldused));
             } else if (c->chunk_item->type & BLOCK_FLAG_RAID10) {
                 FIXME("RAID10 not yet supported\n");
                 ExFreePool(bgi);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             } else if (c->chunk_item->type & BLOCK_FLAG_RAID5) {
                 FIXME("RAID5 not yet supported\n");
                 ExFreePool(bgi);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             } else if (c->chunk_item->type & BLOCK_FLAG_RAID6) {
                 FIXME("RAID6 not yet supported\n");
                 ExFreePool(bgi);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             } else { // SINGLE
                 Vcb->superblock.bytes_used = Vcb->superblock.bytes_used + c->used - c->oldused;
             }
             
             TRACE("bytes_used = %llx\n", Vcb->superblock.bytes_used);
-
-            free_traverse_ptr(&tp);
             
             c->oldused = c->used;
         }
@@ -2563,7 +2521,6 @@ static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data*
     nt->header.num_items = t->header.num_items - numitems;
     nt->header.flags = HEADER_FLAG_MIXED_BACKREF;
     
-    nt->refcount = 0;
     nt->has_address = FALSE;
     nt->Vcb = Vcb;
     nt->parent = t->parent;
@@ -2617,12 +2574,10 @@ static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data*
     nt->size = t->size - size;
     t->size = size;
     t->header.num_items = numitems;
-    add_to_tree_cache(Vcb, nt, TRUE);
+    nt->write = TRUE;
+    Vcb->write_trees++;
     
     InterlockedIncrement(&Vcb->open_trees);
-#ifdef DEBUG_TREE_REFCOUNTS
-    TRACE("created new split tree %p\n", nt);
-#endif
     InsertTailList(&Vcb->trees, &nt->list_entry);
     
 // //     // TESTING
@@ -2645,19 +2600,14 @@ static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data*
         while (le != &nt->itemlist) {
             tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
             
-            if (td2->treeholder.tree) {
+            if (td2->treeholder.tree)
                 td2->treeholder.tree->parent = nt;
-                increase_tree_rc(nt);
-                free_tree(t);
-            }
             
             le = le->Flink;
         }
     }
     
     if (nt->parent) {
-        increase_tree_rc(nt->parent);
-        
         td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
         if (!td) {
             ERR("out of memory\n");
@@ -2700,7 +2650,6 @@ static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data*
     pt->header.level = nt->header.level + 1;
     pt->header.flags = HEADER_FLAG_MIXED_BACKREF;
     
-    pt->refcount = 2;
     pt->has_address = FALSE;
     pt->Vcb = Vcb;
     pt->parent = NULL;
@@ -2716,9 +2665,6 @@ static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data*
 //     ExInitializeResourceLite(&pt->nonpaged->load_tree_lock);
     
     InterlockedIncrement(&Vcb->open_trees);
-#ifdef DEBUG_TREE_REFCOUNTS
-    TRACE("created new parent tree %p\n", pt);
-#endif
     InsertTailList(&Vcb->trees, &pt->list_entry);
     
     td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
@@ -2755,7 +2701,8 @@ static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data*
     InsertTailList(&pt->itemlist, &td->list_entry);
     nt->paritem = td;
     
-    add_to_tree_cache(Vcb, pt, TRUE);
+    pt->write = TRUE;
+    Vcb->write_trees++;
 
     t->root->treeholder.tree = pt;
     
@@ -2865,9 +2812,6 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
         return Status;
     }
     
-    if (loaded)
-        increase_tree_rc(t->parent);
-        
 //     ExReleaseResourceLite(&t->parent->nonpaged->load_tree_lock);
     
     next_tree = nextparitem->treeholder.tree;
@@ -2884,11 +2828,8 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
             while (le != &next_tree->itemlist) {
                 tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
                 
-                if (td2->treeholder.tree) {
+                if (td2->treeholder.tree)
                     td2->treeholder.tree->parent = t;
-                    increase_tree_rc(t);
-                    free_tree(next_tree);
-                }
                 
                 le = le->Flink;
             }
@@ -2919,7 +2860,6 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
             
             if (!NT_SUCCESS(Status)) {
                 ERR("reduce_tree_extent returned %08x\n", Status);
-                free_tree(next_tree);
                 return Status;
             }
         } else if (next_tree->has_address) {
@@ -2927,7 +2867,6 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
             
             if (!NT_SUCCESS(Status)) {
                 ERR("reduce_tree_extent returned %08x\n", Status);
-                free_tree(next_tree);
                 return Status;
             }
         }
@@ -2940,7 +2879,10 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
         
         par = next_tree->parent;
         while (par) {
-            add_to_tree_cache(Vcb, par, TRUE);
+            if (!par->write) {
+                par->write = TRUE;
+                Vcb->write_trees++;
+            }
             par = par->parent;
         }
         
@@ -2951,21 +2893,6 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
         next_tree->root->root_item.bytes_used -= Vcb->superblock.node_size;
         
         free_tree(next_tree);
-        
-        // remove next_tree from tree cache
-        le = Vcb->tree_cache.Flink;
-        while (le != &Vcb->tree_cache) {
-            tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
-            
-            if (tc2->tree == next_tree) {
-                free_tree(next_tree);
-                RemoveEntryList(le);
-                ExFreePool(tc2);
-                break;
-            }
-            
-            le = le->Flink;
-        }
     } else {
         // rebalance by moving items from second tree into first
         ULONG avg_size = (t->size + next_tree->size) / 2;
@@ -2990,11 +2917,8 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
                 RemoveEntryList(&td->list_entry);
                 InsertTailList(&t->itemlist, &td->list_entry);
                 
-                if (next_tree->header.level > 0 && td->treeholder.tree) {
+                if (next_tree->header.level > 0 && td->treeholder.tree)
                     td->treeholder.tree->parent = t;
-                    increase_tree_rc(t);
-                    free_tree(next_tree);
-                }
                 
                 if (!td->ignore) {
                     next_tree->size -= size;
@@ -3028,11 +2952,12 @@ static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY*
         
         par = next_tree;
         while (par) {
-            add_to_tree_cache(Vcb, par, TRUE);
+            if (!par->write) {
+                par->write = TRUE;
+                Vcb->write_trees++;
+            }
             par = par->parent;
         }
-        
-        free_tree(next_tree);
     }
     
     return STATUS_SUCCESS;
@@ -3062,7 +2987,6 @@ static NTSTATUS update_extent_level(device_extension* Vcb, UINT64 address, tree*
                 
                 if (!eism) {
                     ERR("out of memory\n");
-                    free_traverse_ptr(&tp);
                     return STATUS_INSUFFICIENT_RESOURCES;
                 }
                 
@@ -3075,15 +2999,11 @@ static NTSTATUS update_extent_level(device_extension* Vcb, UINT64 address, tree*
             if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, tp.item->size, NULL, rollback)) {
                 ERR("insert_tree_item failed\n");
                 ExFreePool(eism);
-                free_traverse_ptr(&tp);
                 return STATUS_INTERNAL_ERROR;
             }
             
-            free_traverse_ptr(&tp);
             return STATUS_SUCCESS;
         }
-        
-        free_traverse_ptr(&tp);
     }
     
     searchkey.obj_id = address;
@@ -3101,7 +3021,6 @@ static NTSTATUS update_extent_level(device_extension* Vcb, UINT64 address, tree*
         
         if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) {
             ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_TREE));
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
         
@@ -3109,7 +3028,6 @@ static NTSTATUS update_extent_level(device_extension* Vcb, UINT64 address, tree*
                 
         if (!eit) {
             ERR("out of memory\n");
-            free_traverse_ptr(&tp);
             return STATUS_INSUFFICIENT_RESOURCES;
         }
         
@@ -3122,19 +3040,14 @@ static NTSTATUS update_extent_level(device_extension* Vcb, UINT64 address, tree*
         if (!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, rollback)) {
             ERR("insert_tree_item failed\n");
             ExFreePool(eit);
-            free_traverse_ptr(&tp);
             return STATUS_INTERNAL_ERROR;
         }
-        
-        free_traverse_ptr(&tp);
     
         return STATUS_SUCCESS;
     }
     
     ERR("could not find EXTENT_ITEM for address %llx\n", address);
     
-    free_traverse_ptr(&tp);
-    
     return STATUS_INTERNAL_ERROR;
 }
 
@@ -3146,7 +3059,7 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
     UINT32 min_size;
     BOOL empty, done_deletions = FALSE;
     NTSTATUS Status;
-    tree_cache* tc2;
+    tree* t;
     
     TRACE("(%p)\n", Vcb);
     
@@ -3159,43 +3072,44 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
         
         TRACE("doing level %u\n", level);
         
-        le = Vcb->tree_cache.Flink;
+        le = Vcb->trees.Flink;
     
-        while (le != &Vcb->tree_cache) {
-            tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+        while (le != &Vcb->trees) {
+            t = CONTAINING_RECORD(le, tree, list_entry);
             
             nextle = le->Flink;
             
-            if (tc2->write && tc2->tree->header.level == level) {
+            if (t->write && t->header.level == level) {
                 empty = FALSE;
                 
-                if (tc2->tree->header.num_items == 0) {
-                    if (tc2->tree->parent) {
+                if (t->header.num_items == 0) {
+                    if (t->parent) {
                         LIST_ENTRY* le2;
                         KEY firstitem = {0xcccccccccccccccc,0xcc,0xcccccccccccccccc};
                         
                         done_deletions = TRUE;
             
-                        le2 = tc2->tree->itemlist.Flink;
-                        while (le2 != &tc2->tree->itemlist) {
+                        le2 = t->itemlist.Flink;
+                        while (le2 != &t->itemlist) {
                             tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
                             firstitem = td->key;
                             break;
                         }
+                        
                         TRACE("deleting tree in root %llx (first item was %llx,%x,%llx)\n",
-                              tc2->tree->root->id, firstitem.obj_id, firstitem.obj_type, firstitem.offset);
+                              t->root->id, firstitem.obj_id, firstitem.obj_type, firstitem.offset);
                         
-                        tc2->tree->root->root_item.bytes_used -= Vcb->superblock.node_size;
+                        t->root->root_item.bytes_used -= Vcb->superblock.node_size;
                         
-                        if (tc2->tree->has_new_address) { // delete associated EXTENT_ITEM
-                            Status = reduce_tree_extent(Vcb, tc2->tree->new_address, tc2->tree, rollback);
+                        if (t->has_new_address) { // delete associated EXTENT_ITEM
+                            Status = reduce_tree_extent(Vcb, t->new_address, t, rollback);
                             
                             if (!NT_SUCCESS(Status)) {
                                 ERR("reduce_tree_extent returned %08x\n", Status);
                                 return Status;
                             }
-                        } else if (tc2->tree->has_address) {
-                            Status = reduce_tree_extent(Vcb,tc2->tree->header.address, tc2->tree, rollback);
+                        } else if (t->has_address) {
+                            Status = reduce_tree_extent(Vcb,t->header.address, t, rollback);
                             
                             if (!NT_SUCCESS(Status)) {
                                 ERR("reduce_tree_extent returned %08x\n", Status);
@@ -3203,23 +3117,20 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
                             }
                         }
                         
-                        if (!tc2->tree->paritem->ignore) {
-                            tc2->tree->paritem->ignore = TRUE;
-                            tc2->tree->parent->header.num_items--;
-                            tc2->tree->parent->size -= sizeof(internal_node);
+                        if (!t->paritem->ignore) {
+                            t->paritem->ignore = TRUE;
+                            t->parent->header.num_items--;
+                            t->parent->size -= sizeof(internal_node);
                         }
                         
-                        RemoveEntryList(&tc2->tree->paritem->list_entry);
-                        ExFreePool(tc2->tree->paritem);
-                        tc2->tree->paritem = NULL;
+                        RemoveEntryList(&t->paritem->list_entry);
+                        ExFreePool(t->paritem);
+                        t->paritem = NULL;
                         
-                        free_tree(tc2->tree);
-                        
-                        RemoveEntryList(le);
-                        ExFreePool(tc2);
-                    } else if (tc2->tree->header.level != 0) {
-                        if (tc2->tree->has_new_address) {
-                            Status = update_extent_level(Vcb, tc2->tree->new_address, tc2->tree, 0, rollback);
+                        free_tree(t);
+                    } else if (t->header.level != 0) {
+                        if (t->has_new_address) {
+                            Status = update_extent_level(Vcb, t->new_address, t, 0, rollback);
                             
                             if (!NT_SUCCESS(Status)) {
                                 ERR("update_extent_level returned %08x\n", Status);
@@ -3227,11 +3138,11 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
                             }
                         }
                         
-                        tc2->tree->header.level = 0;
+                        t->header.level = 0;
                     }
-                } else if (tc2->tree->size > Vcb->superblock.node_size - sizeof(tree_header)) {
-                    TRACE("splitting overlarge tree (%x > %x)\n", tc2->tree->size, Vcb->superblock.node_size - sizeof(tree_header));
-                    Status = split_tree(Vcb, tc2->tree);
+                } else if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) {
+                    TRACE("splitting overlarge tree (%x > %x)\n", t->size, Vcb->superblock.node_size - sizeof(tree_header));
+                    Status = split_tree(Vcb, t);
 
                     if (!NT_SUCCESS(Status)) {
                         ERR("split_tree returned %08x\n", Status);
@@ -3256,13 +3167,13 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
     for (level = 0; level <= max_level; level++) {
         LIST_ENTRY* le;
         
-        le = Vcb->tree_cache.Flink;
+        le = Vcb->trees.Flink;
     
-        while (le != &Vcb->tree_cache) {
-            tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+        while (le != &Vcb->trees) {
+            t = CONTAINING_RECORD(le, tree, list_entry);
             
-            if (tc2->write && tc2->tree->header.level == level && tc2->tree->header.num_items > 0 && tc2->tree->parent && tc2->tree->size < min_size) {
-                Status = try_tree_amalgamate(Vcb, tc2->tree, rollback);
+            if (t->write && t->header.level == level && t->header.num_items > 0 && t->parent && t->size < min_size) {
+                Status = try_tree_amalgamate(Vcb, t, rollback);
                 if (!NT_SUCCESS(Status)) {
                     ERR("try_tree_amalgamate returned %08x\n", Status);
                     return Status;
@@ -3279,35 +3190,35 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
         for (level = max_level; level > 0; level--) {
             LIST_ENTRY *le, *nextle;
             
-            le = Vcb->tree_cache.Flink;
-            while (le != &Vcb->tree_cache) {
+            le = Vcb->trees.Flink;
+            while (le != &Vcb->trees) {
                 nextle = le->Flink;
-                tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+                t = CONTAINING_RECORD(le, tree, list_entry);
                 
-                if (tc2->write && tc2->tree->header.level == level) {
-                    if (!tc2->tree->parent && tc2->tree->header.num_items == 1) {
-                        LIST_ENTRY* le2 = tc2->tree->itemlist.Flink;
+                if (t->write && t->header.level == level) {
+                    if (!t->parent && t->header.num_items == 1) {
+                        LIST_ENTRY* le2 = t->itemlist.Flink;
                         tree_data* td;
                         tree* child_tree = NULL;
 
-                        while (le2 != &tc2->tree->itemlist) {
+                        while (le2 != &t->itemlist) {
                             td = CONTAINING_RECORD(le2, tree_data, list_entry);
                             if (!td->ignore)
                                 break;
                             le2 = le2->Flink;
                         }
                         
-                        TRACE("deleting top-level tree in root %llx with one item\n", tc2->tree->root->id);
+                        TRACE("deleting top-level tree in root %llx with one item\n", t->root->id);
                         
-                        if (tc2->tree->has_new_address) { // delete associated EXTENT_ITEM
-                            Status = reduce_tree_extent(Vcb, tc2->tree->new_address, tc2->tree, rollback);
+                        if (t->has_new_address) { // delete associated EXTENT_ITEM
+                            Status = reduce_tree_extent(Vcb, t->new_address, t, rollback);
                             
                             if (!NT_SUCCESS(Status)) {
                                 ERR("reduce_tree_extent returned %08x\n", Status);
                                 return Status;
                             }
-                        } else if (tc2->tree->has_address) {
-                            Status = reduce_tree_extent(Vcb,tc2->tree->header.address, tc2->tree, rollback);
+                        } else if (t->has_address) {
+                            Status = reduce_tree_extent(Vcb,t->header.address, t, rollback);
                             
                             if (!NT_SUCCESS(Status)) {
                                 ERR("reduce_tree_extent returned %08x\n", Status);
@@ -3319,13 +3230,11 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
                             KEY searchkey = {0,0,0};
                             traverse_ptr tp;
                             
-                            Status = find_item(Vcb, tc2->tree->root, &tp, &searchkey, FALSE);
+                            Status = find_item(Vcb, t->root, &tp, &searchkey, FALSE);
                             if (!NT_SUCCESS(Status)) {
                                 ERR("error - find_item returned %08x\n", Status);
                                 return Status;
                             }
-                            
-                            free_traverse_ptr(&tp);
                         }
                         
                         child_tree = td->treeholder.tree;
@@ -3333,18 +3242,14 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
                         if (child_tree) {
                             child_tree->parent = NULL;
                             child_tree->paritem = NULL;
-                            free_tree(tc2->tree);
                         }
                         
-                        tc2->tree->root->root_item.bytes_used -= Vcb->superblock.node_size;
+                        t->root->root_item.bytes_used -= Vcb->superblock.node_size;
 
-                        free_tree(tc2->tree);
+                        free_tree(t);
                         
                         if (child_tree)
                             child_tree->root->treeholder.tree = child_tree;
-                        
-                        RemoveEntryList(le);
-                        ExFreePool(tc2);
                     }
                 }
                 
@@ -3356,15 +3261,145 @@ static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
     return STATUS_SUCCESS;
 }
 
+static NTSTATUS remove_root_extents(device_extension* Vcb, root* r, tree_holder* th, UINT8 level, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    
+    if (level > 0) {
+        if (!th->tree) {
+            Status = load_tree(Vcb, th->address, r, &th->tree);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("load_tree(%llx) returned %08x\n", th->address, Status);
+                return Status;
+            }
+        }
+        
+        if (th->tree->header.level > 0) {
+            LIST_ENTRY* le = th->tree->itemlist.Flink;
+            
+            while (le != &th->tree->itemlist) {
+                tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+                
+                if (!td->ignore) {
+                    Status = remove_root_extents(Vcb, r, &td->treeholder, th->tree->header.level - 1, rollback);
+                    
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("remove_root_extents returned %08x\n", Status);
+                        return Status;
+                    }
+                }
+                
+                le = le->Flink;
+            }
+        }
+    }
+    
+    if (!th->tree || th->tree->has_address) {
+        Status = reduce_tree_extent(Vcb, th->address, NULL, rollback);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("reduce_tree_extent(%llx) returned %08x\n", th->address, Status);
+            return Status;
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS drop_root(device_extension* Vcb, root* r, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp;
+    
+    Status = remove_root_extents(Vcb, r, &r->treeholder, r->root_item.root_level, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("remove_root_extents returned %08x\n", Status);
+        return Status;
+    }
+    
+    // remove entry in uuid root (tree 9)
+    if (Vcb->uuid_root) {
+        RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid.uuid[0], sizeof(UINT64));
+        searchkey.obj_type = TYPE_SUBVOL_UUID;
+        RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(UINT64)], sizeof(UINT64));
+        
+        if (searchkey.obj_id != 0 || searchkey.offset != 0) {
+            Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                WARN("find_item returned %08x\n", Status);
+            } else {
+                if (!keycmp(&tp.item->key, &searchkey))
+                    delete_tree_item(Vcb, &tp, rollback);
+                else
+                    WARN("could not find (%llx,%x,%llx) in uuid tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+            }
+        }
+    }
+    
+    // delete ROOT_ITEM
+    
+    searchkey.obj_id = r->id;
+    searchkey.obj_type = TYPE_ROOT_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
+        delete_tree_item(Vcb, &tp, rollback);
+    else
+        WARN("could not find (%llx,%x,%llx) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+    
+    // delete items in tree cache
+    
+    free_trees_root(Vcb, r);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS drop_roots(device_extension* Vcb, LIST_ENTRY* rollback) {
+    LIST_ENTRY *le = Vcb->drop_roots.Flink, *le2;
+    NTSTATUS Status;
+    
+    while (le != &Vcb->drop_roots) {
+        root* r = CONTAINING_RECORD(le, root, list_entry);
+        
+        le2 = le->Flink;
+        
+        Status = drop_root(Vcb, r, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("drop_root(%llx) returned %08x\n", r->id, Status);
+            return Status;
+        }
+        
+        le = le2;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
 NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback) {
     NTSTATUS Status;
     LIST_ENTRY* le;
+    BOOL cache_changed;
     
     TRACE("(%p)\n", Vcb);
     
+    if (!IsListEmpty(&Vcb->drop_roots)) {
+        Status = drop_roots(Vcb, rollback);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("drop_roots returned %08x\n", Status);
+            return Status;
+        }
+    }
+    
     // If only changing superblock, e.g. changing label, we still need to rewrite
     // the root tree so the generations match, otherwise you won't be able to mount on Linux.
-    if (Vcb->write_trees > 0) {
+    if (Vcb->write_trees == 0) {
         KEY searchkey;
         traverse_ptr tp;
         
@@ -3378,9 +3413,10 @@ NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback) {
             return Status;
         }
         
-        add_to_tree_cache(Vcb, Vcb->root_root->treeholder.tree, TRUE);
-        
-        free_traverse_ptr(&tp);
+        if (!Vcb->root_root->treeholder.tree->write) {
+            Vcb->root_root->treeholder.tree->write = TRUE;
+            Vcb->write_trees++;
+        }
     }
     
     do {
@@ -3407,7 +3443,13 @@ NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback) {
             ERR("update_chunk_usage returned %08x\n", Status);
             goto end;
         }
-    } while (!trees_consistent(Vcb));
+        
+        Status = allocate_cache(Vcb, &cache_changed, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("allocate_cache returned %08x\n", Status);
+            goto end;
+        }
+    } while (cache_changed || !trees_consistent(Vcb, rollback));
     
     TRACE("trees consistent\n");
     
@@ -3423,6 +3465,8 @@ NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback) {
         goto end;
     }
     
+    Vcb->superblock.cache_generation = Vcb->superblock.generation;
+    
     Status = write_superblocks(Vcb);
     if (!NT_SUCCESS(Status)) {
         ERR("write_superblocks returned %08x\n", Status);
@@ -3433,21 +3477,28 @@ NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback) {
     
     Vcb->superblock.generation++;
     
-//     print_trees(tc); // TESTING
-    
     Status = STATUS_SUCCESS;
     
-    le = Vcb->tree_cache.Flink;
-    while (le != &Vcb->tree_cache) {
-        tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+    le = Vcb->trees.Flink;
+    while (le != &Vcb->trees) {
+        tree* t = CONTAINING_RECORD(le, tree, list_entry);
         
-        tc2->write = FALSE;
+        t->write = FALSE;
         
         le = le->Flink;
     }
     
     Vcb->write_trees = 0;
     
+    while (!IsListEmpty(&Vcb->drop_roots)) {
+        LIST_ENTRY* le = RemoveHeadList(&Vcb->drop_roots);
+        root* r = CONTAINING_RECORD(le, root, list_entry);
+
+        ExDeleteResourceLite(&r->nonpaged->load_tree_lock);
+        ExFreePool(r->nonpaged);
+        ExFreePool(r);
+    }
+    
 end:
     TRACE("do_write returning %08x\n", Status);
     
@@ -3478,949 +3529,552 @@ NTSTATUS consider_write(device_extension* Vcb) {
 #endif
 }
 
-static __inline void insert_into_ordered_list(LIST_ENTRY* list, ordered_list* ins) {
-    LIST_ENTRY* le = list->Flink;
-    ordered_list* ol;
-    
-    while (le != list) {
-        ol = (ordered_list*)le;
-        
-        if (ol->key > ins->key) {
-            le->Blink->Flink = &ins->list_entry;
-            ins->list_entry.Blink = le->Blink;
-            le->Blink = &ins->list_entry;
-            ins->list_entry.Flink = le;
-            return;
-        }
-        
-        le = le->Flink;
-    }
-    
-    InsertTailList(list, &ins->list_entry);
-}
-
-static UINT64 get_extent_data_ref_hash(UINT64 root, UINT64 objid, UINT64 offset) {
-    UINT32 high_crc = 0xffffffff, low_crc = 0xffffffff;
-    
-    // FIXME - can we test this?
-
-    // FIXME - make sure numbers here are little-endian
-    high_crc = calc_crc32c(high_crc, (UINT8*)&root, sizeof(UINT64));
-    low_crc = calc_crc32c(low_crc, (UINT8*)&objid, sizeof(UINT64));
-    low_crc = calc_crc32c(low_crc, (UINT8*)&offset, sizeof(UINT64));
-    
-    return ((UINT64)high_crc << 31) ^ (UINT64)low_crc;
-}
-
-NTSTATUS STDCALL add_extent_ref(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, LIST_ENTRY* rollback) {
-    KEY searchkey;
-    traverse_ptr tp;
-    EXTENT_ITEM* ei;
-    UINT8 *siptr, *type;
-    ULONG len;
-    UINT64 hash;
-    EXTENT_DATA_REF* edr;
-    NTSTATUS Status;
-    
-    TRACE("(%p, %llx, %llx, %llx, %llx, %llx)\n", Vcb, address, size, subvol->id, inode, offset);
-    
-    searchkey.obj_id = address;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = size;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+// NTSTATUS STDCALL add_extent_ref(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, LIST_ENTRY* rollback) {
+//     KEY searchkey;
+//     traverse_ptr tp;
+//     EXTENT_ITEM* ei;
+//     UINT8 *siptr, *type;
+//     ULONG len;
+//     UINT64 hash;
+//     EXTENT_DATA_REF* edr;
+//     NTSTATUS Status;
+//     
+//     TRACE("(%p, %llx, %llx, %llx, %llx, %llx)\n", Vcb, address, size, subvol->id, inode, offset);
+//     
+//     searchkey.obj_id = address;
+//     searchkey.obj_type = TYPE_EXTENT_ITEM;
+//     searchkey.offset = size;
+//     
+//     Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//     if (!NT_SUCCESS(Status)) {
+//         ERR("error - find_item returned %08x\n", Status);
+//         return Status;
+//     }
+//     
+//     if (keycmp(&tp.item->key, &searchkey)) {
+//         // create new entry
+//         
+//         len = sizeof(EXTENT_ITEM) + sizeof(UINT8) + sizeof(EXTENT_DATA_REF);
+//         
+//         ei = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
+//         if (!ei) {
+//             ERR("out of memory\n");
+//             return STATUS_INSUFFICIENT_RESOURCES;
+//         }
+//         
+//         ei->refcount = 1;
+//         ei->generation = Vcb->superblock.generation;
+//         ei->flags = EXTENT_ITEM_DATA;
+//         
+//         type = (UINT8*)&ei[1];
+//         *type = TYPE_EXTENT_DATA_REF;
+//         
+//         edr = (EXTENT_DATA_REF*)&type[1];
+//         edr->root = subvol->id;
+//         edr->objid = inode;
+//         edr->offset = offset;
+//         edr->count = 1;
+//         
+//         if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, len, NULL, rollback)) {
+//             ERR("error - failed to insert item\n");
+//             return STATUS_INTERNAL_ERROR;
+//         }
+//         
+//         // FIXME - update free space in superblock and CHUNK_ITEM
+//         
+//         return STATUS_SUCCESS;
+//     }
+//     
+//     if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { // old extent ref, convert
+//         NTSTATUS Status = convert_old_data_extent(Vcb, address, size, rollback);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("convert_old_data_extent returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("error - find_item returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         if (keycmp(&tp.item->key, &searchkey)) {
+//             WARN("extent item not found for address %llx, size %llx\n", address, size);
+//             return STATUS_SUCCESS;
+//         }
+//     }
+//     
+//     ei = (EXTENT_ITEM*)tp.item->data;
+//     
+//     if (tp.item->size < sizeof(EXTENT_ITEM)) {
+//         ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+//         return STATUS_INTERNAL_ERROR;
+//     }
+//     
+//     if (extent_item_is_shared(ei, tp.item->size - sizeof(EXTENT_ITEM))) {
+//         NTSTATUS Status = convert_shared_data_extent(Vcb, address, size, rollback);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("convert_shared_data_extent returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("error - find_item returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         if (keycmp(&tp.item->key, &searchkey)) {
+//             WARN("extent item not found for address %llx, size %llx\n", address, size);
+//             return STATUS_SUCCESS;
+//         }
+//         
+//         ei = (EXTENT_ITEM*)tp.item->data;
+//     }
+//     
+//     if (ei->flags != EXTENT_ITEM_DATA) {
+//         ERR("error - flag was not EXTENT_ITEM_DATA\n");
+//         return STATUS_INTERNAL_ERROR;
+//     }
+//     
+//     hash = get_extent_data_ref_hash(subvol->id, inode, offset);
+//     
+//     len = tp.item->size - sizeof(EXTENT_ITEM);
+//     siptr = (UINT8*)&ei[1];
+//     
+//     do {
+//         if (*siptr == TYPE_EXTENT_DATA_REF) {
+//             UINT64 sihash;
+//             
+//             edr = (EXTENT_DATA_REF*)&siptr[1];
+//             
+//             // already exists - increase refcount
+//             if (edr->root == subvol->id && edr->objid == inode && edr->offset == offset) {
+//                 ei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+//                 
+//                 if (!ei) {
+//                     ERR("out of memory\n");
+//                     return STATUS_INSUFFICIENT_RESOURCES;
+//                 }
+//                 
+//                 RtlCopyMemory(ei, tp.item->data, tp.item->size);
+//                 
+//                 edr = (EXTENT_DATA_REF*)((UINT8*)ei + ((UINT8*)edr - tp.item->data));
+//                 edr->count++;
+//                 ei->refcount++;
+//                 
+//                 delete_tree_item(Vcb, &tp, rollback);
+//                 
+//                 if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, tp.item->size, NULL, rollback)) {
+//                     ERR("error - failed to insert item\n");
+//                     ExFreePool(ei);
+//                     return STATUS_INTERNAL_ERROR;
+//                 }
+//                 
+//                 return STATUS_SUCCESS;
+//             }
+//             
+//             sihash = get_extent_data_ref_hash(edr->root, edr->objid, edr->offset);
+//             
+//             if (sihash >= hash)
+//                 break;
+//             
+//             siptr += sizeof(UINT8) + sizeof(EXTENT_DATA_REF);
+//             
+//             if (len > sizeof(EXTENT_DATA_REF) + sizeof(UINT8)) {
+//                 len -= sizeof(EXTENT_DATA_REF) + sizeof(UINT8);
+//             } else
+//                 break;
+//         // FIXME - TYPE_TREE_BLOCK_REF    0xB0
+//         } else {
+//             ERR("unrecognized extent subitem %x\n", *siptr);
+//             return STATUS_INTERNAL_ERROR;
+//         }
+//     } while (len > 0);
+//     
+//     len = tp.item->size + sizeof(UINT8) + sizeof(EXTENT_DATA_REF); // FIXME - die if too big
+//     ei = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
+//     if (!ei) {
+//         ERR("out of memory\n");
+//         return STATUS_INSUFFICIENT_RESOURCES;
+//     }
+//     
+//     RtlCopyMemory(ei, tp.item->data, siptr - tp.item->data);
+//     ei->refcount++;
+//     
+//     type = (UINT8*)ei + (siptr - tp.item->data);
+//     *type = TYPE_EXTENT_DATA_REF;
+//     
+//     edr = (EXTENT_DATA_REF*)&type[1];
+//     edr->root = subvol->id;
+//     edr->objid = inode;
+//     edr->offset = offset;
+//     edr->count = 1;
+//     
+//     if (siptr < tp.item->data + tp.item->size)
+//         RtlCopyMemory(&edr[1], siptr, tp.item->data + tp.item->size - siptr);
+//     
+//     delete_tree_item(Vcb, &tp, rollback);
+//     
+//     if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, len, NULL, rollback)) {
+//         ERR("error - failed to insert item\n");
+//         ExFreePool(ei);
+//         return STATUS_INTERNAL_ERROR;
+//     }
+//     
+//     return STATUS_SUCCESS;
+// }
+
+static BOOL extent_item_is_shared(EXTENT_ITEM* ei, ULONG len) {
+    UINT8* siptr = (UINT8*)&ei[1];
+    
+    do {
+        if (*siptr == TYPE_TREE_BLOCK_REF) {
+            siptr += sizeof(TREE_BLOCK_REF) + 1;
+            len -= sizeof(TREE_BLOCK_REF) + 1;
+        } else if (*siptr == TYPE_EXTENT_DATA_REF) {
+            siptr += sizeof(EXTENT_DATA_REF) + 1;
+            len -= sizeof(EXTENT_DATA_REF) + 1;
+        } else if (*siptr == TYPE_SHARED_BLOCK_REF) {
+            return TRUE;
+        } else if (*siptr == TYPE_SHARED_DATA_REF) {
+            return TRUE;
+        } else {
+            ERR("unrecognized extent subitem %x\n", *siptr);
+            return FALSE;
+        }
+    } while (len > 0);
+    
+    return FALSE;
+}
+
+// static NTSTATUS STDCALL remove_extent_ref(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+//     KEY searchkey;
+//     traverse_ptr tp;
+//     EXTENT_ITEM* ei;
+//     UINT8* siptr;
+//     ULONG len;
+//     EXTENT_DATA_REF* edr;
+//     BOOL found;
+//     NTSTATUS Status;
+//     
+//     TRACE("(%p, %llx, %llx, %llx, %llx, %llx)\n", Vcb, address, size, subvol->id, inode, offset);
+//     
+//     searchkey.obj_id = address;
+//     searchkey.obj_type = TYPE_EXTENT_ITEM;
+//     searchkey.offset = size;
+//     
+//     Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//     if (!NT_SUCCESS(Status)) {
+//         ERR("error - find_item returned %08x\n", Status);
+//         return Status;
+//     }
+//     
+//     if (keycmp(&tp.item->key, &searchkey)) {
+//         WARN("extent item not found for address %llx, size %llx\n", address, size);
+//         return STATUS_SUCCESS;
+//     }
+//     
+//     if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { // old extent ref, convert
+//         NTSTATUS Status = convert_old_data_extent(Vcb, address, size, rollback);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("convert_old_data_extent returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("error - find_item returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         if (keycmp(&tp.item->key, &searchkey)) {
+//             WARN("extent item not found for address %llx, size %llx\n", address, size);
+//             return STATUS_SUCCESS;
+//         }
+//     }
+//     
+//     ei = (EXTENT_ITEM*)tp.item->data;
+//     
+//     if (tp.item->size < sizeof(EXTENT_ITEM)) {
+//         ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+//         return STATUS_INTERNAL_ERROR;
+//     }
+//     
+//     if (!(ei->flags & EXTENT_ITEM_DATA)) {
+//         ERR("error - EXTENT_ITEM_DATA flag not set\n");
+//         return STATUS_INTERNAL_ERROR;
+//     }
+//     
+//     if (extent_item_is_shared(ei, tp.item->size - sizeof(EXTENT_ITEM))) {
+//         NTSTATUS Status = convert_shared_data_extent(Vcb, address, size, rollback);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("convert_shared_data_extent returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//         if (!NT_SUCCESS(Status)) {
+//             ERR("error - find_item returned %08x\n", Status);
+//             return Status;
+//         }
+//         
+//         if (keycmp(&tp.item->key, &searchkey)) {
+//             WARN("extent item not found for address %llx, size %llx\n", address, size);
+//             return STATUS_SUCCESS;
+//         }
+//         
+//         ei = (EXTENT_ITEM*)tp.item->data;
+//     }
+//     
+//     len = tp.item->size - sizeof(EXTENT_ITEM);
+//     siptr = (UINT8*)&ei[1];
+//     found = FALSE;
+//     
+//     do {
+//         if (*siptr == TYPE_EXTENT_DATA_REF) {
+//             edr = (EXTENT_DATA_REF*)&siptr[1];
+//             
+//             if (edr->root == subvol->id && edr->objid == inode && edr->offset == offset) {
+//                 if (edr->count > 1) {
+//                     ei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+//                 
+//                     if (!ei) {
+//                         ERR("out of memory\n");
+//                         return STATUS_INSUFFICIENT_RESOURCES;
+//                     }
+//                     
+//                     RtlCopyMemory(ei, tp.item->data, tp.item->size);
+//                     
+//                     edr = (EXTENT_DATA_REF*)((UINT8*)ei + ((UINT8*)edr - tp.item->data));
+//                     edr->count--;
+//                     ei->refcount--;
+//                     
+//                     delete_tree_item(Vcb, &tp, rollback);
+//                     
+//                     if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, tp.item->size, NULL, rollback)) {
+//                         ERR("error - failed to insert item\n");
+//                         ExFreePool(ei);
+//                         return STATUS_INTERNAL_ERROR;
+//                     }
+//                     
+//                     return STATUS_SUCCESS;
+//                 }
+//                 
+//                 found = TRUE;
+//                 break;
+//             }
+// 
+//             siptr += sizeof(UINT8) + sizeof(EXTENT_DATA_REF);
+//             
+//             if (len > sizeof(EXTENT_DATA_REF) + sizeof(UINT8)) {
+//                 len -= sizeof(EXTENT_DATA_REF) + sizeof(UINT8);
+//             } else
+//                 break;
+// //         // FIXME - TYPE_TREE_BLOCK_REF    0xB0
+//         } else {
+//             ERR("unrecognized extent subitem %x\n", *siptr);
+//             return STATUS_INTERNAL_ERROR;
+//         }
+//     } while (len > 0);
+//     
+//     if (!found) {
+//         WARN("could not find extent data ref\n");
+//         return STATUS_SUCCESS;
+//     }
+//     
+//     // FIXME - decrease subitem refcount if there already?
+//     
+//     len = tp.item->size - sizeof(UINT8) - sizeof(EXTENT_DATA_REF);
+//     
+//     delete_tree_item(Vcb, &tp, rollback);
+//     
+//     if (len == sizeof(EXTENT_ITEM)) { // extent no longer needed
+//         chunk* c;
+//         LIST_ENTRY* le2;
+//         
+//         if (changed_sector_list) {
+//             changed_sector* sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
+//             if (!sc) {
+//                 ERR("out of memory\n");
+//                 return STATUS_INSUFFICIENT_RESOURCES;
+//             }
+//             
+//             sc->ol.key = address;
+//             sc->checksums = NULL;
+//             sc->length = size / Vcb->superblock.sector_size;
+// 
+//             sc->deleted = TRUE;
+//             
+//             insert_into_ordered_list(changed_sector_list, &sc->ol);
+//         }
+//         
+//         c = NULL;
+//         le2 = Vcb->chunks.Flink;
+//         while (le2 != &Vcb->chunks) {
+//             c = CONTAINING_RECORD(le2, chunk, list_entry);
+//             
+//             TRACE("chunk: %llx, %llx\n", c->offset, c->chunk_item->size);
+//             
+//             if (address >= c->offset && address + size < c->offset + c->chunk_item->size)
+//                 break;
+//             
+//             le2 = le2->Flink;
+//         }
+//         if (le2 == &Vcb->chunks) c = NULL;
+//         
+//         if (c) {
+//             decrease_chunk_usage(c, size);
+//             
+//             add_to_space_list(c, address, size, SPACE_TYPE_DELETING);
+//         }
+// 
+//         return STATUS_SUCCESS;
+//     }
+//     
+//     ei = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
+//     if (!ei) {
+//         ERR("out of memory\n");
+//         return STATUS_INSUFFICIENT_RESOURCES;
+//     }
+//             
+//     RtlCopyMemory(ei, tp.item->data, siptr - tp.item->data);
+//     ei->refcount--;
+//     ei->generation = Vcb->superblock.generation;
+//     
+//     if (tp.item->data + len != siptr)
+//         RtlCopyMemory((UINT8*)ei + (siptr - tp.item->data), siptr + sizeof(UINT8) + sizeof(EXTENT_DATA_REF), tp.item->size - (siptr - tp.item->data) - sizeof(UINT8) - sizeof(EXTENT_DATA_REF));
+//     
+//     if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, len, NULL, rollback)) {
+//         ERR("error - failed to insert item\n");
+//         ExFreePool(ei);
+//         return STATUS_INTERNAL_ERROR;
+//     }
+//     
+//     return STATUS_SUCCESS;
+// }
+
+static __inline BOOL entry_in_ordered_list(LIST_ENTRY* list, UINT64 value) {
+    LIST_ENTRY* le = list->Flink;
+    ordered_list* ol;
+    
+    while (le != list) {
+        ol = (ordered_list*)le;
+        
+        if (ol->key > value)
+            return FALSE;
+        else if (ol->key == value)
+            return TRUE;
+        
+        le = le->Flink;
+    }
+    
+    return FALSE;
+}
+
+static BOOL is_file_prealloc_inode(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 start_data, UINT64 end_data) {
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = start_data;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        return Status;
+        return FALSE;
     }
     
-    if (keycmp(&tp.item->key, &searchkey)) {
-        // create new entry
-        
-        len = sizeof(EXTENT_ITEM) + sizeof(UINT8) + sizeof(EXTENT_DATA_REF);
-        free_traverse_ptr(&tp);
+    if (tp.item->key.obj_id != inode || tp.item->key.obj_type != TYPE_EXTENT_DATA)
+        return FALSE;
+    
+    do {
+        EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
+        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
+        UINT64 len;
         
-        ei = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
-        if (!ei) {
-            ERR("out of memory\n");
-            return STATUS_INSUFFICIENT_RESOURCES;
+        if (tp.item->size < sizeof(EXTENT_DATA)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
+            return FALSE;
         }
         
-        ei->refcount = 1;
-        ei->generation = Vcb->superblock.generation;
-        ei->flags = EXTENT_ITEM_DATA;
-        
-        type = (UINT8*)&ei[1];
-        *type = TYPE_EXTENT_DATA_REF;
-        
-        edr = (EXTENT_DATA_REF*)&type[1];
-        edr->root = subvol->id;
-        edr->objid = inode;
-        edr->offset = offset;
-        edr->count = 1;
-        
-        if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, len, NULL, rollback)) {
-            ERR("error - failed to insert item\n");
-            return STATUS_INTERNAL_ERROR;
+        if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
+            return FALSE;
         }
         
-        // FIXME - update free space in superblock and CHUNK_ITEM
-        
-        return STATUS_SUCCESS;
-    }
-    
-    if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { // old extent ref, convert
-        NTSTATUS Status = convert_old_data_extent(Vcb, address, size, rollback);
-        if (!NT_SUCCESS(Status)) {
-            ERR("convert_old_data_extent returned %08x\n", Status);
-            free_traverse_ptr(&tp);
-            return Status;
-        }
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         
-        free_traverse_ptr(&tp);
+        len = ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes;
         
-        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            return Status;
-        }
+        if (tp.item->key.offset < end_data && tp.item->key.offset + len >= start_data && ed->type == EXTENT_TYPE_PREALLOC)
+            return TRUE;
         
-        if (keycmp(&tp.item->key, &searchkey)) {
-            WARN("extent item not found for address %llx, size %llx\n", address, size);
-            free_traverse_ptr(&tp);
-            return STATUS_SUCCESS;
+        if (b) {
+            tp = next_tp;
+            
+            if (tp.item->key.obj_id > inode || tp.item->key.obj_type > TYPE_EXTENT_DATA || tp.item->key.offset >= end_data)
+                break;
         }
-    }
+    } while (b);
     
-    ei = (EXTENT_ITEM*)tp.item->data;
+    return FALSE;
+}
+
+NTSTATUS excise_extents_inode(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* ii, UINT64 start_data, UINT64 end_data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    NTSTATUS Status;
+    BOOL b, deleted_prealloc = FALSE;
     
-    if (tp.item->size < sizeof(EXTENT_ITEM)) {
-        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-        free_traverse_ptr(&tp);
-        return STATUS_INTERNAL_ERROR;
-    }
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = start_data;
     
-    if (extent_item_is_shared(ei, tp.item->size - sizeof(EXTENT_ITEM))) {
-        NTSTATUS Status = convert_shared_data_extent(Vcb, address, size, rollback);
-        if (!NT_SUCCESS(Status)) {
-            ERR("convert_shared_data_extent returned %08x\n", Status);
-            free_traverse_ptr(&tp);
-            return Status;
-        }
-        
-        free_traverse_ptr(&tp);
-        
-        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            return Status;
-        }
-        
-        if (keycmp(&tp.item->key, &searchkey)) {
-            WARN("extent item not found for address %llx, size %llx\n", address, size);
-            free_traverse_ptr(&tp);
-            return STATUS_SUCCESS;
-        }
-        
-        ei = (EXTENT_ITEM*)tp.item->data;
-    }
-    
-    if (ei->flags != EXTENT_ITEM_DATA) {
-        ERR("error - flag was not EXTENT_ITEM_DATA\n");
-        free_traverse_ptr(&tp);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    // FIXME - is ei->refcount definitely the number of items, or is it the sum of the subitem refcounts?
-
-    hash = get_extent_data_ref_hash(subvol->id, inode, offset);
-    
-    len = tp.item->size - sizeof(EXTENT_ITEM);
-    siptr = (UINT8*)&ei[1];
-    
-    // FIXME - increase subitem refcount if there already?
-    do {
-        if (*siptr == TYPE_EXTENT_DATA_REF) {
-            UINT64 sihash;
-            
-            edr = (EXTENT_DATA_REF*)&siptr[1];
-            sihash = get_extent_data_ref_hash(edr->root, edr->objid, edr->offset);
-            
-            if (sihash >= hash)
-                break;
-            
-            siptr += sizeof(UINT8) + sizeof(EXTENT_DATA_REF);
-            
-            if (len > sizeof(EXTENT_DATA_REF) + sizeof(UINT8)) {
-                len -= sizeof(EXTENT_DATA_REF) + sizeof(UINT8);
-            } else
-                break;
-        // FIXME - TYPE_TREE_BLOCK_REF    0xB0
-        } else {
-            ERR("unrecognized extent subitem %x\n", *siptr);
-            free_traverse_ptr(&tp);
-            return STATUS_INTERNAL_ERROR;
-        }
-    } while (len > 0);
-    
-    len = tp.item->size + sizeof(UINT8) + sizeof(EXTENT_DATA_REF); // FIXME - die if too big
-    ei = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
-    if (!ei) {
-        ERR("out of memory\n");
-        return STATUS_INSUFFICIENT_RESOURCES;
-    }
-    
-    RtlCopyMemory(ei, tp.item->data, siptr - tp.item->data);
-    ei->refcount++;
-    
-    type = (UINT8*)ei + (siptr - tp.item->data);
-    *type = TYPE_EXTENT_DATA_REF;
-    
-    edr = (EXTENT_DATA_REF*)&type[1];
-    edr->root = subvol->id;
-    edr->objid = inode;
-    edr->offset = offset;
-    edr->count = 1;
-    
-    if (siptr < tp.item->data + tp.item->size)
-        RtlCopyMemory(&edr[1], siptr, tp.item->data + tp.item->size - siptr);
-    
-    delete_tree_item(Vcb, &tp, rollback);
-    
-    free_traverse_ptr(&tp);
-    
-    if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, len, NULL, rollback)) {
-        ERR("error - failed to insert item\n");
-        ExFreePool(ei);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    return STATUS_SUCCESS;
-}
-
-typedef struct {
-    EXTENT_DATA_REF edr;
-    LIST_ENTRY list_entry;
-} data_ref;
-
-static void add_data_ref(LIST_ENTRY* data_refs, UINT64 root, UINT64 objid, UINT64 offset) {
-    data_ref* dr = ExAllocatePoolWithTag(PagedPool, sizeof(data_ref), ALLOC_TAG);
-    
-    if (!dr) {
-        ERR("out of memory\n");
-        return;
-    }
-    
-    // FIXME - increase count if entry there already
-    // FIXME - put in order?
-    
-    dr->edr.root = root;
-    dr->edr.objid = objid;
-    dr->edr.offset = offset;
-    dr->edr.count = 1;
-    
-    InsertTailList(data_refs, &dr->list_entry);
-}
-
-static void free_data_refs(LIST_ENTRY* data_refs) {
-    while (!IsListEmpty(data_refs)) {
-        LIST_ENTRY* le = RemoveHeadList(data_refs);
-        data_ref* dr = CONTAINING_RECORD(le, data_ref, list_entry);
-        
-        ExFreePool(dr);
-    }
-}
-
-static NTSTATUS convert_old_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback) {
-    KEY searchkey;
-    traverse_ptr tp, next_tp;
-    BOOL b;
-    LIST_ENTRY data_refs;
-    LIST_ENTRY* le;
-    UINT64 refcount;
-    EXTENT_ITEM* ei;
-    ULONG eisize;
-    UINT8* type;
-    NTSTATUS Status;
-    
-    searchkey.obj_id = address;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = size;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return Status;
-    }
-    
-    if (keycmp(&tp.item->key, &searchkey)) {
-        WARN("extent item not found for address %llx, size %llx\n", address, size);
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    if (tp.item->size != sizeof(EXTENT_ITEM_V0)) {
-        TRACE("extent does not appear to be old - returning STATUS_SUCCESS\n");
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    delete_tree_item(Vcb, &tp, rollback);
-    
-    free_traverse_ptr(&tp);
-    
-    searchkey.obj_id = address;
-    searchkey.obj_type = TYPE_EXTENT_REF_V0;
-    searchkey.offset = 0;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
         return Status;
     }
-    
-    InitializeListHead(&data_refs);
-    
+
     do {
-        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
-        
-        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
-            tree* t;
-            
-            // normally we'd need to acquire load_tree_lock here, but we're protected by the write tree lock
-    
-            Status = load_tree(Vcb, tp.item->key.offset, NULL, &t);
-            
-            if (!NT_SUCCESS(Status)) {
-                ERR("load tree for address %llx returned %08x\n", tp.item->key.offset, Status);
-                free_traverse_ptr(&tp);
-                free_data_refs(&data_refs);
-                return Status;
-            }
-            
-            if (t->header.level == 0) {
-                le = t->itemlist.Flink;
-                while (le != &t->itemlist) {
-                    tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
-                    
-                    if (!td->ignore && td->key.obj_type == TYPE_EXTENT_DATA) {
-                        EXTENT_DATA* ed = (EXTENT_DATA*)td->data;
-                        
-                        if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
-                            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
-                            
-                            if (ed2->address == address)
-                                add_data_ref(&data_refs, t->header.tree_id, td->key.obj_id, td->key.offset);
-                        }
-                    }
-                    
-                    le = le->Flink;
-                }
-            }
-            
-            free_tree(t);
-            
-            delete_tree_item(Vcb, &tp, rollback);
-        }
+        EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
+        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
+        UINT64 len;
         
-        if (b) {
-            free_traverse_ptr(&tp);
-            tp = next_tp;
-            
-            if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
-                break;
+        if (tp.item->size < sizeof(EXTENT_DATA)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
         }
-    } while (b);
-    
-    free_traverse_ptr(&tp);
-    
-    if (IsListEmpty(&data_refs)) {
-        WARN("no data refs found\n");
-        return STATUS_SUCCESS;
-    }
-    
-    // create new entry
-    
-    refcount = 0;
-    
-    le = data_refs.Flink;
-    while (le != &data_refs) {
-        refcount++;
-        le = le->Flink;
-    }
-    
-    eisize = sizeof(EXTENT_ITEM) + ((sizeof(char) + sizeof(EXTENT_DATA_REF)) * refcount);
-    ei = ExAllocatePoolWithTag(PagedPool, eisize, ALLOC_TAG);
-    if (!ei) {
-        ERR("out of memory\n");
-        return STATUS_INSUFFICIENT_RESOURCES;
-    }
-    
-    ei->refcount = refcount;
-    ei->generation = Vcb->superblock.generation;
-    ei->flags = EXTENT_ITEM_DATA;
-    
-    type = (UINT8*)&ei[1];
-    
-    le = data_refs.Flink;
-    while (le != &data_refs) {
-        data_ref* dr = CONTAINING_RECORD(le, data_ref, list_entry);
-        
-        type[0] = TYPE_EXTENT_DATA_REF;
-        RtlCopyMemory(&type[1], &dr->edr, sizeof(EXTENT_DATA_REF));
-        
-        type = &type[1 + sizeof(EXTENT_DATA_REF)];
         
-        le = le->Flink;
-    }
-    
-    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, eisize, NULL, rollback)) {
-        ERR("error - failed to insert item\n");
-        ExFreePool(ei);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    free_data_refs(&data_refs);
-    
-    return STATUS_SUCCESS;
-}
-
-typedef struct {
-    UINT8 type;
-    void* data;
-    BOOL allocated;
-    LIST_ENTRY list_entry;
-} extent_ref;
-
-static void free_extent_refs(LIST_ENTRY* extent_refs) {
-    while (!IsListEmpty(extent_refs)) {
-        LIST_ENTRY* le = RemoveHeadList(extent_refs);
-        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
         
-        if (er->allocated)
-            ExFreePool(er->data);
+        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type)
+            goto cont;
         
-        ExFreePool(er);
-    }
-}
-
-static NTSTATUS convert_shared_data_extent(device_extension* Vcb, UINT64 address, UINT64 size, LIST_ENTRY* rollback) {
-    KEY searchkey;
-    traverse_ptr tp;
-    LIST_ENTRY extent_refs;
-    LIST_ENTRY *le, *next_le;
-    EXTENT_ITEM *ei, *newei;
-    UINT8* siptr;
-    ULONG len;
-    UINT64 count;
-    NTSTATUS Status;
-    
-    searchkey.obj_id = address;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = size;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return Status;
-    }
-    
-    if (keycmp(&tp.item->key, &searchkey)) {
-        WARN("extent item not found for address %llx, size %llx\n", address, size);
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    if (tp.item->size < sizeof(EXTENT_ITEM)) {
-        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-        free_traverse_ptr(&tp);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    ei = (EXTENT_ITEM*)tp.item->data;
-    len = tp.item->size - sizeof(EXTENT_ITEM);
-    
-    InitializeListHead(&extent_refs);
-    
-    siptr = (UINT8*)&ei[1];
-    
-    do {
-        extent_ref* er = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
-        if (!er) {
-            ERR("out of memory\n");
-            free_traverse_ptr(&tp);
-            return STATUS_INSUFFICIENT_RESOURCES;
+        if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
         }
-
-        er->type = *siptr;
-        er->data = siptr+1;
-        er->allocated = FALSE;
         
-        InsertTailList(&extent_refs, &er->list_entry);
-        
-        if (*siptr == TYPE_TREE_BLOCK_REF) {
-            siptr += sizeof(TREE_BLOCK_REF);
-            len -= sizeof(TREE_BLOCK_REF) + 1;
-        } else if (*siptr == TYPE_EXTENT_DATA_REF) {
-            siptr += sizeof(EXTENT_DATA_REF);
-            len -= sizeof(EXTENT_DATA_REF) + 1;
-        } else if (*siptr == TYPE_SHARED_BLOCK_REF) {
-            siptr += sizeof(SHARED_BLOCK_REF);
-            len -= sizeof(SHARED_BLOCK_REF) + 1;
-        } else if (*siptr == TYPE_SHARED_DATA_REF) {
-            siptr += sizeof(SHARED_DATA_REF);
-            len -= sizeof(SHARED_DATA_REF) + 1;
-        } else {
-            ERR("unrecognized extent subitem %x\n", *siptr);
-            free_traverse_ptr(&tp);
-            free_extent_refs(&extent_refs);
-            return STATUS_INTERNAL_ERROR;
-        }
-    } while (len > 0);
-    
-    le = extent_refs.Flink;
-    while (le != &extent_refs) {
-        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
-        next_le = le->Flink;
+        len = ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes;
         
-        if (er->type == TYPE_SHARED_DATA_REF) {
-            // normally we'd need to acquire load_tree_lock here, but we're protected by the write tree lock
-            SHARED_DATA_REF* sdr = er->data;
-            tree* t;
-            
-            Status = load_tree(Vcb, sdr->offset, NULL, &t);
-            if (!NT_SUCCESS(Status)) {
-                ERR("load_tree for address %llx returned %08x\n", sdr->offset, Status);
-                free_traverse_ptr(&tp);
-                free_data_refs(&extent_refs);
-                return Status;
+        if (tp.item->key.offset < end_data && tp.item->key.offset + len >= start_data) {
+            if (ed->compression != BTRFS_COMPRESSION_NONE) {
+                FIXME("FIXME - compression not supported at present\n");
+                Status = STATUS_NOT_SUPPORTED;
+                goto end;
             }
             
-            if (t->header.level == 0) {
-                LIST_ENTRY* le2 = t->itemlist.Flink;
-                while (le2 != &t->itemlist) {
-                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
-                    
-                    if (!td->ignore && td->key.obj_type == TYPE_EXTENT_DATA) {
-                        EXTENT_DATA* ed = (EXTENT_DATA*)td->data;
-                        
-                        if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
-                            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
-                            
-                            if (ed2->address == address) {
-                                extent_ref* er2;
-                                EXTENT_DATA_REF* edr;
-                                
-                                er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
-                                if (!er2) {
-                                    ERR("out of memory\n");
-                                    free_traverse_ptr(&tp);
-                                    return STATUS_INSUFFICIENT_RESOURCES;
-                                }
-                                
-                                edr = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA_REF), ALLOC_TAG);
-                                if (!edr) {
-                                    ERR("out of memory\n");
-                                    free_traverse_ptr(&tp);
-                                    ExFreePool(er2);
-                                    return STATUS_INSUFFICIENT_RESOURCES;
-                                }
-                                
-                                edr->root = t->header.tree_id;
-                                edr->objid = td->key.obj_id;
-                                edr->offset = td->key.offset;
-                                edr->count = 1;
-                                
-                                er2->type = TYPE_EXTENT_DATA_REF;
-                                er2->data = edr;
-                                er2->allocated = TRUE;
-                                
-                                InsertTailList(&extent_refs, &er2->list_entry); // FIXME - list should be in order
-                            }
-                        }
-                    }
-                    
-                    le2 = le2->Flink;
-                }
-            }
-            
-            free_tree(t);
-
-            RemoveEntryList(&er->list_entry);
-            
-            if (er->allocated)
-                ExFreePool(er->data);
-            
-            ExFreePool(er);
-        }
-        // FIXME - also do for SHARED_BLOCK_REF?
-
-        le = next_le;
-    }
-    
-    if (IsListEmpty(&extent_refs)) {
-        WARN("no extent refs found\n");
-        delete_tree_item(Vcb, &tp, rollback);
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    len = 0;
-    count = 0;
-    le = extent_refs.Flink;
-    while (le != &extent_refs) {
-        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
-        
-        len++;
-        if (er->type == TYPE_TREE_BLOCK_REF) {
-            len += sizeof(TREE_BLOCK_REF);
-        } else if (er->type == TYPE_EXTENT_DATA_REF) {
-            len += sizeof(EXTENT_DATA_REF);
-        } else {
-            ERR("unexpected extent subitem %x\n", er->type);
-        }
-        
-        count++;
-        
-        le = le->Flink;
-    }
-    
-    newei = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM) + len, ALLOC_TAG);
-    if (!newei) {
-        ERR("out of memory\n");
-        free_traverse_ptr(&tp);
-        return STATUS_INSUFFICIENT_RESOURCES;
-    }
-    
-    RtlCopyMemory(newei, ei, sizeof(EXTENT_ITEM));
-    newei->refcount = count;
-    
-    siptr = (UINT8*)&newei[1];
-    le = extent_refs.Flink;
-    while (le != &extent_refs) {
-        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
-        
-        *siptr = er->type;
-        siptr++;
-        
-        if (er->type == TYPE_TREE_BLOCK_REF) {
-            RtlCopyMemory(siptr, er->data, sizeof(TREE_BLOCK_REF));
-        } else if (er->type == TYPE_EXTENT_DATA_REF) {
-            RtlCopyMemory(siptr, er->data, sizeof(EXTENT_DATA_REF));
-        } else {
-            ERR("unexpected extent subitem %x\n", er->type);
-        }
-        
-        le = le->Flink;
-    }
-    
-    delete_tree_item(Vcb, &tp, rollback);
-    free_traverse_ptr(&tp);
-    
-    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, newei, sizeof(EXTENT_ITEM) + len, NULL, rollback)) {
-        ERR("error - failed to insert item\n");
-        ExFreePool(newei);
-        free_extent_refs(&extent_refs);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    free_extent_refs(&extent_refs);
-    
-    return STATUS_SUCCESS;
-}
-
-static BOOL extent_item_is_shared(EXTENT_ITEM* ei, ULONG len) {
-    UINT8* siptr = (UINT8*)&ei[1];
-    
-    do {
-        if (*siptr == TYPE_TREE_BLOCK_REF) {
-            siptr += sizeof(TREE_BLOCK_REF) + 1;
-            len -= sizeof(TREE_BLOCK_REF) + 1;
-        } else if (*siptr == TYPE_EXTENT_DATA_REF) {
-            siptr += sizeof(EXTENT_DATA_REF) + 1;
-            len -= sizeof(EXTENT_DATA_REF) + 1;
-        } else if (*siptr == TYPE_SHARED_BLOCK_REF) {
-            return TRUE;
-        } else if (*siptr == TYPE_SHARED_DATA_REF) {
-            return TRUE;
-        } else {
-            ERR("unrecognized extent subitem %x\n", *siptr);
-            return FALSE;
-        }
-    } while (len > 0);
-    
-    return FALSE;
-}
-
-NTSTATUS STDCALL remove_extent_ref(device_extension* Vcb, UINT64 address, UINT64 size, root* subvol, UINT64 inode, UINT64 offset, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
-    KEY searchkey;
-    traverse_ptr tp;
-    EXTENT_ITEM* ei;
-    UINT8* siptr;
-    ULONG len;
-    EXTENT_DATA_REF* edr;
-    BOOL found;
-    NTSTATUS Status;
-    
-    TRACE("(%p, %llx, %llx, %llx, %llx, %llx)\n", Vcb, address, size, subvol->id, inode, offset);
-    
-    searchkey.obj_id = address;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = size;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return Status;
-    }
-    
-    if (keycmp(&tp.item->key, &searchkey)) {
-        WARN("extent item not found for address %llx, size %llx\n", address, size);
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { // old extent ref, convert
-        NTSTATUS Status = convert_old_data_extent(Vcb, address, size, rollback);
-        if (!NT_SUCCESS(Status)) {
-            ERR("convert_old_data_extent returned %08x\n", Status);
-            free_traverse_ptr(&tp);
-            return Status;
-        }
-        
-        free_traverse_ptr(&tp);
-        
-        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            return Status;
-        }
-        
-        if (keycmp(&tp.item->key, &searchkey)) {
-            WARN("extent item not found for address %llx, size %llx\n", address, size);
-            free_traverse_ptr(&tp);
-            return STATUS_SUCCESS;
-        }
-    }
-    
-    ei = (EXTENT_ITEM*)tp.item->data;
-    
-    if (tp.item->size < sizeof(EXTENT_ITEM)) {
-        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-        free_traverse_ptr(&tp);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    if (!(ei->flags & EXTENT_ITEM_DATA)) {
-        ERR("error - EXTENT_ITEM_DATA flag not set\n");
-        free_traverse_ptr(&tp);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    // FIXME - is ei->refcount definitely the number of items, or is it the sum of the subitem refcounts?
-    
-    if (extent_item_is_shared(ei, tp.item->size - sizeof(EXTENT_ITEM))) {
-        NTSTATUS Status = convert_shared_data_extent(Vcb, address, size, rollback);
-        if (!NT_SUCCESS(Status)) {
-            ERR("convert_shared_data_extent returned %08x\n", Status);
-            free_traverse_ptr(&tp);
-            return Status;
-        }
-        
-        free_traverse_ptr(&tp);
-        
-        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            return Status;
-        }
-        
-        if (keycmp(&tp.item->key, &searchkey)) {
-            WARN("extent item not found for address %llx, size %llx\n", address, size);
-            free_traverse_ptr(&tp);
-            return STATUS_SUCCESS;
-        }
-        
-        ei = (EXTENT_ITEM*)tp.item->data;
-    }
-    
-    len = tp.item->size - sizeof(EXTENT_ITEM);
-    siptr = (UINT8*)&ei[1];
-    found = FALSE;
-    
-    do {
-        if (*siptr == TYPE_EXTENT_DATA_REF) {
-            edr = (EXTENT_DATA_REF*)&siptr[1];
-            
-            if (edr->root == subvol->id && edr->objid == inode && edr->offset == offset) {
-                found = TRUE;
-                break;
-            }
-
-            siptr += sizeof(UINT8) + sizeof(EXTENT_DATA_REF);
-            
-            if (len > sizeof(EXTENT_DATA_REF) + sizeof(UINT8)) {
-                len -= sizeof(EXTENT_DATA_REF) + sizeof(UINT8);
-            } else
-                break;
-//         // FIXME - TYPE_TREE_BLOCK_REF    0xB0
-        } else {
-            ERR("unrecognized extent subitem %x\n", *siptr);
-            free_traverse_ptr(&tp);
-            return STATUS_INTERNAL_ERROR;
-        }
-    } while (len > 0);
-    
-    if (!found) {
-        WARN("could not find extent data ref\n");
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    // FIXME - decrease subitem refcount if there already?
-    
-    len = tp.item->size - sizeof(UINT8) - sizeof(EXTENT_DATA_REF);
-    
-    delete_tree_item(Vcb, &tp, rollback);
-    
-    if (len == sizeof(EXTENT_ITEM)) { // extent no longer needed
-        chunk* c;
-        LIST_ENTRY* le2;
-        
-        if (changed_sector_list) {
-            changed_sector* sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
-            if (!sc) {
-                ERR("out of memory\n");
-                free_traverse_ptr(&tp);
-                return STATUS_INSUFFICIENT_RESOURCES;
-            }
-            
-            sc->ol.key = address;
-            sc->checksums = NULL;
-            sc->length = size / Vcb->superblock.sector_size;
-
-            sc->deleted = TRUE;
-            
-            insert_into_ordered_list(changed_sector_list, &sc->ol);
-        }
-        
-        c = NULL;
-        le2 = Vcb->chunks.Flink;
-        while (le2 != &Vcb->chunks) {
-            c = CONTAINING_RECORD(le2, chunk, list_entry);
-            
-            TRACE("chunk: %llx, %llx\n", c->offset, c->chunk_item->size);
-            
-            if (address >= c->offset && address + size < c->offset + c->chunk_item->size)
-                break;
-            
-            le2 = le2->Flink;
-        }
-        if (le2 == &Vcb->chunks) c = NULL;
-        
-        if (c) {
-            decrease_chunk_usage(c, size);
-            
-            add_to_space_list(c, address, size, SPACE_TYPE_DELETING);
-        }
-        
-        free_traverse_ptr(&tp);
-        return STATUS_SUCCESS;
-    }
-    
-    ei = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
-    if (!ei) {
-        ERR("out of memory\n");
-        free_traverse_ptr(&tp);
-        return STATUS_INSUFFICIENT_RESOURCES;
-    }
-            
-    RtlCopyMemory(ei, tp.item->data, siptr - tp.item->data);
-    ei->refcount--;
-    ei->generation = Vcb->superblock.generation;
-    
-    if (tp.item->data + len != siptr)
-        RtlCopyMemory((UINT8*)ei + (siptr - tp.item->data), siptr + sizeof(UINT8) + sizeof(EXTENT_DATA_REF), tp.item->size - (siptr - tp.item->data) - sizeof(UINT8) - sizeof(EXTENT_DATA_REF));
-    
-    free_traverse_ptr(&tp);
-    
-    if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ei, len, NULL, rollback)) {
-        ERR("error - failed to insert item\n");
-        ExFreePool(ei);
-        return STATUS_INTERNAL_ERROR;
-    }
-    
-    return STATUS_SUCCESS;
-}
-
-static __inline BOOL entry_in_ordered_list(LIST_ENTRY* list, UINT64 value) {
-    LIST_ENTRY* le = list->Flink;
-    ordered_list* ol;
-    
-    while (le != list) {
-        ol = (ordered_list*)le;
-        
-        if (ol->key > value)
-            return FALSE;
-        else if (ol->key == value)
-            return TRUE;
-        
-        le = le->Flink;
-    }
-    
-    return FALSE;
-}
-
-NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 end_data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
-    KEY searchkey;
-    traverse_ptr tp, next_tp;
-    NTSTATUS Status;
-    BOOL b;
-    
-    TRACE("(%p, (%llx, %llx), %llx, %llx, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data, end_data, changed_sector_list);
-    
-    searchkey.obj_id = fcb->inode;
-    searchkey.obj_type = TYPE_EXTENT_DATA;
-    searchkey.offset = start_data;
-    
-    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return Status;
-    }
-
-    do {
-        EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
-        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
-        UINT64 len;
-        
-        if (tp.item->size < sizeof(EXTENT_DATA)) {
-            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
-            Status = STATUS_INTERNAL_ERROR;
-            goto end;
-        }
-        
-        if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
-            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
-            Status = STATUS_INTERNAL_ERROR;
-            goto end;
-        }
-        
-        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
-        
-        len = ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes;
-        
-        if (tp.item->key.offset < end_data && tp.item->key.offset + len >= start_data) {
-            if (ed->compression != BTRFS_COMPRESSION_NONE) {
-                FIXME("FIXME - compression not supported at present\n");
-                Status = STATUS_NOT_SUPPORTED;
-                goto end;
-            }
-            
-            if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
-                WARN("root %llx, inode %llx, extent %llx: encryption not supported (type %x)\n", fcb->subvol->id, fcb->inode, tp.item->key.offset, ed->encryption);
-                Status = STATUS_NOT_SUPPORTED;
-                goto end;
+            if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
+                WARN("root %llx, inode %llx, extent %llx: encryption not supported (type %x)\n", subvol->id, inode, tp.item->key.offset, ed->encryption);
+                Status = STATUS_NOT_SUPPORTED;
+                goto end;
             }
             
             if (ed->encoding != BTRFS_ENCODING_NONE) {
@@ -4433,7 +4087,8 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                 if (start_data <= tp.item->key.offset && end_data >= tp.item->key.offset + len) { // remove all
                     delete_tree_item(Vcb, &tp, rollback);
                     
-                    fcb->inode_item.st_blocks -= len;
+                    if (ii)
+                        ii->st_blocks -= len;
                 } else if (start_data <= tp.item->key.offset && end_data < tp.item->key.offset + len) { // remove beginning
                     EXTENT_DATA* ned;
                     UINT64 size;
@@ -4458,14 +4113,15 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     
                     RtlCopyMemory(&ned->data[0], &ed->data[end_data - tp.item->key.offset], size);
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
                         goto end;
                     }
                     
-                    fcb->inode_item.st_blocks -= end_data - tp.item->key.offset;
+                    if (ii)
+                        ii->st_blocks -= end_data - tp.item->key.offset;
                 } else if (start_data > tp.item->key.offset && end_data >= tp.item->key.offset + len) { // remove end
                     EXTENT_DATA* ned;
                     UINT64 size;
@@ -4490,14 +4146,15 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     
                     RtlCopyMemory(&ned->data[0], &ed->data[0], size);
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
                         goto end;
                     }
                     
-                    fcb->inode_item.st_blocks -= tp.item->key.offset + len - start_data;
+                    if (ii)
+                        ii->st_blocks -= tp.item->key.offset + len - start_data;
                 } else if (start_data > tp.item->key.offset && end_data < tp.item->key.offset + len) { // remove middle
                     EXTENT_DATA* ned;
                     UINT64 size;
@@ -4522,7 +4179,7 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     
                     RtlCopyMemory(&ned->data[0], &ed->data[0], size);
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
@@ -4547,34 +4204,39 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     
                     RtlCopyMemory(&ned->data[0], &ed->data[end_data - tp.item->key.offset], size);
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + size, NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
                         goto end;
                     }
                     
-                    fcb->inode_item.st_blocks -= end_data - start_data;
+                    if (ii)
+                        ii->st_blocks -= end_data - start_data;
                 }
             } else if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
                 if (start_data <= tp.item->key.offset && end_data >= tp.item->key.offset + len) { // remove all
                     if (ed2->address != 0) {
-                        Status = remove_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset - ed2->offset, changed_sector_list, rollback);
+                        Status = decrease_extent_refcount_data(Vcb, ed2->address, ed2->size, subvol, inode, tp.item->key.offset - ed2->offset, 1, changed_sector_list, rollback);
                         if (!NT_SUCCESS(Status)) {
-                            ERR("remove_extent_ref returned %08x\n", Status);
+                            ERR("decrease_extent_refcount_data returned %08x\n", Status);
                             goto end;
                         }
                         
-                        fcb->inode_item.st_blocks -= len;
+                        if (ii)
+                            ii->st_blocks -= len;
                     }
                     
+                    if (ed->type == EXTENT_TYPE_PREALLOC)
+                        deleted_prealloc = TRUE;
+                    
                     delete_tree_item(Vcb, &tp, rollback);
                 } else if (start_data <= tp.item->key.offset && end_data < tp.item->key.offset + len) { // remove beginning
                     EXTENT_DATA* ned;
                     EXTENT_DATA2* ned2;
                     
-                    if (ed2->address != 0)
-                        fcb->inode_item.st_blocks -= end_data - tp.item->key.offset;
+                    if (ed2->address != 0 && ii)
+                        ii->st_blocks -= end_data - tp.item->key.offset;
                     
                     delete_tree_item(Vcb, &tp, rollback);
                     
@@ -4598,7 +4260,7 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     ned2->offset = ed2->address == 0 ? 0 : (ed2->offset + (end_data - tp.item->key.offset));
                     ned2->num_bytes = ed2->num_bytes - (end_data - tp.item->key.offset);
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
@@ -4608,8 +4270,8 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     EXTENT_DATA* ned;
                     EXTENT_DATA2* ned2;
                     
-                    if (ed2->address != 0)
-                        fcb->inode_item.st_blocks -= tp.item->key.offset + len - start_data;
+                    if (ed2->address != 0 && ii)
+                        ii->st_blocks -= tp.item->key.offset + len - start_data;
                     
                     delete_tree_item(Vcb, &tp, rollback);
                     
@@ -4633,7 +4295,7 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     ned2->offset = ed2->address == 0 ? 0 : ed2->offset;
                     ned2->num_bytes = start_data - tp.item->key.offset;
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
@@ -4643,8 +4305,8 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     EXTENT_DATA* ned;
                     EXTENT_DATA2* ned2;
                     
-                    if (ed2->address != 0)
-                        fcb->inode_item.st_blocks -= end_data - start_data;
+                    if (ed2->address != 0 && ii)
+                        ii->st_blocks -= end_data - start_data;
                     
                     delete_tree_item(Vcb, &tp, rollback);
                     
@@ -4668,7 +4330,7 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     ned2->offset = ed2->address == 0 ? 0 : ed2->offset;
                     ned2->num_bytes = start_data - tp.item->key.offset;
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
@@ -4695,87 +4357,74 @@ NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT
                     ned2->offset = ed2->address == 0 ? 0 : (ed2->offset + (end_data - tp.item->key.offset));
                     ned2->num_bytes = tp.item->key.offset + len - end_data;
                     
-                    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
+                    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, end_data, ned, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
                         ERR("insert_tree_item failed\n");
                         ExFreePool(ned);
                         Status = STATUS_INTERNAL_ERROR;
                         goto end;
                     }
+                    
+                    if (ed2->address != 0) {
+                        Status = increase_extent_refcount_data(Vcb, ed2->address, ed2->size, subvol, inode, tp.item->key.offset - ed2->offset, 1, rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("increase_extent_refcount_data returned %08x\n", Status);
+                            goto end;
+                        }
+                    }
                 }
             }
         }
 
+cont:
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
-            if (tp.item->key.obj_id > fcb->inode || tp.item->key.obj_type > TYPE_EXTENT_DATA || tp.item->key.offset >= end_data)
+            if (tp.item->key.obj_id > inode || tp.item->key.obj_type > TYPE_EXTENT_DATA || tp.item->key.offset >= end_data)
                 break;
         }
     } while (b);
     
     // FIXME - do bitmap analysis of changed extents, and free what we can
     
-    Status = STATUS_SUCCESS;
+    if (ii && deleted_prealloc && !is_file_prealloc_inode(Vcb, subvol, inode, 0, sector_align(ii->st_size, Vcb->superblock.sector_size)))
+        ii->flags &= ~BTRFS_INODE_PREALLOC;
     
 end:
-    free_traverse_ptr(&tp);
     
     return Status;
 }
 
-static BOOL insert_extent_chunk(device_extension* Vcb, fcb* fcb, chunk* c, UINT64 start_data, UINT64 length, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
-    UINT64 address;
+NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 end_data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
     NTSTATUS Status;
-    EXTENT_ITEM_DATA_REF* eidr;
-    EXTENT_DATA* ed;
-    EXTENT_DATA2* ed2;
-    ULONG edsize = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);
-    changed_sector* sc;
-    traverse_ptr tp;
-    int i;
-    
-    TRACE("(%p, (%llx, %llx), %llx, %llx, %llx, %p, %p)\n", Vcb, fcb->subvol->id, fcb->inode, c->offset, start_data, length, data, changed_sector_list);
-    
-    if (!find_address_in_chunk(Vcb, c, length, &address))
-        return FALSE;
-    
-    eidr = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_DATA_REF), ALLOC_TAG);
-    if (!eidr) {
-        ERR("out of memory\n");
-        return FALSE;
-    }
     
-    eidr->ei.refcount = 1;
-    eidr->ei.generation = Vcb->superblock.generation;
-    eidr->ei.flags = EXTENT_ITEM_DATA;
-    eidr->type = TYPE_EXTENT_DATA_REF;
-    eidr->edr.root = fcb->subvol->id;
-    eidr->edr.objid = fcb->inode;
-    eidr->edr.offset = start_data;
-    eidr->edr.count = 1;
+    TRACE("(%p, (%llx, %llx), %llx, %llx, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data, end_data, changed_sector_list);
     
-    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, length, eidr, sizeof(EXTENT_ITEM_DATA_REF), &tp, rollback)) {
-        ERR("insert_tree_item failed\n");
-        ExFreePool(eidr);
-        return FALSE;
+    Status = excise_extents_inode(Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, start_data, end_data, changed_sector_list, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("excise_extents_inode returned %08x\n");
+        return Status;
     }
     
-    tp.tree->header.generation = eidr->ei.generation;
-    
-    free_traverse_ptr(&tp);
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS do_write_data(device_extension* Vcb, UINT64 address, void* data, UINT64 length, LIST_ENTRY* changed_sector_list) {
+    NTSTATUS Status;
+    changed_sector* sc;
+    int i;
     
     Status = write_data(Vcb, address, data, length);
     if (!NT_SUCCESS(Status)) {
         ERR("write_data returned %08x\n", Status);
-        return FALSE;
+        return Status;
     }
     
     if (changed_sector_list) {
         sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
         if (!sc) {
             ERR("out of memory\n");
-            return FALSE;
+            return STATUS_INSUFFICIENT_RESOURCES;
         }
         
         sc->ol.key = address;
@@ -4786,14 +4435,44 @@ static BOOL insert_extent_chunk(device_extension* Vcb, fcb* fcb, chunk* c, UINT6
         if (!sc->checksums) {
             ERR("out of memory\n");
             ExFreePool(sc);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        for (i = 0; i < sc->length; i++) {
+            sc->checksums[i] = ~calc_crc32c(0xffffffff, (UINT8*)data + (i * Vcb->superblock.sector_size), Vcb->superblock.sector_size);
+        }
+
+        insert_into_ordered_list(changed_sector_list, &sc->ol);
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+BOOL insert_extent_chunk_inode(device_extension* Vcb, root* subvol, UINT64 inode, INODE_ITEM* inode_item, chunk* c, UINT64 start_data,
+                               UINT64 length, BOOL prealloc, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    UINT64 address;
+    NTSTATUS Status;
+    EXTENT_DATA* ed;
+    EXTENT_DATA2* ed2;
+    ULONG edsize = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);
+    
+    TRACE("(%p, (%llx, %llx), %llx, %llx, %llx, %p, %p)\n", Vcb, subvol->id, inode, c->offset, start_data, length, data, changed_sector_list);
+    
+    if (!find_address_in_chunk(Vcb, c, length, &address))
+        return FALSE;
+    
+    Status = increase_extent_refcount_data(Vcb, address, length, subvol, inode, start_data, 1, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("increase_extent_refcount_data returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (data) {
+        Status = do_write_data(Vcb, address, data, length, changed_sector_list);
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_write_data returned %08x\n", Status);
             return FALSE;
         }
-        
-        for (i = 0; i < sc->length; i++) {
-            sc->checksums[i] = ~calc_crc32c(0xffffffff, (UINT8*)data + (i * Vcb->superblock.sector_size), Vcb->superblock.sector_size);
-        }
-
-        insert_into_ordered_list(changed_sector_list, &sc->ol);
     }
     
     // add extent data to inode
@@ -4808,7 +4487,7 @@ static BOOL insert_extent_chunk(device_extension* Vcb, fcb* fcb, chunk* c, UINT6
     ed->compression = BTRFS_COMPRESSION_NONE;
     ed->encryption = BTRFS_ENCRYPTION_NONE;
     ed->encoding = BTRFS_ENCODING_NONE;
-    ed->type = EXTENT_TYPE_REGULAR;
+    ed->type = prealloc ? EXTENT_TYPE_PREALLOC : EXTENT_TYPE_REGULAR;
     
     ed2 = (EXTENT_DATA2*)ed->data;
     ed2->address = address;
@@ -4816,7 +4495,7 @@ static BOOL insert_extent_chunk(device_extension* Vcb, fcb* fcb, chunk* c, UINT6
     ed2->offset = 0;
     ed2->num_bytes = length;
     
-    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, start_data, ed, edsize, NULL, rollback)) {
+    if (!insert_tree_item(Vcb, subvol, inode, TYPE_EXTENT_DATA, start_data, ed, edsize, NULL, rollback)) {
         ERR("insert_tree_item failed\n");
         ExFreePool(ed);
         return FALSE;
@@ -4825,11 +4504,20 @@ static BOOL insert_extent_chunk(device_extension* Vcb, fcb* fcb, chunk* c, UINT6
     increase_chunk_usage(c, length);
     add_to_space_list(c, address, length, SPACE_TYPE_WRITING);
     
-    fcb->inode_item.st_blocks += length;
+    if (inode_item) {
+        inode_item->st_blocks += length;
+        
+        if (prealloc)
+            inode_item->flags |= BTRFS_INODE_PREALLOC;
+    }
     
     return TRUE;
 }
 
+static BOOL insert_extent_chunk(device_extension* Vcb, fcb* fcb, chunk* c, UINT64 start_data, UINT64 length, BOOL prealloc, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    return insert_extent_chunk_inode(Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, c, start_data, length, prealloc, data, changed_sector_list, rollback);
+}
+
 static BOOL extend_data(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 length, void* data,
                         LIST_ENTRY* changed_sector_list, traverse_ptr* edtp, traverse_ptr* eitp, LIST_ENTRY* rollback) {
     EXTENT_DATA* ed;
@@ -4998,753 +4686,1252 @@ static BOOL try_extend_data(device_extension* Vcb, fcb* fcb, UINT64 start_data,
         goto end;
     }
     
-    searchkey.obj_id = ed2->address;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = ed2->size;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        goto end;
-    }
+    searchkey.obj_id = ed2->address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = ed2->size;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        goto end;
+    }
+    
+    if (keycmp(&tp2.item->key, &searchkey)) {
+        ERR("error - extent %llx,%llx not found in tree\n", ed2->address, ed2->size);
+        int3; // TESTING
+        goto end;
+    }
+    
+    if (tp2.item->size == sizeof(EXTENT_ITEM_V0)) { // old extent ref, convert
+        NTSTATUS Status = convert_old_data_extent(Vcb, ed2->address, ed2->size, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("convert_old_data_extent returned %08x\n", Status);
+            goto end;
+        }
+        
+        Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            goto end;
+        }
+        
+        if (keycmp(&tp2.item->key, &searchkey)) {
+            WARN("extent item not found for address %llx, size %llx\n", ed2->address, ed2->size);
+            goto end;
+        }
+    }
+    
+    ei = (EXTENT_ITEM*)tp2.item->data;
+    
+    if (tp.item->size < sizeof(EXTENT_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+        goto end;
+    }
+    
+    // FIXME - test this
+    if (extent_item_is_shared(ei, tp2.item->size - sizeof(EXTENT_ITEM))) {
+        NTSTATUS Status = convert_shared_data_extent(Vcb, ed2->address, ed2->size, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("convert_shared_data_extent returned %08x\n", Status);
+            goto end;
+        }
+        
+        Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            goto end;
+        }
+        
+        if (keycmp(&tp2.item->key, &searchkey)) {
+            WARN("extent item not found for address %llx, size %llx\n", ed2->address, ed2->size);
+            goto end;
+        }
+        
+        ei = (EXTENT_ITEM*)tp2.item->data;
+        
+        if (tp.item->size < sizeof(EXTENT_ITEM)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+            goto end;
+        }
+    }
+    
+    if (ei->refcount != 1) {
+        TRACE("extent refcount was not 1\n");
+        goto end;
+    }
+    
+    if (ei->flags != EXTENT_ITEM_DATA) {
+        ERR("error - extent was not a data extent\n");
+        goto end;
+    }
+    
+    c = get_chunk_from_address(Vcb, ed2->address);
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        if (s->offset == ed2->address + ed2->size) {
+            if (s->type == SPACE_TYPE_FREE && s->size >= length) {
+                success = extend_data(Vcb, fcb, start_data, length, data, changed_sector_list, &tp, &tp2, rollback);
+            }
+            break;
+        } else if (s->offset > ed2->address + ed2->size)
+            break;
+        
+        le = le->Flink;
+    }
+    
+end:
+        
+    return success;
+}
+
+static NTSTATUS insert_prealloc_extent(fcb* fcb, UINT64 start, UINT64 length, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = fcb->Vcb->chunks.Flink;
+    chunk* c;
+    UINT64 flags;
+    
+    // FIXME - how do we know which RAID level to put this to?
+    flags = BLOCK_FLAG_DATA; // SINGLE
+    
+    // FIXME - if length is more than max chunk size, loop through and
+    // create the new chunks first
+    
+    while (le != &fcb->Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+            if (insert_extent_chunk(fcb->Vcb, fcb, c, start, length, TRUE, NULL, NULL, rollback))
+                return STATUS_SUCCESS;
+        }
+
+        le = le->Flink;
+    }
+    
+    if ((c = alloc_chunk(fcb->Vcb, flags, rollback))) {
+        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+            if (insert_extent_chunk(fcb->Vcb, fcb, c, start, length, TRUE, NULL, NULL, rollback))
+                return STATUS_SUCCESS;
+        }
+    }
+    
+    // FIXME - rebalance chunks if free space elsewhere?
+    WARN("couldn't find any data chunks with %llx bytes free\n", length);
+
+    return STATUS_DISK_FULL;
+}
+
+NTSTATUS insert_sparse_extent(device_extension* Vcb, root* r, UINT64 inode, UINT64 start, UINT64 length, LIST_ENTRY* rollback) {
+    EXTENT_DATA* ed;
+    EXTENT_DATA2* ed2;
+    
+    TRACE("(%p, %llx, %llx, %llx, %llx)\n", Vcb, r->id, inode, start, length);
+    
+    ed = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
+    if (!ed) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    ed->generation = Vcb->superblock.generation;
+    ed->decoded_size = length;
+    ed->compression = BTRFS_COMPRESSION_NONE;
+    ed->encryption = BTRFS_ENCRYPTION_NONE;
+    ed->encoding = BTRFS_ENCODING_NONE;
+    ed->type = EXTENT_TYPE_REGULAR;
+    
+    ed2 = (EXTENT_DATA2*)ed->data;
+    ed2->address = 0;
+    ed2->size = 0;
+    ed2->offset = 0;
+    ed2->num_bytes = length;
+    
+    if (!insert_tree_item(Vcb, r, inode, TYPE_EXTENT_DATA, start, ed, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(ed);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+// static void print_tree(tree* t) {
+//     LIST_ENTRY* le = t->itemlist.Flink;
+//     while (le != &t->itemlist) {
+//         tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+//         ERR("%llx,%x,%llx (ignore = %s)\n", td->key.obj_id, td->key.obj_type, td->key.offset, td->ignore ? "TRUE" : "FALSE");
+//         le = le->Flink;
+//     }
+// }
+
+static NTSTATUS insert_extent(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 length, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = Vcb->chunks.Flink;
+    chunk* c;
+    KEY searchkey;
+    UINT64 flags;
+    
+    TRACE("(%p, (%llx, %llx), %llx, %llx, %p, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data, length, data, changed_sector_list);
+    
+    // FIXME - split data up if not enough space for just one extent
+    
+    if (start_data > 0 && try_extend_data(Vcb, fcb, start_data, length, data, changed_sector_list, rollback))
+        return STATUS_SUCCESS;
+    
+    // if there is a gap before start_data, plug it with a sparse extent
+    if (start_data > 0) {
+        traverse_ptr tp;
+        NTSTATUS Status;
+        EXTENT_DATA* ed;
+        UINT64 len;
+        
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_EXTENT_DATA;
+        searchkey.offset = start_data;
+        
+        Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+//         if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || tp.item->key.offset >= start_data) {
+//             traverse_ptr next_tp;
+//             
+//             ERR("error - did not find EXTENT_DATA expected - looking for %llx,%x,%llx, found %llx,%x,%llx\n",
+//                 searchkey.obj_id, searchkey.obj_type, searchkey.offset, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+//             print_tree(tp.tree);
+//             
+//             if (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
+//                 ERR("---\n");
+//                 ERR("key = %llx,%x,%llx\n", next_tp.tree->paritem->key.obj_id, next_tp.tree->paritem->key.obj_type, next_tp.tree->paritem->key.offset);
+//                 print_tree(next_tp.tree);
+//                 
+//                 free_traverse_ptr(&next_tp);
+//             } else
+//                 ERR("next item not found\n");
+//             
+//             int3;
+//             free_traverse_ptr(&tp);
+//             return STATUS_INTERNAL_ERROR;
+//         }
+
+        if (tp.item->key.obj_type == TYPE_EXTENT_DATA && tp.item->size >= sizeof(EXTENT_DATA)) {
+            EXTENT_DATA2* ed2;
+            
+            ed = (EXTENT_DATA*)tp.item->data;
+            ed2 = (EXTENT_DATA2*)ed->data;
+            
+            len = ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes;
+        } else
+            ed = NULL;
+        
+        if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || !ed || tp.item->key.offset + len < start_data) {
+            if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA)
+                Status = insert_sparse_extent(Vcb, fcb->subvol, fcb->inode, 0, start_data, rollback);
+            else if (!ed)
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
+            else {
+                Status = insert_sparse_extent(Vcb, fcb->subvol, fcb->inode, tp.item->key.offset + len,
+                                              start_data - tp.item->key.offset - len, rollback);
+            }
+            if (!NT_SUCCESS(Status)) {
+                ERR("insert_sparse_extent returned %08x\n", Status);
+                return Status;
+            }
+        }
+    }
+    
+    // FIXME - how do we know which RAID level to put this to?
+    flags = BLOCK_FLAG_DATA; // SINGLE
     
-    if (keycmp(&tp2.item->key, &searchkey)) {
-        ERR("error - extent %llx,%llx not found in tree\n", ed2->address, ed2->size);
-        int3; // TESTING
-        goto end2;
-    }
+//     if (!chunk_test) { // TESTING
+//         if ((c = alloc_chunk(Vcb, flags, NULL))) {
+//             ERR("chunk_item->type = %llx\n", c->chunk_item->type);
+//             ERR("size = %llx\n", c->chunk_item->size);
+//             ERR("used = %llx\n", c->used);
+//             
+//             if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+//                 if (insert_extent_chunk(Vcb, fcb, c, start_data, length, data, changed_sector_list)) {
+// //                     chunk_test = TRUE;
+//                     ERR("SUCCESS\n");
+//                     return STATUS_SUCCESS;
+//                 } else
+//                     ERR(":-(\n");
+//             } else
+//                 ERR("???\n");
+//         }
+//     }
     
-    if (tp2.item->size == sizeof(EXTENT_ITEM_V0)) { // old extent ref, convert
-        NTSTATUS Status = convert_old_data_extent(Vcb, ed2->address, ed2->size, rollback);
-        if (!NT_SUCCESS(Status)) {
-            ERR("convert_old_data_extent returned %08x\n", Status);
-            goto end2;
-        }
-        
-        free_traverse_ptr(&tp2);
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
         
-        Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            goto end;
+        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+            if (insert_extent_chunk(Vcb, fcb, c, start_data, length, FALSE, data, changed_sector_list, rollback))
+                return STATUS_SUCCESS;
         }
-        
-        if (keycmp(&tp2.item->key, &searchkey)) {
-            WARN("extent item not found for address %llx, size %llx\n", ed2->address, ed2->size);
-            goto end2;
+
+        le = le->Flink;
+    }
+    
+    if ((c = alloc_chunk(Vcb, flags, rollback))) {
+        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
+            if (insert_extent_chunk(Vcb, fcb, c, start_data, length, FALSE, data, changed_sector_list, rollback))
+                return STATUS_SUCCESS;
         }
     }
     
-    ei = (EXTENT_ITEM*)tp2.item->data;
+    // FIXME - rebalance chunks if free space elsewhere?
+    WARN("couldn't find any data chunks with %llx bytes free\n", length);
+
+    return STATUS_DISK_FULL;
+}
+
+void update_checksum_tree(device_extension* Vcb, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = changed_sector_list->Flink;
+    changed_sector* cs;
+    traverse_ptr tp, next_tp;
+    KEY searchkey;
+    UINT32* data;
+    NTSTATUS Status;
     
-    if (tp.item->size < sizeof(EXTENT_ITEM)) {
-        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-        goto end2;
+    if (!Vcb->checksum_root) {
+        ERR("no checksum root\n");
+        goto exit;
     }
     
-    // FIXME - test this
-    if (extent_item_is_shared(ei, tp2.item->size - sizeof(EXTENT_ITEM))) {
-        NTSTATUS Status = convert_shared_data_extent(Vcb, ed2->address, ed2->size, rollback);
-        if (!NT_SUCCESS(Status)) {
-            ERR("convert_shared_data_extent returned %08x\n", Status);
-            goto end2;
-        }
-        
-        free_traverse_ptr(&tp2);
-        
-        Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            goto end;
-        }
+    while (le != changed_sector_list) {
+        UINT64 startaddr, endaddr;
+        ULONG len;
+        UINT32* checksums;
+        RTL_BITMAP bmp;
+        ULONG* bmparr;
+        ULONG runlength, index;
         
-        if (keycmp(&tp2.item->key, &searchkey)) {
-            WARN("extent item not found for address %llx, size %llx\n", ed2->address, ed2->size);
-            goto end2;
-        }
+        cs = (changed_sector*)le;
         
-        ei = (EXTENT_ITEM*)tp2.item->data;
+        searchkey.obj_id = EXTENT_CSUM_ID;
+        searchkey.obj_type = TYPE_EXTENT_CSUM;
+        searchkey.offset = cs->ol.key;
         
-        if (tp.item->size < sizeof(EXTENT_ITEM)) {
-            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-            goto end2;
-        }
-    }
-    
-    if (ei->refcount != 1) {
-        TRACE("extent refcount was not 1\n");
-        goto end2;
-    }
-    
-    if (ei->flags != EXTENT_ITEM_DATA) {
-        ERR("error - extent was not a data extent\n");
-        goto end2;
-    }
-    
-    c = get_chunk_from_address(Vcb, ed2->address);
-    
-    le = c->space.Flink;
-    while (le != &c->space) {
-        s = CONTAINING_RECORD(le, space, list_entry);
+        // FIXME - create checksum_root if it doesn't exist at all
         
-        if (s->offset == ed2->address + ed2->size) {
-            if (s->type == SPACE_TYPE_FREE && s->size >= length) {
-                success = extend_data(Vcb, fcb, start_data, length, data, changed_sector_list, &tp, &tp2, rollback);
+        Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) { // tree is completely empty
+            // FIXME - do proper check here that tree is empty
+            if (!cs->deleted) {
+                checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * cs->length, ALLOC_TAG);
+                if (!checksums) {
+                    ERR("out of memory\n");
+                    goto exit;
+                }
+                
+                RtlCopyMemory(checksums, cs->checksums, sizeof(UINT32) * cs->length);
+                
+                if (!insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, cs->ol.key, checksums, sizeof(UINT32) * cs->length, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(checksums);
+                    goto exit;
+                }
+            }
+        } else {
+            UINT32 tplen;
+            
+            // FIXME - check entry is TYPE_EXTENT_CSUM?
+            
+            if (tp.item->key.offset < cs->ol.key && tp.item->key.offset + (tp.item->size * Vcb->superblock.sector_size / sizeof(UINT32)) >= cs->ol.key)
+                startaddr = tp.item->key.offset;
+            else
+                startaddr = cs->ol.key;
+            
+            searchkey.obj_id = EXTENT_CSUM_ID;
+            searchkey.obj_type = TYPE_EXTENT_CSUM;
+            searchkey.offset = cs->ol.key + (cs->length * Vcb->superblock.sector_size);
+            
+            Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                goto exit;
+            }
+            
+            tplen = tp.item->size / sizeof(UINT32);
+            
+            if (tp.item->key.offset + (tplen * Vcb->superblock.sector_size) >= cs->ol.key + (cs->length * Vcb->superblock.sector_size))
+                endaddr = tp.item->key.offset + (tplen * Vcb->superblock.sector_size);
+            else
+                endaddr = cs->ol.key + (cs->length * Vcb->superblock.sector_size);
+            
+            TRACE("cs starts at %llx (%x sectors)\n", cs->ol.key, cs->length);
+            TRACE("startaddr = %llx\n", startaddr);
+            TRACE("endaddr = %llx\n", endaddr);
+            
+            len = (endaddr - startaddr) / Vcb->superblock.sector_size;
+            
+            checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * len, ALLOC_TAG);
+            if (!checksums) {
+                ERR("out of memory\n");
+                goto exit;
+            }
+            
+            bmparr = ExAllocatePoolWithTag(PagedPool, sizeof(ULONG) * ((len/8)+1), ALLOC_TAG);
+            if (!bmparr) {
+                ERR("out of memory\n");
+                ExFreePool(checksums);
+                goto exit;
+            }
+                
+            RtlInitializeBitMap(&bmp, bmparr, len);
+            RtlSetAllBits(&bmp);
+            
+            searchkey.obj_id = EXTENT_CSUM_ID;
+            searchkey.obj_type = TYPE_EXTENT_CSUM;
+            searchkey.offset = cs->ol.key;
+            
+            Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                goto exit;
+            }
+            
+            // set bit = free space, cleared bit = allocated sector
+            
+    //         ERR("start loop\n");
+            while (tp.item->key.offset < endaddr) {
+    //             ERR("%llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                if (tp.item->key.offset >= startaddr) {
+                    if (tp.item->size > 0) {
+                        RtlCopyMemory(&checksums[(tp.item->key.offset - startaddr) / Vcb->superblock.sector_size], tp.item->data, tp.item->size);
+                        RtlClearBits(&bmp, (tp.item->key.offset - startaddr) / Vcb->superblock.sector_size, tp.item->size / sizeof(UINT32));
+                    }
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                }
+                
+                if (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
+                    tp = next_tp;
+                } else
+                    break;
+            }
+    //         ERR("end loop\n");
+            
+            if (cs->deleted) {
+                RtlSetBits(&bmp, (cs->ol.key - startaddr) / Vcb->superblock.sector_size, cs->length);
+            } else {
+                RtlCopyMemory(&checksums[(cs->ol.key - startaddr) / Vcb->superblock.sector_size], cs->checksums, cs->length * sizeof(UINT32));
+                RtlClearBits(&bmp, (cs->ol.key - startaddr) / Vcb->superblock.sector_size, cs->length);
+            }
+            
+            runlength = RtlFindFirstRunClear(&bmp, &index);
+            
+            while (runlength != 0) {
+                do {
+                    ULONG rl;
+                    
+                    if (runlength * sizeof(UINT32) > MAX_CSUM_SIZE)
+                        rl = MAX_CSUM_SIZE / sizeof(UINT32);
+                    else
+                        rl = runlength;
+                    
+                    data = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * rl, ALLOC_TAG);
+                    if (!data) {
+                        ERR("out of memory\n");
+                        ExFreePool(bmparr);
+                        ExFreePool(checksums);
+                        goto exit;
+                    }
+                    
+                    RtlCopyMemory(data, &checksums[index], sizeof(UINT32) * rl);
+                    
+                    if (!insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, startaddr + (index * Vcb->superblock.sector_size), data, sizeof(UINT32) * rl, NULL, rollback)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(data);
+                        ExFreePool(bmparr);
+                        ExFreePool(checksums);
+                        goto exit;
+                    }
+                    
+                    runlength -= rl;
+                    index += rl;
+                } while (runlength > 0);
+                
+                runlength = RtlFindNextForwardRunClear(&bmp, index, &index);
             }
-            break;
-        } else if (s->offset > ed2->address + ed2->size)
-            break;
+            
+            ExFreePool(bmparr);
+            ExFreePool(checksums);
+        }
         
         le = le->Flink;
     }
     
-end2:
-    free_traverse_ptr(&tp2);
-    
-end:
-    free_traverse_ptr(&tp);
+exit:
+    while (!IsListEmpty(changed_sector_list)) {
+        le = RemoveHeadList(changed_sector_list);
+        cs = (changed_sector*)le;
         
-    return success;
+        if (cs->checksums)
+            ExFreePool(cs->checksums);
+        
+        ExFreePool(cs);
+    }
 }
 
-NTSTATUS insert_sparse_extent(device_extension* Vcb, root* r, UINT64 inode, UINT64 start, UINT64 length, LIST_ENTRY* rollback) {
-    EXTENT_DATA* ed;
-    EXTENT_DATA2* ed2;
+NTSTATUS truncate_file(fcb* fcb, UINT64 end, LIST_ENTRY* rollback) {
+    LIST_ENTRY changed_sector_list;
+    NTSTATUS Status;
+    BOOL nocsum = fcb->inode_item.flags & BTRFS_INODE_NODATASUM;
     
-    TRACE("(%p, %llx, %llx, %llx, %llx)\n", Vcb, r->id, inode, start, length);
+    if (!nocsum)
+        InitializeListHead(&changed_sector_list);
     
-    ed = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
-    if (!ed) {
-        ERR("out of memory\n");
-        return STATUS_INSUFFICIENT_RESOURCES;
+    // FIXME - convert into inline extent if short enough
+    
+    Status = excise_extents(fcb->Vcb, fcb, sector_align(end, fcb->Vcb->superblock.sector_size),
+                            sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), nocsum ? NULL : &changed_sector_list, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - excise_extents failed\n");
+        return Status;
     }
     
-    ed->generation = Vcb->superblock.generation;
-    ed->decoded_size = length;
-    ed->compression = BTRFS_COMPRESSION_NONE;
-    ed->encryption = BTRFS_ENCRYPTION_NONE;
-    ed->encoding = BTRFS_ENCODING_NONE;
-    ed->type = EXTENT_TYPE_REGULAR;
+    fcb->inode_item.st_size = end;
+    TRACE("setting st_size to %llx\n", end);
+
+    fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
+    fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;
+    fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;
+    // FIXME - inform cache manager of this
     
-    ed2 = (EXTENT_DATA2*)ed->data;
-    ed2->address = 0;
-    ed2->size = 0;
-    ed2->offset = 0;
-    ed2->num_bytes = length;
+    TRACE("fcb %p FileSize = %llx\n", fcb, fcb->Header.FileSize.QuadPart);
     
-    if (!insert_tree_item(Vcb, r, inode, TYPE_EXTENT_DATA, start, ed, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), NULL, rollback)) {
-        ERR("insert_tree_item failed\n");
-        ExFreePool(ed);
-        return STATUS_INTERNAL_ERROR;
+    if (!nocsum)
+        update_checksum_tree(fcb->Vcb, &changed_sector_list, rollback);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS extend_file(fcb* fcb, file_ref* fileref, UINT64 end, BOOL prealloc, LIST_ENTRY* rollback) {
+    UINT64 oldalloc, newalloc;
+    KEY searchkey;
+    traverse_ptr tp;
+    BOOL cur_inline;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %x, %p)\n", fcb, end, rollback);
+
+    if (fcb->ads)
+        return stream_set_end_of_file_information(fcb->Vcb, end, fcb, fileref, NULL, FALSE, rollback) ;
+    else {
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_EXTENT_DATA;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        oldalloc = 0;
+        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_EXTENT_DATA) {
+            EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
+            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
+            
+            if (tp.item->size < sizeof(EXTENT_DATA)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            oldalloc = tp.item->key.offset + (ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes);
+            cur_inline = ed->type == EXTENT_TYPE_INLINE;
+        
+            if (cur_inline && end > fcb->Vcb->max_inline) {
+                LIST_ENTRY changed_sector_list;
+                BOOL nocsum = fcb->inode_item.flags & BTRFS_INODE_NODATASUM;
+                UINT64 origlength, length;
+                UINT8* data;
+                
+                TRACE("giving inline file proper extents\n");
+                
+                origlength = ed->decoded_size;
+                
+                cur_inline = FALSE;
+                
+                if (!nocsum)
+                    InitializeListHead(&changed_sector_list);
+                
+                delete_tree_item(fcb->Vcb, &tp, rollback);
+                
+                length = sector_align(origlength, fcb->Vcb->superblock.sector_size);
+                
+                data = ExAllocatePoolWithTag(PagedPool, length, ALLOC_TAG);
+                if (!data) {
+                    ERR("could not allocate %llx bytes for data\n", length);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                if (length > origlength)
+                    RtlZeroMemory(data + origlength, length - origlength);
+                
+                RtlCopyMemory(data, ed->data, origlength);
+                
+                fcb->inode_item.st_blocks -= origlength;
+                
+                Status = insert_extent(fcb->Vcb, fcb, tp.item->key.offset, length, data, nocsum ? NULL : &changed_sector_list, rollback);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("insert_extent returned %08x\n", Status);
+                    ExFreePool(data);
+                    return Status;
+                }
+                
+                oldalloc = tp.item->key.offset + length;
+                
+                ExFreePool(data);
+                
+                if (!nocsum)
+                    update_checksum_tree(fcb->Vcb, &changed_sector_list, rollback);
+            }
+            
+            if (cur_inline) {
+                ULONG edsize;
+                
+                if (end > oldalloc) {
+                    edsize = sizeof(EXTENT_DATA) - 1 + end - tp.item->key.offset;
+                    ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);
+                    
+                    if (!ed) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    RtlZeroMemory(ed, edsize);
+                    RtlCopyMemory(ed, tp.item->data, tp.item->size);
+                    
+                    ed->decoded_size = end - tp.item->key.offset;
+                    
+                    delete_tree_item(fcb->Vcb, &tp, rollback);
+                    
+                    if (!insert_tree_item(fcb->Vcb, fcb->subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ed, edsize, NULL, rollback)) {
+                        ERR("error - failed to insert item\n");
+                        ExFreePool(ed);
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                }
+                
+                TRACE("extending inline file (oldalloc = %llx, end = %llx)\n", oldalloc, end);
+                
+                fcb->inode_item.st_size = end;
+                TRACE("setting st_size to %llx\n", end);
+                
+                fcb->inode_item.st_blocks = end;
+
+                fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
+            } else {
+                newalloc = sector_align(end, fcb->Vcb->superblock.sector_size);
+            
+                if (newalloc > oldalloc) {
+                    if (prealloc) {
+                        // FIXME - try and extend previous extent first
+                        
+                        Status = insert_prealloc_extent(fcb, oldalloc, newalloc - oldalloc, rollback);
+                    
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("insert_prealloc_extent returned %08x\n", Status);
+                            return Status;
+                        }
+                    } else {
+                        Status = insert_sparse_extent(fcb->Vcb, fcb->subvol, fcb->inode, oldalloc, newalloc - oldalloc, rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("insert_sparse_extent returned %08x\n", Status);
+                            return Status;
+                        }
+                    }
+                }
+                
+                fcb->inode_item.st_size = end;
+                TRACE("setting st_size to %llx\n", end);
+                
+                TRACE("newalloc = %llx\n", newalloc);
+                
+                fcb->Header.AllocationSize.QuadPart = newalloc;
+                fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
+            }
+        } else {
+            if (end > fcb->Vcb->max_inline) {
+                newalloc = sector_align(end, fcb->Vcb->superblock.sector_size);
+            
+                if (prealloc) {
+                    Status = insert_prealloc_extent(fcb, 0, newalloc, rollback);
+                    
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("insert_prealloc_extent returned %08x\n", Status);
+                        return Status;
+                    }
+                } else {
+                    Status = insert_sparse_extent(fcb->Vcb, fcb->subvol, fcb->inode, 0, newalloc, rollback);
+                    
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("insert_sparse_extent returned %08x\n", Status);
+                        return Status;
+                    }
+                }
+                
+                fcb->inode_item.st_size = end;
+                TRACE("setting st_size to %llx\n", end);
+                
+                TRACE("newalloc = %llx\n", newalloc);
+                
+                fcb->Header.AllocationSize.QuadPart = newalloc;
+                fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
+            } else {
+                EXTENT_DATA* ed;
+                ULONG edsize;
+                
+                edsize = sizeof(EXTENT_DATA) - 1 + end;
+                ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);
+                
+                if (!ed) {
+                    ERR("out of memory\n");
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                ed->generation = fcb->Vcb->superblock.generation;
+                ed->decoded_size = end;
+                ed->compression = BTRFS_COMPRESSION_NONE;
+                ed->encryption = BTRFS_ENCRYPTION_NONE;
+                ed->encoding = BTRFS_ENCODING_NONE;
+                ed->type = EXTENT_TYPE_INLINE;
+                
+                RtlZeroMemory(ed->data, end);
+
+                if (!insert_tree_item(fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, ed, edsize, NULL, rollback)) {
+                    ERR("error - failed to insert item\n");
+                    ExFreePool(ed);
+                    return STATUS_INTERNAL_ERROR;
+                }
+                
+                fcb->inode_item.st_size = end;
+                TRACE("setting st_size to %llx\n", end);
+                
+                fcb->inode_item.st_blocks = end;
+
+                fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
+            }
+        }
     }
     
     return STATUS_SUCCESS;
 }
 
-// static void print_tree(tree* t) {
-//     LIST_ENTRY* le = t->itemlist.Flink;
-//     while (le != &t->itemlist) {
-//         tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
-//         ERR("%llx,%x,%llx (ignore = %s)\n", td->key.obj_id, td->key.obj_type, td->key.offset, td->ignore ? "TRUE" : "FALSE");
-//         le = le->Flink;
-//     }
-// }
-
-static NTSTATUS insert_extent(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 length, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
-    LIST_ENTRY* le = Vcb->chunks.Flink;
-    chunk* c;
+static UINT64 get_extent_item_refcount(device_extension* Vcb, UINT64 address) {
     KEY searchkey;
-    UINT64 flags;
-    
-    TRACE("(%p, (%llx, %llx), %llx, %llx, %p, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data, length, data, changed_sector_list);
-    
-    // FIXME - split data up if not enough space for just one extent
+    traverse_ptr tp;
+    EXTENT_ITEM* ei;
+    UINT64 rc;
+    NTSTATUS Status;
     
-    if (start_data > 0 && try_extend_data(Vcb, fcb, start_data, length, data, changed_sector_list, rollback))
-        return STATUS_SUCCESS;
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
     
-    // if there is a gap before start_data, plug it with a sparse extent
-    if (start_data > 0) {
-        traverse_ptr tp;
-        NTSTATUS Status;
-        EXTENT_DATA* ed;
-        UINT64 len;
-        
-        searchkey.obj_id = fcb->inode;
-        searchkey.obj_type = TYPE_EXTENT_DATA;
-        searchkey.offset = start_data;
-        
-        Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            return Status;
-        }
-        
-//         if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || tp.item->key.offset >= start_data) {
-//             traverse_ptr next_tp;
-//             
-//             ERR("error - did not find EXTENT_DATA expected - looking for %llx,%x,%llx, found %llx,%x,%llx\n",
-//                 searchkey.obj_id, searchkey.obj_type, searchkey.offset, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
-//             print_tree(tp.tree);
-//             
-//             if (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
-//                 ERR("---\n");
-//                 ERR("key = %llx,%x,%llx\n", next_tp.tree->paritem->key.obj_id, next_tp.tree->paritem->key.obj_type, next_tp.tree->paritem->key.offset);
-//                 print_tree(next_tp.tree);
-//                 
-//                 free_traverse_ptr(&next_tp);
-//             } else
-//                 ERR("next item not found\n");
-//             
-//             int3;
-//             free_traverse_ptr(&tp);
-//             return STATUS_INTERNAL_ERROR;
-//         }
-
-        if (tp.item->key.obj_type == TYPE_EXTENT_DATA && tp.item->size >= sizeof(EXTENT_DATA)) {
-            EXTENT_DATA2* ed2;
-            
-            ed = (EXTENT_DATA*)tp.item->data;
-            ed2 = (EXTENT_DATA2*)ed->data;
-            
-            len = ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes;
-        } else
-            ed = NULL;
-        
-        if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || !ed || tp.item->key.offset + len < start_data) {
-            if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA)
-                Status = insert_sparse_extent(Vcb, fcb->subvol, fcb->inode, 0, start_data, rollback);
-            else if (!ed)
-                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
-            else {
-                Status = insert_sparse_extent(Vcb, fcb->subvol, fcb->inode, tp.item->key.offset + len,
-                                              start_data - tp.item->key.offset - len, rollback);
-            }
-            if (!NT_SUCCESS(Status)) {
-                ERR("insert_sparse_extent returned %08x\n", Status);
-                free_traverse_ptr(&tp);
-                return Status;
-            }
-        }
-        
-        free_traverse_ptr(&tp);
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return 0;
     }
     
-    // FIXME - how do we know which RAID level to put this to?
-    flags = BLOCK_FLAG_DATA; // SINGLE
-    
-//     if (!chunk_test) { // TESTING
-//         if ((c = alloc_chunk(Vcb, flags, NULL))) {
-//             ERR("chunk_item->type = %llx\n", c->chunk_item->type);
-//             ERR("size = %llx\n", c->chunk_item->size);
-//             ERR("used = %llx\n", c->used);
-//             
-//             if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
-//                 if (insert_extent_chunk(Vcb, fcb, c, start_data, length, data, changed_sector_list)) {
-// //                     chunk_test = TRUE;
-//                     ERR("SUCCESS\n");
-//                     return STATUS_SUCCESS;
-//                 } else
-//                     ERR(":-(\n");
-//             } else
-//                 ERR("???\n");
-//         }
-//     }
-    
-    while (le != &Vcb->chunks) {
-        c = CONTAINING_RECORD(le, chunk, list_entry);
-        
-        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
-            if (insert_extent_chunk(Vcb, fcb, c, start_data, length, data, changed_sector_list, rollback))
-                return STATUS_SUCCESS;
-        }
-
-        le = le->Flink;
+    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
+        ERR("error - could not find EXTENT_ITEM for %llx\n", address);
+        return 0;
     }
     
-    if ((c = alloc_chunk(Vcb, flags, rollback))) {
-        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {
-            if (insert_extent_chunk(Vcb, fcb, c, start_data, length, data, changed_sector_list, rollback))
-                return STATUS_SUCCESS;
-        }
+    if (tp.item->size < sizeof(EXTENT_ITEM)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
+        return 0;
     }
     
-    // FIXME - rebalance chunks if free space elsewhere?
-    WARN("couldn't find any data chunks with %llx bytes free\n", length);
+    ei = (EXTENT_ITEM*)tp.item->data;
+    rc = ei->refcount;
+    
+    return rc;
+}
 
-    return STATUS_DISK_FULL;
+static BOOL is_file_prealloc(fcb* fcb, UINT64 start_data, UINT64 end_data) {
+    return is_file_prealloc_inode(fcb->Vcb, fcb->subvol, fcb->inode, start_data, end_data);
 }
 
-void update_checksum_tree(device_extension* Vcb, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
-    LIST_ENTRY* le = changed_sector_list->Flink;
-    changed_sector* cs;
-    traverse_ptr tp, next_tp;
-    KEY searchkey;
-    UINT32* data;
+static NTSTATUS do_cow_write(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 end_data, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
     NTSTATUS Status;
     
-    if (!Vcb->checksum_root) {
-        ERR("no checksum root\n");
-        goto exit;
+    Status = excise_extents(fcb->Vcb, fcb, start_data, end_data, changed_sector_list, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - excise_extents returned %08x\n", Status);
+        goto end;
     }
     
-    while (le != changed_sector_list) {
-        UINT64 startaddr, endaddr;
-        ULONG len;
-        UINT32* checksums;
-        RTL_BITMAP bmp;
-        ULONG* bmparr;
-        ULONG runlength, index;
-        
-        cs = (changed_sector*)le;
-        
-        searchkey.obj_id = EXTENT_CSUM_ID;
-        searchkey.obj_type = TYPE_EXTENT_CSUM;
-        searchkey.offset = cs->ol.key;
-        
-        // FIXME - create checksum_root if it doesn't exist at all
-        
-        Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) { // tree is completely empty
-            // FIXME - do proper check here that tree is empty
-            if (!cs->deleted) {
-                checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * cs->length, ALLOC_TAG);
-                if (!checksums) {
-                    ERR("out of memory\n");
-                    goto exit;
-                }
-                
-                RtlCopyMemory(checksums, cs->checksums, sizeof(UINT32) * cs->length);
-                
-                if (!insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, cs->ol.key, checksums, sizeof(UINT32) * cs->length, NULL, rollback)) {
-                    ERR("insert_tree_item failed\n");
-                    ExFreePool(checksums);
-                    goto exit;
-                }
-            }
-        } else {
-            UINT32 tplen;
-            
-            // FIXME - check entry is TYPE_EXTENT_CSUM?
-            
-            if (tp.item->key.offset < cs->ol.key && tp.item->key.offset + (tp.item->size * Vcb->superblock.sector_size / sizeof(UINT32)) >= cs->ol.key)
-                startaddr = tp.item->key.offset;
-            else
-                startaddr = cs->ol.key;
-            
-            free_traverse_ptr(&tp);
-            
-            searchkey.obj_id = EXTENT_CSUM_ID;
-            searchkey.obj_type = TYPE_EXTENT_CSUM;
-            searchkey.offset = cs->ol.key + (cs->length * Vcb->superblock.sector_size);
-            
-            Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, FALSE);
-            if (!NT_SUCCESS(Status)) {
-                ERR("error - find_item returned %08x\n", Status);
-                goto exit;
-            }
-            
-            tplen = tp.item->size / sizeof(UINT32);
-            
-            if (tp.item->key.offset + (tplen * Vcb->superblock.sector_size) >= cs->ol.key + (cs->length * Vcb->superblock.sector_size))
-                endaddr = tp.item->key.offset + (tplen * Vcb->superblock.sector_size);
-            else
-                endaddr = cs->ol.key + (cs->length * Vcb->superblock.sector_size);
-            
-            free_traverse_ptr(&tp);
-            
-            TRACE("cs starts at %llx (%x sectors)\n", cs->ol.key, cs->length);
-            TRACE("startaddr = %llx\n", startaddr);
-            TRACE("endaddr = %llx\n", endaddr);
-            
-            len = (endaddr - startaddr) / Vcb->superblock.sector_size;
-            
-            checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * len, ALLOC_TAG);
-            if (!checksums) {
-                ERR("out of memory\n");
-                goto exit;
-            }
+    Status = insert_extent(fcb->Vcb, fcb, start_data, end_data - start_data, data, changed_sector_list, rollback);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - insert_extent returned %08x\n", Status);
+        goto end;
+    }
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    return Status;
+}
+
+static NTSTATUS merge_data_extents(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 end_data, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    NTSTATUS Status;
+    BOOL b;
+    EXTENT_DATA* ed;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = start_data;
+    
+    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA) {
+        ERR("error - EXTENT_DATA not found\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (tp.item->key.offset > 0) {
+        traverse_ptr tp2, prev_tp;
+        
+        tp2 = tp;
+        do {
+            b = find_prev_item(Vcb, &tp2, &prev_tp, FALSE);
             
-            bmparr = ExAllocatePoolWithTag(PagedPool, sizeof(ULONG) * ((len/8)+1), ALLOC_TAG);
-            if (!bmparr) {
-                ERR("out of memory\n");
-                ExFreePool(checksums);
-                goto exit;
-            }
+            if (b) {
+                if (!prev_tp.item->ignore)
+                    break;
                 
-            RtlInitializeBitMap(&bmp, bmparr, len);
-            RtlSetAllBits(&bmp);
-            
-            searchkey.obj_id = EXTENT_CSUM_ID;
-            searchkey.obj_type = TYPE_EXTENT_CSUM;
-            searchkey.offset = cs->ol.key;
-            
-            Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, FALSE);
-            if (!NT_SUCCESS(Status)) {
-                ERR("error - find_item returned %08x\n", Status);
-                goto exit;
+                tp2 = prev_tp;
             }
+        } while (b);
+        
+        if (b) {
+            if (prev_tp.item->key.obj_id == fcb->inode && prev_tp.item->key.obj_type == TYPE_EXTENT_DATA)
+                tp = prev_tp;
+        }
+    }
+    
+    ed = (EXTENT_DATA*)tp.item->data;
+    if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+        ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    do {
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+        
+        if (b) {
+            EXTENT_DATA* ned;
             
-            // set bit = free space, cleared bit = allocated sector
+            if (next_tp.item->key.obj_id != fcb->inode || next_tp.item->key.obj_type != TYPE_EXTENT_DATA)
+                return STATUS_SUCCESS;
             
-    //         ERR("start loop\n");
-            while (tp.item->key.offset < endaddr) {
-    //             ERR("%llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
-                if (tp.item->key.offset >= startaddr) {
-                    if (tp.item->size > 0) {
-                        RtlCopyMemory(&checksums[(tp.item->key.offset - startaddr) / Vcb->superblock.sector_size], tp.item->data, tp.item->size);
-                        RtlClearBits(&bmp, (tp.item->key.offset - startaddr) / Vcb->superblock.sector_size, tp.item->size / sizeof(UINT32));
-                    }
-                    
-                    delete_tree_item(Vcb, &tp, rollback);
-                }
-                
-                if (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
-                    free_traverse_ptr(&tp);
-                    tp = next_tp;
-                } else
-                    break;
+            if (next_tp.item->size < sizeof(EXTENT_DATA)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", next_tp.item->key.obj_id, next_tp.item->key.obj_type, next_tp.item->key.offset, next_tp.item->size, sizeof(EXTENT_DATA));
+                return STATUS_INTERNAL_ERROR;
             }
-    //         ERR("end loop\n");
             
-            free_traverse_ptr(&tp);
-            
-            if (cs->deleted) {
-                RtlSetBits(&bmp, (cs->ol.key - startaddr) / Vcb->superblock.sector_size, cs->length);
-            } else {
-                RtlCopyMemory(&checksums[(cs->ol.key - startaddr) / Vcb->superblock.sector_size], cs->checksums, cs->length * sizeof(UINT32));
-                RtlClearBits(&bmp, (cs->ol.key - startaddr) / Vcb->superblock.sector_size, cs->length);
+            ned = (EXTENT_DATA*)next_tp.item->data;
+            if ((ned->type == EXTENT_TYPE_REGULAR || ned->type == EXTENT_TYPE_PREALLOC) && next_tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", next_tp.item->key.obj_id, next_tp.item->key.obj_type, next_tp.item->key.offset, next_tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
+                return STATUS_INTERNAL_ERROR;
             }
             
-            runlength = RtlFindFirstRunClear(&bmp, &index);
-            
-            while (runlength != 0) {
-                do {
-                    ULONG rl;
-                    
-                    if (runlength * sizeof(UINT32) > MAX_CSUM_SIZE)
-                        rl = MAX_CSUM_SIZE / sizeof(UINT32);
-                    else
-                        rl = runlength;
+            if (ed->type == ned->type && (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC)) {
+                EXTENT_DATA2 *ed2, *ned2;
+                
+                ed2 = (EXTENT_DATA2*)ed->data;
+                ned2 = (EXTENT_DATA2*)ned->data;
+                
+                if (next_tp.item->key.offset == tp.item->key.offset + ed2->num_bytes && ed2->address == ned2->address && ed2->size == ned2->size && ned2->offset == ed2->offset + ed2->num_bytes) {
+                    EXTENT_DATA* buf = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                    EXTENT_DATA2* buf2;
+                    traverse_ptr tp2;
                     
-                    data = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * rl, ALLOC_TAG);
-                    if (!data) {
+                    if (!buf) {
                         ERR("out of memory\n");
-                        ExFreePool(bmparr);
-                        ExFreePool(checksums);
-                        goto exit;
+                        return STATUS_INSUFFICIENT_RESOURCES;
                     }
                     
-                    RtlCopyMemory(data, &checksums[index], sizeof(UINT32) * rl);
+                    RtlCopyMemory(buf, tp.item->data, tp.item->size);
+                    buf->generation = Vcb->superblock.generation;
                     
-                    if (!insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, startaddr + (index * Vcb->superblock.sector_size), data, sizeof(UINT32) * rl, NULL, rollback)) {
+                    buf2 = (EXTENT_DATA2*)buf->data;
+                    buf2->num_bytes += ned2->num_bytes;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    delete_tree_item(Vcb, &next_tp, rollback);
+                    
+                    if (!insert_tree_item(Vcb, fcb->subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, buf, tp.item->size, &tp2, rollback)) {
                         ERR("insert_tree_item failed\n");
-                        ExFreePool(data);
-                        ExFreePool(bmparr);
-                        ExFreePool(checksums);
-                        goto exit;
+                        ExFreePool(buf);
+                        return STATUS_INTERNAL_ERROR;
                     }
                     
-                    runlength -= rl;
-                    index += rl;
-                } while (runlength > 0);
-                
-                runlength = RtlFindNextForwardRunClear(&bmp, index, &index);
+                    Status = decrease_extent_refcount_data(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset - buf2->offset, 1, NULL, rollback);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("decrease_extent_refcount_data returned %08x\n", Status);
+                        return Status;
+                    }
+                        
+                    tp = tp2;
+                    
+                    continue;
+                }
             }
-            
-            ExFreePool(bmparr);
-            ExFreePool(checksums);
+
+            tp = next_tp;
+            ed = ned;
         }
-        
-        le = le->Flink;
-    }
+    } while (b);
     
-exit:
-    while (!IsListEmpty(changed_sector_list)) {
-        le = RemoveHeadList(changed_sector_list);
-        cs = (changed_sector*)le;
-        
-        if (cs->checksums)
-            ExFreePool(cs->checksums);
-        
-        ExFreePool(cs);
-    }
+    return STATUS_SUCCESS;
 }
 
-NTSTATUS truncate_file(fcb* fcb, UINT64 end, LIST_ENTRY* rollback) {
-    LIST_ENTRY changed_sector_list;
+static NTSTATUS do_prealloc_write(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 end_data, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
     NTSTATUS Status;
-    BOOL nocsum = fcb->inode_item.flags & BTRFS_INODE_NODATASUM;
-    
-    if (!nocsum)
-        InitializeListHead(&changed_sector_list);
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b, deleted_prealloc = FALSE;
+    UINT64 last_written = start_data;
     
-    // FIXME - convert into inline extent if short enough
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = start_data;
     
-    Status = excise_extents(fcb->Vcb, fcb, sector_align(end, fcb->Vcb->superblock.sector_size),
-                            sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), nocsum ? NULL : &changed_sector_list, rollback);
+    Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
-        ERR("error - excise_extents failed\n");
+        ERR("error - find_item returned %08x\n", Status);
         return Status;
     }
     
-    fcb->inode_item.st_size = end;
-    TRACE("setting st_size to %llx\n", end);
-
-    fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
-    fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;
-    fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;
-    // FIXME - inform cache manager of this
-    
-    TRACE("fcb %p FileSize = %llx\n", fcb, fcb->Header.FileSize.QuadPart);
-    
-    if (!nocsum)
-        update_checksum_tree(fcb->Vcb, &changed_sector_list, rollback);
-    
-    return STATUS_SUCCESS;
-}
-
-NTSTATUS extend_file(fcb* fcb, UINT64 end, LIST_ENTRY* rollback) {
-    UINT64 oldalloc, newalloc;
-    KEY searchkey;
-    traverse_ptr tp;
-    BOOL cur_inline;
-    NTSTATUS Status;
+    if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA)
+        return do_cow_write(Vcb, fcb, start_data, end_data, data, changed_sector_list, rollback);
     
-    TRACE("(%p, %x, %p)\n", fcb, end, rollback);
-
-    if (fcb->ads) {
-        FIXME("FIXME - support streams here\n"); // FIXME
-        return STATUS_NOT_IMPLEMENTED;
-    } else {
-        searchkey.obj_id = fcb->inode;
-        searchkey.obj_type = TYPE_EXTENT_DATA;
-        searchkey.offset = 0xffffffffffffffff;
+    do {
+        EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
+        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
         
-        Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
-        if (!NT_SUCCESS(Status)) {
-            ERR("error - find_item returned %08x\n", Status);
-            return Status;
+        if (tp.item->size < sizeof(EXTENT_DATA)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
+            return STATUS_INTERNAL_ERROR;
         }
         
-        oldalloc = 0;
-        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_EXTENT_DATA) {
-            EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
-            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
-            
-            if (tp.item->size < sizeof(EXTENT_DATA)) {
-                ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
-                free_traverse_ptr(&tp);
-                return STATUS_INTERNAL_ERROR;
+        if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+            ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
+        
+        if (ed->type == EXTENT_TYPE_PREALLOC) {
+            if (tp.item->key.offset > last_written) {
+                Status = do_cow_write(Vcb, fcb, last_written, tp.item->key.offset, (UINT8*)data + last_written - start_data, changed_sector_list, rollback);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("do_cow_write returned %08x\n", Status);
+                    
+                    return Status;
+                }
+                
+                last_written = tp.item->key.offset;
             }
             
-            oldalloc = tp.item->key.offset + (ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes);
-            cur_inline = ed->type == EXTENT_TYPE_INLINE;
-        
-            if (cur_inline && end > fcb->Vcb->max_inline) {
-                LIST_ENTRY changed_sector_list;
-                BOOL nocsum = fcb->inode_item.flags & BTRFS_INODE_NODATASUM;
-                UINT64 origlength, length;
-                UINT8* data;
+            if (start_data <= tp.item->key.offset && end_data >= tp.item->key.offset + ed2->num_bytes) { // replace all
+                EXTENT_DATA* ned = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
                 
-                TRACE("giving inline file proper extents\n");
+                if (!ned) {
+                    ERR("out of memory\n");
+                    
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
                 
-                origlength = ed->decoded_size;
+                RtlCopyMemory(ned, tp.item->data, tp.item->size);
                 
-                cur_inline = FALSE;
+                ned->type = EXTENT_TYPE_REGULAR;
                 
-                if (!nocsum)
-                    InitializeListHead(&changed_sector_list);
+                delete_tree_item(Vcb, &tp, rollback);
+                
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    
+                    return STATUS_INTERNAL_ERROR;
+                }
+                
+                Status = do_write_data(Vcb, ed2->address + ed2->offset, (UINT8*)data + tp.item->key.offset - start_data, ed2->num_bytes, changed_sector_list);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("do_write_data returned %08x\n", Status);
+                    
+                    return Status;
+                }
+                
+                deleted_prealloc = TRUE;
+                
+                last_written = tp.item->key.offset + ed2->num_bytes;
+            } else if (start_data <= tp.item->key.offset && end_data < tp.item->key.offset + ed2->num_bytes) { // replace beginning
+                EXTENT_DATA *ned, *nedb;
+                EXTENT_DATA2* ned2;
+                
+                ned = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                
+                if (!ned) {
+                    ERR("out of memory\n");
+                    
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                nedb = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                
+                if (!nedb) {
+                    ERR("out of memory\n");
+                    ExFreePool(ned);
+                    
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
                 
-                delete_tree_item(fcb->Vcb, &tp, rollback);
+                delete_tree_item(Vcb, &tp, rollback);
                 
-                length = sector_align(origlength, fcb->Vcb->superblock.sector_size);
+                RtlCopyMemory(ned, tp.item->data, tp.item->size);
                 
-                data = ExAllocatePoolWithTag(PagedPool, length, ALLOC_TAG);
-                if (!data) {
-                    ERR("could not allocate %llx bytes for data\n", length);
-                    free_traverse_ptr(&tp);
-                    return STATUS_INSUFFICIENT_RESOURCES;
+                ned->type = EXTENT_TYPE_REGULAR;
+                ned2 = (EXTENT_DATA2*)ned->data;
+                ned2->num_bytes = end_data - tp.item->key.offset;
+                
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(ned);
+                    ExFreePool(nedb);
+                    
+                    return STATUS_INTERNAL_ERROR;
                 }
                 
-                if (length > origlength)
-                    RtlZeroMemory(data + origlength, length - origlength);
+                RtlCopyMemory(nedb, tp.item->data, tp.item->size);
+                ned2 = (EXTENT_DATA2*)nedb->data;
+                ned2->offset += end_data - tp.item->key.offset;
+                ned2->num_bytes -= end_data - tp.item->key.offset;
                 
-                RtlCopyMemory(data, ed->data, origlength);
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, end_data, nedb, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(nedb);
+                    
+                    return STATUS_INTERNAL_ERROR;
+                }
                 
-                fcb->inode_item.st_blocks -= origlength;
+                Status = do_write_data(Vcb, ed2->address + ed2->offset, (UINT8*)data + tp.item->key.offset - start_data, end_data - tp.item->key.offset, changed_sector_list);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("do_write_data returned %08x\n", Status);
+                    
+                    return Status;
+                }
                 
-                Status = insert_extent(fcb->Vcb, fcb, tp.item->key.offset, length, data, nocsum ? NULL : &changed_sector_list, rollback);
+                Status = increase_extent_refcount_data(Vcb, ned2->address, ned2->size, fcb->subvol, fcb->inode, tp.item->key.offset - ed2->offset, 1, rollback);
                 if (!NT_SUCCESS(Status)) {
-                    ERR("insert_extent returned %08x\n", Status);
-                    free_traverse_ptr(&tp);
-                    ExFreePool(data);
+                    ERR("increase_extent_refcount_data returned %08x\n", Status);
                     return Status;
                 }
                 
-                oldalloc = tp.item->key.offset + length;
+                last_written = end_data;
+            } else if (start_data > tp.item->key.offset && end_data >= tp.item->key.offset + ed2->num_bytes) { // replace end
+                EXTENT_DATA *ned, *nedb;
+                EXTENT_DATA2* ned2;
                 
-                ExFreePool(data);
+                // FIXME - test this
                 
-                if (!nocsum)
-                    update_checksum_tree(fcb->Vcb, &changed_sector_list, rollback);
-            }
-            
-            if (cur_inline) {
-                ULONG edsize;
+                ned = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
                 
-                if (end > oldalloc) {
-                    edsize = sizeof(EXTENT_DATA) - 1 + end - tp.item->key.offset;
-                    ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);
-                    
-                    if (!ed) {
-                        ERR("out of memory\n");
-                        free_traverse_ptr(&tp);
-                        return STATUS_INSUFFICIENT_RESOURCES;
-                    }
-                    
-                    RtlZeroMemory(ed, edsize);
-                    RtlCopyMemory(ed, tp.item->data, tp.item->size);
-                    
-                    ed->decoded_size = end - tp.item->key.offset;
+                if (!ned) {
+                    ERR("out of memory\n");
                     
-                    delete_tree_item(fcb->Vcb, &tp, rollback);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                nedb = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                
+                if (!nedb) {
+                    ERR("out of memory\n");
+                    ExFreePool(ned);
                     
-                    if (!insert_tree_item(fcb->Vcb, fcb->subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ed, edsize, NULL, rollback)) {
-                        ERR("error - failed to insert item\n");
-                        ExFreePool(ed);
-                        free_traverse_ptr(&tp);
-                        return STATUS_INTERNAL_ERROR;
-                    }
+                    return STATUS_INSUFFICIENT_RESOURCES;
                 }
                 
-                TRACE("extending inline file (oldalloc = %llx, end = %llx)\n", oldalloc, end);
+                delete_tree_item(Vcb, &tp, rollback);
                 
-                fcb->inode_item.st_size = end;
-                TRACE("setting st_size to %llx\n", end);
+                RtlCopyMemory(ned, tp.item->data, tp.item->size);
                 
-                fcb->inode_item.st_blocks = end;
-
-                fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
-            } else {
-                newalloc = sector_align(end, fcb->Vcb->superblock.sector_size);
-            
-                if (newalloc > oldalloc) {
-                    Status = insert_sparse_extent(fcb->Vcb, fcb->subvol, fcb->inode, oldalloc, newalloc - oldalloc, rollback);
+                ned2 = (EXTENT_DATA2*)ned->data;
+                ned2->num_bytes = start_data - tp.item->key.offset;
+                
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(ned);
+                    ExFreePool(nedb);
                     
-                    if (!NT_SUCCESS(Status)) {
-                        ERR("insert_sparse_extent returned %08x\n", Status);
-                        free_traverse_ptr(&tp);
-                        return Status;
-                    }
+                    return STATUS_INTERNAL_ERROR;
                 }
                 
-                fcb->inode_item.st_size = end;
-                TRACE("setting st_size to %llx\n", end);
+                RtlCopyMemory(nedb, tp.item->data, tp.item->size);
                 
-                TRACE("newalloc = %llx\n", newalloc);
+                nedb->type = EXTENT_TYPE_REGULAR;
+                ned2 = (EXTENT_DATA2*)nedb->data;
+                ned2->offset += start_data - tp.item->key.offset;
+                ned2->num_bytes = tp.item->key.offset + ed2->num_bytes - start_data;
                 
-                fcb->Header.AllocationSize.QuadPart = newalloc;
-                fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
-            }
-        } else {
-            if (end > fcb->Vcb->max_inline) {
-                newalloc = sector_align(end, fcb->Vcb->superblock.sector_size);
-            
-                Status = insert_sparse_extent(fcb->Vcb, fcb->subvol, fcb->inode, 0, newalloc, rollback);
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, start_data, nedb, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(nedb);
+                    
+                    return STATUS_INTERNAL_ERROR;
+                }
                 
+                Status = do_write_data(Vcb, ed2->address + ned2->offset, data, ned2->num_bytes, changed_sector_list);
                 if (!NT_SUCCESS(Status)) {
-                    ERR("insert_sparse_extent returned %08x\n", Status);
-                    free_traverse_ptr(&tp);
+                    ERR("do_write_data returned %08x\n", Status);
+                    
                     return Status;
                 }
                 
-                fcb->inode_item.st_size = end;
-                TRACE("setting st_size to %llx\n", end);
+                Status = increase_extent_refcount_data(Vcb, ned2->address, ned2->size, fcb->subvol, fcb->inode, tp.item->key.offset - ed2->offset, 1, rollback);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("increase_extent_refcount_data returned %08x\n", Status);
+                    
+                    return Status;
+                }
                 
-                TRACE("newalloc = %llx\n", newalloc);
+                last_written = start_data + ned2->num_bytes;
+            } else if (start_data > tp.item->key.offset && end_data < tp.item->key.offset + ed2->num_bytes) { // replace middle
+                EXTENT_DATA *ned, *nedb, *nedc;
+                EXTENT_DATA2* ned2;
                 
-                fcb->Header.AllocationSize.QuadPart = newalloc;
-                fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
-            } else {
-                EXTENT_DATA* ed;
-                ULONG edsize;
+                // FIXME - test this
                 
-                edsize = sizeof(EXTENT_DATA) - 1 + end;
-                ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);
+                ned = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
                 
-                if (!ed) {
+                if (!ned) {
                     ERR("out of memory\n");
-                    free_traverse_ptr(&tp);
+                    
                     return STATUS_INSUFFICIENT_RESOURCES;
                 }
                 
-                ed->generation = fcb->Vcb->superblock.generation;
-                ed->decoded_size = end;
-                ed->compression = BTRFS_COMPRESSION_NONE;
-                ed->encryption = BTRFS_ENCRYPTION_NONE;
-                ed->encoding = BTRFS_ENCODING_NONE;
-                ed->type = EXTENT_TYPE_INLINE;
+                nedb = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
                 
-                RtlZeroMemory(ed->data, end);
-
-                if (!insert_tree_item(fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, ed, edsize, NULL, rollback)) {
-                    ERR("error - failed to insert item\n");
-                    ExFreePool(ed);
-                    free_traverse_ptr(&tp);
+                if (!nedb) {
+                    ERR("out of memory\n");
+                    ExFreePool(ned);
+                    
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                nedc = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                
+                if (!nedb) {
+                    ERR("out of memory\n");
+                    ExFreePool(nedb);
+                    ExFreePool(ned);
+                    
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                delete_tree_item(Vcb, &tp, rollback);
+                
+                RtlCopyMemory(ned, tp.item->data, tp.item->size);
+                RtlCopyMemory(nedb, tp.item->data, tp.item->size);
+                RtlCopyMemory(nedc, tp.item->data, tp.item->size);
+                
+                ned2 = (EXTENT_DATA2*)ned->data;
+                ned2->num_bytes = start_data - tp.item->key.offset;
+                
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, tp.item->key.offset, ned, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(ned);
+                    ExFreePool(nedb);
+                    ExFreePool(nedc);
+                    
                     return STATUS_INTERNAL_ERROR;
                 }
                 
-                fcb->inode_item.st_size = end;
-                TRACE("setting st_size to %llx\n", end);
+                nedb->type = EXTENT_TYPE_REGULAR;
+                ned2 = (EXTENT_DATA2*)nedb->data;
+                ned2->offset += start_data - tp.item->key.offset;
+                ned2->num_bytes = end_data - start_data;
                 
-                fcb->inode_item.st_blocks = end;
-
-                fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, start_data, nedb, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(nedb);
+                    ExFreePool(nedc);
+                    
+                    return STATUS_INTERNAL_ERROR;
+                }
+                
+                ned2 = (EXTENT_DATA2*)nedc->data;
+                ned2->offset += end_data - tp.item->key.offset;
+                ned2->num_bytes -= end_data - tp.item->key.offset;
+                
+                if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, end_data, nedc, tp.item->size, NULL, rollback)) {
+                    ERR("insert_tree_item failed\n");
+                    ExFreePool(nedc);
+                    
+                    return STATUS_INTERNAL_ERROR;
+                }
+                
+                ned2 = (EXTENT_DATA2*)nedb->data;
+                Status = do_write_data(Vcb, ed2->address + ned2->offset, data, end_data - start_data, changed_sector_list);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("do_write_data returned %08x\n", Status);
+                    
+                    return Status;
+                }
+                
+                Status = increase_extent_refcount_data(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset - ed2->offset, 2, rollback);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("increase_extent_refcount_data returned %08x\n", Status);
+                    return Status;
+                }
+                
+                last_written = end_data;
             }
         }
         
-        free_traverse_ptr(&tp);
-    }
-    
-    return STATUS_SUCCESS;
-}
-
-static UINT64 get_extent_item_refcount(device_extension* Vcb, UINT64 address) {
-    KEY searchkey;
-    traverse_ptr tp;
-    EXTENT_ITEM* ei;
-    UINT64 rc;
-    NTSTATUS Status;
-    
-    searchkey.obj_id = address;
-    searchkey.obj_type = TYPE_EXTENT_ITEM;
-    searchkey.offset = 0xffffffffffffffff;
-    
-    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
-    if (!NT_SUCCESS(Status)) {
-        ERR("error - find_item returned %08x\n", Status);
-        return 0;
-    }
+        if (b) {
+            tp = next_tp;
+            
+            if (tp.item->key.obj_id > fcb->inode || tp.item->key.obj_type > TYPE_EXTENT_DATA || tp.item->key.offset >= end_data)
+                break;
+        }
+    } while (b);
     
-    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
-        ERR("error - could not find EXTENT_ITEM for %llx\n", address);
-        free_traverse_ptr(&tp);
-        return 0;
+    if (last_written < end_data) {
+        Status = do_cow_write(Vcb, fcb, last_written, end_data, (UINT8*)data + last_written - start_data, changed_sector_list, rollback);
+                
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_cow_write returned %08x\n", Status);
+            return Status;
+        }
     }
     
-    if (tp.item->size < sizeof(EXTENT_ITEM)) {
-        ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
-        free_traverse_ptr(&tp);
-        return 0;
+    Status = merge_data_extents(Vcb, fcb, start_data, end_data, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("merge_data_extents returned %08x\n", Status);
+        return Status;
     }
     
-    ei = (EXTENT_ITEM*)tp.item->data;
-    rc = ei->refcount;
-    
-    free_traverse_ptr(&tp);
+    if (deleted_prealloc && !is_file_prealloc(fcb, 0, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size)))
+        fcb->inode_item.flags &= ~BTRFS_INODE_PREALLOC;
     
-    return rc;
+    return STATUS_SUCCESS;
 }
 
 static NTSTATUS do_nocow_write(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 length, void* data, LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
@@ -5914,7 +6101,6 @@ static NTSTATUS do_nocow_write(device_extension* Vcb, fcb* fcb, UINT64 start_dat
         last_write = new_end;
         
         if (b) {
-            free_traverse_ptr(&tp);
             tp = next_tp;
             
             if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || tp.item->key.offset >= start_data + length)
@@ -5940,7 +6126,6 @@ static NTSTATUS do_nocow_write(device_extension* Vcb, fcb* fcb, UINT64 start_dat
     Status = STATUS_SUCCESS;
     
 end:
-    free_traverse_ptr(&tp);
     
     return Status;
 }
@@ -5995,7 +6180,7 @@ static void check_extents_consistent(device_extension* Vcb, fcb* fcb) {
     Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
     if (!NT_SUCCESS(Status)) {
         ERR("error - find_item returned %08x\n", Status);
-        goto failure2;
+        goto failure;
     }
     
     if (keycmp(&searchkey, &tp.item->key)) {
@@ -6022,12 +6207,9 @@ static void check_extents_consistent(device_extension* Vcb, fcb* fcb) {
     }
     
     while (find_next_item(Vcb, &tp, &next_tp, FALSE)) {
-        if (next_tp.item->key.obj_id != searchkey.obj_id || next_tp.item->key.obj_type != searchkey.obj_type) {
-            free_traverse_ptr(&next_tp);
+        if (next_tp.item->key.obj_id != searchkey.obj_id || next_tp.item->key.obj_type != searchkey.obj_type)
             break;
-        }
         
-        free_traverse_ptr(&tp);
         tp = next_tp;
         
         if (tp.item->size < sizeof(EXTENT_DATA)) {
@@ -6065,14 +6247,9 @@ static void check_extents_consistent(device_extension* Vcb, fcb* fcb) {
 //         goto failure;
 //     }
     
-    free_traverse_ptr(&tp);
-    
     return;
     
 failure:
-    free_traverse_ptr(&tp);
-    
-failure2:
     if (fcb->subvol->treeholder.tree)
         print_loaded_trees(fcb->subvol->treeholder.tree, 0);
 
@@ -6170,6 +6347,8 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
     LARGE_INTEGER time;
     BTRFS_TIME now;
     fcb* fcb;
+    ccb* ccb;
+    file_ref* fileref;
     BOOL paging_lock = FALSE;
     
     TRACE("(%p, %p, %llx, %p, %x, %u, %u)\n", Vcb, FileObject, offset.QuadPart, buf, *length, paging_io, no_cache);
@@ -6185,10 +6364,12 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
     }
     
     fcb = FileObject->FsContext;
+    ccb = FileObject->FsContext2;
+    fileref = ccb ? ccb->fileref : NULL;
     
     if (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK) {
         WARN("tried to write to something other than a file or symlink (inode %llx, type %u, %p, %p)\n", fcb->inode, fcb->type, &fcb->type, fcb);
-        return STATUS_ACCESS_DENIED;
+        return STATUS_INVALID_DEVICE_REQUEST;
     }
     
     if (offset.LowPart == FILE_WRITE_TO_END_OF_FILE && offset.HighPart == -1) {
@@ -6240,7 +6421,7 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
         if (paging_io) {
             if (offset.QuadPart >= newlength) {
                 TRACE("paging IO tried to write beyond end of file (file size = %llx, offset = %llx, length = %x)\n", newlength, offset.QuadPart, *length);
-                TRACE("filename %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+                TRACE("filename %S\n", file_desc(FileObject));
                 TRACE("FileObject: AllocationSize = %llx, FileSize = %llx, ValidDataLength = %llx\n",
                     fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
                 Status = STATUS_SUCCESS;
@@ -6260,7 +6441,7 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
     
     if (changed_length) {
         if (newlength > fcb->Header.AllocationSize.QuadPart) {
-            Status = extend_file(fcb, newlength, rollback);
+            Status = extend_file(fcb, fileref, newlength, FALSE, rollback);
             if (!NT_SUCCESS(Status)) {
                 ERR("extend_file returned %08x\n", Status);
                 goto end;
@@ -6302,15 +6483,22 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
 //             wait = IoIsOperationSynchronous(Irp) ? TRUE : FALSE;
         wait = TRUE;
         
-        TRACE("CcCopyWrite(%p, %llx, %x, %u, %p)\n", FileObject, offset.QuadPart, *length, wait, buf);
-        if (!CcCopyWrite(FileObject, &offset, *length, wait, buf)) {
-            TRACE("CcCopyWrite failed.\n");
-            
-            IoMarkIrpPending(Irp);
-            Status = STATUS_PENDING;
+        if (IrpSp->MinorFunction & IRP_MN_MDL) {
+            CcPrepareMdlWrite(FileObject, &offset, *length, &Irp->MdlAddress, &Irp->IoStatus);
+
+            Status = Irp->IoStatus.Status;
             goto end;
+        } else {
+            TRACE("CcCopyWrite(%p, %llx, %x, %u, %p)\n", FileObject, offset.QuadPart, *length, wait, buf);
+            if (!CcCopyWrite(FileObject, &offset, *length, wait, buf)) {
+                TRACE("CcCopyWrite failed.\n");
+                
+                IoMarkIrpPending(Irp);
+                Status = STATUS_PENDING;
+                goto end;
+            }
+            TRACE("CcCopyWrite finished\n");
         }
-        TRACE("CcCopyWrite finished\n");
         
         Status = STATUS_SUCCESS;
         goto end;
@@ -6343,22 +6531,18 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
             
             if (keycmp(&tp.item->key, &searchkey)) {
                 ERR("error - could not find key for xattr\n");
-                free_traverse_ptr(&tp);
                 Status = STATUS_INTERNAL_ERROR;
                 goto end;
             }
             
             if (tp.item->size < datalen) {
                 ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, datalen);
-                free_traverse_ptr(&tp);
                 Status = STATUS_INTERNAL_ERROR;
                 goto end;
             }
             
             maxlen -= tp.item->size - datalen; // subtract XATTR_ITEM overhead
             
-            free_traverse_ptr(&tp);
-            
             if (newlength > maxlen) {
                 ERR("error - xattr too long (%llu > %u)\n", newlength, maxlen);
                 Status = STATUS_DISK_FULL;
@@ -6441,7 +6625,7 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
         if (!nocsum)
             InitializeListHead(&changed_sector_list);
 
-        if (make_inline || !nocow) {
+        if (make_inline) {
             Status = excise_extents(fcb->Vcb, fcb, start_data, end_data, nocsum ? NULL : &changed_sector_list, rollback);
             if (!NT_SUCCESS(Status)) {
                 ERR("error - excise_extents returned %08x\n", Status);
@@ -6449,29 +6633,37 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
                 goto end;
             }
             
-            if (!make_inline) {
-                Status = insert_extent(fcb->Vcb, fcb, start_data, end_data - start_data, data, nocsum ? NULL : &changed_sector_list, rollback);
+            ed2 = (EXTENT_DATA*)data;
+            ed2->generation = fcb->Vcb->superblock.generation;
+            ed2->decoded_size = newlength;
+            ed2->compression = BTRFS_COMPRESSION_NONE;
+            ed2->encryption = BTRFS_ENCRYPTION_NONE;
+            ed2->encoding = BTRFS_ENCODING_NONE;
+            ed2->type = EXTENT_TYPE_INLINE;
+            
+            insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, ed2, sizeof(EXTENT_DATA) - 1 + newlength, NULL, rollback);
+            
+            fcb->inode_item.st_blocks += newlength;
+        } else if (!nocow) {
+            if (is_file_prealloc(fcb, start_data, end_data)) {
+                Status = do_prealloc_write(fcb->Vcb, fcb, start_data, end_data, data, nocsum ? NULL : &changed_sector_list, rollback);
                 
                 if (!NT_SUCCESS(Status)) {
-                    ERR("error - insert_extent returned %08x\n", Status);
+                    ERR("error - do_prealloc_write returned %08x\n", Status);
                     ExFreePool(data);
                     goto end;
                 }
-                
-                ExFreePool(data);
             } else {
-                ed2 = (EXTENT_DATA*)data;
-                ed2->generation = fcb->Vcb->superblock.generation;
-                ed2->decoded_size = newlength;
-                ed2->compression = BTRFS_COMPRESSION_NONE;
-                ed2->encryption = BTRFS_ENCRYPTION_NONE;
-                ed2->encoding = BTRFS_ENCODING_NONE;
-                ed2->type = EXTENT_TYPE_INLINE;
-                
-                insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, ed2, sizeof(EXTENT_DATA) - 1 + newlength, NULL, rollback);
+                Status = do_cow_write(fcb->Vcb, fcb, start_data, end_data, data, nocsum ? NULL : &changed_sector_list, rollback);
                 
-                fcb->inode_item.st_blocks += newlength;
+                if (!NT_SUCCESS(Status)) {
+                    ERR("error - do_cow_write returned %08x\n", Status);
+                    ExFreePool(data);
+                    goto end;
+                }
             }
+            
+            ExFreePool(data);
         } else {
             Status = do_nocow_write(fcb->Vcb, fcb, start_data, end_data - start_data, data, nocsum ? NULL : &changed_sector_list, rollback);
             
@@ -6505,9 +6697,15 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
 //         }
 //     }
     
-    if (fcb->ads)
-        origii = &fcb->par->inode_item;
-    else
+    if (fcb->ads) {
+        if (fileref && fileref->parent)
+            origii = &fileref->parent->fcb->inode_item;
+        else {
+            ERR("no parent fcb found for stream\n");
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+    } else
         origii = &fcb->inode_item;
     
     origii->transid = Vcb->superblock.generation;
@@ -6538,7 +6736,6 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
     ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
     if (!ii) {
         ERR("out of memory\n");
-        free_traverse_ptr(&tp);
         Status = STATUS_INSUFFICIENT_RESOURCES;
         goto end;
     }
@@ -6546,8 +6743,6 @@ NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void
     RtlCopyMemory(ii, origii, sizeof(INODE_ITEM));
     insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
     
-    free_traverse_ptr(&tp);
-    
     // FIXME - update inode_item of open FCBs pointing to the same inode (i.e. hardlinked files)
     
     if (!nocsum)
@@ -6614,44 +6809,6 @@ NTSTATUS write_file(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     
     Irp->IoStatus.Information = 0;
     
-    switch (IrpSp->MinorFunction) {
-        case IRP_MN_COMPLETE:
-            FIXME("unsupported - IRP_MN_COMPLETE\n");
-            break;
-
-        case IRP_MN_COMPLETE_MDL:
-            FIXME("unsupported - IRP_MN_COMPLETE_MDL\n");
-            break;
-
-        case IRP_MN_COMPLETE_MDL_DPC:
-            FIXME("unsupported - IRP_MN_COMPLETE_MDL_DPC\n");
-            break;
-
-        case IRP_MN_COMPRESSED:
-            FIXME("unsupported - IRP_MN_COMPRESSED\n");
-            break;
-
-        case IRP_MN_DPC:
-            FIXME("unsupported - IRP_MN_DPC\n");
-            break;
-
-        case IRP_MN_MDL:
-            FIXME("unsupported - IRP_MN_MDL\n");
-            break;
-
-        case IRP_MN_MDL_DPC:
-            FIXME("unsupported - IRP_MN_MDL_DPC\n");
-            break;
-
-        case IRP_MN_NORMAL:
-            TRACE("IRP_MN_NORMAL\n");
-            break;
-
-        default:
-            WARN("unknown minor function %x\n", IrpSp->MinorFunction);
-            break;
-    }
-    
     TRACE("offset = %llx\n", offset.QuadPart);
     TRACE("length = %x\n", IrpSp->Parameters.Write.Length);
     
@@ -6668,8 +6825,10 @@ NTSTATUS write_file(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
     
     TRACE("buf = %p\n", buf);
     
-    acquire_tree_lock(Vcb, TRUE);
-    locked = TRUE;
+    if (Irp->Flags & IRP_NOCACHE) {
+        acquire_tree_lock(Vcb, TRUE);
+        locked = TRUE;
+    }
     
     if (fcb && !(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForWriteAccess(&fcb->lock, Irp)) {
         WARN("tried to write to locked region\n");
@@ -6685,13 +6844,15 @@ NTSTATUS write_file(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
         goto exit;
     }
     
-    Status = consider_write(Vcb);
+    if (locked)
+        Status = consider_write(Vcb);
 
     if (NT_SUCCESS(Status)) {
         Irp->IoStatus.Information = IrpSp->Parameters.Write.Length;
     
 #ifdef DEBUG_PARANOID
-        check_extents_consistent(Vcb, FileObject->FsContext); // TESTING
+        if (locked)
+            check_extents_consistent(Vcb, FileObject->FsContext); // TESTING
     
 //         check_extent_tree_consistent(Vcb);
 #endif