[BTRFS]
authorPierre Schweitzer <pierre@reactos.org>
Wed, 23 Mar 2016 20:35:05 +0000 (20:35 +0000)
committerPierre Schweitzer <pierre@reactos.org>
Wed, 23 Mar 2016 20:35:05 +0000 (20:35 +0000)
Import the WinBtrfs 0.2 driver from https://github.com/maharmstone/btrfs.

Based on the initial work from Peter Hater, with various modification and patches sent upstream (yay, yet another collaboration :-)).

This driver is in its earlies, so expect crashes, issues, and so on. We'll keep it updated to get rid of these issues.
For now, it reads really well from a btrfs volume!

CORE-10892

svn path=/trunk/; revision=71037

22 files changed:
reactos/drivers/filesystems/CMakeLists.txt
reactos/drivers/filesystems/btrfs/CMakeLists.txt [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/btrfs.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/btrfs.h [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/btrfs.rc [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/btrfs_drv.h [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/cache.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/crc32c.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/create.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/dirctrl.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/fastio.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/fileinfo.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/flushthread.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/fsctl.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/loader.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/read.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/reparse.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/resource.h [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/search.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/security.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/treefuncs.c [new file with mode: 0644]
reactos/drivers/filesystems/btrfs/write.c [new file with mode: 0644]

index 20a81ae..ac42b35 100644 (file)
@@ -1,4 +1,5 @@
 
+add_subdirectory(btrfs)
 add_subdirectory(cdfs)
 add_subdirectory(ext2)
 add_subdirectory(fastfat)
diff --git a/reactos/drivers/filesystems/btrfs/CMakeLists.txt b/reactos/drivers/filesystems/btrfs/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8fc8628
--- /dev/null
@@ -0,0 +1,37 @@
+
+include_directories(${REACTOS_SOURCE_DIR}/include/reactos/drivers
+                    inc)
+
+list(APPEND SOURCE
+    btrfs.c
+    cache.c
+    crc32c.c
+    create.c
+    dirctrl.c
+    fastio.c
+    fileinfo.c
+    flushthread.c
+    fsctl.c
+    read.c
+    reparse.c
+    search.c
+    security.c
+    treefuncs.c
+    write.c
+    btrfs_drv.h)
+
+add_library(btrfs SHARED ${SOURCE} btrfs.rc)
+
+if(NOT MSVC)
+    replace_compile_flags("-Werror" " ")
+else()
+    replace_compile_flags("/we\"4189\"" " ")
+endif()
+
+add_definitions(-D__KERNEL__)
+set_module_type(btrfs kernelmodedriver)
+target_link_libraries(btrfs ntoskrnl_vista)
+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)
+
diff --git a/reactos/drivers/filesystems/btrfs/btrfs.c b/reactos/drivers/filesystems/btrfs/btrfs.c
new file mode 100644 (file)
index 0000000..90bb09e
--- /dev/null
@@ -0,0 +1,4363 @@
+/* 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/>. */
+
+#ifdef _DEBUG
+#define DEBUG
+#endif
+
+#include "btrfs_drv.h"
+#ifndef __REACTOS__
+#ifndef _MSC_VER
+#include <cpuid.h>
+#else
+#include <intrin.h>
+#endif
+#endif
+#include "btrfs.h"
+#ifndef __REACTOS__
+#include <winioctl.h>
+#else
+#include <rtlfuncs.h>
+#endif
+
+#define INCOMPAT_SUPPORTED (BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF | BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL | BTRFS_INCOMPAT_FLAGS_BIG_METADATA | \
+                            BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)
+#define COMPAT_RO_SUPPORTED 0
+
+static WCHAR device_name[] = {'\\','B','t','r','f','s',0};
+static WCHAR dosdevice_name[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\','B','t','r','f','s',0};
+
+PDRIVER_OBJECT drvobj;
+PDEVICE_OBJECT devobj;
+#ifndef __REACTOS__
+BOOL have_sse42 = FALSE;
+#endif
+UINT64 num_reads = 0;
+LIST_ENTRY uid_map_list;
+LIST_ENTRY volumes;
+LIST_ENTRY VcbList;
+ERESOURCE global_loading_lock;
+UINT32 debug_log_level = 0;
+BOOL log_started = FALSE;
+UNICODE_STRING log_device, log_file;
+
+#ifdef _DEBUG
+PFILE_OBJECT comfo = NULL;
+PDEVICE_OBJECT comdo = NULL;
+HANDLE log_handle = NULL;
+#endif
+
+static NTSTATUS STDCALL close_file(device_extension* Vcb, PFILE_OBJECT FileObject);
+
+typedef struct {
+    KEVENT Event;
+    IO_STATUS_BLOCK iosb;
+} read_context;
+
+#ifdef _DEBUG
+static NTSTATUS STDCALL dbg_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
+    read_context* context = conptr;
+    
+//     DbgPrint("dbg_completion\n");
+    
+    context->iosb = Irp->IoStatus;
+    KeSetEvent(&context->Event, 0, FALSE);
+    
+//     return STATUS_SUCCESS;
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+#ifdef DEBUG_LONG_MESSAGES
+void STDCALL _debug_message(const char* func, UINT8 priority, const char* file, unsigned int line, char* s, ...) {
+#else
+void STDCALL _debug_message(const char* func, UINT8 priority, char* s, ...) {
+#endif
+    LARGE_INTEGER offset;
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS Status;
+    PIRP Irp;
+    va_list ap;
+    char *buf2 = NULL, *buf;
+    read_context* context = NULL;
+    UINT32 length;
+    
+    if (log_started && priority > debug_log_level)
+        return;
+    
+    buf2 = ExAllocatePoolWithTag(NonPagedPool, 1024, ALLOC_TAG);
+    
+    if (!buf2) {
+        DbgPrint("Couldn't allocate buffer in debug_message\n");
+        return;
+    }
+    
+#ifdef DEBUG_LONG_MESSAGES
+    sprintf(buf2, "%p:%s:%s:%u:", PsGetCurrentThreadId(), func, file, line);
+#else
+    sprintf(buf2, "%p:%s:", PsGetCurrentThreadId(), func);
+#endif
+    buf = &buf2[strlen(buf2)];
+    
+    va_start(ap, s);
+    vsprintf(buf, s, ap);
+    
+    if (!log_started || (log_device.Length == 0 && log_file.Length == 0)) {
+        DbgPrint(buf2);
+    } else if (log_device.Length > 0) {
+        if (!comdo) {
+            DbgPrint("comdo is NULL :-(\n");
+            DbgPrint(buf2);
+            goto exit2;
+        }
+        
+        length = (UINT32)strlen(buf2);
+        
+        offset.u.LowPart = 0;
+        offset.u.HighPart = 0;
+        
+        context = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_context), ALLOC_TAG);
+        if (!context) {
+            DbgPrint("Couldn't allocate context in debug_message\n");
+            return;
+        }
+        
+        RtlZeroMemory(context, sizeof(read_context));
+        
+        KeInitializeEvent(&context->Event, NotificationEvent, FALSE);
+
+    //     status = ZwWriteFile(comh, NULL, NULL, NULL, &io, buf2, strlen(buf2), &offset, NULL);
+        
+        Irp = IoAllocateIrp(comdo->StackSize, FALSE);
+        
+        if (!Irp) {
+            DbgPrint("IoAllocateIrp failed\n");
+            goto exit2;
+        }
+        
+        IrpSp = IoGetNextIrpStackLocation(Irp);
+        IrpSp->MajorFunction = IRP_MJ_WRITE;
+        
+        if (comdo->Flags & DO_BUFFERED_IO) {
+            Irp->AssociatedIrp.SystemBuffer = buf2;
+
+            Irp->Flags = IRP_BUFFERED_IO;
+        } else if (comdo->Flags & DO_DIRECT_IO) {
+            Irp->MdlAddress = IoAllocateMdl(buf2, length, FALSE, FALSE, NULL);
+            if (!Irp->MdlAddress) {
+                DbgPrint("IoAllocateMdl failed\n");
+                goto exit;
+            }
+            
+            MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoWriteAccess);
+        } else {
+            Irp->UserBuffer = buf2;
+        }
+
+        IrpSp->Parameters.Write.Length = length;
+        IrpSp->Parameters.Write.ByteOffset = offset;
+        
+        Irp->UserIosb = &context->iosb;
+
+        Irp->UserEvent = &context->Event;
+
+        IoSetCompletionRoutine(Irp, dbg_completion, context, TRUE, TRUE, TRUE);
+
+        Status = IoCallDriver(comdo, Irp);
+
+        if (Status == STATUS_PENDING) {
+            KeWaitForSingleObject(&context->Event, Executive, KernelMode, FALSE, NULL);
+            Status = context->iosb.Status;
+        }
+        
+        if (comdo->Flags & DO_DIRECT_IO) {
+            MmUnlockPages(Irp->MdlAddress);
+            IoFreeMdl(Irp->MdlAddress);
+        }
+        
+        if (!NT_SUCCESS(Status)) {
+            DbgPrint("failed to write to COM1 - error %08x\n", Status);
+            goto exit;
+        }
+        
+exit:
+        IoFreeIrp(Irp);
+    } else if (log_handle != NULL) {
+        IO_STATUS_BLOCK iosb;
+        
+        length = (UINT32)strlen(buf2);
+        
+        Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, buf2, length, NULL, NULL);
+        
+        if (!NT_SUCCESS(Status)) {
+            DbgPrint("failed to write to file - error %08x\n", Status);
+        }
+    }
+    
+exit2:
+    va_end(ap);
+    
+    if (context)
+        ExFreePool(context);
+    
+    if (buf2)
+        ExFreePool(buf2);
+}
+#endif
+
+ULONG sector_align( ULONG NumberToBeAligned, ULONG Alignment )
+{
+    if( Alignment & ( Alignment - 1 ) )
+    {
+        //
+        //  Alignment not a power of 2
+        //  Just returning
+        //
+        return NumberToBeAligned;
+    }
+    if( ( NumberToBeAligned & ( Alignment - 1 ) ) != 0 )
+    {
+        NumberToBeAligned = NumberToBeAligned + Alignment;
+        NumberToBeAligned = NumberToBeAligned & ( ~ (Alignment-1) );
+    }
+    return NumberToBeAligned;
+}
+
+int keycmp(const KEY* key1, const KEY* key2) {
+    if (key1->obj_id < key2->obj_id) {
+        return -1;
+    } else if (key1->obj_id > key2->obj_id) {
+        return 1;
+    }
+    
+    if (key1->obj_type < key2->obj_type) {
+        return -1;
+    } else if (key1->obj_type > key2->obj_type) {
+        return 1;
+    }
+    
+    if (key1->offset < key2->offset) {
+        return -1;
+    } else if (key1->offset > key2->offset) {
+        return 1;
+    }
+    
+    return 0;
+}
+
+BOOL is_top_level(PIRP Irp) {
+    if (!IoGetTopLevelIrp()) {
+        IoSetTopLevelIrp(Irp);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void STDCALL DriverUnload(PDRIVER_OBJECT DriverObject) {
+    UNICODE_STRING dosdevice_nameW;
+
+    ERR("DriverUnload\n");
+    
+    free_cache();
+    
+    IoUnregisterFileSystem(DriverObject->DeviceObject);
+   
+    dosdevice_nameW.Buffer = dosdevice_name;
+    dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = (USHORT)wcslen(dosdevice_name) * sizeof(WCHAR);
+
+    IoDeleteSymbolicLink(&dosdevice_nameW);
+    IoDeleteDevice(DriverObject->DeviceObject);
+    
+    while (!IsListEmpty(&uid_map_list)) {
+        LIST_ENTRY* le = RemoveHeadList(&uid_map_list);
+        uid_map* um = CONTAINING_RECORD(le, uid_map, listentry);
+        
+        ExFreePool(um->sid);
+
+        ExFreePool(um);
+    }
+    
+    // FIXME - free volumes and their devpaths
+    
+#ifdef _DEBUG
+    if (comfo)
+        ObDereferenceObject(comfo);
+    
+    if (log_handle)
+        ZwClose(log_handle);
+#endif
+    
+    ExDeleteResourceLite(&global_loading_lock);
+    
+    if (log_device.Buffer)
+        ExFreePool(log_device.Buffer);
+    
+    if (log_file.Buffer)
+        ExFreePool(log_file.Buffer);
+}
+
+BOOL STDCALL get_last_inode(device_extension* Vcb, root* r) {
+    KEY searchkey;
+    traverse_ptr tp, prev_tp;
+    NTSTATUS Status;
+    
+    // get last entry
+    searchkey.obj_id = 0xffffffffffffffff;
+    searchkey.obj_type = 0xff;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, r, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    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) {
+            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);
+    
+    return TRUE;
+}
+
+BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char* name, UINT32 crc32, UINT8** data, UINT16* datalen) {
+    KEY searchkey;
+    traverse_ptr tp;
+    DIR_ITEM* xa;
+    ULONG size, xasize;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %llx, %llx, %s, %08x, %p, %p)\n", Vcb, subvol->id, inode, name, crc32, data, datalen);
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_XATTR_ITEM;
+    searchkey.offset = crc32;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    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;
+    }
+    
+    xa = (DIR_ITEM*)tp.item->data;
+    size = tp.item->size;
+    
+    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;
+        }
+        
+        if (xa->n == strlen(name) && RtlCompareMemory(name, xa->name, xa->n) == xa->n) {
+            TRACE("found xattr %s in (%llx,%x,%llx)\n", name, searchkey.obj_id, searchkey.obj_type, searchkey.offset);
+            
+            *datalen = xa->m;
+            
+            if (xa->m > 0) {
+                *data = ExAllocatePoolWithTag(PagedPool, xa->m, ALLOC_TAG);
+                if (!*data) {
+                    ERR("out of memory\n");
+                    free_traverse_ptr(&tp);
+                    return FALSE;
+                }
+                
+                RtlCopyMemory(*data, &xa->name[xa->n], xa->m);
+            } else
+                *data = NULL;
+            
+            free_traverse_ptr(&tp);
+            return TRUE;
+        }
+        
+        xasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n;
+
+        if (size > xasize) {
+            size -= xasize;
+            xa = (DIR_ITEM*)&xa->name[xa->m + xa->n];
+        } else
+            break;
+    }
+    
+    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;
+}
+
+NTSTATUS STDCALL set_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char* name, UINT32 crc32, UINT8* data, UINT16 datalen, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    ULONG xasize;
+    DIR_ITEM* xa;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %llx, %llx, %s, %08x, %p, %u)\n", Vcb, subvol->id, inode, name, crc32, data, datalen);
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_XATTR_ITEM;
+    searchkey.offset = crc32;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    xasize = sizeof(DIR_ITEM) - 1 + (ULONG)strlen(name) + datalen;
+    
+    if (!keycmp(&tp.item->key, &searchkey)) { // key exists
+        UINT8* newdata;
+        ULONG size = tp.item->size;
+        
+        xa = (DIR_ITEM*)tp.item->data;
+        
+        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 {
+            while (TRUE) {
+                ULONG oldxasize;
+                
+                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);
+                    break;
+                }
+                
+                oldxasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n;
+                
+                if (xa->n == strlen(name) && RtlCompareMemory(name, xa->name, xa->n) == xa->n) {
+                    UINT64 pos;
+                    
+                    // replace
+                    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;
+                    }
+                    
+                    pos = (UINT8*)xa - tp.item->data;
+                    if (pos + oldxasize < tp.item->size) { // copy after changed xattr
+                        RtlCopyMemory(newdata + pos + xasize, tp.item->data + pos + oldxasize, tp.item->size - pos - oldxasize);
+                    }
+                    
+                    if (pos > 0) { // copy before changed xattr
+                        RtlCopyMemory(newdata, tp.item->data, pos);
+                        xa = (DIR_ITEM*)(newdata + pos);
+                    } else
+                        xa = (DIR_ITEM*)newdata;
+                    
+                    xa->key.obj_id = 0;
+                    xa->key.obj_type = 0;
+                    xa->key.offset = 0;
+                    xa->transid = Vcb->superblock.generation;
+                    xa->m = datalen;
+                    xa->n = (UINT16)strlen(name);
+                    xa->type = BTRFS_TYPE_EA;
+                    RtlCopyMemory(xa->name, name, strlen(name));
+                    RtlCopyMemory(xa->name + strlen(name), data, datalen);
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    insert_tree_item(Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, newdata, tp.item->size + xasize - oldxasize, NULL, rollback);
+                    
+                    break;
+                }
+                
+                if (xa->m + xa->n >= size) { // FIXME - test this works
+                    // not found, add to end of data
+                    newdata = ExAllocatePoolWithTag(PagedPool, tp.item->size + xasize, ALLOC_TAG);
+                    if (!newdata) {
+                        ERR("out of memory\n");
+                        free_traverse_ptr(&tp);
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    RtlCopyMemory(newdata, tp.item->data, tp.item->size);
+                    
+                    xa = (DIR_ITEM*)((UINT8*)newdata + tp.item->size);
+                    xa->key.obj_id = 0;
+                    xa->key.obj_type = 0;
+                    xa->key.offset = 0;
+                    xa->transid = Vcb->superblock.generation;
+                    xa->m = datalen;
+                    xa->n = (UINT16)strlen(name);
+                    xa->type = BTRFS_TYPE_EA;
+                    RtlCopyMemory(xa->name, name, strlen(name));
+                    RtlCopyMemory(xa->name + strlen(name), data, datalen);
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    insert_tree_item(Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, newdata, tp.item->size + xasize, NULL, rollback);
+                    
+                    break;
+                } else {
+                    xa = (DIR_ITEM*)&xa->name[xa->m + xa->n];
+                    size -= oldxasize;
+                }
+            }
+        }
+    } else {
+        // add new DIR_ITEM struct
+        
+        xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG);
+        if (!xa) {
+            ERR("out of memory\n");
+            free_traverse_ptr(&tp);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        xa->key.obj_id = 0;
+        xa->key.obj_type = 0;
+        xa->key.offset = 0;
+        xa->transid = Vcb->superblock.generation;
+        xa->m = datalen;
+        xa->n = (UINT16)strlen(name);
+        xa->type = BTRFS_TYPE_EA;
+        RtlCopyMemory(xa->name, name, strlen(name));
+        RtlCopyMemory(xa->name + strlen(name), data, datalen);
+        
+        insert_tree_item(Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, NULL, rollback);
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+BOOL STDCALL delete_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char* name, UINT32 crc32, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    DIR_ITEM* xa;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %llx, %llx, %s, %08x)\n", Vcb, subvol->id, inode, name, crc32);
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_XATTR_ITEM;
+    searchkey.offset = crc32;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (!keycmp(&tp.item->key, &searchkey)) { // key exists
+        ULONG size = tp.item->size;
+        
+        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;
+            
+            while (TRUE) {
+                ULONG oldxasize;
+                
+                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;
+                }
+                
+                oldxasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n;
+                
+                if (xa->n == strlen(name) && RtlCompareMemory(name, xa->name, xa->n) == xa->n) {
+                    ULONG newsize;
+                    UINT8 *newdata, *dioff;
+                    
+                    newsize = tp.item->size - (sizeof(DIR_ITEM) - 1 + xa->n + xa->m);
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    if (newsize == 0) {
+                        TRACE("xattr %s deleted\n", name);
+                        free_traverse_ptr(&tp);
+                        
+                        return TRUE;
+                    }
+
+                    // FIXME - deleting collisions almost certainly works, but we should test it properly anyway
+                    newdata = ExAllocatePoolWithTag(PagedPool, newsize, ALLOC_TAG);
+                    if (!newdata) {
+                        ERR("out of memory\n");
+                        free_traverse_ptr(&tp);
+                        return FALSE;
+                    }
+
+                    if ((UINT8*)xa > tp.item->data) {
+                        RtlCopyMemory(newdata, tp.item->data, (UINT8*)xa - tp.item->data);
+                        dioff = newdata + ((UINT8*)xa - tp.item->data);
+                    } else {
+                        dioff = newdata;
+                    }
+                    
+                    if ((UINT8*)&xa->name[xa->n+xa->m] - tp.item->data < tp.item->size)
+                        RtlCopyMemory(dioff, &xa->name[xa->n+xa->m], tp.item->size - ((UINT8*)&xa->name[xa->n+xa->m] - tp.item->data));
+                    
+                    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 {
+                    xa = (DIR_ITEM*)&xa->name[xa->m + xa->n];
+                    size -= oldxasize;
+                }
+            }
+        }
+    } else {
+        WARN("xattr %s not found\n", name);
+        free_traverse_ptr(&tp);
+        
+        return FALSE;
+    }
+}
+
+NTSTATUS add_dir_item(device_extension* Vcb, root* subvol, UINT64 inode, UINT32 crc32, DIR_ITEM* di, ULONG disize, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    UINT8* di2;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_DIR_ITEM;
+    searchkey.offset = crc32;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!keycmp(&tp.item->key, &searchkey)) {
+        ULONG maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
+        
+        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;
+        }
+        
+        if (tp.item->size > 0)
+            RtlCopyMemory(di2, tp.item->data, tp.item->size);
+        
+        RtlCopyMemory(di2 + tp.item->size, di, disize);
+        
+        delete_tree_item(Vcb, &tp, rollback);
+        
+        insert_tree_item(Vcb, subvol, inode, TYPE_DIR_ITEM, crc32, di2, tp.item->size + disize, NULL, rollback);
+        
+        ExFreePool(di);
+    } else {
+        insert_tree_item(Vcb, subvol, inode, TYPE_DIR_ITEM, crc32, di, disize, NULL, rollback);
+    }
+
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+UINT64 find_next_dir_index(device_extension* Vcb, root* subvol, UINT64 inode) {
+    KEY searchkey;
+    traverse_ptr tp, prev_tp;
+    UINT64 dirpos;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_DIR_INDEX + 1;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return 0;
+    }
+    
+    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);
+        }
+    }
+    
+    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_DIR_INDEX) {
+        dirpos = tp.item->key.offset + 1;
+    } else
+        dirpos = 2;
+    
+    free_traverse_ptr(&tp);
+    
+    return dirpos;
+}
+
+static NTSTATUS STDCALL drv_close(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp;
+    BOOL top_level;
+
+    TRACE("close\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    if (DeviceObject == devobj) {
+        TRACE("Closing file system\n");
+        Status = STATUS_SUCCESS;
+        goto exit;
+    }
+    
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    
+    // FIXME - unmount if called for volume
+    // FIXME - call FsRtlNotifyUninitializeSync(&Vcb->NotifySync) if unmounting
+    
+    Status = close_file(DeviceObject->DeviceExtension, IrpSp->FileObject);
+
+exit:
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+    
+    IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+    
+    TRACE("returning %08x\n", Status);
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    BOOL top_level;
+
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+//     ERR("recursive = %s\n", Irp != IoGetTopLevelIrp() ? "TRUE" : "FALSE");
+    
+    Status = write_file(DeviceObject, Irp);
+    
+    Irp->IoStatus.Status = Status;
+
+    TRACE("wrote %u bytes\n", Irp->IoStatus.Information);
+    
+    if (Status != STATUS_PENDING)
+        IoCompleteRequest(Irp, IO_NO_INCREMENT);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+    
+    TRACE("returning %08x\n", Status);
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    BOOL top_level;
+
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    FIXME("STUB: query ea\n");
+    Status = STATUS_NOT_IMPLEMENTED;
+    
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    BOOL top_level;
+
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    FIXME("STUB: set ea\n");
+    Status = STATUS_NOT_IMPLEMENTED;
+    
+    if (Vcb->readonly)
+        Status = STATUS_MEDIA_WRITE_PROTECTED;
+    
+    // FIXME - return STATUS_ACCESS_DENIED if subvol readonly
+    
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_flush_buffers(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    fcb* fcb = FileObject->FsContext;
+    BOOL top_level;
+
+    TRACE("flush buffers\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_SUCCESS;
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+    
+    if (fcb->type != BTRFS_TYPE_DIRECTORY) {
+        CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &Irp->IoStatus);
+        
+        if (fcb->Header.PagingIoResource) {
+            ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, TRUE);
+            ExReleaseResourceLite(fcb->Header.PagingIoResource);
+        }
+        
+        Status = Irp->IoStatus.Status;
+    }
+    
+    IoCompleteRequest(Irp, IO_NO_INCREMENT);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_query_volume_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS Status;
+    ULONG BytesCopied = 0;
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    BOOL top_level;
+    
+#ifndef __REACTOS__
+    // An unfortunate necessity - we have to lie about our FS type. MPR!MprGetConnection polls for this,
+    // and compares it to a whitelist. If it doesn't match, it will return ERROR_NO_NET_OR_BAD_PATH,
+    // which prevents UAC from working.
+    // FIXME - only lie if we detect that we're being called by mpr.dll
+    
+    WCHAR* fs_name = L"NTFS";
+    ULONG fs_name_len = 4 * sizeof(WCHAR);
+#else
+    WCHAR* fs_name = L"Btrfs";
+    ULONG fs_name_len = 5 * sizeof(WCHAR);
+#endif
+
+    TRACE("query volume information\n");
+    
+    FsRtlEnterFileSystem();
+    top_level = is_top_level(Irp);
+    
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    
+    Status = STATUS_NOT_IMPLEMENTED;
+    
+    switch (IrpSp->Parameters.QueryVolume.FsInformationClass) {
+        case FileFsAttributeInformation:
+        {
+            FILE_FS_ATTRIBUTE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;
+            BOOL overflow = FALSE;
+            ULONG orig_fs_name_len = fs_name_len;
+            
+            TRACE("FileFsAttributeInformation\n");
+            
+            if (IrpSp->Parameters.QueryVolume.Length < sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len) {
+                if (IrpSp->Parameters.QueryVolume.Length > sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR))
+                    fs_name_len = IrpSp->Parameters.QueryVolume.Length - sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + sizeof(WCHAR);
+                else
+                    fs_name_len = 0;
+                
+                overflow = TRUE;
+            }
+            
+            data->FileSystemAttributes = FILE_CASE_PRESERVED_NAMES | FILE_CASE_SENSITIVE_SEARCH |
+                                         FILE_UNICODE_ON_DISK | FILE_NAMED_STREAMS | FILE_SUPPORTS_HARD_LINKS | FILE_PERSISTENT_ACLS |
+                                         FILE_SUPPORTS_REPARSE_POINTS;
+            if (Vcb->readonly)
+                data->FileSystemAttributes |= FILE_READ_ONLY_VOLUME;
+                                         
+            // should also be FILE_FILE_COMPRESSION when supported
+            data->MaximumComponentNameLength = 255; // FIXME - check
+            data->FileSystemNameLength = orig_fs_name_len;
+            RtlCopyMemory(data->FileSystemName, fs_name, fs_name_len);
+            
+            BytesCopied = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len;
+            Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
+            break;
+        }
+
+        case FileFsControlInformation:
+            FIXME("STUB: FileFsControlInformation\n");
+            break;
+
+        case FileFsDeviceInformation:
+            FIXME("STUB: FileFsDeviceInformation\n");
+            break;
+
+        case FileFsDriverPathInformation:
+            FIXME("STUB: FileFsDriverPathInformation\n");
+            break;
+
+        case FileFsFullSizeInformation:
+        {
+            FILE_FS_FULL_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer;
+            UINT64 totalsize, freespace;
+            
+            TRACE("FileFsFullSizeInformation\n");
+            
+            // FIXME - calculate correctly for RAID
+            totalsize = Vcb->superblock.total_bytes / Vcb->superblock.sector_size;
+            freespace = (Vcb->superblock.total_bytes - Vcb->superblock.bytes_used) / Vcb->superblock.sector_size;
+            
+            ffsi->TotalAllocationUnits.QuadPart = totalsize;
+            ffsi->ActualAvailableAllocationUnits.QuadPart = freespace;
+            ffsi->CallerAvailableAllocationUnits.QuadPart = ffsi->ActualAvailableAllocationUnits.QuadPart;
+            ffsi->SectorsPerAllocationUnit = 1;
+            ffsi->BytesPerSector = Vcb->superblock.sector_size;
+            
+            BytesCopied = sizeof(FILE_FS_FULL_SIZE_INFORMATION);
+            Status = STATUS_SUCCESS;
+            
+            break;
+        }
+
+        case FileFsObjectIdInformation:
+            FIXME("STUB: FileFsObjectIdInformation\n");
+            break;
+
+        case FileFsSizeInformation:
+        {
+            FILE_FS_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer;
+            UINT64 totalsize, freespace;
+            
+            TRACE("FileFsSizeInformation\n");
+            
+            // FIXME - calculate correctly for RAID
+            // FIXME - is this returning the right free space?
+            totalsize = Vcb->superblock.dev_item.num_bytes / Vcb->superblock.sector_size;
+            freespace = (Vcb->superblock.dev_item.num_bytes - Vcb->superblock.dev_item.bytes_used) / Vcb->superblock.sector_size;
+            
+            ffsi->TotalAllocationUnits.QuadPart = totalsize;
+            ffsi->AvailableAllocationUnits.QuadPart = freespace;
+            ffsi->SectorsPerAllocationUnit = 1;
+            ffsi->BytesPerSector = Vcb->superblock.sector_size;
+            
+            BytesCopied = sizeof(FILE_FS_SIZE_INFORMATION);
+            Status = STATUS_SUCCESS;
+            
+            break;
+        }
+
+        case FileFsVolumeInformation:
+        {
+            FILE_FS_VOLUME_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;
+            FILE_FS_VOLUME_INFORMATION ffvi;
+            BOOL overflow = FALSE;
+            ULONG label_len, orig_label_len;
+            
+            TRACE("FileFsVolumeInformation\n");
+            TRACE("max length = %u\n", IrpSp->Parameters.QueryVolume.Length);
+            
+            acquire_tree_lock(Vcb, FALSE);
+            
+//             orig_label_len = label_len = (ULONG)(wcslen(Vcb->label) * sizeof(WCHAR));
+            RtlUTF8ToUnicodeN(NULL, 0, &label_len, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));
+            orig_label_len = label_len;
+            
+            if (IrpSp->Parameters.QueryVolume.Length < sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR) + label_len) {
+                if (IrpSp->Parameters.QueryVolume.Length > sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR))
+                    label_len = IrpSp->Parameters.QueryVolume.Length - sizeof(FILE_FS_VOLUME_INFORMATION) + sizeof(WCHAR);
+                else
+                    label_len = 0;
+                
+                overflow = TRUE;
+            }
+            
+            TRACE("label_len = %u\n", label_len);
+            
+            ffvi.VolumeCreationTime.QuadPart = 0; // FIXME
+            ffvi.VolumeSerialNumber = Vcb->superblock.uuid.uuid[12] << 24 | Vcb->superblock.uuid.uuid[13] << 16 | Vcb->superblock.uuid.uuid[14] << 8 | Vcb->superblock.uuid.uuid[15];
+            ffvi.VolumeLabelLength = orig_label_len;
+            ffvi.SupportsObjects = FALSE;
+            
+            RtlCopyMemory(data, &ffvi, min(sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR), IrpSp->Parameters.QueryVolume.Length));
+            
+            if (label_len > 0) {
+                ULONG bytecount;
+                
+//                 RtlCopyMemory(&data->VolumeLabel[0], Vcb->label, label_len);
+                RtlUTF8ToUnicodeN(&data->VolumeLabel[0], label_len, &bytecount, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));
+                TRACE("label = %.*S\n", label_len / sizeof(WCHAR), data->VolumeLabel);
+            }
+            
+            release_tree_lock(Vcb, FALSE);
+
+            BytesCopied = sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR) + label_len;
+            Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
+            break;
+        }
+
+        default:
+            Status = STATUS_INVALID_PARAMETER;
+            WARN("unknown FsInformatClass %u\n", IrpSp->Parameters.QueryVolume.FsInformationClass);
+            break;
+    }
+    
+//     if (NT_SUCCESS(Status) && IrpSp->Parameters.QueryVolume.Length < BytesCopied) { // FIXME - should not copy anything if overflow
+//         WARN("overflow: %u < %u\n", IrpSp->Parameters.QueryVolume.Length, BytesCopied);
+//         BytesCopied = IrpSp->Parameters.QueryVolume.Length;
+//         Status = STATUS_BUFFER_OVERFLOW;
+//     }
+
+    Irp->IoStatus.Status = Status;
+    
+    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
+        Irp->IoStatus.Information = 0;
+    else
+        Irp->IoStatus.Information = BytesCopied;
+    
+    IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+    
+    TRACE("query volume information returning %08x\n", Status);
+
+    return Status;
+}
+
+static NTSTATUS STDCALL read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
+    read_context* context = conptr;
+    
+//     DbgPrint("read_completion\n");
+    
+    context->iosb = Irp->IoStatus;
+    KeSetEvent(&context->Event, 0, FALSE);
+    
+//     return STATUS_SUCCESS;
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+// static void test_tree_deletion(device_extension* Vcb) {
+//     KEY searchkey/*, endkey*/;
+//     traverse_ptr tp, next_tp;
+//     root* r;
+//     
+//     searchkey.obj_id = 0x100;
+//     searchkey.obj_type = 0x54;
+//     searchkey.offset = 0xca4ab2f5;
+//     
+// //     endkey.obj_id = 0x100;
+// //     endkey.obj_type = 0x60;
+// //     endkey.offset = 0x15a;
+//     
+//     r = Vcb->roots;
+//     while (r && r->id != 0x102)
+//         r = r->next;
+//     
+//     if (!r) {
+//         ERR("error - could not find root\n");
+//         return;
+//     }
+//     
+//     if (!find_item(Vcb, r, &tp, &searchkey, NULL, FALSE)) {
+//         ERR("error - could not find key\n");
+//         return;
+//     }
+//     
+//     while (TRUE/*keycmp(&tp.item->key, &endkey) < 1*/) {
+//         tp.item->ignore = TRUE;
+//         add_to_tree_cache(tc, tp.tree);
+//         
+//         if (find_next_item(Vcb, &tp, &next_tp, NULL, FALSE)) {
+//             free_traverse_ptr(&tp);
+//             tp = next_tp;
+//         } else
+//             break;
+//     }
+//     
+//     free_traverse_ptr(&tp);
+// }
+
+// static void test_tree_splitting(device_extension* Vcb) {
+//     int i;
+//     
+//     for (i = 0; i < 1000; i++) {
+//         char* data = ExAllocatePoolWithTag(PagedPool, 4, ALLOC_TAG);
+//         
+//         insert_tree_item(Vcb, Vcb->extent_root, 0, 0xfd, i, data, 4, NULL);
+//     }
+// }
+
+static NTSTATUS STDCALL set_label(device_extension* Vcb, FILE_FS_LABEL_INFORMATION* ffli) {
+    ULONG utf8len;
+    NTSTATUS Status;
+    
+    TRACE("label = %.*S\n", ffli->VolumeLabelLength / sizeof(WCHAR), ffli->VolumeLabel);
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, ffli->VolumeLabel, ffli->VolumeLabelLength);
+    if (!NT_SUCCESS(Status))
+        goto end;
+    
+    if (utf8len > MAX_LABEL_SIZE) {
+        Status = STATUS_INVALID_VOLUME_LABEL;
+        goto end;
+    }
+    
+    // FIXME - check for '/' and '\\' and reject
+    
+    acquire_tree_lock(Vcb, TRUE);
+    
+//     utf8 = ExAllocatePoolWithTag(PagedPool, utf8len + 1, ALLOC_TAG);
+    
+    Status = RtlUnicodeToUTF8N((PCHAR)&Vcb->superblock.label, MAX_LABEL_SIZE * sizeof(WCHAR), &utf8len, ffli->VolumeLabel, ffli->VolumeLabelLength);
+    if (!NT_SUCCESS(Status))
+        goto release;
+    
+    if (utf8len < MAX_LABEL_SIZE * sizeof(WCHAR))
+        RtlZeroMemory(Vcb->superblock.label + utf8len, (MAX_LABEL_SIZE * sizeof(WCHAR)) - utf8len);
+    
+//     test_tree_deletion(Vcb); // TESTING
+//     test_tree_splitting(Vcb);
+    
+    Status = consider_write(Vcb);
+    
+release:  
+    release_tree_lock(Vcb, TRUE);
+
+end:
+    TRACE("returning %08x\n", Status);
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_set_volume_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    NTSTATUS Status;
+    BOOL top_level;
+
+    TRACE("set volume information\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_NOT_IMPLEMENTED;
+    
+    if (Vcb->readonly) {
+        Status = STATUS_MEDIA_WRITE_PROTECTED;
+        goto end;
+    }
+    
+    switch (IrpSp->Parameters.SetVolume.FsInformationClass) {
+        case FileFsControlInformation:
+            FIXME("STUB: FileFsControlInformation\n");
+            break;
+
+        case FileFsLabelInformation:
+            TRACE("FileFsLabelInformation\n");
+    
+            Status = set_label(Vcb, Irp->AssociatedIrp.SystemBuffer);
+            break;
+
+        case FileFsObjectIdInformation:
+            FIXME("STUB: FileFsObjectIdInformation\n");
+            break;
+
+        default:
+            WARN("Unrecognized FsInformationClass 0x%x\n", IrpSp->Parameters.SetVolume.FsInformationClass);
+            break;
+    }
+    
+end:
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+NTSTATUS delete_dir_item(device_extension* Vcb, root* subvol, UINT64 parinode, UINT32 crc32, PANSI_STRING utf8, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_DIR_ITEM;
+    searchkey.offset = crc32;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        if (tp.item->size < sizeof(DIR_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(DIR_ITEM));
+        } else {
+            DIR_ITEM* di;
+            LONG len;
+            
+            di = (DIR_ITEM*)tp.item->data;
+            len = tp.item->size;
+            
+            do {
+                if (di->n == utf8->Length && RtlCompareMemory(di->name, utf8->Buffer, di->n) == di->n) {
+                    ULONG newlen = tp.item->size - (sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m);
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    if (newlen == 0) {
+                        TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    } else {
+                        UINT8 *newdi = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *dioff;
+                        
+                        if (!newdi) {
+                            ERR("out of memory\n");
+                            free_traverse_ptr(&tp);
+                            return STATUS_INSUFFICIENT_RESOURCES;
+                        }
+                        
+                        TRACE("modifying (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+
+                        if ((UINT8*)di > tp.item->data) {
+                            RtlCopyMemory(newdi, tp.item->data, (UINT8*)di - tp.item->data);
+                            dioff = newdi + ((UINT8*)di - tp.item->data);
+                        } else {
+                            dioff = newdi;
+                        }
+                        
+                        if ((UINT8*)&di->name[di->n + di->m] - tp.item->data < tp.item->size)
+                            RtlCopyMemory(dioff, &di->name[di->n + di->m], tp.item->size - ((UINT8*)&di->name[di->n + di->m] - tp.item->data));
+                        
+                        insert_tree_item(Vcb, subvol, parinode, TYPE_DIR_ITEM, crc32, newdi, newlen, NULL, rollback);
+                    }
+                    
+                    break;
+                }
+                
+                len -= sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m;
+                di = (DIR_ITEM*)&di->name[di->n + di->m];
+            } while (len > 0);
+        }
+    } else {
+        WARN("could not find DIR_ITEM for crc32 %08x\n", crc32);
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS delete_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, PANSI_STRING utf8, UINT64* index, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    BOOL changed = FALSE;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_INODE_REF;
+    searchkey.offset = parinode;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        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 len;
+            
+            ir = (INODE_REF*)tp.item->data;
+            len = tp.item->size;
+            
+            do {
+                ULONG itemlen;
+                
+                if (len < sizeof(INODE_REF) || len < sizeof(INODE_REF) - 1 + ir->n) {
+                    ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    break;
+                }
+                
+                itemlen = sizeof(INODE_REF) - sizeof(char) + ir->n;
+                
+                if (ir->n == utf8->Length && RtlCompareMemory(ir->name, utf8->Buffer, ir->n) == ir->n) {
+                    ULONG newlen = tp.item->size - itemlen;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    changed = TRUE;
+                    
+                    if (newlen == 0) {
+                        TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    } else {
+                        UINT8 *newir = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *iroff;
+                        
+                        if (!newir) {
+                            ERR("out of memory\n");
+                            free_traverse_ptr(&tp);
+                            return STATUS_INSUFFICIENT_RESOURCES;
+                        }
+                        
+                        TRACE("modifying (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+
+                        if ((UINT8*)ir > tp.item->data) {
+                            RtlCopyMemory(newir, tp.item->data, (UINT8*)ir - tp.item->data);
+                            iroff = newir + ((UINT8*)ir - tp.item->data);
+                        } else {
+                            iroff = newir;
+                        }
+                        
+                        if ((UINT8*)&ir->name[ir->n] - tp.item->data < tp.item->size)
+                            RtlCopyMemory(iroff, &ir->name[ir->n], tp.item->size - ((UINT8*)&ir->name[ir->n] - tp.item->data));
+                        
+                        insert_tree_item(Vcb, subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newir, newlen, NULL, rollback);
+                    }
+                    
+                    if (index)
+                        *index = ir->index;
+                    
+                    break;
+                }
+                
+                if (len > itemlen) {
+                    len -= itemlen;
+                    ir = (INODE_REF*)&ir->name[ir->n];
+                } else
+                    break;
+            } while (len > 0);
+            
+            if (!changed) {
+                WARN("found INODE_REF entry, but couldn't find filename\n");
+            }
+        }
+    } else {
+        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;
+    
+    if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF))
+        return STATUS_INTERNAL_ERROR;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_INODE_EXTREF;
+    searchkey.offset = calc_crc32c((UINT32)parinode, (UINT8*)utf8->Buffer, utf8->Length);
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        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 len;
+            
+            ier = (INODE_EXTREF*)tp.item->data;
+            len = tp.item->size;
+            
+            do {
+                ULONG itemlen;
+                
+                if (len < sizeof(INODE_EXTREF) || len < sizeof(INODE_EXTREF) - 1 + ier->n) {
+                    ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    break;
+                }
+                
+                itemlen = sizeof(INODE_EXTREF) - sizeof(char) + ier->n;
+                
+                if (ier->dir == parinode && ier->n == utf8->Length && RtlCompareMemory(ier->name, utf8->Buffer, ier->n) == ier->n) {
+                    ULONG newlen = tp.item->size - itemlen;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    changed = TRUE;
+                    
+                    if (newlen == 0) {
+                        TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    } else {
+                        UINT8 *newier = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *ieroff;
+                        
+                        if (!newier) {
+                            ERR("out of memory\n");
+                            free_traverse_ptr(&tp);
+                            return STATUS_INSUFFICIENT_RESOURCES;
+                        }
+                        
+                        TRACE("modifying (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+
+                        if ((UINT8*)ier > tp.item->data) {
+                            RtlCopyMemory(newier, tp.item->data, (UINT8*)ier - tp.item->data);
+                            ieroff = newier + ((UINT8*)ier - tp.item->data);
+                        } else {
+                            ieroff = newier;
+                        }
+                        
+                        if ((UINT8*)&ier->name[ier->n] - tp.item->data < tp.item->size)
+                            RtlCopyMemory(ieroff, &ier->name[ier->n], tp.item->size - ((UINT8*)&ier->name[ier->n] - tp.item->data));
+                        
+                        insert_tree_item(Vcb, subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newier, newlen, NULL, rollback);
+                    }
+                    
+                    if (index)
+                        *index = ier->index;
+                    
+                    break;
+                }
+                
+                if (len > itemlen) {
+                    len -= itemlen;
+                    ier = (INODE_EXTREF*)&ier->name[ier->n];
+                } else
+                    break;
+            } while (len > 0);
+        }
+    } else {
+        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) {
+    ULONG bytecount;
+    NTSTATUS Status;
+    char* utf8 = NULL;
+    UINT32 crc32;
+    KEY searchkey;
+    traverse_ptr tp, tp2;
+    UINT64 parinode, index;
+    INODE_ITEM *ii, *dirii;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    LIST_ENTRY changed_sector_list;
+#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) {
+        ERR("trying to delete already-deleted file\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (!fcb->par) {
+        ERR("error - trying to delete root FCB\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+#ifdef _DEBUG
+    time1 = KeQueryPerformanceCounter(&freq);
+#endif
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+    if (fcb->ads) {
+        char* s;
+        TRACE("deleting ADS\n");
+        
+        s = ExAllocatePoolWithTag(PagedPool, fcb->adsxattr.Length + 1, ALLOC_TAG);
+        if (!s) {
+            ERR("out of memory\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto exit;
+        }
+        
+        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)) {
+            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;
+        
+        searchkey.obj_id = fcb->par->inode;
+        searchkey.obj_type = TYPE_INODE_ITEM;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(fcb->Vcb, fcb->par->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);
+            Status = STATUS_INTERNAL_ERROR;
+            goto exit;
+        }
+
+        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));
+        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);
+        
+        free_traverse_ptr(&tp);
+        
+        fcb->par->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+        fcb->par->subvol->root_item.ctime = now;
+        
+        goto success;
+    }
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &bytecount, fcb->filepart.Buffer, fcb->filepart.Length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N failed with error %08x\n", Status);
+        return Status;
+    }
+    
+    utf8 = ExAllocatePoolWithTag(PagedPool, bytecount + 1, ALLOC_TAG);
+    if (!utf8) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlUnicodeToUTF8N(utf8, bytecount, &bytecount, fcb->filepart.Buffer, fcb->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);
+    
+    if (fcb->par->subvol == fcb->subvol)
+        parinode = fcb->par->inode;
+    else
+        parinode = SUBVOL_ROOT_INODE;
+    
+    // delete DIR_ITEM (0x54)
+    
+    Status = delete_dir_item(fcb->Vcb, fcb->subvol, parinode, crc32, &fcb->utf8, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("delete_dir_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    // delete INODE_REF (0xc)
+    
+    index = 0;
+    
+    Status = delete_inode_ref(fcb->Vcb, fcb->subvol, fcb->inode, parinode, &fcb->utf8, &index, rollback);
+    
+    // delete DIR_INDEX (0x60)
+    
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_DIR_INDEX;
+    searchkey.offset = index;
+    
+    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;
+    }
+    
+    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);
+    }
+    
+    // delete INODE_ITEM (0x1)
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0;
+    
+    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;
+    }
+    
+    ii = (INODE_ITEM*)tp.item->data;
+    TRACE("nlink = %u\n", ii->st_nlink);
+    
+    if (ii->st_nlink > 1) {
+        INODE_ITEM* newii;
+        
+        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;
+        }
+        
+        RtlCopyMemory(newii, ii, sizeof(INODE_ITEM));
+        newii->st_nlink--;
+        newii->transid = fcb->Vcb->superblock.generation;
+        newii->sequence++;
+        newii->st_ctime = now;
+        
+        TRACE("replacing (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, 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, 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);
+    
+    // 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) {
+            // FIXME - do metadata thing here too?
+            if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
+                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
+            break;
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    // excise extents
+    
+    InitializeListHead(&changed_sector_list);
+    
+    if (fcb->type != BTRFS_TYPE_DIRECTORY) {
+        Status = excise_extents(fcb->Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), &changed_sector_list, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("excise_extents returned %08x\n", Status);
+            goto exit;
+        }
+        
+        if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM))
+            update_checksum_tree(fcb->Vcb, &changed_sector_list, rollback);
+    }
+    
+success2:
+    // update INODE_ITEM of parent
+    
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0;
+    
+    Status = find_item(fcb->Vcb, fcb->subvol, &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);
+        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;
+
+    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));
+    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);
+    
+    fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = now;
+    
+success:
+    consider_write(fcb->Vcb);
+    
+    fcb->deleted = TRUE;
+    
+    fcb->Header.AllocationSize.QuadPart = 0;
+    fcb->Header.FileSize.QuadPart = 0;
+    fcb->Header.ValidDataLength.QuadPart = 0;
+    
+    if (FileObject && FileObject->PrivateCacheMap) {
+        CC_FILE_SIZES ccfs;
+        
+        ccfs.AllocationSize = fcb->Header.AllocationSize;
+        ccfs.FileSize = fcb->Header.FileSize;
+        ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+        
+        CcSetFileSizes(FileObject, &ccfs);
+    }
+    
+    // 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);
+    
+#ifdef _DEBUG
+    time2 = KeQueryPerformanceCounter(NULL);
+#endif
+    
+    TRACE("time = %u (freq = %u)\n", (UINT32)(time2.QuadPart - time1.QuadPart), (UINT32)freq.QuadPart);
+    
+    Status = STATUS_SUCCESS;
+    
+exit:
+    if (utf8)
+        ExFreePool(utf8);
+    
+    return Status;
+}
+
+void _free_fcb(fcb* fcb, const char* func, const char* file, unsigned int line) {
+    ULONG 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, file, line, "fcb %p: refcount now %i (%.*S)\n", fcb, rc, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+#else
+    _debug_message(func, "fcb %p: refcount now %i (%.*S)\n", fcb, rc, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+#endif
+#endif
+    
+    if (rc > 0)
+        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*/) {
+        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);
+    
+    if (fcb->adsxattr.Buffer)
+        ExFreePool(fcb->adsxattr.Buffer);
+    
+    if (fcb->utf8.Buffer)
+        ExFreePool(fcb->utf8.Buffer);
+    
+    FsRtlUninitializeFileLock(&fcb->lock);
+    
+    ExReleaseResourceLite(&fcb->Vcb->fcb_lock);
+    
+    ExFreePool(fcb);
+#ifdef DEBUG_FCB_REFCOUNTS
+#ifdef DEBUG_LONG_MESSAGES
+    _debug_message(func, file, line, "freeing fcb %p\n", fcb);
+#else
+    _debug_message(func, "freeing fcb %p\n", fcb);
+#endif
+#endif
+}
+
+static NTSTATUS STDCALL close_file(device_extension* Vcb, PFILE_OBJECT FileObject) {
+    fcb* fcb;
+    ccb* ccb;
+    
+    TRACE("FileObject = %p\n", FileObject);
+    
+    fcb = FileObject->FsContext;
+    if (!fcb) {
+        TRACE("FCB was NULL, returning success\n");
+        return STATUS_SUCCESS;
+    }
+    
+    ccb = FileObject->FsContext2;
+    
+    TRACE("close called for %.*S (fcb == %p)\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb);
+    
+    FsRtlNotifyCleanup(Vcb->NotifySync, &Vcb->DirNotifyList, ccb);
+    
+    // FIXME - make sure notification gets sent if file is being deleted
+    
+    if (ccb) {    
+        if (ccb->query_string.Buffer)
+            RtlFreeUnicodeString(&ccb->query_string);
+        
+        ExFreePool(ccb);
+    }
+    
+    if (fcb->refcount == 1)
+        CcUninitializeCacheMap(FileObject, NULL, NULL);
+    
+    free_fcb(fcb);
+    
+    FileObject->FsContext = NULL;
+    
+    return STATUS_SUCCESS;
+}
+
+static void STDCALL uninit(device_extension* Vcb) {
+    chunk* c;
+    space* s;
+    UINT64 i;
+    LIST_ENTRY rollback;
+    
+    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);
+
+    release_tree_lock(Vcb, TRUE);
+
+    while (Vcb->roots) {
+        root* r = Vcb->roots->next;
+
+        ExDeleteResourceLite(&Vcb->roots->nonpaged->load_tree_lock);
+        ExFreePool(Vcb->roots->nonpaged);
+        ExFreePool(Vcb->roots);
+        
+        Vcb->roots = r;
+    }
+    
+    while (!IsListEmpty(&Vcb->chunks)) {
+        LIST_ENTRY* le = RemoveHeadList(&Vcb->chunks);
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        while (!IsListEmpty(&c->space)) {
+            LIST_ENTRY* le2 = RemoveHeadList(&c->space);
+            s = CONTAINING_RECORD(le2, space, list_entry);
+            
+            ExFreePool(s);
+        }
+        
+        if (c->devices)
+            ExFreePool(c->devices);
+        
+        ExFreePool(c->chunk_item);
+        ExFreePool(c);
+    }
+    
+    free_fcb(Vcb->volume_fcb);
+    free_fcb(Vcb->root_fcb);
+    
+    for (i = 0; i < Vcb->superblock.num_devices; i++) {
+        while (!IsListEmpty(&Vcb->devices[i].disk_holes)) {
+            LIST_ENTRY* le = RemoveHeadList(&Vcb->devices[i].disk_holes);
+            disk_hole* dh = CONTAINING_RECORD(le, disk_hole, listentry);
+            
+            ExFreePool(dh);
+        }
+    }
+    
+    ExFreePool(Vcb->devices);
+    
+    ExDeleteResourceLite(&Vcb->fcb_lock);
+    ExDeleteResourceLite(&Vcb->load_lock);
+    ExDeleteResourceLite(&Vcb->tree_lock);
+    
+    ZwClose(Vcb->flush_thread_handle);
+}
+
+static NTSTATUS STDCALL drv_cleanup(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    fcb* fcb;
+    BOOL top_level;
+
+    TRACE("cleanup\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    if (DeviceObject == devobj) {
+        TRACE("closing file system\n");
+        Status = STATUS_SUCCESS;
+        goto exit;
+    }
+    
+    if (FileObject) {
+        LONG oc;
+        
+        fcb = FileObject->FsContext;
+        
+        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);
+        
+        IoRemoveShareAccess(FileObject, &fcb->share_access);
+        
+        oc = InterlockedDecrement(&fcb->open_count);
+        
+        if (oc == 0) {
+            if (fcb->delete_on_close && fcb != fcb->Vcb->root_fcb && fcb != fcb->Vcb->volume_fcb) {
+                LIST_ENTRY rollback;
+                InitializeListHead(&rollback);
+                
+                acquire_tree_lock(fcb->Vcb, TRUE);
+                
+                Status = delete_fcb(fcb, FileObject, &rollback);
+                
+                if (NT_SUCCESS(Status)) {
+                    LARGE_INTEGER newlength;
+
+                    if (FileObject->Flags & FO_CACHE_SUPPORTED && fcb->nonpaged->segment_object.DataSectionObject)
+                        CcPurgeCacheSection(&fcb->nonpaged->segment_object, NULL, 0, FALSE);
+                    
+                    newlength.QuadPart = 0;
+                    
+                    if (!CcUninitializeCacheMap(FileObject, &newlength, NULL)) {
+                        TRACE("CcUninitializeCacheMap failed\n");
+                    }
+                    
+                    clear_rollback(&rollback);
+                } else
+                    do_rollback(fcb->Vcb, &rollback);
+                
+                release_tree_lock(fcb->Vcb, TRUE);
+            } else if (FileObject->Flags & FO_CACHE_SUPPORTED && fcb->nonpaged->segment_object.DataSectionObject) {
+                IO_STATUS_BLOCK iosb;
+                CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);
+                
+                if (!NT_SUCCESS(iosb.Status)) {
+                    ERR("CcFlushCache returned %08x\n", iosb.Status);
+                }
+
+                ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, TRUE);
+                ExReleaseResourceLite(fcb->Header.PagingIoResource);
+
+                CcPurgeCacheSection(&fcb->nonpaged->segment_object, NULL, 0, FALSE);
+                
+                TRACE("flushed cache on close (FileObject = %p, fcb = %p, AllocationSize = %llx, FileSize = %llx, ValidDataLength = %llx)\n",
+                      FileObject, fcb, fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
+            }
+        }
+        
+        if (fcb->Vcb && fcb != fcb->Vcb->volume_fcb)
+            CcUninitializeCacheMap(FileObject, NULL, NULL);
+        
+        FileObject->Flags |= FO_CLEANUP_COMPLETE;
+    }
+    
+    Status = STATUS_SUCCESS;
+
+exit:
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+    
+    IoCompleteRequest(Irp, IO_NO_INCREMENT);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+ULONG STDCALL get_file_attributes(device_extension* Vcb, INODE_ITEM* ii, root* r, UINT64 inode, UINT8 type, BOOL dotfile, BOOL ignore_xa) {
+    ULONG att;
+    char* eaval;
+    UINT16 ealen;
+    
+    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') {
+                int i;
+                ULONG dosnum = 0;
+
+                for (i = 2; i < ealen; i++) {
+                    dosnum *= 0x10;
+                    
+                    if (eaval[i] >= '0' && eaval[i] <= '9')
+                        dosnum |= eaval[i] - '0';
+                    else if (eaval[i] >= 'a' && eaval[i] <= 'f')
+                        dosnum |= eaval[i] + 10 - 'a';
+                    else if (eaval[i] >= 'A' && eaval[i] <= 'F')
+                        dosnum |= eaval[i] + 10 - 'a';
+                }
+                
+                TRACE("DOSATTRIB: %08x\n", dosnum);
+
+                ExFreePool(eaval);
+                
+                return dosnum;
+            }
+        }
+        
+        ExFreePool(eaval);
+    }
+    
+    switch (type) {
+        case BTRFS_TYPE_DIRECTORY:
+            att = FILE_ATTRIBUTE_DIRECTORY;
+            break;
+            
+        case BTRFS_TYPE_SYMLINK:
+            att = FILE_ATTRIBUTE_REPARSE_POINT;
+            break;
+           
+        default:
+            att = 0;
+            break;
+    }
+    
+    if (dotfile) {
+        att |= FILE_ATTRIBUTE_HIDDEN;
+    }
+    
+    att |= FILE_ATTRIBUTE_ARCHIVE;
+    
+    // FIXME - get READONLY from ii->st_mode
+    // FIXME - return SYSTEM for block/char devices?
+    
+    if (att == 0)
+        att = FILE_ATTRIBUTE_NORMAL;
+    
+    return att;
+}
+
+// static int STDCALL utf8icmp(char* a, char* b) {
+//     return strcmp(a, b); // FIXME - don't treat as ASCII
+// }
+
+NTSTATUS sync_read_phys(PDEVICE_OBJECT DeviceObject, LONGLONG StartingOffset, ULONG Length, PUCHAR Buffer) {
+    IO_STATUS_BLOCK* IoStatus;
+    LARGE_INTEGER Offset;
+    PIRP Irp;
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS Status;
+    read_context* context;
+    
+    num_reads++;
+    
+    context = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_context), ALLOC_TAG);
+    if (!context) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context, sizeof(read_context));
+    KeInitializeEvent(&context->Event, NotificationEvent, FALSE);
+    
+    IoStatus = ExAllocatePoolWithTag(NonPagedPool, sizeof(IO_STATUS_BLOCK), ALLOC_TAG);
+    if (!IoStatus) {
+        ERR("out of memory\n");
+        ExFreePool(context);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    Offset.QuadPart = StartingOffset;
+
+//     Irp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, DeviceObject, Buffer, Length, &Offset, /*&Event*/NULL, IoStatus);
+    Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
+    
+    if (!Irp) {
+        ERR("IoAllocateIrp failed\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto exit;
+    }
+    
+    IrpSp = IoGetNextIrpStackLocation(Irp);
+    IrpSp->MajorFunction = IRP_MJ_READ;
+    
+    if (DeviceObject->Flags & DO_BUFFERED_IO) {
+        FIXME("FIXME - buffered IO\n");
+    } else if (DeviceObject->Flags & DO_DIRECT_IO) {
+//         TRACE("direct IO\n");
+        
+        Irp->MdlAddress = IoAllocateMdl(Buffer, Length, FALSE, FALSE, NULL);
+        if (!Irp->MdlAddress) {
+            ERR("IoAllocateMdl failed\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+    //         IoFreeIrp(Irp);
+            goto exit;
+//         } else {
+//             TRACE("got MDL %p from buffer %p\n", Irp->MdlAddress, Buffer);
+        }
+        
+        MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoWriteAccess);
+    } else {
+//         TRACE("neither buffered nor direct IO\n");
+        Irp->UserBuffer = Buffer;
+    }
+
+    IrpSp->Parameters.Read.Length = Length;
+    IrpSp->Parameters.Read.ByteOffset = Offset;
+    
+    Irp->UserIosb = IoStatus;
+//     Irp->Tail.Overlay.Thread = PsGetCurrentThread();
+    
+    Irp->UserEvent = &context->Event;
+
+//     IoQueueThreadIrp(Irp);
+    
+    IoSetCompletionRoutine(Irp, read_completion, context, TRUE, TRUE, TRUE);
+
+//     if (Override)
+//     {
+//         Stack = IoGetNextIrpStackLocation(Irp);
+//         Stack->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
+//     }
+
+//     TRACE("Calling IO Driver... with irp %p\n", Irp);
+    Status = IoCallDriver(DeviceObject, Irp);
+
+//     TRACE("Waiting for IO Operation for %p\n", Irp);
+    if (Status == STATUS_PENDING) {
+//         TRACE("Operation pending\n");
+        KeWaitForSingleObject(&context->Event, Executive, KernelMode, FALSE, NULL);
+//         TRACE("Getting IO Status... for %p\n", Irp);
+        Status = context->iosb.Status;
+    }
+    
+    if (DeviceObject->Flags & DO_DIRECT_IO) {
+        MmUnlockPages(Irp->MdlAddress);
+        IoFreeMdl(Irp->MdlAddress);
+    }
+    
+exit:
+    IoFreeIrp(Irp);
+
+    ExFreePool(IoStatus);
+    ExFreePool(context);
+    
+    return Status;
+}
+
+static NTSTATUS STDCALL read_superblock(device_extension* Vcb, PDEVICE_OBJECT device) {
+    NTSTATUS Status;
+    superblock* sb;
+    unsigned int i, to_read;
+    UINT32 crc32;
+    
+    to_read = sector_align(sizeof(superblock), device->SectorSize);
+    
+    sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);
+    if (!sb) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    i = 0;
+    
+    while (superblock_addrs[i] > 0) {
+        if (i > 0 && superblock_addrs[i] + sizeof(superblock) > Vcb->length)
+            break;
+        
+        Status = sync_read_phys(device, superblock_addrs[i], to_read, (PUCHAR)sb);
+        if (!NT_SUCCESS(Status)) {
+            ERR("Failed to read superblock %u: %08x\n", i, Status);
+            ExFreePool(sb);
+            return Status;
+        }
+        
+        TRACE("got superblock %u!\n", i);
+
+        if (i == 0 || sb->generation > Vcb->superblock.generation)
+            RtlCopyMemory(&Vcb->superblock, sb, sizeof(superblock));
+        
+        i++;
+    }
+    
+    ExFreePool(sb);
+    
+    crc32 = calc_crc32c(0xffffffff, (UINT8*)&Vcb->superblock.uuid, (ULONG)sizeof(superblock) - sizeof(Vcb->superblock.checksum));
+    crc32 = ~crc32;
+    TRACE("crc32 was %08x, expected %08x\n", crc32, *((UINT32*)Vcb->superblock.checksum));
+    
+    if (crc32 != *((UINT32*)Vcb->superblock.checksum))
+        return STATUS_INTERNAL_ERROR; // FIXME - correct error?
+    
+    TRACE("label is %s\n", Vcb->superblock.label);
+//     utf8_to_utf16(Vcb->superblock.label, Vcb->label, MAX_LABEL_SIZE * sizeof(WCHAR));
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL dev_ioctl(PDEVICE_OBJECT DeviceObject, ULONG ControlCode, PVOID InputBuffer,
+                    ULONG InputBufferSize, PVOID OutputBuffer, ULONG OutputBufferSize, BOOLEAN Override)
+{
+    PIRP Irp;
+    KEVENT Event;
+    NTSTATUS Status;
+    PIO_STACK_LOCATION Stack;
+    IO_STATUS_BLOCK IoStatus;
+
+    KeInitializeEvent(&Event, NotificationEvent, FALSE);
+
+    Irp = IoBuildDeviceIoControlRequest(ControlCode,
+                                        DeviceObject,
+                                        InputBuffer,
+                                        InputBufferSize,
+                                        OutputBuffer,
+                                        OutputBufferSize,
+                                        FALSE,
+                                        &Event,
+                                        &IoStatus);
+
+    if (!Irp) return STATUS_INSUFFICIENT_RESOURCES;
+
+    if (Override) {
+        Stack = IoGetNextIrpStackLocation(Irp);
+        Stack->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
+    }
+
+    Status = IoCallDriver(DeviceObject, Irp);
+
+    if (Status == STATUS_PENDING) {
+        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
+        Status = IoStatus.Status;
+    }
+
+    return Status;
+}
+
+// static void STDCALL find_chunk_root(device_extension* Vcb) {
+//     UINT32 i;
+//     KEY* key;
+//     
+//     i = 0;
+//     while (i < Vcb->superblock.n) {
+//         key = &Vcb->superblock.sys_chunk_array[i];
+//         i += sizeof(KEY);
+//     }
+//     
+//     // FIXME
+// }
+
+// static void STDCALL insert_ltp(device_extension* Vcb, log_to_phys* ltp) {
+//     if (!Vcb->log_to_phys) {
+//         Vcb->log_to_phys = ltp;
+//         ltp->next = NULL;
+//         return;
+//     }
+//     
+//     // FIXME - these should be ordered
+//     ltp->next = Vcb->log_to_phys;
+//     Vcb->log_to_phys = ltp;
+// }
+
+static NTSTATUS STDCALL add_root(device_extension* Vcb, UINT64 id, UINT64 addr, traverse_ptr* tp) {
+    root* r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG);
+    if (!r) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    r->id = id;
+    r->treeholder.address = addr;
+    r->treeholder.tree = NULL;
+    init_tree_holder(&r->treeholder);
+    r->prev = NULL;
+    r->next = Vcb->roots;
+
+    r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);
+    if (!r->nonpaged) {
+        ERR("out of memory\n");
+        ExFreePool(r);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    ExInitializeResourceLite(&r->nonpaged->load_tree_lock);
+    
+    r->lastinode = 0;
+    
+    if (tp) {
+        RtlCopyMemory(&r->root_item, tp->item->data, min(sizeof(ROOT_ITEM), tp->item->size));
+        if (tp->item->size < sizeof(ROOT_ITEM))
+            RtlZeroMemory(((UINT8*)&r->root_item) + tp->item->size, sizeof(ROOT_ITEM) - tp->item->size);
+    }
+    
+    if (Vcb->roots)
+        Vcb->roots->prev = r;
+    
+    Vcb->roots = r;
+    
+    switch (r->id) {
+        case BTRFS_ROOT_ROOT:
+            Vcb->root_root = r;
+            break;
+            
+        case BTRFS_ROOT_EXTENT:
+            Vcb->extent_root = r;
+            break;
+            
+        case BTRFS_ROOT_CHUNK:
+            Vcb->chunk_root = r;
+            break;
+            
+        case BTRFS_ROOT_DEVTREE:
+            Vcb->dev_root = r;
+            break;
+            
+        case BTRFS_ROOT_CHECKSUM:
+            Vcb->checksum_root = r;
+            break;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL look_for_roots(device_extension* Vcb) {
+    traverse_ptr tp, next_tp;
+    KEY searchkey;
+    BOOL b;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = 0;
+    searchkey.obj_type = 0;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_tree returned %08x\n", Status);
+        return Status;
+    }
+    
+    do {
+        TRACE("(%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_ROOT_ITEM) {
+            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
+            
+            if (tp.item->size < offsetof(ROOT_ITEM, byte_limit)) {
+                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(ROOT_ITEM, byte_limit));
+            } else {
+                TRACE("root %llx - address %llx\n", tp.item->key.obj_id, ri->block_number);
+                
+                Status = add_root(Vcb, tp.item->key.obj_id, ri->block_number, &tp);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("add_root returned %08x\n", Status);
+                    return Status;
+                }
+            }
+        }
+    
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+        
+        if (b) {
+            free_traverse_ptr(&tp);
+            tp = next_tp;
+        }
+    } while (b);
+    
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS add_disk_hole(LIST_ENTRY* disk_holes, UINT64 address, UINT64 size) {
+    disk_hole* dh = ExAllocatePoolWithTag(PagedPool, sizeof(disk_hole), ALLOC_TAG);
+    
+    if (!dh) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    dh->address = address;
+    dh->size = size;
+    dh->provisional = FALSE;
+    
+    InsertTailList(disk_holes, &dh->listentry);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS find_disk_holes(device_extension* Vcb, device* dev) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    UINT64 lastaddr;
+    NTSTATUS Status;
+    
+    InitializeListHead(&dev->disk_holes);
+    
+    searchkey.obj_id = dev->devitem.dev_id;
+    searchkey.obj_type = TYPE_DEV_EXTENT;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_tree returned %08x\n", Status);
+        return Status;
+    }
+    
+    lastaddr = 0;
+    
+    do {
+        if (tp.item->key.obj_id == dev->devitem.dev_id && tp.item->key.obj_type == TYPE_DEV_EXTENT) {
+            if (tp.item->size >= sizeof(DEV_EXTENT)) {
+                DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data;
+                
+                if (tp.item->key.offset > lastaddr) {
+                    Status = add_disk_hole(&dev->disk_holes, lastaddr, tp.item->key.offset - lastaddr);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("add_disk_hole returned %08x\n", Status);
+                        return Status;
+                    }
+                }
+
+                lastaddr = tp.item->key.offset + de->length;
+            } else {
+                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(DEV_EXTENT));
+            }
+        }
+    
+        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)) {
+            ERR("add_disk_hole returned %08x\n", Status);
+            return Status;
+        }
+    }
+    
+    // FIXME - free disk_holes when unmounting
+    
+    return STATUS_SUCCESS;
+}
+
+device* find_device_from_uuid(device_extension* Vcb, BTRFS_UUID* uuid) {
+    UINT64 i;
+    
+    for (i = 0; i < Vcb->superblock.num_devices; i++) {
+        TRACE("device %llx, uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", i,
+            Vcb->devices[i].devitem.device_uuid.uuid[0], Vcb->devices[i].devitem.device_uuid.uuid[1], Vcb->devices[i].devitem.device_uuid.uuid[2], Vcb->devices[i].devitem.device_uuid.uuid[3], Vcb->devices[i].devitem.device_uuid.uuid[4], Vcb->devices[i].devitem.device_uuid.uuid[5], Vcb->devices[i].devitem.device_uuid.uuid[6], Vcb->devices[i].devitem.device_uuid.uuid[7],
+            Vcb->devices[i].devitem.device_uuid.uuid[8], Vcb->devices[i].devitem.device_uuid.uuid[9], Vcb->devices[i].devitem.device_uuid.uuid[10], Vcb->devices[i].devitem.device_uuid.uuid[11], Vcb->devices[i].devitem.device_uuid.uuid[12], Vcb->devices[i].devitem.device_uuid.uuid[13], Vcb->devices[i].devitem.device_uuid.uuid[14], Vcb->devices[i].devitem.device_uuid.uuid[15]);
+        
+        if (Vcb->devices[i].devobj && RtlCompareMemory(&Vcb->devices[i].devitem.device_uuid, uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
+            TRACE("returning device %llx\n", i);
+            return &Vcb->devices[i];
+        }
+    }
+    
+    WARN("could not find device with uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+         uuid->uuid[0], uuid->uuid[1], uuid->uuid[2], uuid->uuid[3], uuid->uuid[4], uuid->uuid[5], uuid->uuid[6], uuid->uuid[7],
+         uuid->uuid[8], uuid->uuid[9], uuid->uuid[10], uuid->uuid[11], uuid->uuid[12], uuid->uuid[13], uuid->uuid[14], uuid->uuid[15]);
+    
+    return NULL;
+}
+
+static NTSTATUS STDCALL load_chunk_root(device_extension* Vcb) {
+    traverse_ptr tp, next_tp;
+    KEY searchkey;
+    BOOL b;
+    chunk* c;
+    UINT64 i;
+    NTSTATUS Status;
+
+    searchkey.obj_id = 0;
+    searchkey.obj_type = 0;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    do {
+        TRACE("(%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        
+        if (tp.item->key.obj_id == 1 && tp.item->key.obj_type == TYPE_DEV_ITEM && tp.item->key.offset == 1) {
+            // FIXME - this is a hack; make this work with multiple devices!
+            if (tp.item->size > 0)
+                RtlCopyMemory(&Vcb->devices[0].devitem, tp.item->data, min(tp.item->size, sizeof(DEV_ITEM)));
+        } else if (tp.item->key.obj_type == TYPE_CHUNK_ITEM) {
+            if (tp.item->size < sizeof(CHUNK_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(CHUNK_ITEM));
+            } else {            
+                c = ExAllocatePoolWithTag(PagedPool, sizeof(chunk), ALLOC_TAG);
+                
+                if (!c) {
+                    ERR("out of memory\n");
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                c->size = tp.item->size;
+                c->offset = tp.item->key.offset;
+                c->used = c->oldused = 0;
+                c->space_changed = FALSE;
+                
+                c->chunk_item = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                
+                if (!c->chunk_item) {
+                    ERR("out of memory\n");
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+            
+                RtlCopyMemory(c->chunk_item, tp.item->data, tp.item->size);
+                
+                if (c->chunk_item->num_stripes > 0) {
+                    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
+                    
+                    c->devices = ExAllocatePoolWithTag(PagedPool, sizeof(device*) * c->chunk_item->num_stripes, ALLOC_TAG);
+                    
+                    if (!c->devices) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    for (i = 0; i < c->chunk_item->num_stripes; i++) {
+                        c->devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid);
+                        TRACE("device %llu = %p\n", i, c->devices[i]);
+                    }
+                } else
+                    c->devices = NULL;
+                
+                InitializeListHead(&c->space);
+
+                InsertTailList(&Vcb->chunks, &c->list_entry);
+            }
+        }
+    
+        b = find_next_item(Vcb, &tp, &next_tp, FALSE);
+        
+        if (b) {
+            free_traverse_ptr(&tp);
+            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;
+    
+    // FIXME - this will need modifying for RAID
+    
+    while (superblock_addrs[i] != 0) {
+        CHUNK_ITEM* ci = c->chunk_item;
+        CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];
+        
+        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)) {
+                TRACE("cut out superblock in chunk %llx\n", c->offset);
+                
+                addr = (superblock_addrs[i] - cis[j].offset) + c->offset;
+                TRACE("addr %llx\n", addr);
+                
+                add_to_space_list(c, addr, sizeof(superblock), SPACE_TYPE_USED);
+            }
+        }
+        
+        i++;
+    }
+}
+
+static NTSTATUS STDCALL find_chunk_usage(device_extension* Vcb) {
+    LIST_ENTRY* le = Vcb->chunks.Flink;
+    chunk* c;
+    KEY searchkey;
+    traverse_ptr tp;
+    BLOCK_GROUP_ITEM* bgi;
+    NTSTATUS Status;
+    
+// c00000,c0,800000
+// block_group_item size=7f0000 chunktreeid=100 flags=1
+    
+    searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;
+    
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        searchkey.obj_id = c->offset;
+        searchkey.offset = c->chunk_item->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(&searchkey, &tp.item->key)) {
+            if (tp.item->size >= sizeof(BLOCK_GROUP_ITEM)) {
+                bgi = (BLOCK_GROUP_ITEM*)tp.item->data;
+                
+                c->used = c->oldused = bgi->used;
+                
+                TRACE("chunk %llx has %llx bytes used\n", c->offset, c->used);
+            } else {
+                ERR("(%llx;%llx,%x,%llx) is %u bytes, expected %u\n",
+                    Vcb->extent_root->id, 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);
+//         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];
+// 
+//             return (addr - c->offset) + cis->offset;
+//         }
+        
+        // FIXME - make sure we free occasionally after doing one of these, or we
+        // might use up a lot of memory with a big disk.
+        
+        Status = load_free_space_cache(Vcb, c);
+        if (!NT_SUCCESS(Status)) {
+            ERR("load_free_space_cache returned %08x\n", Status);
+            return Status;
+        }        
+        
+        protect_superblocks(Vcb, c);
+
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+// static void STDCALL root_test(device_extension* Vcb) {
+//     root* r;
+//     KEY searchkey;
+//     traverse_ptr tp, next_tp;
+//     BOOL b;
+//     
+//     r = Vcb->roots;
+//     while (r) {
+//         if (r->id == 0x102)
+//             break;
+//         r = r->next;
+//     }
+//     
+//     if (!r) {
+//         ERR("Could not find root tree.\n");
+//         return;
+//     }
+//     
+//     searchkey.obj_id = 0x1b6;
+//     searchkey.obj_type = 0xb;
+//     searchkey.offset = 0;
+//     
+//     if (!find_item(Vcb, r, &tp, &searchkey, NULL, FALSE)) {
+//         ERR("Could not find first item.\n");
+//         return;
+//     }
+//     
+//     b = TRUE;
+//     do {
+//         TRACE("%x,%x,%x\n", (UINT32)tp.item->key.obj_id, tp.item->key.obj_type, (UINT32)tp.item->key.offset);
+//         
+//         b = find_prev_item(Vcb, &tp, &next_tp, NULL, FALSE);
+//         
+//         if (b) {
+//             free_traverse_ptr(&tp);
+//             tp = next_tp;
+//         }
+//     } while (b);
+//     
+//     free_traverse_ptr(&tp);
+// }
+
+static NTSTATUS load_sys_chunks(device_extension* Vcb) {
+    KEY key;
+    ULONG n = Vcb->superblock.n;
+    
+    while (n > 0) {
+        if (n > sizeof(KEY)) {
+            RtlCopyMemory(&key, &Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n], sizeof(KEY));
+            n -= sizeof(KEY);
+        } else
+            return STATUS_SUCCESS;
+        
+        TRACE("bootstrap: %llx,%x,%llx\n", key.obj_id, key.obj_type, key.offset);
+        
+        if (key.obj_type == TYPE_CHUNK_ITEM) {
+            CHUNK_ITEM* ci;
+            ULONG cisize;
+            sys_chunk* sc;
+            
+            if (n < sizeof(CHUNK_ITEM))
+                return STATUS_SUCCESS;
+            
+            ci = (CHUNK_ITEM*)&Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n];
+            cisize = sizeof(CHUNK_ITEM) + (ci->num_stripes * sizeof(CHUNK_ITEM_STRIPE));
+            
+            if (n < cisize)
+                return STATUS_SUCCESS;
+            
+            sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG);
+            
+            if (!sc) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            sc->key = key;
+            sc->size = cisize;
+            sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG);
+            
+            if (!sc->data) {
+                ERR("out of memory\n");
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            RtlCopyMemory(sc->data, ci, sc->size);
+            InsertTailList(&Vcb->sys_chunks, &sc->list_entry);
+            
+            n -= cisize;
+        } else {
+            ERR("unexpected item %llx,%x,%llx in bootstrap\n", key.obj_id, key.obj_type, key.offset);
+            return STATUS_INTERNAL_ERROR;
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static root* find_default_subvol(device_extension* Vcb) {
+    root* subvol;
+    UINT64 inode;
+    UINT8 type;
+    UNICODE_STRING filename;
+    
+    static WCHAR fn[] = L"default";
+    static UINT32 crc32 = 0x8dbfc2d2;
+    
+    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL) {
+        filename.Buffer = fn;
+        filename.Length = filename.MaximumLength = (USHORT)wcslen(fn) * sizeof(WCHAR);
+        
+        if (!find_file_in_dir_with_crc32(Vcb, &filename, crc32, Vcb->root_root, Vcb->superblock.root_dir_objectid, &subvol, &inode, &type, NULL))
+            WARN("couldn't find default subvol DIR_ITEM, using default tree\n");
+        else
+            return subvol;
+    }
+    
+    subvol = Vcb->roots;
+    while (subvol && subvol->id != BTRFS_ROOT_FSTREE)
+        subvol = subvol->next;
+    
+    return subvol;
+}
+
+static NTSTATUS STDCALL mount_vol(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION Stack;
+    PDEVICE_OBJECT NewDeviceObject = NULL;
+    PDEVICE_OBJECT DeviceToMount;
+    NTSTATUS Status;
+    device_extension* Vcb = NULL;
+    PARTITION_INFORMATION_EX piex;
+    UINT64 i;
+    LIST_ENTRY* le;
+    KEY searchkey;
+    traverse_ptr tp;
+    
+    TRACE("mount_vol called\n");
+    
+    if (DeviceObject != devobj)
+    {
+        Status = STATUS_INVALID_DEVICE_REQUEST;
+        goto exit;
+    }
+
+    Stack = IoGetCurrentIrpStackLocation(Irp);
+    DeviceToMount = Stack->Parameters.MountVolume.DeviceObject;
+
+//     Status = NtfsHasFileSystem(DeviceToMount);
+//     if (!NT_SUCCESS(Status))
+//     {
+//         goto ByeBye;
+//     }
+
+    Status = dev_ioctl(DeviceToMount, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0,
+                       &piex, sizeof(piex), TRUE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error reading partition information: %08x\n", Status);
+        goto exit;
+    }
+
+    Status = IoCreateDevice(drvobj,
+                            sizeof(device_extension),
+                            NULL,
+                            FILE_DEVICE_DISK_FILE_SYSTEM,
+                            0,
+                            FALSE,
+                            &NewDeviceObject);
+    if (!NT_SUCCESS(Status))
+        goto exit;
+    
+//     TRACE("DEV_ITEM = %x, superblock = %x\n", sizeof(DEV_ITEM), sizeof(superblock));
+
+    NewDeviceObject->Flags |= DO_DIRECT_IO;
+    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;
+    Vcb->write_trees = 0;
+
+    ExInitializeResourceLite(&Vcb->fcb_lock);
+    ExInitializeResourceLite(&Vcb->DirResource);
+
+    ExAcquireResourceExclusiveLite(&global_loading_lock, TRUE);
+    InsertTailList(&VcbList, &Vcb->list_entry);
+    ExReleaseResourceLite(&global_loading_lock);
+
+    ExInitializeResourceLite(&Vcb->load_lock);
+    ExAcquireResourceExclusiveLite(&Vcb->load_lock, TRUE);
+
+//     Vcb->Identifier.Type = NTFS_TYPE_VCB;
+//     Vcb->Identifier.Size = sizeof(NTFS_TYPE_VCB);
+// 
+//     Status = NtfsGetVolumeData(DeviceToMount,
+//                                Vcb);
+//     if (!NT_SUCCESS(Status))
+//         goto ByeBye;
+    
+//     Vcb->device = DeviceToMount;
+    DeviceToMount->Flags |= DO_DIRECT_IO;
+    
+//     Status = dev_ioctl(DeviceToMount, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,
+//                        &Vcb->geometry, sizeof(DISK_GEOMETRY), TRUE);
+//     if (!NT_SUCCESS(Status)) {
+//         ERR("error reading disk geometry: %08x\n", Status);
+//         goto exit;
+//     } else {
+//         TRACE("media type = %u, cylinders = %u, tracks per cylinder = %u, sectors per track = %u, bytes per sector = %u\n",
+//                       Vcb->geometry.MediaType, Vcb->geometry.Cylinders, Vcb->geometry.TracksPerCylinder,
+//                       Vcb->geometry.SectorsPerTrack, Vcb->geometry.BytesPerSector);
+//     }
+    
+    Vcb->length = piex.PartitionLength.QuadPart;
+    TRACE("partition length = %u\n", piex.PartitionLength);
+
+    Status = read_superblock(Vcb, DeviceToMount);
+    if (!NT_SUCCESS(Status)) {
+        Status = STATUS_UNRECOGNIZED_VOLUME;
+        goto exit;
+    }
+
+    if (Vcb->superblock.magic != BTRFS_MAGIC) {
+        ERR("not a BTRFS volume\n");
+        Status = STATUS_UNRECOGNIZED_VOLUME;
+        goto exit;
+    } else {
+        TRACE("btrfs magic found\n");
+    }
+
+    if (Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED) {
+        WARN("cannot mount because of unsupported incompat flags (%llx)\n", Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED);
+        Status = STATUS_UNRECOGNIZED_VOLUME;
+        goto exit;
+    }
+    
+    le = volumes.Flink;
+    while (le != &volumes) {
+        volume* v = CONTAINING_RECORD(le, volume, list_entry);
+        
+        if (RtlCompareMemory(&Vcb->superblock.uuid, &v->fsuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID) && v->devnum < Vcb->superblock.dev_item.dev_id) {
+            // skipping over device in RAID which isn't the first one
+            // FIXME - hide this in My Computer
+            Status = STATUS_UNRECOGNIZED_VOLUME;
+            goto exit;
+        }
+        
+        le = le->Flink;
+    }
+    
+    // FIXME - remove this when we can
+    if (Vcb->superblock.num_devices > 1) {
+        WARN("not mounting - multiple devices not yet implemented\n");
+        Status = STATUS_UNRECOGNIZED_VOLUME;
+        goto exit;
+    }
+    
+    Vcb->readonly = FALSE;
+    if (Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED) {
+        WARN("mounting read-only because of unsupported flags (%llx)\n", Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED);
+        Vcb->readonly = TRUE;
+    }
+    
+    Vcb->superblock.generation++;
+    Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF;
+    
+    Vcb->devices = ExAllocatePoolWithTag(PagedPool, sizeof(device) * Vcb->superblock.num_devices, ALLOC_TAG);
+    if (!Vcb->devices) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto exit;
+    }
+    
+    Vcb->devices[0].devobj = DeviceToMount;
+    RtlCopyMemory(&Vcb->devices[0].devitem, &Vcb->superblock.dev_item, sizeof(DEV_ITEM));
+    
+    if (Vcb->superblock.num_devices > 1)
+        RtlZeroMemory(&Vcb->devices[1], sizeof(DEV_ITEM) * (Vcb->superblock.num_devices - 1));
+    
+    // FIXME - find other devices, if there are any
+    
+    TRACE("DeviceToMount = %p\n", DeviceToMount);
+    TRACE("Stack->Parameters.MountVolume.Vpb = %p\n", Stack->Parameters.MountVolume.Vpb);
+
+    NewDeviceObject->StackSize = DeviceToMount->StackSize + 1;
+    NewDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
+    
+//     find_chunk_root(Vcb);
+//     Vcb->chunk_root_phys_addr = Vcb->superblock.chunk_tree_addr; // FIXME - map from logical to physical (bootstrapped)
+    
+//     Vcb->root_tree_phys_addr = logical_to_physical(Vcb, Vcb->superblock.root_tree_addr);
+    
+    Vcb->roots = NULL;
+    Vcb->log_to_phys_loaded = FALSE;
+    
+    Vcb->max_inline = Vcb->superblock.node_size / 2;
+    
+//     Vcb->write_trees = NULL;
+
+    add_root(Vcb, BTRFS_ROOT_CHUNK, Vcb->superblock.chunk_tree_addr, NULL);
+    
+    if (!Vcb->chunk_root) {
+        ERR("Could not load chunk root.\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto exit;
+    }
+    
+    InitializeListHead(&Vcb->sys_chunks);
+    Status = load_sys_chunks(Vcb);
+    if (!NT_SUCCESS(Status)) {
+        ERR("load_sys_chunks returned %08x\n", Status);
+        goto exit;
+    }
+    
+    InitializeListHead(&Vcb->chunks);
+    InitializeListHead(&Vcb->trees);
+    
+    InitializeListHead(&Vcb->DirNotifyList);
+
+    FsRtlNotifyInitializeSync(&Vcb->NotifySync);
+    
+    Status = load_chunk_root(Vcb);
+    if (!NT_SUCCESS(Status)) {
+        ERR("load_chunk_root returned %08x\n", Status);
+        goto exit;
+    }
+    
+    add_root(Vcb, BTRFS_ROOT_ROOT, Vcb->superblock.root_tree_addr, NULL);
+    
+    if (!Vcb->root_root) {
+        ERR("Could not load root of roots.\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto exit;
+    }
+    
+    Status = look_for_roots(Vcb);
+    if (!NT_SUCCESS(Status)) {
+        ERR("look_for_roots returned %08x\n", Status);
+        goto exit;
+    }
+    
+    Status = find_chunk_usage(Vcb);
+    if (!NT_SUCCESS(Status)) {
+        ERR("find_chunk_usage returned %08x\n", Status);
+        goto exit;
+    }
+    
+    Vcb->volume_fcb = create_fcb();
+    if (!Vcb->volume_fcb) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto exit;
+    }
+    
+    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) {
+        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);
+    
+#ifdef DEBUG_FCB_REFCOUNTS
+    WARN("volume FCB = %p\n", Vcb->volume_fcb);
+    WARN("root FCB = %p\n", Vcb->root_fcb);
+#endif
+    
+    Vcb->root_fcb->subvol = find_default_subvol(Vcb);
+
+    if (!Vcb->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_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, Vcb->root_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("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));
+    
+    free_traverse_ptr(&tp);
+    
+    fcb_get_sd(Vcb->root_fcb);
+    
+    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);
+      
+    for (i = 0; i < Vcb->superblock.num_devices; i++) {
+        Status = find_disk_holes(Vcb, &Vcb->devices[i]);
+        if (!NT_SUCCESS(Status)) {
+            ERR("find_disk_holes returned %08x\n", Status);
+            goto exit;
+        }
+    }
+    
+//     root_test(Vcb);
+    
+//     Vcb->StreamFileObject = IoCreateStreamFileObject(NULL,
+//                                                      Vcb->StorageDevice);
+// 
+//     InitializeListHead(&Vcb->FcbListHead);
+// 
+//     Fcb = NtfsCreateFCB(NULL, Vcb);
+//     if (Fcb == NULL)
+//     {
+//         Status = STATUS_INSUFFICIENT_RESOURCES;
+//         goto ByeBye;
+//     }
+// 
+//     Ccb = ExAllocatePoolWithTag(NonPagedPool,
+//                                 sizeof(NTFS_CCB),
+//                                 TAG_CCB);
+//     if (Ccb == NULL)
+//     {
+//         Status =  STATUS_INSUFFICIENT_RESOURCES;
+//         goto ByeBye;
+//     }
+// 
+//     RtlZeroMemory(Ccb, sizeof(NTFS_CCB));
+// 
+//     Ccb->Identifier.Type = NTFS_TYPE_CCB;
+//     Ccb->Identifier.Size = sizeof(NTFS_TYPE_CCB);
+// 
+//     Vcb->StreamFileObject->FsContext = Fcb;
+//     Vcb->StreamFileObject->FsContext2 = Ccb;
+//     Vcb->StreamFileObject->SectionObjectPointer = &Fcb->SectionObjectPointers;
+//     Vcb->StreamFileObject->PrivateCacheMap = NULL;
+//     Vcb->StreamFileObject->Vpb = Vcb->Vpb;
+//     Ccb->PtrFileObject = Vcb->StreamFileObject;
+//     Fcb->FileObject = Vcb->StreamFileObject;
+//     Fcb->Vcb = (PDEVICE_EXTENSION)Vcb->StorageDevice;
+// 
+//     Fcb->Flags = FCB_IS_VOLUME_STREAM;
+// 
+//     Fcb->RFCB.FileSize.QuadPart = Vcb->NtfsInfo.SectorCount * Vcb->NtfsInfo.BytesPerSector;
+//     Fcb->RFCB.ValidDataLength.QuadPart = Vcb->NtfsInfo.SectorCount * Vcb->NtfsInfo.BytesPerSector;
+//     Fcb->RFCB.AllocationSize.QuadPart = Vcb->NtfsInfo.SectorCount * Vcb->NtfsInfo.BytesPerSector; /* Correct? */
+// 
+//     CcInitializeCacheMap(Vcb->StreamFileObject,
+//                          (PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
+//                          FALSE,
+//                          &(NtfsGlobalData->CacheMgrCallbacks),
+//                          Fcb);
+// 
+//     ExInitializeResourceLite(&Vcb->LogToPhysLock);
+
+    KeInitializeSpinLock(&Vcb->FcbListLock);
+// 
+//     /* Get serial number */
+//     NewDeviceObject->Vpb->SerialNumber = Vcb->NtfsInfo.SerialNumber;
+// 
+//     /* Get volume label */
+//     NewDeviceObject->Vpb->VolumeLabelLength = Vcb->NtfsInfo.VolumeLabelLength;
+//     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;
+    Stack->Parameters.MountVolume.Vpb->RealDevice = DeviceToMount;
+    Stack->Parameters.MountVolume.Vpb->Flags |= VPB_MOUNTED;
+    NewDeviceObject->Vpb->VolumeLabelLength = 4; // FIXME
+    NewDeviceObject->Vpb->VolumeLabel[0] = '?';
+    NewDeviceObject->Vpb->VolumeLabel[1] = 0;
+    NewDeviceObject->Vpb->ReferenceCount++; // FIXME - should we deref this at any point?
+    
+    Status = STATUS_SUCCESS;
+
+exit:
+//     if (!NT_SUCCESS(Status))
+//     {
+//         /* Cleanup */
+//         if (Vcb && Vcb->StreamFileObject)
+//             ObDereferenceObject(Vcb->StreamFileObject);
+// 
+//         if (Fcb)
+//             ExFreePool(Fcb);
+// 
+//         if (Ccb)
+//             ExFreePool(Ccb);
+// 
+//         if (NewDeviceObject)
+//             IoDeleteDevice(NewDeviceObject);
+//     }
+
+    if (Vcb) {
+        ExReleaseResourceLite(&Vcb->load_lock);
+    }
+
+    if (!NT_SUCCESS(Status)) {
+        if (Vcb) {
+            if (Vcb->root_fcb)
+                free_fcb(Vcb->root_fcb);
+
+            if (Vcb->volume_fcb)
+                free_fcb(Vcb->volume_fcb);
+
+            ExDeleteResourceLite(&Vcb->tree_lock);
+            ExDeleteResourceLite(&Vcb->load_lock);
+            ExDeleteResourceLite(&Vcb->fcb_lock);
+            ExDeleteResourceLite(&Vcb->DirResource);
+
+            if (Vcb->devices)
+                ExFreePoolWithTag(Vcb->devices, ALLOC_TAG);
+
+            RemoveEntryList(&Vcb->list_entry);
+        }
+
+        if (NewDeviceObject)
+            IoDeleteDevice(NewDeviceObject);
+    }
+
+    TRACE("mount_vol done (status: %lx)\n", Status);
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_file_system_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS status;
+    BOOL top_level;
+
+    TRACE("file system control\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    status = STATUS_NOT_IMPLEMENTED;
+
+    IrpSp = IoGetCurrentIrpStackLocation( Irp );
+    
+    Irp->IoStatus.Information = 0;
+    
+    switch (IrpSp->MinorFunction) {
+        case IRP_MN_MOUNT_VOLUME:
+            TRACE("IRP_MN_MOUNT_VOLUME\n");
+            
+//             Irp->IoStatus.Status = STATUS_SUCCESS;
+            status = mount_vol(DeviceObject, Irp);
+//             IrpSp->Parameters.MountVolume.DeviceObject = 0x0badc0de;
+//             IrpSp->Parameters.MountVolume.Vpb = 0xdeadbeef;
+            
+//             IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+            
+//             return Irp->IoStatus.Status;
+            break;
+            
+        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");
+            break;
+            
+        case IRP_MN_USER_FS_REQUEST:
+            TRACE("IRP_MN_USER_FS_REQUEST\n");
+            
+            status = fsctl_request(DeviceObject, Irp, IrpSp->Parameters.FileSystemControl.FsControlCode, TRUE);
+            break;
+            
+        case IRP_MN_VERIFY_VOLUME:
+            TRACE("IRP_MN_VERIFY_VOLUME\n");
+            break;
+           
+        default:
+            WARN("unknown minor %u\n", IrpSp->MinorFunction);
+            break;
+            
+    }
+
+    Irp->IoStatus.Status = status;
+
+    IoCompleteRequest(Irp, IO_NO_INCREMENT);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return status;
+}
+
+static NTSTATUS STDCALL drv_lock_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    fcb* fcb = IrpSp->FileObject->FsContext;
+    BOOL top_level;
+
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    TRACE("lock control\n");
+    
+    Status = FsRtlProcessFileLock(&fcb->lock, Irp, NULL);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+    
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    fcb* fcb;
+    BOOL top_level;
+
+    FIXME("STUB: device control\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Irp->IoStatus.Information = 0;
+    
+    WARN("control code = %x\n", IrpSp->Parameters.DeviceIoControl.IoControlCode);
+    
+    if (!FileObject) {
+        ERR("FileObject was NULL\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+        
+    }
+    
+    fcb = FileObject->FsContext;
+    
+    if (!fcb) {
+        ERR("FCB was NULL\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    if (fcb == Vcb->volume_fcb) {
+        FIXME("FIXME - pass through\n");
+        Status = STATUS_NOT_IMPLEMENTED;
+    } else {
+        TRACE("filename = %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+        
+        switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {
+            case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME:
+                TRACE("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME\n");
+                Status = STATUS_INVALID_PARAMETER;
+                break;
+
+            default:
+                WARN("unknown control code %x (DeviceType = %x, Access = %x, Function = %x, Method = %x)\n",
+                                        IrpSp->Parameters.DeviceIoControl.IoControlCode, (IrpSp->Parameters.DeviceIoControl.IoControlCode & 0xff0000) >> 16,
+                                        (IrpSp->Parameters.DeviceIoControl.IoControlCode & 0xc000) >> 14, (IrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3ffc) >> 2,
+                                        IrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3);
+                Status = STATUS_INVALID_PARAMETER;
+                break;
+        }
+    }
+    
+end:
+    Irp->IoStatus.Status = Status;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+static NTSTATUS STDCALL drv_shutdown(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    BOOL top_level;
+
+    ERR("shutdown\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_SUCCESS;
+
+    while (!IsListEmpty(&VcbList)) {
+        LIST_ENTRY* le = RemoveHeadList(&VcbList);
+        device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry);
+        
+        TRACE("shutting down Vcb %p\n", Vcb);
+        
+        uninit(Vcb);
+    }
+    
+    Irp->IoStatus.Status = Status;
+    Irp->IoStatus.Information = 0;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    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");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_NOT_IMPLEMENTED;
+    
+    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;
+    }
+
+//     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);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+#ifdef _DEBUG
+static void STDCALL init_serial() {
+    NTSTATUS Status;
+    
+    Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo);
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
+    }
+}
+#endif
+
+#ifndef __REACTOS__
+static void STDCALL check_cpu() {
+    unsigned int cpuInfo[4];
+#ifndef _MSC_VER
+    __get_cpuid(1, &cpuInfo[0], &cpuInfo[1], &cpuInfo[2], &cpuInfo[3]);
+    have_sse42 = cpuInfo[2] & bit_SSE4_2;
+#else
+   __cpuid(cpuInfo, 1);
+   have_sse42 = cpuInfo[2] & (1 << 20);
+#endif
+
+    if (have_sse42)
+        TRACE("SSE4.2 is supported\n");
+    else
+        TRACE("SSE4.2 not supported\n");
+}
+#endif
+
+static void STDCALL read_registry(PUNICODE_STRING regpath) {
+    HANDLE h;
+    UNICODE_STRING us;
+    OBJECT_ATTRIBUTES oa;
+    ULONG dispos;
+    NTSTATUS Status;
+    WCHAR* path;
+    ULONG kvfilen, retlen, i;
+    KEY_VALUE_FULL_INFORMATION* kvfi;
+    
+    const WCHAR mappings[] = L"\\Mappings";
+#ifndef __REACTOS__
+    static WCHAR def_log_file[] = L"\\??\\C:\\btrfs.log";
+#endif
+    
+    path = ExAllocatePoolWithTag(PagedPool, regpath->Length + (wcslen(mappings) * sizeof(WCHAR)), ALLOC_TAG);
+    if (!path) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    RtlCopyMemory(path, regpath->Buffer, regpath->Length);
+    RtlCopyMemory((UINT8*)path + regpath->Length, mappings, wcslen(mappings) * sizeof(WCHAR));
+    
+    us.Buffer = path;
+    us.Length = us.MaximumLength = regpath->Length + ((USHORT)wcslen(mappings) * sizeof(WCHAR));
+    
+    InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
+    
+    // FIXME - keep open and do notify for changes
+    Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("ZwCreateKey returned %08x\n", Status);
+        ExFreePool(path);
+        return;
+    }
+
+    if (dispos == REG_OPENED_EXISTING_KEY) {
+        kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256;
+        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
+        
+        if (!kvfi) {
+            ERR("out of memory\n");
+            ExFreePool(path);
+            ZwClose(h);
+            return;
+        }
+        
+        i = 0;
+        do {
+            Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen);
+            
+            if (NT_SUCCESS(Status) && kvfi->DataLength > 0) {
+                UINT32 val = 0;
+                
+                RtlCopyMemory(&val, (UINT8*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(UINT32)));
+                
+                TRACE("entry %u = %.*S = %u\n", i, kvfi->NameLength / sizeof(WCHAR), kvfi->Name, val);
+                
+                add_user_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val);
+            }
+            
+            i = i + 1;
+        } while (Status != STATUS_NO_MORE_ENTRIES);
+    }
+    
+    ZwClose(h);
+
+    ExFreePool(path);
+    
+#ifdef _DEBUG
+    InitializeObjectAttributes(&oa, regpath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
+    
+    Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("ZwCreateKey returned %08x\n", Status);
+        return;
+    }
+    
+    RtlInitUnicodeString(&us, L"DebugLogLevel");
+    
+    kvfi = NULL;
+    kvfilen = 0;
+    Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);
+    
+    if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) {
+        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
+        
+        if (!kvfi) {
+            ERR("out of memory\n");
+            ZwClose(h);
+            return;
+        }
+        
+        Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);
+        
+        if (NT_SUCCESS(Status)) {
+            if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(UINT32)) {
+                RtlCopyMemory(&debug_log_level, ((UINT8*)kvfi) + kvfi->DataOffset, sizeof(UINT32));
+            } else {
+                Status = ZwDeleteValueKey(h, &us);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("ZwDeleteValueKey returned %08x\n", Status);
+                }
+
+                Status = ZwSetValueKey(h, &us, 0, REG_DWORD, &debug_log_level, sizeof(debug_log_level));
+                if (!NT_SUCCESS(Status)) {
+                    ERR("ZwSetValueKey reutrned %08x\n", Status);
+                }
+            }
+        }
+        
+        ExFreePool(kvfi);
+    } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
+        Status = ZwSetValueKey(h, &us, 0, REG_DWORD, &debug_log_level, sizeof(debug_log_level));
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("ZwSetValueKey reutrned %08x\n", Status);
+        }
+    } else {
+        ERR("ZwQueryValueKey returned %08x\n", Status);
+    }
+    
+    RtlInitUnicodeString(&us, L"LogDevice");
+    
+    kvfi = NULL;
+    kvfilen = 0;
+    Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);
+    
+    if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) {
+        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
+        
+        if (!kvfi) {
+            ERR("out of memory\n");
+            ZwClose(h);
+            return;
+        }
+        
+        Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);
+        
+        if (NT_SUCCESS(Status)) {
+            if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) {
+                log_device.Length = log_device.MaximumLength = kvfi->DataLength;
+                log_device.Buffer = ExAllocatePoolWithTag(PagedPool, kvfi->DataLength, ALLOC_TAG);
+                
+                if (!log_device.Buffer) {
+                    ERR("out of memory\n");
+                    ExFreePool(kvfi);
+                    ZwClose(h);
+                    return;
+                }
+
+                RtlCopyMemory(log_device.Buffer, ((UINT8*)kvfi) + kvfi->DataOffset, kvfi->DataLength);
+                
+                if (log_device.Buffer[(log_device.Length / sizeof(WCHAR)) - 1] == 0)
+                    log_device.Length -= sizeof(WCHAR);
+            } else {
+                ERR("LogDevice was type %u, length %u\n", kvfi->Type, kvfi->DataLength);
+                
+                Status = ZwDeleteValueKey(h, &us);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("ZwDeleteValueKey returned %08x\n", Status);
+                }
+            }
+        }
+        
+        ExFreePool(kvfi);
+    } else if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {
+        ERR("ZwQueryValueKey returned %08x\n", Status);
+    }
+    
+    RtlInitUnicodeString(&us, L"LogFile");
+    
+    kvfi = NULL;
+    kvfilen = 0;
+    Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);
+    
+    if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) {
+        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);
+        
+        if (!kvfi) {
+            ERR("out of memory\n");
+            ZwClose(h);
+            return;
+        }
+        
+        Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);
+        
+        if (NT_SUCCESS(Status)) {
+            if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) {
+                log_file.Length = log_file.MaximumLength = kvfi->DataLength;
+                log_file.Buffer = ExAllocatePoolWithTag(PagedPool, kvfi->DataLength, ALLOC_TAG);
+                
+                if (!log_file.Buffer) {
+                    ERR("out of memory\n");
+                    ExFreePool(kvfi);
+                    ZwClose(h);
+                    return;
+                }
+
+                RtlCopyMemory(log_file.Buffer, ((UINT8*)kvfi) + kvfi->DataOffset, kvfi->DataLength);
+                
+                if (log_file.Buffer[(log_file.Length / sizeof(WCHAR)) - 1] == 0)
+                    log_file.Length -= sizeof(WCHAR);
+            } else {
+                ERR("LogFile was type %u, length %u\n", kvfi->Type, kvfi->DataLength);
+                
+                Status = ZwDeleteValueKey(h, &us);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("ZwDeleteValueKey returned %08x\n", Status);
+                }
+            }
+        }
+        
+        ExFreePool(kvfi);
+    } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
+        Status = ZwSetValueKey(h, &us, 0, REG_SZ, def_log_file, (wcslen(def_log_file) + 1) * sizeof(WCHAR));
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("ZwSetValueKey returned %08x\n", Status);
+        }
+    } else {
+        ERR("ZwQueryValueKey returned %08x\n", Status);
+    }
+    
+    if (log_file.Length == 0) {
+        log_file.Length = log_file.MaximumLength = wcslen(def_log_file) * sizeof(WCHAR);
+        log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG);
+        
+        if (!log_file.Buffer) {
+            ERR("out of memory\n");
+            ZwClose(h);
+            return;
+        }
+        
+        RtlCopyMemory(log_file.Buffer, def_log_file, log_file.Length);
+    }
+    
+    ZwClose(h);
+#endif
+}
+
+#ifdef _DEBUG
+static void init_logging() {
+    if (log_device.Length > 0)
+        init_serial();
+    else if (log_file.Length > 0) {
+        NTSTATUS Status;
+        OBJECT_ATTRIBUTES oa;
+        IO_STATUS_BLOCK iosb;
+        char* dateline;
+        LARGE_INTEGER time;
+        TIME_FIELDS tf;
+        
+        InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
+        
+        Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
+                              FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("ZwCreateFile returned %08x\n", Status);
+            return;
+        }
+        
+        if (iosb.Information == FILE_OPENED) { // already exists
+            FILE_STANDARD_INFORMATION fsi;
+            FILE_POSITION_INFORMATION fpi;
+            
+            static char delim[] = "\n---\n";
+            
+            // move to end of file
+            
+            Status = ZwQueryInformationFile(log_handle, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("ZwQueryInformationFile returned %08x\n", Status);
+                return;
+            }
+            
+            fpi.CurrentByteOffset = fsi.EndOfFile;
+            
+            Status = ZwSetInformationFile(log_handle, &iosb, &fpi, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("ZwSetInformationFile returned %08x\n", Status);
+                return;
+            }
+
+            Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, delim, strlen(delim), NULL, NULL);
+        
+            if (!NT_SUCCESS(Status)) {
+                ERR("ZwWriteFile returned %08x\n", Status);
+                return;
+            }
+        }
+        
+        dateline = ExAllocatePoolWithTag(PagedPool, 256, ALLOC_TAG);
+        
+        if (!dateline) {
+            ERR("out of memory\n");
+            return;
+        }
+        
+        KeQuerySystemTime(&time);
+        
+        RtlTimeToTimeFields(&time, &tf);
+        
+        sprintf(dateline, "Starting logging at %04u-%02u-%02u %02u:%02u:%02u\n", tf.Year, tf.Month, tf.Day, tf.Hour, tf.Minute, tf.Second);
+
+        Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, dateline, strlen(dateline), NULL, NULL);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("ZwWriteFile returned %08x\n", Status);
+            return;
+        }
+        
+        ExFreePool(dateline);
+    }
+}
+#endif
+
+NTSTATUS STDCALL DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
+    NTSTATUS Status;
+    PDEVICE_OBJECT DeviceObject;
+    UNICODE_STRING device_nameW;
+    UNICODE_STRING dosdevice_nameW;
+    
+    InitializeListHead(&uid_map_list);
+    
+    log_device.Buffer = NULL;
+    log_device.Length = log_device.MaximumLength = 0;
+    log_file.Buffer = NULL;
+    log_file.Length = log_file.MaximumLength = 0;
+    
+    read_registry(RegistryPath);
+    
+#ifdef _DEBUG
+    if (debug_log_level > 0)
+        init_logging();
+    
+    log_started = TRUE;
+#endif
+
+    TRACE("DriverEntry\n");
+   
+#ifndef __REACTOS__
+    check_cpu();
+#endif
+   
+//    TRACE("check CRC32C: %08x\n", calc_crc32c((UINT8*)"123456789", 9)); // should be e3069283
+   
+    drvobj = DriverObject;
+
+    DriverObject->DriverUnload = DriverUnload;
+
+    DriverObject->MajorFunction[IRP_MJ_CREATE]                   = (PDRIVER_DISPATCH)drv_create;
+    DriverObject->MajorFunction[IRP_MJ_CLOSE]                    = (PDRIVER_DISPATCH)drv_close;
+    DriverObject->MajorFunction[IRP_MJ_READ]                     = (PDRIVER_DISPATCH)drv_read;
+    DriverObject->MajorFunction[IRP_MJ_WRITE]                    = (PDRIVER_DISPATCH)drv_write;
+    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION]        = (PDRIVER_DISPATCH)drv_query_information;
+    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION]          = (PDRIVER_DISPATCH)drv_set_information;
+    DriverObject->MajorFunction[IRP_MJ_QUERY_EA]                 = (PDRIVER_DISPATCH)drv_query_ea;
+    DriverObject->MajorFunction[IRP_MJ_SET_EA]                   = (PDRIVER_DISPATCH)drv_set_ea;
+    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]            = (PDRIVER_DISPATCH)drv_flush_buffers;
+    DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = (PDRIVER_DISPATCH)drv_query_volume_information;
+    DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION]   = (PDRIVER_DISPATCH)drv_set_volume_information;
+    DriverObject->MajorFunction[IRP_MJ_CLEANUP]                  = (PDRIVER_DISPATCH)drv_cleanup;
+    DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]        = (PDRIVER_DISPATCH)drv_directory_control;
+    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL]      = (PDRIVER_DISPATCH)drv_file_system_control;
+    DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL]             = (PDRIVER_DISPATCH)drv_lock_control;
+    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]           = (PDRIVER_DISPATCH)drv_device_control;
+    DriverObject->MajorFunction[IRP_MJ_SHUTDOWN]                 = (PDRIVER_DISPATCH)drv_shutdown;
+    DriverObject->MajorFunction[IRP_MJ_PNP]                      = (PDRIVER_DISPATCH)drv_pnp;
+    DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY]           = (PDRIVER_DISPATCH)drv_query_security;
+    DriverObject->MajorFunction[IRP_MJ_SET_SECURITY]             = (PDRIVER_DISPATCH)drv_set_security;
+
+    init_fast_io_dispatch(&DriverObject->FastIoDispatch);
+
+    device_nameW.Buffer = device_name;
+    device_nameW.Length = device_nameW.MaximumLength = (USHORT)wcslen(device_name) * sizeof(WCHAR);
+    dosdevice_nameW.Buffer = dosdevice_name;
+    dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = (USHORT)wcslen(dosdevice_name) * sizeof(WCHAR);
+
+    Status = IoCreateDevice(DriverObject, 0, &device_nameW, FILE_DEVICE_DISK_FILE_SYSTEM, 0, FALSE, &DeviceObject);
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoCreateDevice returned %08x\n", Status);
+        return Status;
+    }
+    
+    devobj = DeviceObject;
+    
+    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
+
+    Status = IoCreateSymbolicLink(&dosdevice_nameW, &device_nameW);
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoCreateSymbolicLink returned %08x\n", Status);
+        return Status;
+    }
+    
+    Status = init_cache();
+    if (!NT_SUCCESS(Status)) {
+        ERR("init_cache returned %08x\n", Status);
+        return Status;
+    }
+
+    InitializeListHead(&volumes);
+    look_for_vols(&volumes);
+    
+    InitializeListHead(&VcbList);
+    ExInitializeResourceLite(&global_loading_lock);
+    
+    IoRegisterFileSystem(DeviceObject);
+
+    return STATUS_SUCCESS;
+}
diff --git a/reactos/drivers/filesystems/btrfs/btrfs.h b/reactos/drivers/filesystems/btrfs/btrfs.h
new file mode 100644 (file)
index 0000000..ad5ef71
--- /dev/null
@@ -0,0 +1,403 @@
+/* btrfs.h
+ * Generic btrfs header file. Thanks to whoever it was who wrote
+ * https://btrfs.wiki.kernel.org/index.php/On-disk_Format - you saved me a lot of time!
+ *
+ * I release this file, and this file only, into the public domain - do whatever
+ * you want with it. You don't have to, but I'd appreciate if you let me know if you
+ * use it anything cool - mark@harmstone.com. */
+
+#ifndef BTRFS_H_DEFINED
+#define BTRFS_H_DEFINED
+
+static const UINT64 superblock_addrs[] = { 0x10000, 0x4000000, 0x4000000000, 0x4000000000000, 0 };
+
+#define BTRFS_MAGIC         0x4d5f53665248425f
+#define MAX_LABEL_SIZE      0x100
+#define SUBVOL_ROOT_INODE   0x100
+
+#define TYPE_INODE_ITEM        0x01
+#define TYPE_INODE_REF         0x0C
+#define TYPE_INODE_EXTREF      0x0D
+#define TYPE_XATTR_ITEM        0x18
+#define TYPE_DIR_ITEM          0x54
+#define TYPE_DIR_INDEX         0x60
+#define TYPE_EXTENT_DATA       0x6C
+#define TYPE_EXTENT_CSUM       0x80
+#define TYPE_ROOT_ITEM         0x84
+#define TYPE_ROOT_BACKREF      0x90
+#define TYPE_ROOT_REF          0x9C
+#define TYPE_EXTENT_ITEM       0xA8
+#define TYPE_METADATA_ITEM     0xA9
+#define TYPE_TREE_BLOCK_REF    0xB0
+#define TYPE_EXTENT_DATA_REF   0xB2
+#define TYPE_EXTENT_REF_V0     0xB4
+#define TYPE_SHARED_BLOCK_REF  0xB6
+#define TYPE_SHARED_DATA_REF   0xB8
+#define TYPE_BLOCK_GROUP_ITEM  0xC0
+#define TYPE_DEV_EXTENT        0xCC
+#define TYPE_DEV_ITEM          0xD8
+#define TYPE_CHUNK_ITEM        0xE4
+
+#define BTRFS_ROOT_ROOT         1
+#define BTRFS_ROOT_EXTENT       2
+#define BTRFS_ROOT_CHUNK        3
+#define BTRFS_ROOT_DEVTREE      4
+#define BTRFS_ROOT_FSTREE       5
+#define BTRFS_ROOT_CHECKSUM     7
+
+#define BTRFS_COMPRESSION_NONE  0
+#define BTRFS_COMPRESSION_ZLIB  1
+#define BTRFS_COMPRESSION_LZO   2
+
+#define BTRFS_ENCRYPTION_NONE   0
+
+#define BTRFS_ENCODING_NONE     0
+
+#define EXTENT_TYPE_INLINE      0
+#define EXTENT_TYPE_REGULAR     1
+#define EXTENT_TYPE_PREALLOC    2
+
+#define BLOCK_FLAG_DATA         0x001
+#define BLOCK_FLAG_SYSTEM       0x002
+#define BLOCK_FLAG_METADATA     0x004
+#define BLOCK_FLAG_RAID0        0x008
+#define BLOCK_FLAG_RAID1        0x010
+#define BLOCK_FLAG_DUPLICATE    0x020
+#define BLOCK_FLAG_RAID10       0x040
+#define BLOCK_FLAG_RAID5        0x080
+#define BLOCK_FLAG_RAID6        0x100
+
+#define FREE_SPACE_CACHE_ID     0xFFFFFFFFFFFFFFF5
+#define EXTENT_CSUM_ID          0xFFFFFFFFFFFFFFF6
+
+#define BTRFS_INODE_NODATASUM   0x001
+#define BTRFS_INODE_NODATACOW   0x002
+#define BTRFS_INODE_READONLY    0x004
+#define BTRFS_INODE_NOCOMPRESS  0x008
+#define BTRFS_INODE_PREALLOC    0x010
+#define BTRFS_INODE_SYNC        0x020
+#define BTRFS_INODE_IMMUTABLE   0x040
+#define BTRFS_INODE_APPEND      0x080
+#define BTRFS_INODE_NODUMP      0x100
+#define BTRFS_INODE_NOATIME     0x200
+#define BTRFS_INODE_DIRSYNC     0x400
+#define BTRFS_INODE_COMPRESS    0x800
+
+#define BTRFS_SUBVOL_READONLY   0x1
+
+#define BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE  0x1
+
+#define BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF      0x0001
+#define BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL     0x0002
+#define BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS       0x0004
+#define BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO       0x0008
+#define BTRFS_INCOMPAT_FLAGS_COMPRESS_LZOV2     0x0010
+#define BTRFS_INCOMPAT_FLAGS_BIG_METADATA       0x0020
+#define BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF      0x0040
+#define BTRFS_INCOMPAT_FLAGS_RAID56             0x0080
+#define BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA    0x0100
+#define BTRFS_INCOMPAT_FLAGS_NO_HOLES           0x0200
+
+#pragma pack(push, 1)
+
+typedef struct {
+    UINT8 uuid[16];
+} BTRFS_UUID;
+
+typedef struct {
+    UINT64 obj_id;
+    UINT8 obj_type;
+    UINT64 offset;
+} KEY;
+
+#define HEADER_FLAG_MIXED_BACKREF   0x100000000000000
+#define HEADER_FLAG_SHARED_BACKREF  0x000000000000002
+
+typedef struct {
+    UINT8 csum[32];
+    BTRFS_UUID fs_uuid;
+    UINT64 address;
+    UINT64 flags;
+    BTRFS_UUID chunk_tree_uuid;
+    UINT64 generation;
+    UINT64 tree_id;
+    UINT32 num_items;
+    UINT8 level;
+} tree_header;
+
+typedef struct {
+    KEY key;
+    UINT32 offset;
+    UINT32 size;
+} leaf_node;
+
+typedef struct {
+    KEY key;
+    UINT64 address;
+    UINT64 generation;
+} internal_node;
+
+typedef struct {
+    UINT64 dev_id;
+    UINT64 num_bytes;
+    UINT64 bytes_used;
+    UINT32 optimal_io_align;
+    UINT32 optimal_io_width;
+    UINT32 minimal_io_size;
+    UINT64 type;
+    UINT64 generation;
+    UINT64 start_offset;
+    UINT32 dev_group;
+    UINT8 seek_speed;
+    UINT8 bandwidth;
+    BTRFS_UUID device_uuid;
+    BTRFS_UUID fs_uuid;
+} DEV_ITEM;
+
+#define SYS_CHUNK_ARRAY_SIZE 0x800
+
+typedef struct {
+    UINT8 checksum[32];
+    BTRFS_UUID uuid;
+    UINT64 sb_phys_addr;
+    UINT64 flags;
+    UINT64 magic;
+    UINT64 generation;
+    UINT64 root_tree_addr;
+    UINT64 chunk_tree_addr;
+    UINT64 log_tree_addr;
+    UINT64 log_root_transid;
+    UINT64 total_bytes;
+    UINT64 bytes_used;
+    UINT64 root_dir_objectid;
+    UINT64 num_devices;
+    UINT32 sector_size;
+    UINT32 node_size;
+    UINT32 leaf_size;
+    UINT32 stripe_size;
+    UINT32 n;
+    UINT64 chunk_root_generation;
+    UINT64 compat_flags;
+    UINT64 compat_ro_flags;
+    UINT64 incompat_flags;
+    UINT16 csum_type;
+    UINT8 root_level;
+    UINT8 chunk_root_level;
+    UINT8 log_root_level;
+    DEV_ITEM dev_item;
+    char label[MAX_LABEL_SIZE];
+    UINT64 cache_generation;
+    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;
+
+#define BTRFS_TYPE_UNKNOWN   0
+#define BTRFS_TYPE_FILE      1
+#define BTRFS_TYPE_DIRECTORY 2
+#define BTRFS_TYPE_CHARDEV   3
+#define BTRFS_TYPE_BLOCKDEV  4
+#define BTRFS_TYPE_FIFO      5
+#define BTRFS_TYPE_SOCKET    6
+#define BTRFS_TYPE_SYMLINK   7
+#define BTRFS_TYPE_EA        8
+
+typedef struct {
+    KEY key;
+    UINT64 transid;
+    UINT16 m;
+    UINT16 n;
+    UINT8 type;
+    char name[1];
+} DIR_ITEM;
+
+typedef struct {
+    UINT64 seconds;
+    UINT32 nanoseconds;
+} BTRFS_TIME;
+
+typedef struct {
+    UINT64 generation;
+    UINT64 transid;
+    UINT64 st_size;
+    UINT64 st_blocks;
+    UINT64 block_group;
+    UINT32 st_nlink;
+    UINT32 st_uid;
+    UINT32 st_gid;
+    UINT32 st_mode;
+    UINT64 st_rdev;
+    UINT64 flags;
+    UINT64 sequence;
+    UINT8 reserved[32];
+    BTRFS_TIME st_atime;
+    BTRFS_TIME st_ctime;
+    BTRFS_TIME st_mtime;
+    BTRFS_TIME otime;
+} INODE_ITEM;
+
+typedef struct {
+    INODE_ITEM inode;
+    UINT64 generation;
+    UINT64 objid;
+    UINT64 block_number;
+    UINT64 byte_limit;
+    UINT64 bytes_used;
+    UINT64 last_snapshot_generation;
+    UINT64 flags;
+    UINT32 num_references;
+    KEY drop_progress;
+    UINT8 drop_level;
+    UINT8 root_level;
+    UINT64 generation2;
+    BTRFS_UUID uuid;
+    BTRFS_UUID parent_uuid;
+    BTRFS_UUID received_uuid;
+    UINT64 ctransid;
+    UINT64 otransid;
+    UINT64 stransid;
+    UINT64 rtransid;
+    BTRFS_TIME ctime;
+    BTRFS_TIME otime;
+    BTRFS_TIME stime;
+    BTRFS_TIME rtime;
+    UINT64 reserved[8];
+} ROOT_ITEM;
+
+typedef struct {
+    UINT64 size;
+    UINT64 root_id;
+    UINT64 stripe_length;
+    UINT64 type;
+    UINT32 opt_io_alignment;
+    UINT32 opt_io_width;
+    UINT32 sector_size;
+    UINT16 num_stripes;
+    UINT16 sub_stripes;
+} CHUNK_ITEM;
+
+typedef struct {
+    UINT64 dev_id;
+    UINT64 offset;
+    BTRFS_UUID dev_uuid;
+} CHUNK_ITEM_STRIPE;
+
+typedef struct {
+    UINT64 generation;
+    UINT64 decoded_size;
+    UINT8 compression;
+    UINT8 encryption;
+    UINT16 encoding;
+    UINT8 type;
+    UINT8 data[1];
+} EXTENT_DATA;
+
+typedef struct {
+    UINT64 address;
+    UINT64 size;
+    UINT64 offset;
+    UINT64 num_bytes;
+} EXTENT_DATA2;
+
+typedef struct {
+    UINT64 index;
+    UINT16 n;
+    char name[1];
+} INODE_REF;
+
+typedef struct {
+    UINT64 dir;
+    UINT64 index;
+    UINT16 n;
+    char name[1];
+} INODE_EXTREF;
+
+#define EXTENT_ITEM_DATA            0x001
+#define EXTENT_ITEM_TREE_BLOCK      0x002
+#define EXTENT_ITEM_SHARED_BACKREFS 0x100
+
+typedef struct {
+    UINT64 refcount;
+    UINT64 generation;
+    UINT64 flags;
+} EXTENT_ITEM;
+
+typedef struct {
+    UINT32 refcount;
+} EXTENT_ITEM_V0;
+
+typedef struct {
+    EXTENT_ITEM extent_item;
+    KEY firstitem;
+    UINT8 level;
+} EXTENT_ITEM_TREE;
+
+typedef struct {
+    UINT64 offset;
+} TREE_BLOCK_REF;
+
+typedef struct {
+    UINT64 root;
+    UINT64 objid;
+    UINT64 offset;
+    UINT32 count;
+} EXTENT_DATA_REF;
+
+typedef struct {
+    UINT64 used;
+    UINT64 chunk_tree;
+    UINT64 flags;
+} BLOCK_GROUP_ITEM;
+
+typedef struct {
+    UINT64 root;
+    UINT64 gen;
+    UINT64 objid;
+    UINT64 count;
+} EXTENT_REF_V0;
+
+typedef struct {
+    UINT64 offset;
+} SHARED_BLOCK_REF;
+
+typedef struct {
+    UINT64 offset;
+    UINT32 count;
+} SHARED_DATA_REF;
+
+#define FREE_SPACE_EXTENT 1
+#define FREE_SPACE_BITMAP 2
+
+typedef struct {
+    UINT64 offset;
+    UINT64 size;
+    UINT8 type;
+} FREE_SPACE_ENTRY;
+
+typedef struct {
+    KEY key;
+    UINT64 generation;
+    UINT64 num_entries;
+    UINT64 num_bitmaps;
+} FREE_SPACE_ITEM;
+
+typedef struct {
+    UINT64 dir;
+    UINT64 index;
+    UINT16 n;
+    char name[1];
+} ROOT_REF;
+
+typedef struct {
+    UINT64 chunktree;
+    UINT64 objid;
+    UINT64 address;
+    UINT64 length;
+    BTRFS_UUID chunktree_uuid;
+} DEV_EXTENT;
+
+#pragma pack(pop)
+
+#endif
diff --git a/reactos/drivers/filesystems/btrfs/btrfs.rc b/reactos/drivers/filesystems/btrfs/btrfs.rc
new file mode 100644 (file)
index 0000000..b4b5116
--- /dev/null
@@ -0,0 +1,101 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.K.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE 
+BEGIN
+    "#include ""afxres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE 
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,0,0,1
+ PRODUCTVERSION 1,0,0,1
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "080904b0"
+        BEGIN
+            VALUE "FileDescription", "WinBtrfs"
+            VALUE "FileVersion", "0.2"
+            VALUE "InternalName", "btrfs"
+            VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2016"
+            VALUE "OriginalFilename", "btrfs.sys"
+            VALUE "ProductName", "WinBtrfs"
+            VALUE "ProductVersion", "0.2"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x809, 1200
+    END
+END
+
+#endif    // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/reactos/drivers/filesystems/btrfs/btrfs_drv.h b/reactos/drivers/filesystems/btrfs/btrfs_drv.h
new file mode 100644 (file)
index 0000000..8eec01b
--- /dev/null
@@ -0,0 +1,644 @@
+/* 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/>. */
+
+#ifndef BTRFS_DRV_H_DEFINED
+#define BTRFS_DRV_H_DEFINED
+
+#ifndef __REACTOS__
+#undef _WIN32_WINNT
+#undef NTDDI_VERSION
+
+#define _WIN32_WINNT 0x0600
+#define NTDDI_VERSION 0x06010000 // Win 7
+#define _CRT_SECURE_NO_WARNINGS
+#endif /* __REACTOS__ */
+
+#include <ntifs.h>
+#include <ntddk.h>
+#ifdef __REACTOS__
+#include <ntdddisk.h>
+#endif /* __REACTOS__ */
+#include <mountmgr.h>
+#ifdef __REACTOS__
+#include <rtlfuncs.h>
+#include <iotypes.h>
+#endif /* __REACTOS__ */
+//#include <windows.h>
+#include <windef.h>
+#include <wdm.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include "btrfs.h"
+
+#ifdef _DEBUG
+// #define DEBUG_TREE_REFCOUNTS
+// #define DEBUG_FCB_REFCOUNTS
+// #define DEBUG_LONG_MESSAGES
+#define DEBUG_PARANOID
+#endif
+
+#define BTRFS_NODE_TYPE_CCB 0x2295
+#define BTRFS_NODE_TYPE_FCB 0x2296
+
+#define ALLOC_TAG 0x7442484D //'MHBt'
+
+#define STDCALL __stdcall
+
+#define UID_NOBODY 65534
+#define GID_NOBODY 65534
+
+#define EA_NTACL "security.NTACL"
+#define EA_NTACL_HASH 0x45922146
+
+#define EA_DOSATTRIB "user.DOSATTRIB"
+#define EA_DOSATTRIB_HASH 0x914f9939
+
+#define READ_AHEAD_GRANULARITY 0x10000 // 64 KB
+
+#ifdef _MSC_VER
+#define try __try
+#define except __except
+#define finally __finally
+#else
+#define try if (1)
+#define except(x) if (0 && (x))
+#define finally if (1)
+#endif
+
+// #pragma pack(push, 1)
+
+struct device_extension;
+
+typedef struct {
+    PDEVICE_OBJECT devobj;
+    BTRFS_UUID fsuuid;
+    BTRFS_UUID devuuid;
+    UINT64 devnum;
+    UNICODE_STRING devpath;
+    BOOL processed;
+    LIST_ENTRY list_entry;
+} volume;
+
+typedef struct _fcb_nonpaged {
+    FAST_MUTEX HeaderMutex;
+    SECTION_OBJECT_POINTERS segment_object;
+    ERESOURCE resource;
+    ERESOURCE paging_resource;
+} fcb_nonpaged;
+
+struct _root;
+
+typedef struct _fcb {
+    FSRTL_ADVANCED_FCB_HEADER Header;
+    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;
+    
+    BOOL ads;
+    UINT32 adssize;
+    UINT32 adshash;
+    ANSI_STRING adsxattr;
+    
+    LIST_ENTRY list_entry;
+} fcb;
+
+typedef struct _ccb {
+    USHORT NodeType;
+    CSHORT NodeSize;
+    ULONG disposition;
+    ULONG options;
+    UINT64 query_dir_offset;
+//     char* query_string;
+    UNICODE_STRING query_string;
+    BOOL has_wildcard;
+    BOOL specific_file;
+} ccb;
+
+// typedef struct _log_to_phys {
+//     UINT64 address;
+//     UINT64 size;
+//     UINT64 physaddr;
+//     UINT32 sector_size;
+//     struct _log_to_phys* next;
+// } log_to_phys;
+
+struct _device_extension;
+
+// enum tree_holder_status {
+//     tree_holder_unloaded,
+//     tree_holder_loading,
+//     tree_holder_loaded,
+//     tree_holder_unloading
+// };
+
+// typedef struct {
+//     enum tree_holder_status status;
+//     KSPIN_LOCK spin_lock;
+//     ERESOURCE lock;
+// } tree_holder_nonpaged;
+
+typedef struct {
+    UINT64 address;
+    UINT64 generation;
+    struct _tree* tree;
+//     tree_holder_nonpaged* nonpaged;
+} tree_holder;
+
+typedef struct _tree_data {
+    KEY key;
+    LIST_ENTRY list_entry;
+    BOOL ignore;
+    BOOL inserted;
+    
+    union {
+        tree_holder treeholder;
+        
+        struct {
+            UINT32 size;
+            UINT8* data;
+        };
+    };
+} tree_data;
+
+// typedef struct _tree_nonpaged {
+//     ERESOURCE load_tree_lock;
+// } tree_nonpaged;
+
+typedef struct _tree {
+//     UINT64 address;
+//     UINT8 level;
+    tree_header header;
+    LONG refcount;
+    UINT32 size;
+    struct _device_extension* Vcb;
+    struct _tree* parent;
+    tree_data* paritem;
+    struct _root* root;
+//     tree_nonpaged* nonpaged;
+    LIST_ENTRY itemlist;
+    LIST_ENTRY list_entry;
+    UINT64 new_address;
+    UINT64 flags;
+} tree;
+
+typedef struct {
+//     KSPIN_LOCK load_tree_lock;
+    ERESOURCE load_tree_lock;
+} root_nonpaged;
+
+typedef struct _root {
+    UINT64 id;
+    tree_holder treeholder;
+    root_nonpaged* nonpaged;
+    UINT64 lastinode;
+    ROOT_ITEM root_item;
+    
+    struct _root* prev;
+    struct _root* next;
+} root;
+
+typedef struct {
+    tree* tree;
+    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;
+} root_cache;
+
+#define SPACE_TYPE_FREE     0
+#define SPACE_TYPE_USED     1
+#define SPACE_TYPE_DELETING 2
+#define SPACE_TYPE_WRITING  3
+
+typedef struct {
+    UINT64 offset;
+    UINT64 size;
+    UINT8 type;
+    LIST_ENTRY list_entry;
+} space;
+
+typedef struct {
+    UINT64 address;
+    UINT64 size;
+    BOOL provisional;
+    LIST_ENTRY listentry;
+} disk_hole;
+
+typedef struct {
+    PDEVICE_OBJECT devobj;
+    DEV_ITEM devitem;
+    LIST_ENTRY disk_holes;
+} device;
+
+typedef struct {
+    CHUNK_ITEM* chunk_item;
+    UINT32 size;
+    UINT64 offset;
+    UINT64 used;
+    UINT32 oldused;
+    BOOL space_changed;
+    device** devices;
+    LIST_ENTRY space;
+    LIST_ENTRY list_entry;
+} chunk;
+
+typedef struct {
+    KEY key;
+    void* data;
+    USHORT size;
+    LIST_ENTRY list_entry;
+} sys_chunk;
+
+typedef struct _device_extension {
+    device* devices;
+//     DISK_GEOMETRY geometry;
+    UINT64 length;
+    superblock superblock;
+//     WCHAR label[MAX_LABEL_SIZE];
+    BOOL readonly;
+    fcb* fcbs;
+    fcb* volume_fcb;
+    fcb* root_fcb;
+    ERESOURCE DirResource;
+    KSPIN_LOCK FcbListLock;
+    ERESOURCE fcb_lock;
+    ERESOURCE load_lock;
+    ERESOURCE tree_lock;
+    PNOTIFY_SYNC NotifySync;
+    LIST_ENTRY DirNotifyList;
+    LONG tree_lock_counter;
+    LONG open_trees;
+    ULONG write_trees;
+//     ERESOURCE LogToPhysLock;
+//     UINT64 chunk_root_phys_addr;
+    UINT64 root_tree_phys_addr;
+//     log_to_phys* log_to_phys;
+    root* roots;
+    root* chunk_root;
+    root* root_root;
+    root* extent_root;
+    root* checksum_root;
+    root* dev_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;
+
+typedef struct {
+    LIST_ENTRY listentry;
+    PSID sid;
+    UINT32 uid;
+} uid_map;
+
+// #pragma pack(pop)
+
+static __inline void init_tree_holder(tree_holder* th) {
+//     th->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_holder_nonpaged), ALLOC_TAG);
+//     KeInitializeSpinLock(&th->nonpaged->spin_lock);
+//     ExInitializeResourceLite(&th->nonpaged->lock); // FIXME - delete this later
+}
+
+static __inline void* map_user_buffer(PIRP Irp) {
+    if (!Irp->MdlAddress) {
+        return Irp->UserBuffer;
+    } else {
+        return MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
+    }
+}
+
+static __inline UINT64 unix_time_to_win(BTRFS_TIME* t) {
+    return (t->seconds * 10000000) + (t->nanoseconds / 100) + 116444736000000000;
+}
+
+static __inline void win_time_to_unix(LARGE_INTEGER t, BTRFS_TIME* out) {
+    ULONGLONG l = t.QuadPart - 116444736000000000;
+    
+    out->seconds = l / 10000000;
+    out->nanoseconds = (l % 10000000) * 100;
+}
+
+// in btrfs.c
+device* find_device_from_uuid(device_extension* Vcb, BTRFS_UUID* uuid);
+ULONG sector_align( ULONG NumberToBeAligned, ULONG Alignment );
+int keycmp(const KEY* key1, const KEY* key2);
+ULONG STDCALL get_file_attributes(device_extension* Vcb, INODE_ITEM* ii, root* r, UINT64 inode, UINT8 type, BOOL dotfile, BOOL ignore_xa);
+BOOL STDCALL get_xattr(device_extension* Vcb, root* subvol, UINT64 inode, char* name, UINT32 crc32, UINT8** data, UINT16* datalen);
+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);
+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);
+fcb* create_fcb();
+void protect_superblocks(device_extension* Vcb, chunk* c);
+BOOL is_top_level(PIRP Irp);
+
+#ifdef _MSC_VER
+#define funcname __FUNCTION__
+#else
+#define funcname __func__
+#endif
+
+// 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__)
+
+#ifdef _DEBUG
+
+#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__)
+
+void STDCALL _debug_message(const char* func, UINT8 priority, 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__)
+
+void STDCALL _debug_message(const char* func, UINT8 priority, char* s, ...);
+
+#endif
+
+#else
+
+#define TRACE(s, ...)
+#define WARN(s, ...)
+#ifndef __REACTOS__
+#define FIXME(s, ...) DbgPrint("Btrfs FIXME : " funcname " : " s, ##__VA_ARGS__)
+#define ERR(s, ...) DbgPrint("Btrfs ERR : " funcname " : " s, ##__VA_ARGS__)
+#else
+#define FIXME(s, ...) DbgPrint("Btrfs FIXME : %s : " s, funcname, ##__VA_ARGS__)
+#define ERR(s, ...) DbgPrint("Btrfs ERR : %s : " s, funcname, ##__VA_ARGS__)
+#endif
+
+#endif
+
+// in fastio.c
+void STDCALL init_fast_io_dispatch(FAST_IO_DISPATCH** fiod);
+
+// in crc32c.c
+UINT32 STDCALL calc_crc32c(UINT32 seed, UINT8* msg, ULONG msglen);
+
+// in treefuncs.c
+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);
+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);
+
+#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
+void STDCALL look_for_vols(LIST_ENTRY* volumes);
+
+// in cache.c
+NTSTATUS STDCALL init_cache();
+void STDCALL free_cache();
+extern CACHE_MANAGER_CALLBACKS* cache_callbacks;
+
+// in write.c
+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 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);
+
+// in dirctrl.c
+NTSTATUS STDCALL drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
+
+// 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);
+// UINT32 STDCALL get_uid();
+void add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, UINT32 uid);
+NTSTATUS fcb_get_new_sd(fcb* fcb, 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);
+
+// 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);
+
+// 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);
+
+// in fsctl.c
+NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL user);
+
+// in flushthread.c
+void STDCALL flush_thread(void* context);
+
+// in read.c
+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);
+
+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);
+
+        le = le->Flink;
+    }
+}
+
+static __inline void InsertAfter(LIST_ENTRY* head, LIST_ENTRY* item, LIST_ENTRY* before) {
+    item->Flink = before->Flink;
+    before->Flink = item;
+    item->Blink = before;
+
+    if (item->Flink != head)
+        item->Flink->Blink = item;
+    else
+        head->Blink = item;
+}
+
+#ifdef _MSC_VER
+// #define int3 __asm { int 3 }
+#define int3 __debugbreak()
+#else
+#define int3 asm("int3;")
+#endif
+
+#define acquire_tree_lock(Vcb, exclusive) {\
+    LONG ref = InterlockedIncrement(&Vcb->tree_lock_counter); \
+    ref = ref; \
+    if (exclusive) { \
+        TRACE("getting tree_lock (exclusive) %u->%u\n", ref-1, ref); \
+        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE); \
+        TRACE("open tree count = %i\n", Vcb->open_trees); \
+    } else { \
+        TRACE("getting tree_lock %u->%u\n", ref-1, ref); \
+        ExAcquireResourceSharedLite(&Vcb->tree_lock, TRUE); \
+    } \
+} 
+
+// if (Vcb->open_trees > 0) { ERR("open tree count = %i\n", Vcb->open_trees); print_open_trees(Vcb); int3; }
+// else TRACE("open tree count = %i\n", Vcb->open_trees);
+
+// FIXME - find a way to catch unfreed trees again
+
+#define release_tree_lock(Vcb, exclusive) {\
+    LONG ref = InterlockedDecrement(&Vcb->tree_lock_counter); \
+    ref = ref; \
+    TRACE("releasing tree_lock %u->%u\n", ref+1, ref); \
+    if (exclusive) {\
+        TRACE("open tree count = %i\n", Vcb->open_trees); \
+    } \
+    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.  */
+#define __S_IFCHR       0020000 /* Character device.  */
+#define __S_IFBLK       0060000 /* Block device.  */
+#define __S_IFREG       0100000 /* Regular file.  */
+#define __S_IFIFO       0010000 /* FIFO.  */
+#define __S_IFLNK       0120000 /* Symbolic link.  */
+#define __S_IFSOCK      0140000 /* Socket.  */
+#define __S_ISTYPE(mode, mask)  (((mode) & __S_IFMT) == (mask))
+
+#ifndef S_ISDIR
+#define S_ISDIR(mode)    __S_ISTYPE((mode), __S_IFDIR)
+#endif
+
+#ifndef S_IXUSR
+#define S_IXUSR 0000100
+#endif
+
+#ifdef __REACTOS__
+#define S_IFDIR __S_IFDIR
+#define S_IFREG __S_IFREG
+#endif /* __REACTOS__ */
+
+#ifndef S_IXGRP
+#define S_IXGRP (S_IXUSR >> 3)
+#endif
+
+#ifndef S_IXOTH
+#define S_IXOTH (S_IXGRP >> 3)
+#endif
+
+#if defined(__REACTOS__) && (NTDDI_VERSION < NTDDI_WIN7)
+NTSTATUS WINAPI RtlUnicodeToUTF8N(CHAR *utf8_dest, ULONG utf8_bytes_max,
+                                  ULONG *utf8_bytes_written,
+                                  const WCHAR *uni_src, ULONG uni_bytes);
+NTSTATUS WINAPI RtlUTF8ToUnicodeN(WCHAR *uni_dest, ULONG uni_bytes_max,
+                                  ULONG *uni_bytes_written,
+                                  const CHAR *utf8_src, ULONG utf8_bytes);
+#endif /* defined(__REACTOS__) && (NTDDI_VERSION < NTDDI_WIN7) */
+#if defined(__REACTOS__) && (NTDDI_VERSION < NTDDI_VISTA)
+NTSTATUS NTAPI FsRtlRemoveDotsFromPath(PWSTR OriginalString,
+                                       USHORT PathLength, USHORT *NewLength);
+#endif /* defined(__REACTOS__) && (NTDDI_VERSION < NTDDI_WIN7) */
+
+#endif
diff --git a/reactos/drivers/filesystems/btrfs/cache.c b/reactos/drivers/filesystems/btrfs/cache.c
new file mode 100644 (file)
index 0000000..de6cfa6
--- /dev/null
@@ -0,0 +1,76 @@
+/* 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"
+#include <wdm.h>
+
+CACHE_MANAGER_CALLBACKS* cache_callbacks;
+
+static BOOLEAN STDCALL acquire_for_lazy_write(PVOID Context, BOOLEAN Wait) {
+    PFILE_OBJECT FileObject = Context;
+    fcb* fcb = FileObject->FsContext;
+    
+    TRACE("(%p, %u)\n", Context, Wait);
+    
+    if (!fcb || FileObject->Flags & FO_CLEANUP_COMPLETE)
+        return FALSE;
+    
+    fcb->lazy_writer_thread = KeGetCurrentThread();
+    
+    return TRUE;
+}
+
+static void STDCALL release_from_lazy_write(PVOID Context) {
+    PFILE_OBJECT FileObject = Context;
+    fcb* fcb = FileObject->FsContext;
+    
+    TRACE("(%p)\n", Context);
+    
+    if (!fcb || FileObject->Flags & FO_CLEANUP_COMPLETE)
+        return;
+    
+    fcb->lazy_writer_thread = NULL;
+}
+
+static BOOLEAN STDCALL acquire_for_read_ahead(PVOID Context, BOOLEAN Wait) {
+    TRACE("(%p, %u)\n", Context, Wait);
+    
+    return TRUE;
+}
+
+static void STDCALL release_from_read_ahead(PVOID Context) {
+    TRACE("(%p)\n", Context);
+}
+
+NTSTATUS STDCALL init_cache() {
+    cache_callbacks = ExAllocatePoolWithTag(NonPagedPool, sizeof(CACHE_MANAGER_CALLBACKS), ALLOC_TAG);
+    if (!cache_callbacks) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    cache_callbacks->AcquireForLazyWrite = acquire_for_lazy_write;
+    cache_callbacks->ReleaseFromLazyWrite = release_from_lazy_write;
+    cache_callbacks->AcquireForReadAhead = acquire_for_read_ahead;
+    cache_callbacks->ReleaseFromReadAhead = release_from_read_ahead;
+    
+    return STATUS_SUCCESS;
+}
+
+void STDCALL free_cache() {
+    ExFreePool(cache_callbacks);
+}
diff --git a/reactos/drivers/filesystems/btrfs/crc32c.c b/reactos/drivers/filesystems/btrfs/crc32c.c
new file mode 100644 (file)
index 0000000..791c459
--- /dev/null
@@ -0,0 +1,108 @@
+/* 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 <windef.h>
+#ifndef __REACTOS__
+#include <smmintrin.h>
+
+extern BOOL have_sse42;
+#endif /* __REACTOS__ */
+
+static const UINT32 crctable[] = {
+    0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, 
+    0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 
+    0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, 
+    0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, 
+    0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 
+    0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, 
+    0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, 
+    0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 
+    0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, 
+    0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, 
+    0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 
+    0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, 
+    0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, 
+    0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 
+    0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, 
+    0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, 
+    0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 
+    0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, 
+    0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, 
+    0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 
+    0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, 
+    0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, 
+    0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 
+    0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, 
+    0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, 
+    0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 
+    0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, 
+    0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, 
+    0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 
+    0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, 
+    0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, 
+    0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351,
+};
+
+#ifndef __REACTOS__
+// HW code taken from https://github.com/rurban/smhasher/blob/master/crc32_hw.c
+#define ALIGN_SIZE      0x08UL
+#define ALIGN_MASK      (ALIGN_SIZE - 1)
+#define CALC_CRC(op, crc, type, buf, len)                               \
+  do {                                                                  \
+    for (; (len) >= sizeof (type); (len) -= sizeof(type), buf += sizeof (type)) { \
+      (crc) = op((crc), *(type *) (buf));                               \
+    }                                                                   \
+  } while(0)
+
+static UINT32 crc32c_hw(const void *input, int len, UINT32 crc) {
+    const char* buf = (const char*)input;
+
+    for (; (len > 0) && ((size_t)buf & ALIGN_MASK); len--, buf++) {
+        crc = _mm_crc32_u8(crc, *buf);
+    }
+
+#ifdef __x86_64__
+    CALC_CRC(_mm_crc32_u64, crc, UINT64, buf, len);
+#endif
+    CALC_CRC(_mm_crc32_u32, crc, UINT32, buf, len);
+    CALC_CRC(_mm_crc32_u16, crc, UINT16, buf, len);
+    CALC_CRC(_mm_crc32_u8, crc, UINT8, buf, len);
+
+    return crc;
+}
+#endif
+
+UINT32 __stdcall calc_crc32c(UINT32 seed, UINT8* msg, ULONG msglen) {
+    UINT32 rem;
+    ULONG i;
+    
+#ifndef __REACTOS__
+    if (have_sse42) {
+        return crc32c_hw(msg, msglen, seed);
+    } else {
+#endif
+        rem = seed;
+        
+        for (i = 0; i < msglen; i++) {
+            rem = crctable[(rem ^ msg[i]) & 0xff] ^ (rem >> 8);
+        }
+#ifndef __REACTOS__
+    }
+#endif
+    
+    return rem;
+}
diff --git a/reactos/drivers/filesystems/btrfs/create.c b/reactos/drivers/filesystems/btrfs/create.c
new file mode 100644 (file)
index 0000000..a1106f7
--- /dev/null
@@ -0,0 +1,2396 @@
+/* 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 <sys/stat.h>
+#include "btrfs_drv.h"
+
+extern PDEVICE_OBJECT devobj;
+
+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) {
+    DIR_ITEM* di;
+    KEY searchkey;
+    traverse_ptr tp, tp2, next_tp;
+    BOOL b;
+    NTSTATUS Status;
+    ULONG stringlen;
+    
+    TRACE("(%p, %.*S, %08x, %p, %llx, %p, %p, %p)\n", Vcb, filename->Length / sizeof(WCHAR), filename->Buffer, crc32, r, parinode, subvol, inode, type);
+    
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_DIR_ITEM;
+    searchkey.offset = crc32;
+    
+    Status = find_item(Vcb, r, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    TRACE("found item %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        UINT32 size = tp.item->size;
+        
+        // found by hash
+        
+        if (tp.item->size < sizeof(DIR_ITEM)) {
+            WARN("(%llx;%llx,%x,%llx) was %u bytes, expected at least %u\n", r->id, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
+        } else {
+            di = (DIR_ITEM*)tp.item->data;
+            
+            while (size > 0) {
+                if (size < sizeof(DIR_ITEM) || size < (sizeof(DIR_ITEM) - 1 + di->m + di->n)) {
+                    WARN("(%llx,%x,%llx) is truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    break;
+                }
+                
+                size -= sizeof(DIR_ITEM) - sizeof(char);
+                size -= di->n;
+                size -= di->m;
+                
+                Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, di->name, di->n);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+                } else {
+                    WCHAR* utf16 = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
+                    UNICODE_STRING us;
+                    
+                    if (!utf16) {
+                        ERR("out of memory\n");
+                        free_traverse_ptr(&tp);
+                        return FALSE;
+                    }
+                    
+                    Status = RtlUTF8ToUnicodeN(utf16, stringlen, &stringlen, di->name, di->n);
+
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+                    } else {
+                        us.Buffer = utf16;
+                        us.Length = us.MaximumLength = (USHORT)stringlen;
+                        
+                        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;
+                                
+                                *subvol = fcbroot;
+                                *inode = SUBVOL_ROOT_INODE;
+                                *type = BTRFS_TYPE_DIRECTORY;
+                            } else {
+                                *subvol = r;
+                                *inode = di->key.obj_id;
+                                *type = di->type;
+                            }
+                            
+                            if (utf8) {
+                                utf8->MaximumLength = di->n;
+                                utf8->Length = utf8->MaximumLength;
+                                utf8->Buffer = ExAllocatePoolWithTag(PagedPool, utf8->MaximumLength, ALLOC_TAG);
+                                if (!utf8->Buffer) {
+                                    ERR("out of memory\n");
+                                    free_traverse_ptr(&tp);
+                                    ExFreePool(utf16);
+                                    return FALSE;
+                                }
+                                
+                                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);
+                            
+                            return TRUE;
+                        }
+                    }
+                    
+                    ExFreePool(utf16);
+                }
+                
+                di = (DIR_ITEM*)&di->name[di->n + di->m];
+            }
+        }
+    }
+    
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_DIR_INDEX;
+    searchkey.offset = 2;
+    
+    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);
+        return FALSE;
+    }
+    
+    b = TRUE;
+    do {
+        TRACE("key: %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        di = (DIR_ITEM*)tp.item->data;
+        
+        if (tp.item->size < sizeof(DIR_ITEM) || tp.item->size < (sizeof(DIR_ITEM) - 1 + di->m + di->n)) {
+            WARN("(%llx,%x,%llx) is truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        } else {    
+            TRACE("%.*s\n", di->n, di->name);
+            
+            Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, di->name, di->n);
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+            } else {
+                WCHAR* utf16 = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
+                UNICODE_STRING us;
+                
+                if (!utf16) {
+                    ERR("out of memory\n");
+                    
+                    free_traverse_ptr(&tp);
+                    return FALSE;
+                }
+                
+                Status = RtlUTF8ToUnicodeN(utf16, stringlen, &stringlen, di->name, di->n);
+
+                if (!NT_SUCCESS(Status)) {
+                    ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+                } else {
+                    us.Buffer = utf16;
+                    us.Length = us.MaximumLength = (USHORT)stringlen;
+                    
+                    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;
+                            
+                            *subvol = fcbroot;
+                            *inode = SUBVOL_ROOT_INODE;
+                            *type = BTRFS_TYPE_DIRECTORY;
+                        } else {
+                            *subvol = r;
+                            *inode = di->key.obj_id;
+                            *type = di->type;
+                        }
+                        TRACE("found %.*S at (%llx,%llx)\n", filename->Length / sizeof(WCHAR), filename->Buffer, (*subvol)->id, *inode);
+                        
+                        if (utf8) {
+                            utf8->MaximumLength = di->n;
+                            utf8->Length = utf8->MaximumLength;
+                            utf8->Buffer = ExAllocatePoolWithTag(PagedPool, utf8->MaximumLength, ALLOC_TAG);
+                            if (!utf8->Buffer) {
+                                ERR("out of memory\n");
+                                free_traverse_ptr(&tp);
+                                ExFreePool(utf16);
+                                
+                                return FALSE;
+                            }
+                            
+                            RtlCopyMemory(utf8->Buffer, di->name, di->n);
+                        }
+                        
+                        free_traverse_ptr(&tp);
+                        ExFreePool(utf16);
+                        
+                        return TRUE;
+                    }
+                }
+                
+                ExFreePool(utf16);
+            }
+        }
+        
+        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;
+}
+
+fcb* create_fcb() {
+    fcb* fcb;
+    
+    fcb = ExAllocatePoolWithTag(PagedPool, sizeof(struct _fcb), ALLOC_TAG);
+    if (!fcb) {
+        ERR("out of memory\n");
+        return NULL;
+    }
+    
+#ifdef DEBUG_FCB_REFCOUNTS
+    WARN("allocating fcb %p\n", fcb);
+#endif
+    RtlZeroMemory(fcb, sizeof(struct _fcb));
+    
+    fcb->Header.NodeTypeCode = BTRFS_NODE_TYPE_FCB;
+    fcb->Header.NodeByteSize = sizeof(struct _fcb);
+    
+    fcb->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(struct _fcb_nonpaged), ALLOC_TAG);
+    if (!fcb->nonpaged) {
+        ERR("out of memory\n");
+        ExFreePool(fcb);
+        return NULL;
+    }
+    RtlZeroMemory(fcb->nonpaged, sizeof(struct _fcb_nonpaged));
+    
+    ExInitializeResourceLite(&fcb->nonpaged->paging_resource);
+    fcb->Header.PagingIoResource = &fcb->nonpaged->paging_resource;
+    
+    ExInitializeFastMutex(&fcb->nonpaged->HeaderMutex);
+    FsRtlSetupAdvancedHeader(&fcb->Header, &fcb->nonpaged->HeaderMutex);
+    
+    fcb->refcount = 1;
+#ifdef DEBUG_FCB_REFCOUNTS
+    WARN("fcb %p: refcount now %i\n", fcb, fcb->refcount);
+#endif
+    
+    ExInitializeResourceLite(&fcb->nonpaged->resource);
+    fcb->Header.Resource = &fcb->nonpaged->resource;
+    
+    FsRtlInitializeFileLock(&fcb->lock, NULL, NULL);
+    
+    InitializeListHead(&fcb->children);
+    
+    return fcb;
+}
+
+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;
+    UINT32 crc32;
+    BOOL ret;
+    ULONG utf8len;
+    NTSTATUS Status;
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, filename->Buffer, filename->Length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N 1 returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    fn = ExAllocatePoolWithTag(PagedPool, utf8len, ALLOC_TAG);
+    if (!fn) {
+        ERR("out of memory\n");
+        return FALSE;
+    }
+    
+    Status = RtlUnicodeToUTF8N(fn, utf8len, &utf8len, filename->Buffer, filename->Length);
+    if (!NT_SUCCESS(Status)) {
+        ExFreePool(fn);
+        ERR("RtlUnicodeToUTF8N 2 returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    TRACE("%.*s\n", utf8len, fn);
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)fn, (ULONG)utf8len);
+    TRACE("crc32c(%.*s) = %08x\n", utf8len, fn, crc32);
+    
+    ret = find_file_in_dir_with_crc32(Vcb, filename, crc32, r, parinode, subvol, inode, type, utf8);
+    
+    return ret;
+}
+
+static BOOL find_stream(device_extension* Vcb, fcb* fcb, PUNICODE_STRING stream, PUNICODE_STRING newstreamname, UINT32* size, UINT32* hash, PANSI_STRING xattr) {
+    NTSTATUS Status;
+    ULONG utf8len;
+    char* utf8;
+    UINT32 crc32;
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL success = FALSE, b;
+    
+    static char xapref[] = "user.";
+    ULONG xapreflen = strlen(xapref);
+    
+    TRACE("(%p, %p, %.*S)\n", Vcb, fcb, stream->Length / sizeof(WCHAR), stream->Buffer);
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, stream->Buffer, stream->Length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N 1 returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    TRACE("utf8len = %u\n", utf8len);
+    
+    utf8 = ExAllocatePoolWithTag(PagedPool, xapreflen + utf8len + 1, ALLOC_TAG);
+    if (!utf8) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    RtlCopyMemory(utf8, xapref, xapreflen);
+    
+    Status = RtlUnicodeToUTF8N(&utf8[xapreflen], utf8len, &utf8len, stream->Buffer, stream->Length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlUnicodeToUTF8N 2 returned %08x\n", Status);
+        goto end;
+    }
+    
+    utf8len += xapreflen;
+    utf8[utf8len] = 0;
+    
+    TRACE("utf8 = %s\n", utf8);
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8, utf8len);
+    TRACE("crc32 = %08x\n", crc32);
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_XATTR_ITEM;
+    searchkey.offset = crc32;
+    
+    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(&tp.item->key, &searchkey)) {
+        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 {
+            ULONG len = tp.item->size, xasize;
+            DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
+            
+            TRACE("found match on hash\n");
+            
+            while (len > 0) {
+                if (len < sizeof(DIR_ITEM) || len < sizeof(DIR_ITEM) - 1 + di->m + di->n) {
+                    ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    break;
+                }
+                
+                if (RtlCompareMemory(di->name, utf8, utf8len) == utf8len) {
+                    TRACE("found exact match for %s\n", utf8);
+                    
+                    *size = di->m;
+                    *hash = tp.item->key.offset;
+                    
+                    xattr->Buffer = ExAllocatePoolWithTag(PagedPool, di->n + 1, ALLOC_TAG);
+                    if (!xattr->Buffer) {
+                        ERR("out of memory\n");
+                        free_traverse_ptr(&tp);
+                        goto end;
+                    }
+                    
+                    xattr->Length = xattr->MaximumLength = di->n;
+                    RtlCopyMemory(xattr->Buffer, di->name, di->n);
+                    xattr->Buffer[di->n] = 0;
+                    
+                    free_traverse_ptr(&tp);
+                    
+                    success = TRUE;
+                    goto end;
+                }
+                
+                xasize = sizeof(DIR_ITEM) - 1 + di->m + di->n;
+                
+                if (len > xasize) {
+                    len -= xasize;
+                    di = (DIR_ITEM*)&di->name[di->m + di->n];
+                } else
+                    break;
+            }
+        }
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    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;
+    }
+    
+    do {
+        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM && tp.item->key.offset != crc32) {
+            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 {
+                ULONG len = tp.item->size, xasize;
+                DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
+                ULONG utf16len;
+                
+                TRACE("found xattr with hash %08x\n", (UINT32)tp.item->key.offset);
+                
+                while (len > 0) {
+                    if (len < sizeof(DIR_ITEM) || len < sizeof(DIR_ITEM) - 1 + di->m + di->n) {
+                        ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                        break;
+                    }
+                    
+                    if (di->n > xapreflen && RtlCompareMemory(di->name, xapref, xapreflen) == xapreflen) {
+                        TRACE("found potential xattr %.*s\n", di->n, di->name);
+                    }
+                    
+                    Status = RtlUTF8ToUnicodeN(NULL, 0, &utf16len, &di->name[xapreflen], di->n - xapreflen);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+                    } else {
+                        WCHAR* utf16 = ExAllocatePoolWithTag(PagedPool, utf16len, ALLOC_TAG);
+                        if (!utf16) {
+                            ERR("out of memory\n");
+                            free_traverse_ptr(&tp);
+                            goto end;
+                        }
+                        
+                        Status = RtlUTF8ToUnicodeN(utf16, utf16len, &utf16len, &di->name[xapreflen], di->n - xapreflen);
+
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+                        } else {
+                            UNICODE_STRING us;
+                            
+                            us.Buffer = utf16;
+                            us.Length = us.MaximumLength = (USHORT)utf16len;
+                            
+                            if (FsRtlAreNamesEqual(stream, &us, TRUE, NULL)) {
+                                TRACE("found case-insensitive match for %s\n", utf8);
+                                
+                                *newstreamname = us;
+                                *size = di->m;
+                                *hash = tp.item->key.offset;
+                                
+                                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;
+                                }
+                                
+                                xattr->Length = xattr->MaximumLength = di->n;
+                                RtlCopyMemory(xattr->Buffer, di->name, di->n);
+                                xattr->Buffer[di->n] = 0;
+                                
+                                free_traverse_ptr(&tp);
+                                
+                                success = TRUE;
+                                goto end;
+                            }
+                        }
+                        
+                        ExFreePool(utf16);
+                    }
+                    
+                    xasize = sizeof(DIR_ITEM) - 1 + di->m + di->n;
+                    
+                    if (len > xasize) {
+                        len -= xasize;
+                        di = (DIR_ITEM*)&di->name[di->m + di->n];
+                    } else
+                        break;
+                }
+            }
+        }
+        
+        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)
+                break;
+        }
+    } while (b);
+    
+    free_traverse_ptr(&tp);
+   
+end:
+    ExFreePool(utf8);
+    
+    return success;
+}
+
+static NTSTATUS split_path(PUNICODE_STRING path, UNICODE_STRING** parts, ULONG* num_parts, BOOL* stream) {
+    ULONG len, i, j, np;
+    BOOL has_stream;
+    UNICODE_STRING* ps;
+    WCHAR* buf;
+    
+    np = 1;
+    
+    len = path->Length / sizeof(WCHAR);
+    if (len > 0 && (path->Buffer[len - 1] == '/' || path->Buffer[len - 1] == '\\'))
+        len--;
+    
+    has_stream = FALSE;
+    for (i = 0; i < len; i++) {
+        if (path->Buffer[i] == '/' || path->Buffer[i] == '\\') {
+            np++;
+            has_stream = FALSE;
+        } else if (path->Buffer[i] == ':') {
+            has_stream = TRUE;
+        }
+    }
+    
+    if (has_stream)
+        np++;
+    
+    ps = ExAllocatePoolWithTag(PagedPool, np * sizeof(UNICODE_STRING), ALLOC_TAG);
+    if (!ps) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(ps, np * sizeof(UNICODE_STRING));
+    
+    buf = path->Buffer;
+    
+    j = 0;
+    for (i = 0; i < len; i++) {
+        if (path->Buffer[i] == '/' || path->Buffer[i] == '\\') {
+            ps[j].Buffer = buf;
+            ps[j].Length = (&path->Buffer[i] - buf) * sizeof(WCHAR);
+            ps[j].MaximumLength = ps[j].Length;
+            
+            buf = &path->Buffer[i+1];
+            j++;
+        }
+    }
+    
+    ps[j].Buffer = buf;
+    ps[j].Length = (&path->Buffer[i] - buf) * sizeof(WCHAR);
+    ps[j].MaximumLength = ps[j].Length;
+    
+    if (has_stream) {
+        static WCHAR datasuf[] = {':','$','D','A','T','A',0};
+        UNICODE_STRING dsus;
+        
+        dsus.Buffer = datasuf;
+        dsus.Length = dsus.MaximumLength = wcslen(datasuf) * sizeof(WCHAR);
+        
+        for (i = 0; i < ps[j].Length / sizeof(WCHAR); i++) {
+            if (ps[j].Buffer[i] == ':') {
+                ps[j+1].Buffer = &ps[j].Buffer[i+1];
+                ps[j+1].Length = ps[j].Length - (i * sizeof(WCHAR)) - sizeof(WCHAR);
+                
+                ps[j].Length = i * sizeof(WCHAR);
+                ps[j].MaximumLength = ps[j].Length;
+                
+                j++;
+                
+                break;
+            }
+        }
+        
+        // FIXME - should comparison be case-insensitive?
+        // remove :$DATA suffix
+        if (ps[j].Length >= dsus.Length && RtlCompareMemory(&ps[j].Buffer[(ps[j].Length - dsus.Length)/sizeof(WCHAR)], dsus.Buffer, dsus.Length) == dsus.Length)
+            ps[j].Length -= dsus.Length;
+        
+        if (ps[j].Length == 0) {
+            np--;
+            has_stream = FALSE;
+        }
+    }
+    
+    // if path is just stream name, remove first empty item
+    if (has_stream && path->Length >= sizeof(WCHAR) && path->Buffer[0] == ':') {
+        ps[0] = ps[1];
+        np--;
+    }
+
+//     for (i = 0; i < np; i++) {
+//         ERR("part %u: %u, (%.*S)\n", i, ps[i].Length, ps[i].Length / sizeof(WCHAR), ps[i].Buffer);
+//     }
+    
+    *num_parts = np;
+    *parts = ps;
+    *stream = has_stream;
+    
+    return STATUS_SUCCESS;
+}
+
+static fcb* search_fcb_children(fcb* dir, PUNICODE_STRING name) {
+    LIST_ENTRY* le;
+    fcb *c, *deleted = NULL;
+    
+    le = dir->children.Flink;
+    while (le != &dir->children) {
+        c = CONTAINING_RECORD(le, fcb, list_entry);
+        
+        if (c->refcount > 0 && FsRtlAreNamesEqual(&c->filepart, name, TRUE, NULL)) {
+            if (c->deleted) {
+                deleted = c;
+            } else {
+                c->refcount++;
+#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);
+#endif
+                return c;
+            }
+        }
+        
+        le = le->Flink;
+    }
+    
+    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
+
+NTSTATUS get_fcb(device_extension* Vcb, fcb** pfcb, PUNICODE_STRING fnus, fcb* relatedfcb, BOOL parent) {
+    fcb *dir, *sf, *sf2;
+    ULONG i, num_parts;
+    UNICODE_STRING fnus2;
+    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");
+    
+#ifdef DEBUG_FCB_REFCOUNTS
+    print_fcbs(Vcb);
+#endif
+    
+    fnus2 = *fnus;
+    
+    if (fnus2.Length < sizeof(WCHAR) && !relatedfcb) {
+        ERR("error - fnus was too short\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    if (relatedfcb) {
+        dir = relatedfcb;
+    } else {
+        if (fnus2.Buffer[0] != '\\') {
+            ERR("error - filename %.*S did not begin with \\\n", fnus2.Length / sizeof(WCHAR), fnus2.Buffer);
+            return STATUS_OBJECT_PATH_NOT_FOUND;
+        }
+        
+        if (fnus2.Length == sizeof(WCHAR)) {
+            *pfcb = Vcb->root_fcb;
+            Vcb->root_fcb->refcount++;
+#ifdef DEBUG_FCB_REFCOUNTS
+            WARN("fcb %p: refcount now %i (root)\n", Vcb->root_fcb, Vcb->root_fcb->refcount);
+#endif
+            return STATUS_SUCCESS;
+        }
+        
+        dir = Vcb->root_fcb;
+        
+        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);
+        return STATUS_OBJECT_PATH_NOT_FOUND;
+    }
+    
+    if (fnus->Length == 0) {
+        num_parts = 0;
+    } else {
+        Status = split_path(&fnus2, &parts, &num_parts, &has_stream);
+        if (!NT_SUCCESS(Status)) {
+            ERR("split_path returned %08x\n", Status);
+            return Status;
+        }
+    }
+    
+    // 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);
+#endif
+    
+    if (parent) {
+        num_parts--;
+        
+        if (has_stream) {
+            num_parts--;
+            has_stream = FALSE;
+        }
+    }
+    
+    if (num_parts == 0) {
+        Status = STATUS_SUCCESS;
+        *pfcb = 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]);
+        
+        if (sf2 && sf2->type != BTRFS_TYPE_DIRECTORY && !lastpart) {
+            WARN("passed path including file as subdirectory\n");
+            
+            Status = STATUS_OBJECT_PATH_NOT_FOUND;
+            goto end;
+        }
+        
+        if (!sf2) {
+            if (has_stream && i == num_parts - 1) {
+                UNICODE_STRING streamname;
+                ANSI_STRING xattr;
+                UINT32 streamsize, streamhash;
+                
+                streamname.Buffer = NULL;
+                streamname.Length = streamname.MaximumLength = 0;
+                xattr.Buffer = NULL;
+                xattr.Length = xattr.MaximumLength = 0;
+                
+                if (!find_stream(Vcb, sf, &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;
+                    
+                    if (streamhash == EA_DOSATTRIB_HASH && xattr.Length == strlen(EA_DOSATTRIB) &&
+                        RtlCompareMemory(xattr.Buffer, EA_DOSATTRIB, xattr.Length) == xattr.Length) {
+                        WARN("not allowing user.DOSATTRIB to be opened as stream\n");
+                    
+                        Status = STATUS_OBJECT_NAME_NOT_FOUND;
+                        goto end;
+                    }
+                    
+                    sf2 = create_fcb();
+                    if (!sf2) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+        
+                    sf2->Vcb = Vcb;
+        
+                    if (streamname.Buffer) // case has changed
+                        sf2->filepart = streamname;
+                    else {
+                        sf2->filepart.MaximumLength = sf2->filepart.Length = parts[i].Length;
+                        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;
+                        }   
+                        
+                        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)
+                        sf2->name_offset++;
+                    
+                    fnlen = (sf2->name_offset * sizeof(WCHAR)) + sf2->filepart.Length;
+                    
+                    sf2->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
+                    if (!sf2->full_filename.Buffer) {
+                        ERR("out of memory\n");
+                        free_fcb(sf2);
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    sf2->full_filename.Length = sf2->full_filename.MaximumLength = fnlen;
+                    RtlCopyMemory(sf2->full_filename.Buffer, sf->full_filename.Buffer, sf->full_filename.Length);
+                    
+                    sf2->full_filename.Buffer[sf->full_filename.Length / sizeof(WCHAR)] = ':';
+                    
+                    RtlCopyMemory(&sf2->full_filename.Buffer[sf2->name_offset], sf2->filepart.Buffer, sf2->filepart.Length);
+                    
+                    // 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);
+                    
+                    InsertTailList(&sf->children, &sf2->list_entry);
+                }
+            } else {
+                root* subvol;
+                UINT64 inode;
+                UINT8 type;
+                ANSI_STRING utf8;
+                KEY searchkey;
+                traverse_ptr tp;
+                
+                if (!find_file_in_dir(Vcb, &parts[i], sf->subvol, sf->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;
+                    
+                    sf2 = create_fcb();
+                    if (!sf2) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    sf2->Vcb = Vcb;
+
+                    Status = RtlUTF8ToUnicodeN(NULL, 0, &strlen, utf8.Buffer, utf8.Length);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+                        free_fcb(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->next = Vcb->fcbs;
+                    Vcb->fcbs = sf2;
+                    
+                    sf2->name_offset = sf->full_filename.Length / sizeof(WCHAR);
+                    
+                    if (sf != Vcb->root_fcb)
+                        sf2->name_offset++;
+                    
+                    fnlen = (sf2->name_offset * sizeof(WCHAR)) + sf2->filepart.Length;
+                    
+                    sf2->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fnlen, ALLOC_TAG);
+                    if (!sf2->full_filename.Buffer) {
+                        ERR("out of memory\n");
+                        free_fcb(sf2);
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    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)
+                        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);
+                    
+                    InsertTailList(&sf->children, &sf2->list_entry);
+                }
+            }
+        }
+        
+        if (i == num_parts - 1)
+            break;
+        
+        free_fcb(sf);
+        sf = sf2;
+    }
+    
+    Status = STATUS_SUCCESS;
+    *pfcb = sf2;
+    
+end:
+    free_fcb(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) {
+    NTSTATUS Status;
+    fcb* fcb;
+    ULONG utf8len;
+    char* utf8 = NULL;
+    UINT32 crc32;
+    UINT64 dirpos, inode;
+    KEY searchkey;
+    traverse_ptr tp;
+    INODE_ITEM *dirii, *ii;
+    UINT8 type;
+    ULONG disize;
+    DIR_ITEM *di, *di2;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    ANSI_STRING utf8as;
+    ULONG defda;
+    
+    Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, fpus->Buffer, fpus->Length);
+    if (!NT_SUCCESS(Status))
+        return Status;
+    
+    utf8 = ExAllocatePoolWithTag(PagedPool, utf8len + 1, ALLOC_TAG);
+    if (!utf8) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    Status = RtlUnicodeToUTF8N(utf8, utf8len, &utf8len, fpus->Buffer, fpus->Length);
+    if (!NT_SUCCESS(Status)) {
+        ExFreePool(utf8);
+        return Status;
+    }
+    
+    utf8[utf8len] = 0;
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8, utf8len);
+    
+    dirpos = find_next_dir_index(Vcb, parfcb->subvol, parfcb->inode);
+    if (dirpos == 0) {
+        Status = STATUS_INTERNAL_ERROR;
+        ExFreePool(utf8);
+        return Status;
+    }
+    
+    TRACE("filename = %s, crc = %08x, dirpos = %llx\n", utf8, crc32, dirpos);
+    
+    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;
+    
+    searchkey.obj_id = parfcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, parfcb->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);
+        ExFreePool(utf8);
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    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));
+    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);
+    
+    free_traverse_ptr(&tp);
+    
+    if (parfcb->subvol->lastinode == 0)
+        get_last_inode(Vcb, parfcb->subvol);
+    
+    inode = parfcb->subvol->lastinode + 1;
+    
+    type = options & FILE_DIRECTORY_FILE ? BTRFS_TYPE_DIRECTORY : BTRFS_TYPE_FILE;
+    
+    disize = sizeof(DIR_ITEM) - 1 + utf8len;
+    di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        ExFreePool(utf8);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    di->key.obj_id = inode;
+    di->key.obj_type = TYPE_INODE_ITEM;
+    di->key.offset = 0;
+    di->transid = Vcb->superblock.generation;
+    di->m = 0;
+    di->n = (UINT16)utf8len;
+    di->type = type;
+    RtlCopyMemory(di->name, utf8, utf8len);
+    
+    insert_tree_item(Vcb, parfcb->subvol, parfcb->inode, TYPE_DIR_INDEX, dirpos, di, disize, NULL, rollback);
+    
+    di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+    if (!di2) {
+        ERR("out of memory\n");
+        ExFreePool(utf8);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(di2, di, disize);
+    
+    Status = add_dir_item(Vcb, parfcb->subvol, parfcb->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);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_inode_ref returned %08x\n", Status);
+        ExFreePool(utf8);
+        return Status;
+    }
+    
+    // FIXME - link FILE_ATTRIBUTE_READONLY to st_mode
+    
+    TRACE("requested attributes = %x\n", IrpSp->Parameters.Create.FileAttributes);
+    
+    IrpSp->Parameters.Create.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
+    
+    defda = 0;
+    
+    if (utf8[0] == '.')
+        defda |= FILE_ATTRIBUTE_HIDDEN;
+    
+    if (options & FILE_DIRECTORY_FILE) {
+        defda |= FILE_ATTRIBUTE_DIRECTORY;
+        IrpSp->Parameters.Create.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+    }
+    
+    TRACE("defda = %x\n", defda);
+    
+    if (IrpSp->Parameters.Create.FileAttributes == FILE_ATTRIBUTE_NORMAL)
+        IrpSp->Parameters.Create.FileAttributes = defda;
+    
+    if (IrpSp->Parameters.Create.FileAttributes != defda) {
+        char val[64];
+    
+        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);
+        if (!NT_SUCCESS(Status)) {
+            ERR("set_xattr returned %08x\n", Status);
+            ExFreePool(utf8);
+            return Status;
+        }
+    }
+    
+    parfcb->subvol->lastinode++;
+    
+    fcb = create_fcb();
+    if (!fcb) {
+        ERR("out of memory\n");
+        ExFreePool(utf8);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+        
+    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;
+    fcb->inode_item.st_blocks = 0;
+    fcb->inode_item.block_group = 0;
+    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_rdev = 0;
+    fcb->inode_item.flags = 0;
+    fcb->inode_item.sequence = 1;
+    fcb->inode_item.st_atime = now;
+    fcb->inode_item.st_ctime = now;
+    fcb->inode_item.st_mtime = now;
+    fcb->inode_item.otime = now;
+    
+    if (type == BTRFS_TYPE_DIRECTORY)
+        fcb->inode_item.st_mode |= S_IFDIR;
+    else {
+        fcb->inode_item.st_mode |= S_IFREG;
+        fcb->inode_item.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); // remove executable bit if not directory
+    }
+    
+    // inherit nodatacow flag from parent directory
+    if (parfcb->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.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;
+    parfcb->refcount++;
+#ifdef DEBUG_FCB_REFCOUNTS
+    WARN("fcb %p: refcount now %i (%.*S)\n", parfcb, parfcb->refcount, parfcb->full_filename.Length / sizeof(WCHAR), parfcb->full_filename.Buffer);
+#endif
+    fcb->subvol = parfcb->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);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("fcb_get_new_sd returned %08x\n", Status);
+        ExFreePool(utf8);
+        return Status;
+    }
+
+    fcb->filepart = *fpus;
+        
+    Status = set_xattr(Vcb, parfcb->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);
+        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) {
+        ERR("out of memory\n");
+        ExFreePool(utf8);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(fcb->full_filename.Buffer, parfcb->full_filename.Buffer, parfcb->full_filename.Length);
+    
+    if (parfcb->full_filename.Length > sizeof(WCHAR))
+        fcb->full_filename.Buffer[parfcb->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);
+    
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        ExFreePool(utf8);
+        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);
+    
+    *pfcb = fcb;
+    
+    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = now;
+    
+    InsertTailList(&fcb->par->children, &fcb->list_entry);
+    
+    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);
+    
+    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;
+    ULONG i, j;
+    ULONG utf8len;
+    ccb* ccb;
+    static WCHAR datasuf[] = {':','$','D','A','T','A',0};
+    UNICODE_STRING dsus, fpus, stream;
+            
+    TRACE("(%p, %p, %p, %.*S, %x, %x)\n", Irp, Vcb, FileObject, fnus->Length / sizeof(WCHAR), fnus->Buffer, disposition, options);
+    
+    if (Vcb->readonly)
+        return STATUS_MEDIA_WRITE_PROTECTED;
+    
+    dsus.Buffer = datasuf;
+    dsus.Length = dsus.MaximumLength = wcslen(datasuf) * sizeof(WCHAR);
+    fpus.Buffer = NULL;
+    
+    // FIXME - apparently you can open streams using RelatedFileObject. How can we test this?
+    
+    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+    Status = get_fcb(Vcb, &parfcb, fnus, FileObject->RelatedFileObject ? FileObject->RelatedFileObject->FsContext : NULL, TRUE);
+    ExReleaseResourceLite(&Vcb->fcb_lock);
+    if (!NT_SUCCESS(Status))
+        goto end;
+    
+    if (parfcb->type != BTRFS_TYPE_DIRECTORY) {
+        Status = STATUS_OBJECT_PATH_NOT_FOUND;
+        goto end;
+    }
+    
+    if (parfcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
+    i = (fnus->Length / sizeof(WCHAR))-1;
+    while ((fnus->Buffer[i] == '\\' || fnus->Buffer[i] == '/') && i > 0) { i--; }
+    
+    j = i;
+    
+    while (i > 0 && fnus->Buffer[i-1] != '\\' && fnus->Buffer[i-1] != '/') { i--; }
+    
+    fpus.MaximumLength = (j - i + 2) * sizeof(WCHAR);
+    fpus.Buffer = ExAllocatePoolWithTag(PagedPool, fpus.MaximumLength, ALLOC_TAG);
+    if (!fpus.Buffer) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    fpus.Length = (j - i + 1) * sizeof(WCHAR);
+    
+    RtlCopyMemory(fpus.Buffer, &fnus->Buffer[i], (j - i + 1) * sizeof(WCHAR));
+    fpus.Buffer[j - i + 1] = 0;
+    
+    if (fpus.Length > dsus.Length) { // check for :$DATA suffix
+        UNICODE_STRING lb;
+                
+        lb.Buffer = &fpus.Buffer[(fpus.Length - dsus.Length)/sizeof(WCHAR)];
+        lb.Length = lb.MaximumLength = dsus.Length;
+        
+        TRACE("lb = %.*S\n", lb.Length/sizeof(WCHAR), lb.Buffer);
+        
+        if (FsRtlAreNamesEqual(&dsus, &lb, TRUE, NULL)) {
+            TRACE("ignoring :$DATA suffix\n");
+            
+            fpus.Length -= lb.Length;
+            
+            if (fpus.Length > sizeof(WCHAR) && fpus.Buffer[(fpus.Length-1)/sizeof(WCHAR)] == ':')
+                fpus.Length -= sizeof(WCHAR);
+            
+            TRACE("fpus = %.*S\n", fpus.Length / sizeof(WCHAR), fpus.Buffer);
+        }
+    }
+    
+    stream.Length = 0;
+    
+    for (i = 0; i < fpus.Length/sizeof(WCHAR); i++) {
+        if (fpus.Buffer[i] == ':') {
+            stream.Length = fpus.Length - (i*sizeof(WCHAR)) - sizeof(WCHAR);
+            stream.Buffer = &fpus.Buffer[i+1];
+            fpus.Buffer[i] = 0;
+            fpus.Length = i * sizeof(WCHAR);
+            break;
+        }
+    }
+    
+    if (stream.Length > 0) {
+        struct _fcb* newpar;
+        static char xapref[] = "user.";
+        ULONG xapreflen = strlen(xapref), fnlen;
+        LARGE_INTEGER time;
+        BTRFS_TIME now;
+        KEY searchkey;
+        traverse_ptr tp;
+        INODE_ITEM* ii;
+        
+        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);
+        ExReleaseResourceLite(&Vcb->fcb_lock);
+        
+        if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
+            Status = file_create2(Irp, Vcb, &fpus, parfcb, options, &newpar, rollback);
+        
+            if (!NT_SUCCESS(Status)) {
+                ERR("file_create2 returned %08x\n", Status);
+                goto end;
+            }
+        } else if (!NT_SUCCESS(Status)) {
+            ERR("get_fcb returned %08x\n", Status);
+            goto end;
+        }
+        
+        free_fcb(parfcb);
+        parfcb = newpar;
+        
+        if (newpar->type != BTRFS_TYPE_FILE && newpar->type != BTRFS_TYPE_SYMLINK) {
+            WARN("parent not file or symlink\n");
+            Status = STATUS_INVALID_PARAMETER;
+            goto end;
+        }
+        
+        if (options & FILE_DIRECTORY_FILE) {
+            WARN("tried to create directory as stream\n");
+            Status = STATUS_INVALID_PARAMETER;
+            goto end;
+        }
+            
+        fcb = create_fcb();
+        if (!fcb) {
+            ERR("out of memory\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+        
+        fcb->Vcb = Vcb;
+        
+//         fcb->Header.IsFastIoPossible = TRUE;
+        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;
+        parfcb->refcount++;
+#ifdef DEBUG_FCB_REFCOUNTS
+        WARN("fcb %p: refcount now %i (%.*S)\n", parfcb, parfcb->refcount, parfcb->full_filename.Length / sizeof(WCHAR), parfcb->full_filename.Buffer);
+#endif
+        fcb->subvol = parfcb->subvol;
+        fcb->inode = parfcb->inode;
+        fcb->type = parfcb->type;
+        
+        fcb->ads = TRUE;
+        fcb->adssize = 0;
+        
+        Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, stream.Buffer, stream.Length);
+        if (!NT_SUCCESS(Status))
+            goto end;
+        
+        fcb->adsxattr.Length = utf8len + xapreflen;
+        fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1;
+        fcb->adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsxattr.MaximumLength, ALLOC_TAG);
+        if (!fcb->adsxattr.Buffer) {
+            ERR("out of memory\n");
+            free_fcb(fcb);
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+        
+        RtlCopyMemory(fcb->adsxattr.Buffer, xapref, xapreflen);
+        
+        Status = RtlUnicodeToUTF8N(&fcb->adsxattr.Buffer[xapreflen], utf8len, &utf8len, stream.Buffer, stream.Length);
+        if (!NT_SUCCESS(Status)) {
+            free_fcb(fcb);
+            goto end;
+        }
+        
+        fcb->adsxattr.Buffer[fcb->adsxattr.Length] = 0;
+        
+        TRACE("adsxattr = %s\n", fcb->adsxattr.Buffer);
+        
+        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) {
+            ERR("out of memory\n");
+            free_fcb(fcb);
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+        
+        RtlCopyMemory(fcb->filepart.Buffer, stream.Buffer, stream.Length);
+        
+        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");
+            free_fcb(fcb);
+            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(&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);
+        
+        InsertTailList(&fcb->par->children, &fcb->list_entry);
+        
+        Status = set_xattr(Vcb, parfcb->subvol, parfcb->inode, fcb->adsxattr.Buffer, fcb->adshash, (UINT8*)"", 0, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("set_xattr returned %08x\n", Status);
+            free_fcb(fcb);
+            goto end;
+        }
+        
+        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;
+        
+        searchkey.obj_id = parfcb->inode;
+        searchkey.obj_type = TYPE_INODE_ITEM;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(Vcb, parfcb->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);
+        } else {
+            WARN("could not find INODE_ITEM for inode %llx in subvol %llx\n", searchkey.obj_id, parfcb->subvol->id);
+        }
+        
+        free_traverse_ptr(&tp);
+        
+        ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+        if (!ii) {
+            ERR("out of memory\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+    
+        RtlCopyMemory(ii, &parfcb->inode_item, sizeof(INODE_ITEM));
+        
+        insert_tree_item(Vcb, parfcb->subvol, parfcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
+        
+        parfcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+        parfcb->subvol->root_item.ctime = now;
+        
+        ExFreePool(fpus.Buffer);
+        fpus.Buffer = NULL;
+    } else {
+        Status = file_create2(Irp, Vcb, &fpus, parfcb, options, &fcb, rollback);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("file_create2 returned %08x\n", Status);
+            goto end;
+        }
+    }
+    
+    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);
+    
+    ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);
+    if (!ccb) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    RtlZeroMemory(ccb, sizeof(*ccb));
+    ccb->NodeType = BTRFS_NODE_TYPE_CCB;
+    ccb->NodeSize = sizeof(ccb);
+    ccb->disposition = disposition;
+    ccb->options = options;
+    ccb->query_dir_offset = 0;
+    RtlInitUnicodeString(&ccb->query_string, NULL);
+    ccb->has_wildcard = FALSE;
+    ccb->specific_file = FALSE;
+    
+    InterlockedIncrement(&fcb->open_count);
+    
+    FileObject->FsContext2 = ccb;
+
+    FileObject->SectionObjectPointer = &fcb->nonpaged->segment_object;
+    
+    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);
+        
+        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);
+        
+        goto end2;
+    }
+    
+end:    
+    if (fpus.Buffer)
+        ExFreePool(fpus.Buffer);
+    
+end2:
+    if (parfcb)
+        free_fcb(parfcb);
+    
+    return Status;
+}
+
+static __inline void debug_create_options(ULONG RequestedOptions) {
+    if (RequestedOptions != 0) {
+        ULONG options = RequestedOptions;
+        
+        TRACE("requested options:\n");
+        
+        if (options & FILE_DIRECTORY_FILE) {
+            TRACE("    FILE_DIRECTORY_FILE\n");
+            options &= ~FILE_DIRECTORY_FILE;
+        }
+
+        if (options & FILE_WRITE_THROUGH) {
+            TRACE("    FILE_WRITE_THROUGH\n");
+            options &= ~FILE_WRITE_THROUGH;
+        }
+
+        if (options & FILE_SEQUENTIAL_ONLY) {
+            TRACE("    FILE_SEQUENTIAL_ONLY\n");
+            options &= ~FILE_SEQUENTIAL_ONLY;
+        }
+
+        if (options & FILE_NO_INTERMEDIATE_BUFFERING) {
+            TRACE("    FILE_NO_INTERMEDIATE_BUFFERING\n");
+            options &= ~FILE_NO_INTERMEDIATE_BUFFERING;
+        }
+
+        if (options & FILE_SYNCHRONOUS_IO_ALERT) {
+            TRACE("    FILE_SYNCHRONOUS_IO_ALERT\n");
+            options &= ~FILE_SYNCHRONOUS_IO_ALERT;
+        }
+
+        if (options & FILE_SYNCHRONOUS_IO_NONALERT) {
+            TRACE("    FILE_SYNCHRONOUS_IO_NONALERT\n");
+            options &= ~FILE_SYNCHRONOUS_IO_NONALERT;
+        }
+
+        if (options & FILE_NON_DIRECTORY_FILE) {
+            TRACE("    FILE_NON_DIRECTORY_FILE\n");
+            options &= ~FILE_NON_DIRECTORY_FILE;
+        }
+
+        if (options & FILE_CREATE_TREE_CONNECTION) {
+            TRACE("    FILE_CREATE_TREE_CONNECTION\n");
+            options &= ~FILE_CREATE_TREE_CONNECTION;
+        }
+
+        if (options & FILE_COMPLETE_IF_OPLOCKED) {
+            TRACE("    FILE_COMPLETE_IF_OPLOCKED\n");
+            options &= ~FILE_COMPLETE_IF_OPLOCKED;
+        }
+
+        if (options & FILE_NO_EA_KNOWLEDGE) {
+            TRACE("    FILE_NO_EA_KNOWLEDGE\n");
+            options &= ~FILE_NO_EA_KNOWLEDGE;
+        }
+        
+        if (options & FILE_OPEN_REMOTE_INSTANCE) {
+            TRACE("    FILE_OPEN_REMOTE_INSTANCE\n");
+            options &= ~FILE_OPEN_REMOTE_INSTANCE;
+        }
+
+        if (options & FILE_RANDOM_ACCESS) {
+            TRACE("    FILE_RANDOM_ACCESS\n");
+            options &= ~FILE_RANDOM_ACCESS;
+        }
+
+        if (options & FILE_DELETE_ON_CLOSE) {
+            TRACE("    FILE_DELETE_ON_CLOSE\n");
+            options &= ~FILE_DELETE_ON_CLOSE;
+        }
+
+        if (options & FILE_OPEN_BY_FILE_ID) {
+            TRACE("    FILE_OPEN_BY_FILE_ID\n");
+            options &= ~FILE_OPEN_BY_FILE_ID;
+        }
+
+        if (options & FILE_OPEN_FOR_BACKUP_INTENT) {
+            TRACE("    FILE_OPEN_FOR_BACKUP_INTENT\n");
+            options &= ~FILE_OPEN_FOR_BACKUP_INTENT;
+        }
+        
+        if (options & FILE_NO_COMPRESSION) {
+            TRACE("    FILE_NO_COMPRESSION\n");
+            options &= ~FILE_NO_COMPRESSION;
+        }
+
+#if NTDDI_VERSION >= NTDDI_WIN7
+        if (options & FILE_OPEN_REQUIRING_OPLOCK) {
+            TRACE("    FILE_OPEN_REQUIRING_OPLOCK\n");
+            options &= ~FILE_OPEN_REQUIRING_OPLOCK;
+        }
+        
+        if (options & FILE_DISALLOW_EXCLUSIVE) {
+            TRACE("    FILE_DISALLOW_EXCLUSIVE\n");
+            options &= ~FILE_DISALLOW_EXCLUSIVE;
+        }
+#endif
+
+        if (options & FILE_RESERVE_OPFILTER) {
+            TRACE("    FILE_RESERVE_OPFILTER\n");
+            options &= ~FILE_RESERVE_OPFILTER;
+        }
+
+        if (options & FILE_OPEN_REPARSE_POINT) {
+            TRACE("    FILE_OPEN_REPARSE_POINT\n");
+            options &= ~FILE_OPEN_REPARSE_POINT;
+        }
+        
+        if (options & FILE_OPEN_NO_RECALL) {
+            TRACE("    FILE_OPEN_NO_RECALL\n");
+            options &= ~FILE_OPEN_NO_RECALL;
+        }
+        
+        if (options & FILE_OPEN_FOR_FREE_SPACE_QUERY) {
+            TRACE("    FILE_OPEN_FOR_FREE_SPACE_QUERY\n");
+            options &= ~FILE_OPEN_FOR_FREE_SPACE_QUERY;
+        }
+        
+        if (options)
+            TRACE("    unknown options: %x\n", options);
+    } else {
+        TRACE("requested options: (none)\n");
+    }
+}
+
+static 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;
+    
+    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);
+        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 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");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    RtlCopyMemory(newii, ii, sizeof(INODE_ITEM));
+    
+    insert_tree_item(Vcb, subvol, inode, TYPE_INODE_ITEM, 0, newii, sizeof(INODE_ITEM), NULL, rollback);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL create_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;
+    
+    Irp->IoStatus.Information = 0;
+    
+    RequestedDisposition = ((Stack->Parameters.Create.Options >> 24) & 0xff);
+    options = Stack->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;
+    
+    if (options & FILE_DIRECTORY_FILE && RequestedDisposition == FILE_SUPERSEDE) {
+        WARN("error - supersede requested with FILE_DIRECTORY_FILE\n");
+        Status = STATUS_INVALID_PARAMETER;
+        goto exit;
+    }
+
+    FileObject = Stack->FileObject;
+
+    debug_create_options(options);
+    
+    switch (RequestedDisposition) {
+        case FILE_SUPERSEDE:
+            TRACE("requested disposition: FILE_SUPERSEDE\n");
+            break;
+            
+        case FILE_CREATE:
+            TRACE("requested disposition: FILE_CREATE\n");
+            break;
+            
+        case FILE_OPEN:
+            TRACE("requested disposition: FILE_OPEN\n");
+            break;
+            
+        case FILE_OPEN_IF:
+            TRACE("requested disposition: FILE_OPEN_IF\n");
+            break;
+            
+        case FILE_OVERWRITE:
+            TRACE("requested disposition: FILE_OVERWRITE\n");
+            break;
+            
+        case FILE_OVERWRITE_IF:
+            TRACE("requested disposition: FILE_OVERWRITE_IF\n");
+            break;
+            
+        default:
+            ERR("unknown disposition: %x\n", RequestedDisposition);
+            Status = STATUS_NOT_IMPLEMENTED;
+            goto exit;
+    }
+    
+    TRACE("(%.*S)\n", FileObject->FileName.Length / sizeof(WCHAR), FileObject->FileName.Buffer);
+    TRACE("FileObject = %p\n", FileObject);
+    
+    if (Vcb->readonly && (RequestedDisposition == FILE_SUPERSEDE || RequestedDisposition == FILE_CREATE || RequestedDisposition == FILE_OVERWRITE)) {
+        Status = STATUS_MEDIA_WRITE_PROTECTED;
+        goto exit;
+    }
+    
+    // FIXME - if Vcb->readonly or subvol readonly, don't allow the write ACCESS_MASK flags
+
+    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
+    Status = get_fcb(Vcb, &fcb, &FileObject->FileName, FileObject->RelatedFileObject ? FileObject->RelatedFileObject->FsContext : NULL, Stack->Flags & SL_OPEN_TARGET_DIRECTORY);
+    ExReleaseResourceLite(&Vcb->fcb_lock);
+    
+    if (NT_SUCCESS(Status) && fcb->deleted) {
+        free_fcb(fcb);
+        Status = STATUS_OBJECT_NAME_NOT_FOUND;
+    }
+    
+    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);
+            Status = STATUS_OBJECT_NAME_COLLISION;
+            free_fcb(fcb);
+            goto exit;
+        }
+    } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
+        if (RequestedDisposition == FILE_OPEN || RequestedDisposition == FILE_OVERWRITE) {
+            TRACE("file doesn't exist, returning STATUS_OBJECT_NAME_NOT_FOUND\n");
+            goto exit;
+        }
+    } else {
+        TRACE("get_fcb returned %08x\n", Status);
+        goto exit;
+    }
+    
+    if (NT_SUCCESS(Status)) { // file already exists
+        struct _fcb* sf;
+        
+        if (Vcb->readonly && RequestedDisposition == FILE_OVERWRITE_IF) {
+            Status = STATUS_MEDIA_WRITE_PROTECTED;
+            free_fcb(fcb);
+            goto exit;
+        }
+        
+        if (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);
+            goto exit;
+        }
+        
+        TRACE("deleted = %s\n", fcb->deleted ? "TRUE" : "FALSE");
+        
+        sf = fcb;
+        while (sf) {
+            if (sf->delete_on_close) {
+                WARN("could not open as deletion pending\n");
+                Status = STATUS_DELETE_PENDING;
+                free_fcb(fcb);
+                goto exit;
+            }
+            sf = sf->par;
+        }
+        
+        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);
+                goto exit;
+            }
+            
+            Status = STATUS_REPARSE;
+            Irp->IoStatus.Information = IO_REPARSE;
+            free_fcb(fcb);
+            goto exit;
+        }
+        
+        if (!SeAccessCheck(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);
+            goto exit;
+        }
+        
+        if (fcb->open_count > 0) {
+            Status = IoCheckShareAccess(access, Stack->Parameters.Create.ShareAccess, FileObject, &fcb->share_access, TRUE);
+            
+            if (!NT_SUCCESS(Status)) {
+                WARN("IoCheckShareAccess failed, returning %08x\n", Status);
+                free_fcb(fcb);
+                goto exit;
+            }
+        } else {
+            IoSetShareAccess(access, Stack->Parameters.Create.ShareAccess, FileObject, &fcb->share_access);
+        }
+
+        if (access & FILE_WRITE_DATA || options & FILE_DELETE_ON_CLOSE) {
+            if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForWrite)) {
+                Status = (options & FILE_DELETE_ON_CLOSE) ? STATUS_CANNOT_DELETE : STATUS_SHARING_VIOLATION;
+                free_fcb(fcb);
+                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) {
+                WARN("cannot overwrite readonly file\n");
+                Status = STATUS_ACCESS_DENIED;
+                free_fcb(fcb);
+                goto exit;
+            }
+    
+            // FIXME - where did we get this from?
+//             if (fcb->refcount > 1) {
+//                 WARN("cannot overwrite open file (fcb = %p, refcount = %u)\n", fcb, fcb->refcount);
+//                 Status = STATUS_ACCESS_DENIED;
+//                 free_fcb(fcb);
+//                 goto exit;
+//             }
+            InitializeListHead(&changed_sector_list);
+            
+            // FIXME - make sure not ADS!
+            Status = truncate_file(fcb, fcb->inode_item.st_size, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("truncate_file returned %08x\n", Status);
+                free_fcb(fcb);
+                goto exit;
+            }
+            
+            Status = update_inode_item(Vcb, fcb->subvol, fcb->inode, &fcb->inode_item, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("update_inode_item returned %08x\n", Status);
+                free_fcb(fcb);
+                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);
+            
+            if (RequestedDisposition == FILE_SUPERSEDE)
+                fcb->atts = Stack->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
+            else
+                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);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("set_xattr returned %08x\n", Status);
+                    free_fcb(fcb);
+                    goto exit;
+                }
+            } else
+                delete_xattr(Vcb, fcb->subvol, 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);
+            if (!NT_SUCCESS(Status)) {
+                ERR("consider_write returned %08x\n", Status);
+                free_fcb(fcb);
+                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;
+            }
+            
+            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);
+            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;
+        
+        ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);
+        if (!ccb) {
+            ERR("out of memory\n");
+            free_fcb(fcb);
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto exit;
+        }
+        
+        RtlZeroMemory(ccb, sizeof(*ccb));
+        ccb->NodeType = BTRFS_NODE_TYPE_CCB;
+        ccb->NodeSize = sizeof(ccb);
+        ccb->disposition = RequestedDisposition;
+        ccb->options = options;
+        ccb->query_dir_offset = 0;
+        RtlInitUnicodeString(&ccb->query_string, NULL);
+        ccb->has_wildcard = FALSE;
+        ccb->specific_file = FALSE;
+        
+        FileObject->FsContext2 = ccb;
+        
+        FileObject->SectionObjectPointer = &fcb->nonpaged->segment_object;
+        
+        if (NT_SUCCESS(Status)) {
+            switch (RequestedDisposition) {
+                case FILE_SUPERSEDE:
+                    Irp->IoStatus.Information = FILE_SUPERSEDED;
+                    break;
+                    
+                case FILE_OPEN:
+                case FILE_OPEN_IF:
+                    Irp->IoStatus.Information = FILE_OPENED;
+                    break;
+                    
+                case FILE_OVERWRITE:
+                case FILE_OVERWRITE_IF:
+                    Irp->IoStatus.Information = FILE_OVERWRITTEN;
+                    break;
+            }
+        }
+        
+        InterlockedIncrement(&fcb->open_count);
+    } 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))
+        FileObject->Flags |= FO_CACHE_SUPPORTED;
+    
+exit:
+    if (NT_SUCCESS(Status)) {
+        if (!FileObject->Vpb)
+            FileObject->Vpb = DeviceObject->Vpb;
+    } else {
+        if (Status != STATUS_OBJECT_NAME_NOT_FOUND && Status != STATUS_OBJECT_PATH_NOT_FOUND)
+            TRACE("returning %08x\n", Status);
+    }
+    
+    return Status;
+}
+
+NTSTATUS STDCALL drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp;
+    device_extension* Vcb = NULL;
+    BOOL top_level;
+    LIST_ENTRY rollback;
+    
+    TRACE("create (flags = %x)\n", Irp->Flags);
+    
+    InitializeListHead(&rollback);
+    
+    FsRtlEnterFileSystem();
+    
+    top_level = is_top_level(Irp);
+    
+    /* return success if just called for FS device object */
+    if (DeviceObject == devobj)  {
+        TRACE("create called for FS device object\n");
+        
+        Irp->IoStatus.Information = FILE_OPENED;
+        Status = STATUS_SUCCESS;
+
+        goto exit;
+    }
+    
+    Vcb = DeviceObject->DeviceExtension;
+    ExAcquireResourceSharedLite(&Vcb->load_lock, TRUE);
+    
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    
+    if (IrpSp->Flags != 0) {
+        UINT32 flags = IrpSp->Flags;
+        
+        TRACE("flags:\n");
+        
+        if (flags & SL_CASE_SENSITIVE) {
+            TRACE("SL_CASE_SENSITIVE\n");
+            flags &= ~SL_CASE_SENSITIVE;
+        }
+        
+        if (flags & SL_FORCE_ACCESS_CHECK) {
+            TRACE("SL_FORCE_ACCESS_CHECK\n");
+            flags &= ~SL_FORCE_ACCESS_CHECK;
+        }
+        
+        if (flags & SL_OPEN_PAGING_FILE) {
+            TRACE("SL_OPEN_PAGING_FILE\n");
+            flags &= ~SL_OPEN_PAGING_FILE;
+        }
+        
+        if (flags & SL_OPEN_TARGET_DIRECTORY) {
+            TRACE("SL_OPEN_TARGET_DIRECTORY\n");
+            flags &= ~SL_OPEN_TARGET_DIRECTORY;
+        }
+        
+        if (flags & SL_STOP_ON_SYMLINK) {
+            TRACE("SL_STOP_ON_SYMLINK\n");
+            flags &= ~SL_STOP_ON_SYMLINK;
+        }
+        
+        if (flags)
+            WARN("unknown flags: %x\n", flags);
+    } else {
+        TRACE("flags: (none)\n");
+    }
+    
+//     Vpb = DeviceObject->DeviceExtension;
+    
+//     TRACE("create called for something other than FS device object\n");
+    
+    // opening volume
+    // FIXME - also check if RelatedFileObject is Vcb
+    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;
+        
+        TRACE("open operation for volume\n");
+
+        if (RequestedDisposition != FILE_OPEN &&
+            RequestedDisposition != FILE_OPEN_IF)
+        {
+            Status = STATUS_ACCESS_DENIED;
+            goto exit;
+        }
+
+        if (RequestedOptions & FILE_DIRECTORY_FILE)
+        {
+            Status = STATUS_NOT_A_DIRECTORY;
+            goto exit;
+        }
+
+        Vcb->volume_fcb->refcount++;
+#ifdef DEBUG_FCB_REFCOUNTS
+        WARN("fcb %p: refcount now %i (volume)\n", Vcb->volume_fcb, Vcb->volume_fcb->refcount);
+#endif
+        attach_fcb_to_fileobject(Vcb, Vcb->volume_fcb, IrpSp->FileObject);
+// //         NtfsAttachFCBToFileObject(DeviceExt, DeviceExt->VolumeFcb, FileObject);
+// //         DeviceExt->VolumeFcb->RefCount++;
+// 
+        Irp->IoStatus.Information = FILE_OPENED;
+        Status = STATUS_SUCCESS;
+    } else {
+        BOOL exclusive;
+        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);
+        }
+        
+        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;
+
+        acquire_tree_lock(Vcb, exclusive); 
+        
+//         ExAcquireResourceExclusiveLite(&Vpb->DirResource, TRUE);
+    //     Status = NtfsCreateFile(DeviceObject,
+    //                             Irp);
+        Status = create_file(DeviceObject, Irp, &rollback);
+//         ExReleaseResourceLite(&Vpb->DirResource);
+        
+        if (exclusive && !NT_SUCCESS(Status))
+            do_rollback(Vcb, &rollback);
+        else
+            clear_rollback(&rollback);
+        
+        release_tree_lock(Vcb, exclusive);
+        
+//         Status = STATUS_ACCESS_DENIED;
+    }
+    
+exit:
+    Irp->IoStatus.Status = Status;
+    IoCompleteRequest( Irp, NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT );
+//     IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+    
+    TRACE("create returning %08x\n", Status);
+    
+    ExReleaseResourceLite(&Vcb->load_lock);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
diff --git a/reactos/drivers/filesystems/btrfs/dirctrl.c b/reactos/drivers/filesystems/btrfs/dirctrl.c
new file mode 100644 (file)
index 0000000..9f062a0
--- /dev/null
@@ -0,0 +1,879 @@
+/* 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"
+
+enum DirEntryType {
+    DirEntryType_File,
+    DirEntryType_Self,
+    DirEntryType_Parent
+};
+
+typedef struct {
+    KEY key;
+    char* name;
+    ULONG namelen;
+    UINT8 type;
+    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) {
+    PIO_STACK_LOCATION IrpSp;
+    UINT32 needed;
+    UINT64 inode;
+    INODE_ITEM ii;
+    NTSTATUS Status;
+    ULONG stringlen;
+    BOOL dotfile;
+    
+    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;
+        
+        if (!r) {
+            ERR("could not find root %llx\n", de->key.obj_id);
+            return STATUS_OBJECT_NAME_NOT_FOUND;
+        }
+        
+        inode = SUBVOL_ROOT_INODE;
+    } else {
+        inode = de->key.obj_id;
+    }
+    
+    if (IrpSp->Parameters.QueryDirectory.FileInformationClass != FileNamesInformation) { // FIXME - object ID and reparse point classes too?
+        switch (de->dir_entry_type) {
+            case DirEntryType_File:
+            {
+                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 (c->subvol == r && c->inode == inode) {
+                        ii = c->inode_item;
+                        found = TRUE;
+                        break;
+                    }
+                    
+                    le = le->Flink;
+                }
+                
+                if (!found) {
+                    KEY searchkey;
+                    traverse_ptr tp;
+                    
+                    searchkey.obj_id = inode;
+                    searchkey.obj_type = TYPE_INODE_ITEM;
+                    searchkey.offset = 0xffffffffffffffff;
+                    
+                    Status = find_item(fcb->Vcb, r, &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 inode item for inode %llx in root %llx\n", inode, r->id);
+                        free_traverse_ptr(&tp);
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                    
+                    RtlZeroMemory(&ii, sizeof(INODE_ITEM));
+                    
+                    if (tp.item->size > 0)
+                        RtlCopyMemory(&ii, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
+                    
+                    free_traverse_ptr(&tp);
+                }
+                
+                break;
+            }
+            
+            case DirEntryType_Self:
+                ii = fcb->inode_item;
+                r = fcb->subvol;
+                inode = fcb->inode;
+                break;
+                
+            case DirEntryType_Parent:
+                ii = fcb->par->inode_item;
+                r = fcb->par->subvol;
+                inode = fcb->par->inode;
+                break;
+        }
+    }
+    
+    // FICs which return the filename
+    if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation ||
+        IrpSp->Parameters.QueryDirectory.FileInformationClass == FileDirectoryInformation ||
+        IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation ||
+        IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation ||
+        IrpSp->Parameters.QueryDirectory.FileInformationClass == FileNamesInformation) {
+        
+        Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, de->name, de->namelen);
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+            return Status;
+        }
+    }
+    
+    dotfile = de->name[0] == '.' && (de->name[1] != '.' || de->name[2] != 0) && (de->name[1] != 0);
+    
+    switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {
+        case FileBothDirectoryInformation:
+        {
+            FILE_BOTH_DIR_INFORMATION* fbdi = buf;
+            
+            TRACE("FileBothDirectoryInformation\n");
+            
+            needed = sizeof(FILE_BOTH_DIR_INFORMATION) - sizeof(WCHAR) + stringlen;
+            
+            if (needed > *len) {
+                WARN("buffer overflow - %u > %u\n", needed, *len);
+                return STATUS_BUFFER_OVERFLOW;
+            }
+           
+            fbdi->NextEntryOffset = 0;
+            fbdi->FileIndex = 0;
+            fbdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);
+            fbdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);
+            fbdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);
+            fbdi->ChangeTime.QuadPart = 0;
+            fbdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;
+            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->ShortNameLength = 0;
+//             fibdi->ShortName[12];
+            
+            Status = RtlUTF8ToUnicodeN(fbdi->FileName, stringlen, &stringlen, de->name, de->namelen);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                return Status;
+            }
+            
+            *len -= needed;
+            
+            return STATUS_SUCCESS;
+        }
+
+        case FileDirectoryInformation:
+        {
+            FILE_DIRECTORY_INFORMATION* fdi = buf;
+            
+            TRACE("FileDirectoryInformation\n");
+            
+            needed = sizeof(FILE_DIRECTORY_INFORMATION) - sizeof(WCHAR) + stringlen;
+            
+            if (needed > *len) {
+                WARN("buffer overflow - %u > %u\n", needed, *len);
+                return STATUS_BUFFER_OVERFLOW;
+            }
+           
+            fdi->NextEntryOffset = 0;
+            fdi->FileIndex = 0;
+            fdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);
+            fdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);
+            fdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);
+            fdi->ChangeTime.QuadPart = 0;
+            fdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;
+            fdi->AllocationSize.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_blocks;
+            fdi->FileAttributes = get_file_attributes(fcb->Vcb, &ii, r, inode, de->type, dotfile, FALSE);
+            fdi->FileNameLength = stringlen;
+            
+            Status = RtlUTF8ToUnicodeN(fdi->FileName, stringlen, &stringlen, de->name, de->namelen);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                return Status;
+            }
+            
+            *len -= needed;
+            
+            return STATUS_SUCCESS;
+        }
+            
+        case FileFullDirectoryInformation:
+        {
+            FILE_FULL_DIR_INFORMATION* ffdi = buf;
+            
+            TRACE("FileFullDirectoryInformation\n");
+            
+            needed = sizeof(FILE_FULL_DIR_INFORMATION) - sizeof(WCHAR) + stringlen;
+            
+            if (needed > *len) {
+                WARN("buffer overflow - %u > %u\n", needed, *len);
+                return STATUS_BUFFER_OVERFLOW;
+            }
+           
+            ffdi->NextEntryOffset = 0;
+            ffdi->FileIndex = 0;
+            ffdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);
+            ffdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);
+            ffdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);
+            ffdi->ChangeTime.QuadPart = 0;
+            ffdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;
+            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;
+            
+            Status = RtlUTF8ToUnicodeN(ffdi->FileName, stringlen, &stringlen, de->name, de->namelen);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                return Status;
+            }
+            
+            *len -= needed;
+            
+            return STATUS_SUCCESS;
+        }
+
+        case FileIdBothDirectoryInformation:
+        {
+            FILE_ID_BOTH_DIR_INFORMATION* fibdi = buf;
+            
+            TRACE("FileIdBothDirectoryInformation\n");
+            
+            needed = sizeof(FILE_ID_BOTH_DIR_INFORMATION) - sizeof(WCHAR) + stringlen;
+            
+            if (needed > *len) {
+                WARN("buffer overflow - %u > %u\n", needed, *len);
+                return STATUS_BUFFER_OVERFLOW;
+            }
+            
+//             if (!buf)
+//                 return STATUS_INVALID_POINTER;
+            
+            fibdi->NextEntryOffset = 0;
+            fibdi->FileIndex = 0;
+            fibdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);
+            fibdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);
+            fibdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);
+            fibdi->ChangeTime.QuadPart = 0;
+            fibdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;
+            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->ShortNameLength = 0;
+//             fibdi->ShortName[12];
+            fibdi->FileId.QuadPart = inode;
+            
+            Status = RtlUTF8ToUnicodeN(fibdi->FileName, stringlen, &stringlen, de->name, de->namelen);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                return Status;
+            }
+            
+            *len -= needed;
+            
+            return STATUS_SUCCESS;
+        }
+
+        case FileIdFullDirectoryInformation:
+            FIXME("STUB: FileIdFullDirectoryInformation\n");
+            break;
+
+        case FileNamesInformation:
+        {
+            FILE_NAMES_INFORMATION* fni = buf;
+            
+            TRACE("FileNamesInformation\n");
+            
+            needed = sizeof(FILE_NAMES_INFORMATION) - sizeof(WCHAR) + stringlen;
+            
+            if (needed > *len) {
+                WARN("buffer overflow - %u > %u\n", needed, *len);
+                return STATUS_BUFFER_OVERFLOW;
+            }
+            
+            fni->NextEntryOffset = 0;
+            fni->FileIndex = 0;
+            fni->FileNameLength = stringlen;
+            
+            Status = RtlUTF8ToUnicodeN(fni->FileName, stringlen, &stringlen, de->name, de->namelen);
+
+            if (!NT_SUCCESS(Status)) {
+                ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+                return Status;
+            }
+            
+            *len -= needed;
+            
+            return STATUS_SUCCESS;
+        }
+
+        case FileObjectIdInformation:
+            FIXME("STUB: FileObjectIdInformation\n");
+            return STATUS_NOT_IMPLEMENTED;
+
+        case FileQuotaInformation:
+            FIXME("STUB: FileQuotaInformation\n");
+            return STATUS_NOT_IMPLEMENTED;
+
+        case FileReparsePointInformation:
+            FIXME("STUB: FileReparsePointInformation\n");
+            return STATUS_NOT_IMPLEMENTED;
+
+        default:
+            WARN("Unknown FileInformationClass %u\n", IrpSp->Parameters.QueryDirectory.FileInformationClass);
+            return STATUS_NOT_IMPLEMENTED;
+    }
+    
+    return STATUS_NO_MORE_FILES;
+}
+
+static NTSTATUS STDCALL next_dir_entry(fcb* fcb, 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 (*offset == 0) {
+            de->key.obj_id = fcb->inode;
+            de->key.obj_type = TYPE_INODE_ITEM;
+            de->key.offset = 0;
+            de->dir_entry_type = DirEntryType_Self;
+            de->name = ".";
+            de->namelen = 1;
+            de->type = BTRFS_TYPE_DIRECTORY;
+            
+            *offset = 1;
+            
+            return STATUS_SUCCESS;
+        } else if (*offset == 1) {
+            de->key.obj_id = fcb->par->inode;
+            de->key.obj_type = TYPE_INODE_ITEM;
+            de->key.offset = 0;
+            de->dir_entry_type = DirEntryType_Parent;
+            de->name = "..";
+            de->namelen = 2;
+            de->type = BTRFS_TYPE_DIRECTORY;
+            
+            *offset = 2;
+            
+            return STATUS_SUCCESS;
+        }
+    }
+    
+    if (!tp->tree) {
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_DIR_INDEX;
+        searchkey.offset = *offset;
+        
+        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;
+        }
+        
+        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(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);
+            }
+        }
+        
+        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;
+        }
+        
+        *offset = tp->item->key.offset + 1;
+        
+        di = (DIR_ITEM*)tp->item->data;
+        
+        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;
+        }
+        
+        de->key = di->key;
+        de->name = di->name;
+        de->namelen = di->n;
+        de->type = di->type;
+        de->dir_entry_type = DirEntryType_File;
+        
+        return STATUS_SUCCESS;
+    } 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;
+                
+                di = (DIR_ITEM*)tp->item->data;
+                
+                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;
+                }
+        
+                de->key = di->key;
+                de->name = di->name;
+                de->namelen = di->n;
+                de->type = di->type;
+                de->dir_entry_type = DirEntryType_File;
+                
+                return STATUS_SUCCESS;
+            } else {
+                free_traverse_ptr(&next_tp);
+                return STATUS_NO_MORE_FILES;
+            }
+        } else
+            return STATUS_NO_MORE_FILES;
+    }
+}
+
+static NTSTATUS STDCALL query_directory(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS Status, status2;
+    fcb* fcb;
+    ccb* ccb;
+    void* buf;
+    UINT8 *curitem, *lastitem;
+    LONG length;
+    ULONG count;
+    BOOL has_wildcard = FALSE, specific_file = FALSE;
+//     UINT64 num_reads_orig;
+    traverse_ptr tp;
+    dir_entry de;
+    
+    TRACE("query directory\n");
+    
+//     get_uid(); // TESTING
+    
+//     num_reads_orig = num_reads;
+    
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    fcb = IrpSp->FileObject->FsContext;
+    ccb = IrpSp->FileObject->FsContext2;
+    
+    acquire_tree_lock(fcb->Vcb, FALSE);
+    
+    TRACE("%.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    
+    if (IrpSp->Flags == 0) {
+        TRACE("QD flags: (none)\n");
+    } else {
+        ULONG flags = IrpSp->Flags;
+        
+        TRACE("QD flags:\n");
+        
+        if (flags & SL_INDEX_SPECIFIED) {
+            TRACE("    SL_INDEX_SPECIFIED\n");
+            flags &= ~SL_INDEX_SPECIFIED;
+        }
+
+        if (flags & SL_RESTART_SCAN) {
+            TRACE("    SL_RESTART_SCAN\n");
+            flags &= ~SL_RESTART_SCAN;
+        }
+        
+        if (flags & SL_RETURN_SINGLE_ENTRY) {
+            TRACE("    SL_RETURN_SINGLE_ENTRY\n");
+            flags &= ~SL_RETURN_SINGLE_ENTRY;
+        }
+
+        if (flags != 0)
+            TRACE("    unknown flags: %u\n", flags);
+    }
+    
+    if (IrpSp->Flags & SL_RESTART_SCAN) {
+        ccb->query_dir_offset = 0;
+        
+        if (ccb->query_string.Buffer) {
+            RtlFreeUnicodeString(&ccb->query_string);
+            ccb->query_string.Buffer = NULL;
+        }
+    }
+    
+    if (IrpSp->Parameters.QueryDirectory.FileName) {
+//         int i;
+//         WCHAR* us;
+        
+        TRACE("QD filename: %.*S\n", IrpSp->Parameters.QueryDirectory.FileName->Length / sizeof(WCHAR), IrpSp->Parameters.QueryDirectory.FileName->Buffer);
+        
+//         if (IrpSp->Parameters.QueryDirectory.FileName->Length > 1 || IrpSp->Parameters.QueryDirectory.FileName->Buffer[0] != '*') {
+//             specific_file = TRUE;
+//             for (i = 0; i < IrpSp->Parameters.QueryDirectory.FileName->Length; i++) {
+//                 if (IrpSp->Parameters.QueryDirectory.FileName->Buffer[i] == '?' || IrpSp->Parameters.QueryDirectory.FileName->Buffer[i] == '*') {
+//                     has_wildcard = TRUE;
+//                     specific_file = FALSE;
+//                 }
+//             }
+//         }
+        has_wildcard = TRUE;
+
+        if (ccb->query_string.Buffer)
+            RtlFreeUnicodeString(&ccb->query_string);
+        
+//         us = ExAllocatePoolWithTag(PagedPool, IrpSp->Parameters.QueryDirectory.FileName->Length + sizeof(WCHAR), ALLOC_TAG);
+//         RtlCopyMemory(us, IrpSp->Parameters.QueryDirectory.FileName->Buffer, IrpSp->Parameters.QueryDirectory.FileName->Length);
+//         us[IrpSp->Parameters.QueryDirectory.FileName->Length / sizeof(WCHAR)] = 0;
+        
+//         ccb->query_string = ExAllocatePoolWithTag(NonPagedPool, utf16_to_utf8_len(us), ALLOC_TAG);
+//         utf16_to_utf8(us, ccb->query_string);
+        
+//         ccb->query_string.Buffer = ExAllocatePoolWithTag(PagedPool, IrpSp->Parameters.QueryDirectory.FileName->Length, ALLOC_TAG);
+//         RtlCopyMemory(ccb->query_string.Buffer, IrpSp->Parameters.QueryDirectory.FileName->Buffer,
+//                       IrpSp->Parameters.QueryDirectory.FileName->Length);
+//         ccb->query_string.Length = IrpSp->Parameters.QueryDirectory.FileName->Length;
+//         ccb->query_string.MaximumLength = IrpSp->Parameters.QueryDirectory.FileName->Length;
+            RtlUpcaseUnicodeString(&ccb->query_string, IrpSp->Parameters.QueryDirectory.FileName, TRUE);
+          
+        ccb->has_wildcard = has_wildcard;
+        ccb->specific_file = specific_file;
+        
+//         ExFreePool(us);
+    } else {
+        has_wildcard = ccb->has_wildcard;
+        specific_file = ccb->specific_file;
+    }
+    
+    if (ccb->query_string.Buffer) {
+        TRACE("query string = %.*S\n", ccb->query_string.Length / sizeof(WCHAR), ccb->query_string.Buffer);
+    }
+    
+    tp.tree = NULL;
+    Status = next_dir_entry(fcb, &ccb->query_dir_offset, &de, &tp);
+    
+    if (!NT_SUCCESS(Status)) {
+        if (Status == STATUS_NO_MORE_FILES && IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)
+            Status = STATUS_NO_SUCH_FILE;
+        goto end;
+    }
+
+    // FIXME - make this work
+//     if (specific_file) {
+//         UINT64 filesubvol, fileinode;
+//         WCHAR* us;
+//         
+//         us = ExAllocatePoolWithTag(NonPagedPool, IrpSp->Parameters.QueryDirectory.FileName->Length + sizeof(WCHAR), ALLOC_TAG);
+//         RtlCopyMemory(us, IrpSp->Parameters.QueryDirectory.FileName->Buffer, IrpSp->Parameters.QueryDirectory.FileName->Length);
+//         us[IrpSp->Parameters.QueryDirectory.FileName->Length / sizeof(WCHAR)] = 0;
+//         
+//         if (!find_file_in_dir(fcb->Vcb, us, fcb->subvolume, fcb->inode, &filesubvol, &fileinode)) {
+//             ExFreePool(us);
+//             return STATUS_NO_MORE_FILES;
+//         }
+//         
+//         ExFreePool(us);
+//     }
+    
+    buf = map_user_buffer(Irp);
+    
+    if (Irp->MdlAddress && !buf) {
+        ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        if (tp.tree) free_traverse_ptr(&tp);
+        goto end;
+    }
+    
+    length = IrpSp->Parameters.QueryDirectory.Length;
+    
+//     if (specific_file) {
+    if (has_wildcard) {
+        WCHAR* uni_fn;
+        ULONG stringlen;
+        UNICODE_STRING di_uni_fn;
+        
+        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;
+        }
+        
+        Status = RtlUTF8ToUnicodeN(uni_fn, stringlen, &stringlen, de.name, de.namelen);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
+            if (tp.tree) free_traverse_ptr(&tp);
+            goto end;
+        }
+        
+        di_uni_fn.Length = di_uni_fn.MaximumLength = stringlen;
+        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);
+            
+            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;
+                }
+                
+                Status = RtlUTF8ToUnicodeN(uni_fn, stringlen, &stringlen, de.name, de.namelen);
+                
+                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)
+                    Status = STATUS_NO_SUCH_FILE;
+                
+                goto end;
+            }
+        }
+        
+        ExFreePool(uni_fn);
+    }
+    
+    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);
+    
+    count = 0;
+    if (NT_SUCCESS(Status) && !(IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) && !specific_file) {
+        lastitem = (UINT8*)buf;
+        
+        while (length > 0) {
+            switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {
+                case FileBothDirectoryInformation:
+                case FileDirectoryInformation:
+                case FileIdBothDirectoryInformation:
+                case FileFullDirectoryInformation:
+                    length -= length % 8;
+                    break;
+                    
+                case FileNamesInformation:
+                    length -= length % 4;
+                    break;
+                    
+                default:
+                    WARN("unhandled file information class %u\n", IrpSp->Parameters.QueryDirectory.FileInformationClass);
+                    break;
+            }
+            
+            if (length > 0) {
+                WCHAR* uni_fn = NULL;
+                UNICODE_STRING di_uni_fn;
+                
+                Status = next_dir_entry(fcb, &ccb->query_dir_offset, &de, &tp);
+                if (NT_SUCCESS(Status)) {
+                    if (has_wildcard) {
+                        ULONG stringlen;
+                        
+                        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;
+                        }
+                        
+                        Status = RtlUTF8ToUnicodeN(uni_fn, stringlen, &stringlen, de.name, de.namelen);
+                        
+                        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;
+                    }
+                    
+                    if (!has_wildcard || FsRtlIsNameInExpression(&ccb->query_string, &di_uni_fn, TRUE, NULL)) {
+                        curitem = (UINT8*)buf + IrpSp->Parameters.QueryDirectory.Length - length;
+                        count++;
+                        
+                        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);
+                        
+                        if (NT_SUCCESS(status2)) {
+                            ULONG* lastoffset = (ULONG*)lastitem;
+                            
+                            *lastoffset = (ULONG)(curitem - lastitem);
+                            
+                            lastitem = curitem;
+                        } else {
+                            if (uni_fn) ExFreePool(uni_fn);
+
+                            break;
+                        }
+                    }
+                    
+                    if (uni_fn) {
+                        ExFreePool(uni_fn);
+                        uni_fn = NULL;
+                    }
+                } else {
+                    if (Status == STATUS_NO_MORE_FILES)
+                        Status = STATUS_SUCCESS;
+                    
+                    break;
+                }
+            } else
+                break;
+        }
+    }
+    
+    Irp->IoStatus.Information = IrpSp->Parameters.QueryDirectory.Length - length;
+    
+    if (tp.tree) free_traverse_ptr(&tp);
+    
+end:
+    release_tree_lock(fcb->Vcb, FALSE);
+    
+//     TRACE("query directory performed %u reads\n", (UINT32)(num_reads-num_reads_orig));
+    TRACE("returning %08x\n", Status);
+
+    return Status;
+}
+
+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;
+    NTSTATUS Status;
+//     WCHAR fn[MAX_PATH];
+    
+    TRACE("IRP_MN_NOTIFY_CHANGE_DIRECTORY\n");
+    
+    acquire_tree_lock(fcb->Vcb, FALSE);
+    
+    if (fcb->type != BTRFS_TYPE_DIRECTORY) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+    
+    // FIXME - raise exception if FCB marked for deletion?
+    
+    TRACE("%.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    
+    FsRtlNotifyFullChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext2, (PSTRING)&fcb->full_filename,
+        IrpSp->Flags & SL_WATCH_TREE, FALSE, IrpSp->Parameters.NotifyDirectory.CompletionFilter, Irp, NULL, NULL);
+    
+    Status = STATUS_PENDING;
+    
+end:
+    release_tree_lock(fcb->Vcb, FALSE);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS Status;
+    ULONG func;
+    BOOL top_level;
+
+    TRACE("directory control\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    
+    Irp->IoStatus.Information = 0;
+    
+    func = IrpSp->MinorFunction;
+    
+    switch (func) {
+        case IRP_MN_NOTIFY_CHANGE_DIRECTORY:
+            Status = notify_change_directory(DeviceObject->DeviceExtension, Irp);
+            break;
+            
+        case IRP_MN_QUERY_DIRECTORY:
+            Status = query_directory(DeviceObject, Irp);
+            break;
+            
+        default:
+            WARN("unknown minor %u\n", func);
+            Status = STATUS_NOT_IMPLEMENTED;
+            Irp->IoStatus.Status = Status;
+            break;
+    }
+
+    if (func != IRP_MN_NOTIFY_CHANGE_DIRECTORY || Status != STATUS_PENDING) {
+        Irp->IoStatus.Status = Status;
+        IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+    }
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
diff --git a/reactos/drivers/filesystems/btrfs/fastio.c b/reactos/drivers/filesystems/btrfs/fastio.c
new file mode 100644 (file)
index 0000000..acb13a1
--- /dev/null
@@ -0,0 +1,182 @@
+/* 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 <ntifs.h>
+#include "btrfs_drv.h"
+
+FAST_IO_DISPATCH FastIoDispatch;
+
+static void STDCALL acquire_file_for_create_section(PFILE_OBJECT FileObject) {
+    TRACE("STUB: acquire_file_for_create_section\n");
+}
+
+static void STDCALL release_file_for_create_section(PFILE_OBJECT FileObject) {
+    TRACE("STUB: release_file_for_create_section\n");
+}
+
+static BOOLEAN STDCALL fast_query_basic_info(PFILE_OBJECT FileObject, BOOLEAN wait, PFILE_BASIC_INFORMATION buf,
+                                     PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {
+    
+    TRACE("STUB: fast_query_basic_info\n");
+    
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_query_standard_info(PFILE_OBJECT FileObject, BOOLEAN wait, PFILE_STANDARD_INFORMATION buf,
+                                     PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {
+    
+    TRACE("STUB: fast_query_standard_info\n");
+    
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_query_open(PIRP Irp, PFILE_NETWORK_OPEN_INFORMATION  NetworkInformation, PDEVICE_OBJECT DeviceObject) {
+    TRACE("STUB: fast_io_query_open\n");
+    
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_check_if_possible(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait,
+                                         ULONG LockKey, BOOLEAN CheckForReadOperation, PIO_STATUS_BLOCK IoStatus,
+                                         PDEVICE_OBJECT DeviceObject) {
+    fcb* fcb = FileObject->FsContext;
+    LARGE_INTEGER len2;
+    
+    TRACE("(%p, %llx, %x, %x, %x, %x, %p, %p)\n", FileObject, FileOffset->QuadPart, Length, Wait, LockKey, CheckForReadOperation, IoStatus, DeviceObject);
+    
+    len2.QuadPart = Length;
+    
+    if (CheckForReadOperation) {
+        if (FsRtlFastCheckLockForRead(&fcb->lock, FileOffset, &len2, LockKey, FileObject, PsGetCurrentProcess()))
+            return TRUE;
+    } else {
+        if (!fcb->Vcb->readonly && !(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) && FsRtlFastCheckLockForWrite(&fcb->lock, FileOffset, &len2, LockKey, FileObject, PsGetCurrentProcess()))
+            return TRUE;
+    }
+    
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_lock(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PLARGE_INTEGER Length, PEPROCESS ProcessId, ULONG Key, BOOLEAN FailImmediately, BOOLEAN ExclusiveLock, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_lock\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_unlock_single(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PLARGE_INTEGER Length, PEPROCESS ProcessId, ULONG Key, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_unlock_single\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_unlock_all(PFILE_OBJECT FileObject, PEPROCESS ProcessId, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_unlock_all\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_unlock_all_by_key(PFILE_OBJECT FileObject, PVOID ProcessId, ULONG Key, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_unlock_all_by_key\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_device_control(PFILE_OBJECT FileObject, BOOLEAN Wait, PVOID InputBuffer OPTIONAL, ULONG InputBufferLength, PVOID OutputBuffer OPTIONAL, ULONG OutputBufferLength, ULONG IoControlCode, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_device_control\n");
+    return FALSE;
+}
+
+static VOID STDCALL fast_io_detach_device(PDEVICE_OBJECT SourceDevice, PDEVICE_OBJECT TargetDevice){
+    TRACE("STUB: fast_io_detach_device\n");
+}
+
+static BOOLEAN STDCALL fast_io_query_network_open_info(PFILE_OBJECT FileObject, BOOLEAN Wait, struct _FILE_NETWORK_OPEN_INFORMATION *Buffer, struct _IO_STATUS_BLOCK *IoStatus, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_query_network_open_info\n");
+    return FALSE;
+}
+
+static NTSTATUS STDCALL fast_io_acquire_for_mod_write(PFILE_OBJECT FileObject, PLARGE_INTEGER EndingOffset, struct _ERESOURCE **ResourceToRelease, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_acquire_for_mod_write\n");
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+static BOOLEAN STDCALL fast_io_read_compressed(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, ULONG LockKey, PVOID Buffer, PMDL *MdlChain, PIO_STATUS_BLOCK IoStatus, struct _COMPRESSED_DATA_INFO *CompressedDataInfo, ULONG CompressedDataInfoLength, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_read_compressed\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_write_compressed(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, ULONG LockKey, PVOID Buffer, PMDL *MdlChain, PIO_STATUS_BLOCK IoStatus, struct _COMPRESSED_DATA_INFO *CompressedDataInfo, ULONG CompressedDataInfoLength, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_write_compressed\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_mdl_read_complete_compressed(PFILE_OBJECT FileObject, PMDL MdlChain, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_mdl_read_complete_compressed\n");
+    return FALSE;
+}
+
+static BOOLEAN STDCALL fast_io_mdl_write_complete_compressed(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PMDL MdlChain, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_mdl_write_complete_compressed\n");
+    return FALSE;
+}
+
+static NTSTATUS STDCALL fast_io_release_for_mod_write(PFILE_OBJECT FileObject, struct _ERESOURCE *ResourceToRelease, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_release_for_mod_write\n");
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS STDCALL fast_io_acquire_for_ccflush(PFILE_OBJECT FileObject, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_acquire_for_ccflush\n");
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS STDCALL fast_io_release_for_ccflush(PFILE_OBJECT FileObject, PDEVICE_OBJECT DeviceObject){
+    TRACE("STUB: fast_io_release_for_ccflush\n");
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+void __stdcall init_fast_io_dispatch(FAST_IO_DISPATCH** fiod) {
+    RtlZeroMemory(&FastIoDispatch, sizeof(FastIoDispatch));
+
+    FastIoDispatch.SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);
+
+    FastIoDispatch.FastIoCheckIfPossible = fast_io_check_if_possible;
+    FastIoDispatch.FastIoRead = FsRtlCopyRead;
+    FastIoDispatch.FastIoWrite = FsRtlCopyWrite;
+    FastIoDispatch.FastIoQueryBasicInfo = fast_query_basic_info;
+    FastIoDispatch.FastIoQueryStandardInfo = fast_query_standard_info;
+    FastIoDispatch.FastIoLock = fast_io_lock;
+    FastIoDispatch.FastIoUnlockSingle = fast_io_unlock_single;
+    FastIoDispatch.FastIoUnlockAll = fast_io_unlock_all;
+    FastIoDispatch.FastIoUnlockAllByKey = fast_io_unlock_all_by_key;
+    FastIoDispatch.FastIoDeviceControl = fast_io_device_control;
+    FastIoDispatch.AcquireFileForNtCreateSection = acquire_file_for_create_section;
+    FastIoDispatch.ReleaseFileForNtCreateSection = release_file_for_create_section;
+    FastIoDispatch.FastIoDetachDevice = fast_io_detach_device;
+    FastIoDispatch.FastIoQueryNetworkOpenInfo = fast_io_query_network_open_info;
+    FastIoDispatch.AcquireForModWrite = fast_io_acquire_for_mod_write;
+    FastIoDispatch.MdlRead = FsRtlMdlReadDev;
+    FastIoDispatch.MdlReadComplete = FsRtlMdlReadCompleteDev;
+    FastIoDispatch.PrepareMdlWrite = FsRtlPrepareMdlWriteDev;
+    FastIoDispatch.MdlWriteComplete = FsRtlMdlWriteCompleteDev;
+    FastIoDispatch.FastIoReadCompressed = fast_io_read_compressed;
+    FastIoDispatch.FastIoWriteCompressed = fast_io_write_compressed;
+    FastIoDispatch.MdlReadCompleteCompressed = fast_io_mdl_read_complete_compressed;
+    FastIoDispatch.MdlWriteCompleteCompressed = fast_io_mdl_write_complete_compressed;
+    FastIoDispatch.FastIoQueryOpen = fast_io_query_open;
+    FastIoDispatch.ReleaseForModWrite = fast_io_release_for_mod_write;
+    FastIoDispatch.AcquireForCcFlush = fast_io_acquire_for_ccflush;
+    FastIoDispatch.ReleaseForCcFlush = fast_io_release_for_ccflush;
+    
+    *fiod = &FastIoDispatch;
+}
\ No newline at end of file
diff --git a/reactos/drivers/filesystems/btrfs/fileinfo.c b/reactos/drivers/filesystems/btrfs/fileinfo.c
new file mode 100644 (file)
index 0000000..3fc654e
--- /dev/null
@@ -0,0 +1,2944 @@
+/* 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 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 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;
+    ULONG defda;
+    BOOL inode_item_changed = FALSE;
+    NTSTATUS Status;
+    
+    if (fcb->ads)
+        fcb = fcb->par;
+    
+    TRACE("file = %.*S, attributes = %x\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fbi->FileAttributes);
+    
+    if (fbi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY && fcb->type != BTRFS_TYPE_DIRECTORY) {
+        WARN("attempted to set FILE_ATTRIBUTE_DIRECTORY on non-directory\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+    
+    // FIXME - what if FCB is volume or root?
+    // FIXME - what about subvol roots?
+    
+    // FIXME - link FILE_ATTRIBUTE_READONLY to st_mode
+    // FIXME - handle times == -1
+    
+    // FileAttributes == 0 means don't set - undocumented, but seen in fastfat
+    if (fbi->FileAttributes != 0) {
+        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);
+        
+        if (fcb->type == BTRFS_TYPE_DIRECTORY)
+            fbi->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+        else if (fcb->type == BTRFS_TYPE_SYMLINK)
+            fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
+                
+        // create new xattr
+        if (defda != fbi->FileAttributes) {
+            char val[64];
+            
+            TRACE("inserting new DOSATTRIB xattr\n");
+            sprintf(val, "0x%lx", fbi->FileAttributes);
+        
+            Status = set_xattr(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);
+                return Status;
+            }
+        } else
+            delete_xattr(Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, rollback);
+        
+        fcb->atts = fbi->FileAttributes;
+        
+        KeQuerySystemTime(&time);
+        win_time_to_unix(time, &now);
+        
+        fcb->inode_item.st_ctime = now;
+        fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+        fcb->subvol->root_item.ctime = now;
+        
+        inode_item_changed = TRUE;
+    }
+    
+//     FIXME - CreationTime
+//     FIXME - LastAccessTime
+//     FIXME - LastWriteTime
+//     FIXME - ChangeTime
+
+    if (inode_item_changed) {
+        KEY searchkey;
+        traverse_ptr tp;
+        INODE_ITEM* ii;
+        
+        fcb->inode_item.transid = Vcb->superblock.generation;
+        fcb->inode_item.sequence++;
+        
+        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);
+            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("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");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+            
+        RtlCopyMemory(ii, &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)) {
+            ERR("error - failed to insert item\n");
+            ExFreePool(ii);
+            return STATUS_INTERNAL_ERROR;
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+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;
+    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);
+    
+    atts = fcb->ads ? fcb->par->atts : fcb->atts;
+    TRACE("atts = %x\n", atts);
+    
+    if (atts & FILE_ATTRIBUTE_READONLY)
+        return STATUS_CANNOT_DELETE;
+    
+    if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0)
+        return STATUS_DIRECTORY_NOT_EMPTY;
+    
+    if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForDelete)) {
+        WARN("trying to delete file which is being mapped as an image\n");
+        return STATUS_CANNOT_DELETE;
+    }
+    
+    if (fcb->inode == SUBVOL_ROOT_INODE) {
+        FIXME("FIXME - subvol deletion not yet supported\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    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;
+}
+
+static NTSTATUS add_inode_extref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, UINT64 index, PANSI_STRING utf8, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    INODE_EXTREF* ier;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_INODE_EXTREF;
+    searchkey.offset = calc_crc32c((UINT32)parinode, (UINT8*)utf8->Buffer, utf8->Length);
+
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        ULONG iersize = tp.item->size + sizeof(INODE_EXTREF) - 1 + utf8->Length;
+        UINT8* ier2;
+        UINT32 maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
+        
+        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;
+        }
+        
+        if (tp.item->size > 0)
+            RtlCopyMemory(ier2, tp.item->data, tp.item->size);
+        
+        ier = (INODE_EXTREF*)&ier2[tp.item->size];
+        ier->dir = parinode;
+        ier->index = index;
+        ier->n = utf8->Length;
+        RtlCopyMemory(ier->name, utf8->Buffer, utf8->Length);
+        
+        delete_tree_item(Vcb, &tp, rollback);
+        
+        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;
+        }
+
+        ier->dir = parinode;
+        ier->index = index;
+        ier->n = utf8->Length;
+        RtlCopyMemory(ier->name, utf8->Buffer, utf8->Length);
+    
+        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;
+}
+
+NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, UINT64 index, PANSI_STRING utf8, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    INODE_REF* ir;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_INODE_REF;
+    searchkey.offset = parinode;
+    
+    Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        ULONG irsize = tp.item->size + sizeof(INODE_REF) - 1 + utf8->Length;
+        UINT8* ir2;
+        UINT32 maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
+        
+        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;
+            }
+        }
+        
+        ir2 = ExAllocatePoolWithTag(PagedPool, irsize, ALLOC_TAG);
+        if (!ir2) {
+            ERR("out of memory\n");
+            free_traverse_ptr(&tp);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        if (tp.item->size > 0)
+            RtlCopyMemory(ir2, tp.item->data, tp.item->size);
+        
+        ir = (INODE_REF*)&ir2[tp.item->size];
+        ir->index = index;
+        ir->n = utf8->Length;
+        RtlCopyMemory(ir->name, utf8->Buffer, utf8->Length);
+        
+        delete_tree_item(Vcb, &tp, rollback);
+        
+        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;
+        }
+
+        ir->index = index;
+        ir->n = utf8->Length;
+        RtlCopyMemory(ir->name, utf8->Buffer, utf8->Length);
+    
+        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) {
+    LIST_ENTRY* le;
+    fcb* sf2;
+    struct _fcb* c;
+    KEY searchkey;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    
+    le = parent->children.Flink;
+    
+    while (le != &parent->children) {
+        c = CONTAINING_RECORD(le, struct _fcb, list_entry);
+        
+        if (c->refcount > 0 && c->inode == di->key.obj_id && c->subvol == subvol) {
+            c->refcount++;
+#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);
+#endif
+            *pfcb = c;
+            return STATUS_SUCCESS;
+        }
+        
+        le = le->Flink;
+    }
+    
+    sf2 = create_fcb();
+    if (!sf2) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    sf2->Vcb = Vcb;
+
+    sf2->utf8.Length = sf2->utf8.MaximumLength = di->n;
+    sf2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, di->n, ALLOC_TAG);
+    if (!sf2->utf8.Buffer) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(sf2->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);
+#endif
+    
+    if (di->key.obj_type == TYPE_ROOT_ITEM) {
+        root* fcbroot = Vcb->roots;
+        while (fcbroot && fcbroot->id != di->key.obj_id)
+            fcbroot = fcbroot->next;
+        
+        sf2->subvol = fcbroot;
+        sf2->inode = SUBVOL_ROOT_INODE;
+    } else {
+        sf2->subvol = subvol;
+        sf2->inode = di->key.obj_id;
+    }
+    
+    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);
+   
+    if (parent != Vcb->root_fcb)
+        sf2->name_offset++;
+    
+    InsertTailList(&parent->children, &sf2->list_entry);
+    
+    searchkey.obj_id = sf2->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, sf2->subvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        free_fcb(sf2);
+        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);
+        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;
+
+    return STATUS_SUCCESS;
+}
+
+// static LONG get_tree_count(device_extension* Vcb, LIST_ENTRY* tc) {
+//     LONG rc = 0;
+//     LIST_ENTRY* le = Vcb->trees.Flink;
+//     
+//     while (le != &Vcb->trees) {
+//         tree* t = CONTAINING_RECORD(le, tree, list_entry);
+//         
+//         rc += t->refcount;
+//         
+//         le = le->Flink;
+//     }
+//     
+//     le = tc->Flink;
+//     while (le != tc) {
+//         tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+//         tree* t;
+//         
+//         rc--;
+//         
+//         t = tc2->tree->parent;
+//         while (t) {
+//             rc--;
+//             t = t->parent;
+//         }
+//         
+//         le = le->Flink;
+//     }
+//     
+//     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) {
+    UINT64 oldindex, index;
+    UINT32 oldcrc32;
+    INODE_ITEM* ii;
+    BOOL has_hardlink = FALSE;
+    DIR_ITEM* di;
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    NTSTATUS Status;
+    BOOL b;
+    
+    // move INODE_ITEM
+    
+    fcb->inode_item.transid = Vcb->superblock.generation;
+    fcb->inode_item.sequence++;
+    fcb->inode_item.st_ctime = *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);
+        return Status;
+    }
+    
+    if (!keycmp(&searchkey, &tp.item->key)) {
+        delete_tree_item(Vcb, &tp, rollback);
+        
+        if (fcb->inode_item.st_nlink > 1) {
+            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));
+            
+            if (!insert_tree_item(Vcb, fcb->subvol, 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;
+            }
+            
+            has_hardlink = TRUE;
+        }
+    } else {
+        WARN("couldn't find old INODE_ITEM\n");
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    fcb->inode_item.st_nlink = 1;
+    
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(ii, &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);
+    
+    // delete old DIR_ITEM
+    
+    Status = delete_dir_item(Vcb, fcb->subvol, oldparinode, oldcrc32, &fcb->utf8, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("delete_dir_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    // create new DIR_ITEM
+    
+    di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+                        
+    di->key.obj_id = inode;
+    di->key.obj_type = TYPE_INODE_ITEM;
+    di->key.offset = 0;
+    di->transid = Vcb->superblock.generation;
+    di->m = 0;
+    di->n = utf8->Length;
+    di->type = 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);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_dir_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, oldparinode, &fcb->utf8, &oldindex, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("delete_inode_ref returned %08x\n", Status);
+        return Status;
+    }
+    
+    // delete DIR_INDEX
+    
+    if (oldindex == 0) {
+        WARN("couldn't find old INODE_REF\n");
+    } else {    
+        searchkey.obj_id = oldparinode;
+        searchkey.obj_type = TYPE_DIR_INDEX;
+        searchkey.offset = oldindex;
+        
+        Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        if (!keycmp(&searchkey, &tp.item->key))
+            delete_tree_item(Vcb, &tp, rollback);
+        else
+            WARN("couldn't find old DIR_INDEX\n");
+        
+        free_traverse_ptr(&tp);
+    }
+    
+    // get new index
+    
+    searchkey.obj_id = destinode;
+    searchkey.obj_type = TYPE_DIR_INDEX + 1;
+    searchkey.offset = 0;
+        
+    Status = find_item(Vcb, destsubvol, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return Status;
+    }
+        
+    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);
+        }
+    }
+
+    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_DIR_INDEX) {
+        index = tp.item->key.offset + 1;
+    } else
+        index = 2;
+        
+    free_traverse_ptr(&tp);
+    
+    // create INODE_REF
+    
+    Status = add_inode_ref(Vcb, destsubvol, inode, destinode, index, utf8, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_inode_ref returned %08x\n", Status);
+        return Status;
+    }
+    
+    // create DIR_INDEX
+    
+    di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+                        
+    di->key.obj_id = inode;
+    di->key.obj_type = TYPE_INODE_ITEM;
+    di->key.offset = 0;
+    di->transid = Vcb->superblock.generation;
+    di->m = 0;
+    di->n = utf8->Length;
+    di->type = 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)) {
+        ERR("error - failed to insert item\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    // move XATTR_ITEMs
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_XATTR_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);
+        return Status;
+    }
+    
+    do {
+        if (tp.item->key.obj_id == 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;
+            }
+            
+            RtlCopyMemory(di, tp.item->data, tp.item->size);
+            
+            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;
+            }
+            
+            if (!has_hardlink)
+                delete_tree_item(Vcb, &tp, rollback);
+        }
+        
+        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)
+                break;
+        }
+    } while (b);
+    
+    free_traverse_ptr(&tp);
+
+    // do extents
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = 0;
+    
+    Status = find_item(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_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 {
+                EXTENT_DATA* ed = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
+                
+                if (!ed) {
+                    ERR("out of memory\n");
+                    free_traverse_ptr(&tp);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                RtlCopyMemory(ed, tp.item->data, tp.item->size);
+                
+                // FIXME - update ed's generation
+                        
+                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;
+                }
+            
+                if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+                    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);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("add_extent_ref returned %08x\n", Status);
+                            free_traverse_ptr(&tp);
+                            return Status;
+                        }
+                        
+                        if (!has_hardlink) {
+                            Status = remove_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset, NULL, rollback);
+                        
+                            if (!NT_SUCCESS(Status)) {
+                                ERR("remove_extent_ref returned %08x\n", Status);
+                                free_traverse_ptr(&tp);
+                                return Status;
+                            }
+                        }
+                    }
+                }
+                
+                if (!has_hardlink)
+                    delete_tree_item(Vcb, &tp, rollback);
+            }
+        }
+        
+        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)
+                break;
+        }
+    } while (b);
+    
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+typedef struct {
+    fcb* fcb;
+    UINT8 level;
+    UINT32 crc32;
+    UINT64 newinode;
+    UINT64 newparinode;
+    BOOL subvol;
+    ANSI_STRING utf8;
+    LIST_ENTRY list_entry;
+} dir_list;
+
+static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 newparinode, BOOL* empty) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    NTSTATUS Status;
+    
+    *empty = TRUE;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_DIR_INDEX;
+    searchkey.offset = 2;
+    
+    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_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;
+                dir_list* dl2;
+                
+                if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->n + di->m) {
+                    ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                } else {
+                    if (di->key.obj_type == TYPE_INODE_ITEM || di->key.obj_type == TYPE_ROOT_ITEM) {
+                        if (di->key.obj_type == TYPE_ROOT_ITEM)
+                            TRACE("moving subvol %llx\n", di->key.obj_id);
+                        else
+                            TRACE("moving inode %llx\n", di->key.obj_id);
+                        
+                        *empty = FALSE;
+                        
+                        Status = get_fcb_from_dir_item(fcb->Vcb, &child, fcb, fcb->subvol, di);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("get_fcb_from_dir_item returned %08x\n", Status);
+                            free_traverse_ptr(&tp);
+                            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->level = level;
+                        dl2->newparinode = newparinode;
+                        dl2->subvol = di->key.obj_type == TYPE_ROOT_ITEM;
+                        
+                        dl2->utf8.Length = dl2->utf8.MaximumLength = di->n;
+                        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;
+                        }
+                        
+                        RtlCopyMemory(dl2->utf8.Buffer, di->name, dl2->utf8.Length);
+                        dl2->crc32 = calc_crc32c(0xfffffffe, (UINT8*)dl2->utf8.Buffer, (ULONG)dl2->utf8.Length);
+                        
+                        InsertTailList(dl, &dl2->list_entry);
+                    }
+                }
+            }
+        }
+        
+        b = find_next_item(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)
+                break;
+        }
+    } 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) {
+    UINT64 inode, oldparinode;
+    NTSTATUS Status;
+    LIST_ENTRY dl;
+    
+    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;
+    
+    Status = move_inode_across_subvols(Vcb, fcb, 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) {
+        BOOL b, empty;
+        UINT8 level, max_level;
+        LIST_ENTRY* le;
+        
+        InitializeListHead(&dl);
+        
+        add_to_dir_list(fcb, 0, &dl, inode, &b);
+        
+        level = 0;
+        do {
+            empty = TRUE;
+            
+            le = dl.Flink;
+            while (le != &dl) {
+                dir_list* dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
+                
+                if (dl2->level == level && !dl2->subvol) {
+                    inode++;
+                    destsubvol->lastinode++;
+                    
+                    dl2->newinode = inode;
+                    
+                    if (dl2->fcb->type == BTRFS_TYPE_DIRECTORY) {
+                        add_to_dir_list(dl2->fcb, level+1, &dl, dl2->newinode, &b);
+                        if (!b) empty = FALSE;
+                    }
+                }
+                
+                le = le->Flink;
+            }
+            
+            if (!empty) level++;
+        } while (!empty);
+        
+        max_level = level;
+        
+        for (level = 0; level <= max_level; level++) {
+            TRACE("level %u\n", level);
+            
+            le = dl.Flink;
+            while (le != &dl) {
+                dir_list* dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
+                
+                if (dl2->level == level) {
+                    if (dl2->subvol) {
+                        TRACE("subvol %llx\n", dl2->fcb->subvol->id);
+                        
+                        Status = move_subvol(Vcb, dl2->fcb, 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);
+
+                        Status = move_inode_across_subvols(Vcb, dl2->fcb, destsubvol, dl2->newparinode, dl2->newinode, dl2->fcb->par->inode, &dl2->utf8, dl2->crc32, now, rollback);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("move_inode_across_subvols returned %08x\n", Status);
+                            return Status;
+                        }
+                    }
+                }
+                
+                le = le->Flink;
+            }
+        }
+        
+        while (!IsListEmpty(&dl)) {
+            dir_list* dl2;
+            
+            le = RemoveHeadList(&dl);
+            dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
+            
+            ExFreePool(dl2->utf8.Buffer);
+            free_fcb(dl2->fcb);
+            
+            ExFreePool(dl2);
+        }
+    }
+    
+    fcb->inode = inode;
+    fcb->subvol = destsubvol;
+      
+    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = *now;
+    
+    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) {
+    KEY searchkey;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = parsubvolid;
+    searchkey.obj_type = TYPE_ROOT_REF;
+    searchkey.offset = subvolid;
+    
+    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)) {
+        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));
+        } else {
+            ROOT_REF* rr;
+            ULONG len;
+            
+            rr = (ROOT_REF*)tp.item->data;
+            len = tp.item->size;
+            
+            do {
+                ULONG itemlen;
+                
+                if (len < sizeof(ROOT_REF) || len < sizeof(ROOT_REF) - 1 + rr->n) {
+                    ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    break;
+                }
+                
+                itemlen = sizeof(ROOT_REF) - sizeof(char) + rr->n;
+                
+                if (rr->dir == parinode && rr->n == utf8->Length && RtlCompareMemory(rr->name, utf8->Buffer, rr->n) == rr->n) {
+                    ULONG newlen = tp.item->size - itemlen;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    if (newlen == 0) {
+                        TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    } else {
+                        UINT8 *newrr = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *rroff;
+                        
+                        if (!newrr) {
+                            ERR("out of memory\n");
+                            free_traverse_ptr(&tp);
+                            return STATUS_INSUFFICIENT_RESOURCES;
+                        }
+                        
+                        TRACE("modifying (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+
+                        if ((UINT8*)rr > tp.item->data) {
+                            RtlCopyMemory(newrr, tp.item->data, (UINT8*)rr - tp.item->data);
+                            rroff = newrr + ((UINT8*)rr - tp.item->data);
+                        } else {
+                            rroff = newrr;
+                        }
+                        
+                        if ((UINT8*)&rr->name[rr->n] - tp.item->data < tp.item->size)
+                            RtlCopyMemory(rroff, &rr->name[rr->n], tp.item->size - ((UINT8*)&rr->name[rr->n] - tp.item->data));
+                        
+                        insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newrr, newlen, NULL, rollback);
+                    }
+                    
+                    if (index)
+                        *index = rr->index;
+                    
+                    break;
+                }
+                
+                if (len > itemlen) {
+                    len -= itemlen;
+                    rr = (ROOT_REF*)&rr->name[rr->n];
+                } else
+                    break;
+            } while (len > 0);
+        }
+    } else {
+        WARN("could not find ROOT_REF entry for subvol %llx in %llx\n", searchkey.offset, searchkey.obj_id);
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS add_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, ROOT_REF* rr, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = parsubvolid;
+    searchkey.obj_type = TYPE_ROOT_REF;
+    searchkey.offset = subvolid;
+    
+    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)) {
+        ULONG rrsize = tp.item->size + sizeof(ROOT_REF) - 1 + rr->n;
+        UINT8* rr2;
+        
+        rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
+        if (!rr2) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        if (tp.item->size > 0)
+            RtlCopyMemory(rr2, tp.item->data, tp.item->size);
+        
+        RtlCopyMemory(rr2 + tp.item->size, rr, sizeof(ROOT_REF) - 1 + rr->n);
+        ExFreePool(rr);
+        
+        delete_tree_item(Vcb, &tp, rollback);
+        
+        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) {
+    KEY searchkey;
+    traverse_ptr tp;
+    UINT8* data;
+    ULONG datalen;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = parsubvolid;
+    searchkey.obj_type = TYPE_ROOT_REF;
+    searchkey.offset = subvolid;
+    
+    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) && tp.item->size > 0) {
+        datalen = tp.item->size;
+        
+        data = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);
+        if (!data) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        RtlCopyMemory(data, tp.item->data, datalen);
+    } else {
+        datalen = 0;
+    }
+    
+    free_traverse_ptr(&tp);
+    
+    searchkey.obj_id = subvolid;
+    searchkey.obj_type = TYPE_ROOT_BACKREF;
+    searchkey.offset = parsubvolid;
+    
+    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))
+        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");
+            ExFreePool(data);
+            return STATUS_INTERNAL_ERROR;
+        }
+    }
+    
+    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) {
+    DIR_ITEM* di;
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp;
+    UINT64 oldindex, index;
+    ROOT_REF* rr;
+    
+    // delete old DIR_ITEM
+    
+    Status = delete_dir_item(Vcb, fcb->par->subvol, fcb->par->inode, oldcrc32, &fcb->utf8, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("delete_dir_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    // create new DIR_ITEM
+    
+    di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+        
+    di->key.obj_id = 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;
+    RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
+    
+    Status = add_dir_item(Vcb, destsubvol, destinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8->Length, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("add_dir_item returned %08x\n", Status);
+        return Status;
+    }
+    
+    // delete old ROOT_REF
+    
+    oldindex = 0;
+    
+    Status = delete_root_ref(Vcb, fcb->subvol->id, fcb->par->subvol->id, fcb->par->inode, &fcb->utf8, &oldindex, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("delete_root_ref returned %08x\n", Status);
+        return Status;
+    }
+    
+    TRACE("root index = %llx\n", oldindex);
+    
+    // delete old DIR_INDEX
+    
+    if (oldindex != 0) {
+        searchkey.obj_id = fcb->par->inode;
+        searchkey.obj_type = TYPE_DIR_INDEX;
+        searchkey.offset = oldindex;
+        
+        Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - find_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        if (!keycmp(&searchkey, &tp.item->key)) {
+            TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+            
+            delete_tree_item(Vcb, &tp, rollback);
+        } 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) {
+        index = oldindex;
+    } else {
+        index = find_next_dir_index(Vcb, destsubvol, destinode);
+    }
+    
+    di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
+    if (!di) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+        
+    di->key.obj_id = 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;
+    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)) {
+        ERR("error - failed to insert item\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    // create new ROOT_REF
+    
+    rr = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_REF) - 1 + utf8->Length, ALLOC_TAG);
+    if (!rr) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    rr->dir = destinode;
+    rr->index = index;
+    rr->n = utf8->Length;
+    RtlCopyMemory(rr->name, utf8->Buffer, utf8->Length);
+    
+    Status = add_root_ref(Vcb, 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);
+    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 (!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;
+    }
+    
+    destsubvol->root_item.ctransid = Vcb->superblock.generation;
+    destsubvol->root_item.ctime = *now;
+    
+    return STATUS_SUCCESS;
+}
+
+static BOOL has_open_children(fcb* fcb) {
+    LIST_ENTRY* le = fcb->children.Flink;
+    struct _fcb* c;
+    
+    while (le != &fcb->children) {
+        c = CONTAINING_RECORD(le, struct _fcb, list_entry);
+        
+        if (c->refcount > 0) {
+            if (c->open_count > 0)
+                return TRUE;
+            
+            if (has_open_children(c))
+                return TRUE;
+        }
+        
+        le = le->Flink;
+    }
+    
+    return FALSE;
+}
+
+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;
+    root* parsubvol;
+    UINT64 parinode, dirpos;
+    WCHAR* fn;
+    UNICODE_STRING fnus;
+    ULONG fnlen, utf8len, disize;
+    NTSTATUS Status;
+    ANSI_STRING utf8;
+    UINT32 crc32, oldcrc32;
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    DIR_ITEM* di;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    BOOL across_directories;
+    INODE_ITEM* ii;
+    
+    // FIXME - MSDN says we should be able to rename streams here, but I can't get it to work.
+    
+    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);
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+    utf8.Buffer = NULL;
+    
+    if (!fcb->par) {
+        ERR("error - tried to rename file with no parent\n");
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
+    fn = fri->FileName;
+    fnlen = fri->FileNameLength / sizeof(WCHAR);
+    
+    if (!tfo) {
+        parsubvol = fcb->par->subvol;
+        parinode = fcb->par->inode;
+        tfofcb = NULL;
+        
+        across_directories = FALSE;
+    } else {
+        LONG i;
+        
+        tfofcb = tfo->FsContext;
+        parsubvol = tfofcb->subvol;
+        parinode = tfofcb->inode;
+        
+        for (i = fnlen - 1; i >= 0; i--) {
+            if (fri->FileName[i] == '\\' || fri->FileName[i] == '/') {
+                fn = &fri->FileName[i+1];
+                fnlen = (fri->FileNameLength / sizeof(WCHAR)) - i - 1;
+                break;
+            }
+        }
+        
+        across_directories = parsubvol != fcb->par->subvol || parinode != fcb->par->inode;
+    }
+    
+    fnus.Buffer = fn;
+    fnus.Length = fnus.MaximumLength = fnlen * sizeof(WCHAR);
+    
+    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);
+    
+    // FIXME - set to crc32 if utf8 and oldutf8 are identical
+    oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fcb->utf8.Buffer, (ULONG)fcb->utf8.Length);
+    
+//     TRACE("utf8 fn = %s (%08x), old utf8 fn = %s (%08x)\n", utf8, crc32, oldutf8, oldcrc32);
+
+    oldfcb = NULL;
+
+    Status = get_fcb(Vcb, &oldfcb, &fnus, tfo ? tfo->FsContext : NULL, FALSE);
+
+    if (NT_SUCCESS(Status)) {
+        WARN("destination file %.*S already exists\n", oldfcb->full_filename.Length / sizeof(WCHAR), oldfcb->full_filename.Buffer);
+        
+        if (fcb != oldfcb && !(oldfcb->open_count == 0 && oldfcb->deleted)) {
+            if (!ReplaceIfExists) {
+                Status = STATUS_OBJECT_NAME_COLLISION;
+                goto end;
+            } else if (oldfcb->open_count >= 1 && !oldfcb->deleted) {
+                WARN("trying to overwrite open file\n");
+                Status = STATUS_ACCESS_DENIED;
+                goto end;
+            }
+            
+            if (oldfcb->type == BTRFS_TYPE_DIRECTORY) {
+                WARN("trying to overwrite directory\n");
+                Status = STATUS_ACCESS_DENIED;
+                goto end;
+            }
+        }
+    }
+    
+    if (has_open_children(fcb)) {
+        WARN("trying to rename file with open children\n");
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+    
+    if (oldfcb) {
+        Status = delete_fcb(oldfcb, NULL, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("delete_fcb 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);
+        
+        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);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("move_across_subvols returned %08x\n", Status);
+            goto end;
+        }
+    } else {
+        UINT64 oldindex;
+        INODE_ITEM* ii;
+        
+        // delete old DIR_ITEM entry
+        
+        Status = delete_dir_item(Vcb, fcb->subvol, fcb->par->inode, oldcrc32, &fcb->utf8, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("delete_dir_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        // FIXME - make sure fcb's filepart matches the case on disk
+        
+        // create new DIR_ITEM entry
+        
+        di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8.Length, ALLOC_TAG);
+        if (!di) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        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 = utf8.Length;
+        di->type = fcb->type;
+        RtlCopyMemory(di->name, utf8.Buffer, utf8.Length);
+        
+        Status = add_dir_item(Vcb, parsubvol, parinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8.Length, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("add_dir_item returned %08x\n", Status);
+            return Status;
+        }
+        
+        oldindex = 0;
+        
+        Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, fcb->par->inode, &fcb->utf8, &oldindex, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("delete_inode_ref returned %08x\n", Status);
+            return Status;
+        }
+
+        // delete old DIR_INDEX entry
+        
+        if (oldindex != 0) {
+            searchkey.obj_id = fcb->par->inode;
+            searchkey.obj_type = TYPE_DIR_INDEX;
+            searchkey.offset = oldindex;
+            
+            Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                goto end;
+            }
+            
+            if (!keycmp(&tp.item->key, &searchkey))
+                delete_tree_item(Vcb, &tp, rollback);
+            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) {
+            searchkey.obj_id = parinode;
+            searchkey.obj_type = TYPE_DIR_INDEX + 1;
+            searchkey.offset = 0;
+            
+            Status = find_item(Vcb, parsubvol, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                goto end;
+            }
+            
+            dirpos = 2;
+            
+            do {
+                TRACE("%llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                
+                if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_DIR_INDEX) {
+                    dirpos = tp.item->key.offset + 1;
+                    break;
+                }
+                
+                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;
+        
+        disize = (ULONG)(sizeof(DIR_ITEM) - 1 + utf8.Length);
+        di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
+        if (!di) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        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 = (UINT16)utf8.Length;
+        di->type = fcb->type;
+        RtlCopyMemory(di->name, utf8.Buffer, utf8.Length);
+        
+        if (!insert_tree_item(Vcb, parsubvol, parinode, TYPE_DIR_INDEX, dirpos, di, disize, NULL, rollback))
+            ERR("error - failed to insert item\n");
+        
+        // create new INODE_REF entry
+        
+        Status = add_inode_ref(Vcb, parsubvol, fcb->inode, parinode, dirpos, &utf8, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("add_inode_ref returned %08x\n", Status);
+            return Status;
+        }
+        
+        fcb->inode_item.transid = Vcb->superblock.generation;
+        fcb->inode_item.sequence++;
+        fcb->inode_item.st_ctime = now;
+        
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_INODE_ITEM;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(Vcb, parsubvol, &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);
+        
+        free_traverse_ptr(&tp);
+        
+        ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+        if (!ii) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
+        
+        if (!insert_tree_item(Vcb, parsubvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
+            WARN("insert_tree_item failed\n");
+        }
+    }
+    
+    // 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;
+    
+    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);
+    } else {
+        fcb->par->inode_item.st_size -= 2 * fcb->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);
+        tfofcb->inode_item.transid = Vcb->superblock.generation;
+        tfofcb->inode_item.sequence++;
+        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);
+    
+    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);
+    }
+    
+    searchkey.obj_id = fcb->par->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    searchkey.offset = 0xffffffffffffffff;
+    
+    Status = find_item(Vcb, fcb->par->subvol, &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)
+        delete_tree_item(Vcb, &tp, rollback);
+    
+    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));
+    
+    if (!insert_tree_item(Vcb, fcb->par->subvol, fcb->par->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)) {
+        searchkey.obj_id = tfofcb->inode;
+        searchkey.obj_type = TYPE_INODE_ITEM;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        Status = find_item(Vcb, tfofcb->subvol, &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)
+            delete_tree_item(Vcb, &tp, rollback);
+        
+        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, &tfofcb->inode_item, sizeof(INODE_ITEM));
+        
+        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);
+
+    // 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 (!fcb->filepart.Buffer) {
+            ERR("out of memory\n");
+            
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+        
+        RtlCopyMemory(fcb->filepart.Buffer, fn, fcb->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);
+        
+#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);
+#endif
+        free_fcb(oldparfcb);
+    }
+    
+    ExFreePool(fcb->utf8.Buffer);
+    fcb->utf8 = utf8;
+    utf8.Buffer = NULL;
+    
+    // change fcb->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);
+    
+    fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->full_filename.MaximumLength, ALLOC_TAG);
+    if (!fcb->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;
+    
+    if (fcb->par->par) {
+        fcb->full_filename.Buffer[fcb->full_filename.Length / sizeof(WCHAR)] = '\\';
+        fcb->full_filename.Length += sizeof(WCHAR);
+    }
+    fcb->name_offset = fcb->full_filename.Length / sizeof(WCHAR);
+    
+    RtlAppendUnicodeStringToString(&fcb->full_filename, &fcb->filepart);
+    
+    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);
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    if (utf8.Buffer)
+        ExFreePool(utf8.Buffer);
+    
+    if (oldfcb)
+        free_fcb(oldfcb);
+    
+    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;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    KEY searchkey;
+    traverse_ptr tp;
+    INODE_ITEM* ii;
+    CC_FILE_SIZES ccfs;
+    UINT8* data = NULL;
+    UINT16 datalen;
+    NTSTATUS Status;
+    
+    TRACE("setting new end to %llx bytes (currently %x)\n", feofi->EndOfFile.QuadPart, fcb->adssize);
+    
+    if (feofi->EndOfFile.QuadPart < fcb->adssize) {
+        if (advance_only)
+            return STATUS_SUCCESS;
+        
+        TRACE("truncating stream to %llx bytes\n", feofi->EndOfFile.QuadPart);
+        
+        if (feofi->EndOfFile.QuadPart > 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);
+        if (!NT_SUCCESS(Status)) {
+            ERR("set_xattr returned %08x\n", Status);
+            return Status;
+        }
+        
+        fcb->adssize = feofi->EndOfFile.QuadPart;
+        
+        if (data)
+            ExFreePool(data);
+    } else if (feofi->EndOfFile.QuadPart > fcb->adssize) {
+        UINT16 maxlen;
+        UINT8* data2;
+        
+        TRACE("extending stream to %llx bytes\n", feofi->EndOfFile.QuadPart);
+        
+        // find maximum length of xattr
+        maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
+        
+        searchkey.obj_id = fcb->inode;
+        searchkey.obj_type = TYPE_XATTR_ITEM;
+        searchkey.offset = fcb->adshash;
+
+        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 (keycmp(&tp.item->key, &searchkey)) {
+            ERR("error - could not find key for xattr\n");
+            free_traverse_ptr(&tp);
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        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);
+            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);
+            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);
+        if (!data2) {
+            ERR("out of memory\n");
+            ExFreePool(data);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        RtlCopyMemory(data2, data, datalen);
+        ExFreePool(data);
+        
+        RtlZeroMemory(&data2[datalen], feofi->EndOfFile.QuadPart - datalen);
+        
+        Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data2, feofi->EndOfFile.QuadPart, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("set_xattr returned %08x\n", Status);
+            return Status;
+        }
+        
+        fcb->adssize = feofi->EndOfFile.QuadPart;
+        
+        ExFreePool(data2);
+    }
+
+    ccfs.AllocationSize = fcb->Header.AllocationSize;
+    ccfs.FileSize = fcb->Header.FileSize;
+    ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+
+    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;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    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;
+    }
+    
+    if (!keycmp(&tp.item->key, &searchkey))
+        delete_tree_item(Vcb, &tp, rollback);
+    else
+        WARN("couldn't find existing INODE_ITEM\n");
+
+    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));
+    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;
+
+    return STATUS_SUCCESS;
+}
+
+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;
+    NTSTATUS Status;
+    LARGE_INTEGER time;
+    KEY searchkey;
+    traverse_ptr tp;
+    INODE_ITEM* ii;
+    CC_FILE_SIZES ccfs;
+    
+    if (fcb->deleted)
+        return STATUS_FILE_CLOSED;
+    
+    if (fcb->ads)
+        return stream_set_end_of_file_information(Vcb, Irp, FileObject, advance_only, rollback);
+    
+    TRACE("filename %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+    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);
+    
+//     int3;
+    TRACE("setting new end to %llx bytes (currently %llx)\n", feofi->EndOfFile.QuadPart, fcb->inode_item.st_size);
+    
+//     if (feofi->EndOfFile.QuadPart==0x36c000)
+//         int3;
+    
+    if (feofi->EndOfFile.QuadPart < fcb->inode_item.st_size) {
+        if (advance_only)
+            return STATUS_SUCCESS;
+        
+        TRACE("truncating file to %llx bytes\n", feofi->EndOfFile.QuadPart);
+        
+        Status = truncate_file(fcb, feofi->EndOfFile.QuadPart, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - truncate_file failed\n");
+            return Status;
+        }
+    } else if (feofi->EndOfFile.QuadPart > fcb->inode_item.st_size) {
+        if (Irp->Flags & IRP_PAGING_IO) {
+            TRACE("paging IO tried to extend file size\n");
+            return STATUS_SUCCESS;
+        }
+        
+        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);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - extend_file failed\n");
+            return Status;
+        }
+    }
+    
+    ccfs.AllocationSize = fcb->Header.AllocationSize;
+    ccfs.FileSize = fcb->Header.FileSize;
+    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);
+    
+    KeQuerySystemTime(&time);
+    
+    win_time_to_unix(time, &fcb->inode_item.st_mtime);
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    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;
+    }
+    
+    if (!keycmp(&tp.item->key, &searchkey))
+        delete_tree_item(Vcb, &tp, rollback);
+    else
+        WARN("couldn't find existing INODE_ITEM\n");
+
+    ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
+    if (!ii) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    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;
+}
+
+// static NTSTATUS STDCALL set_allocation_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
+//     FILE_ALLOCATION_INFORMATION* fai = (FILE_ALLOCATION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
+//     fcb* fcb = FileObject->FsContext;
+//     
+//     FIXME("FIXME\n");
+//     ERR("fcb = %p (%.*S)\n", fcb, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
+//     ERR("AllocationSize = %llx\n", fai->AllocationSize.QuadPart);
+//     
+//     return STATUS_NOT_IMPLEMENTED;
+// }
+
+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
+    
+    // FIXME - make sure aligned for FO_NO_INTERMEDIATE_BUFFERING
+    
+    FileObject->CurrentByteOffset = fpi->CurrentByteOffset;
+    
+    return STATUS_SUCCESS;
+}
+
+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:
+            FIXME("STUB: FileLinkInformation\n");
+            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:
+            FIXME("STUB: FileStandardLinkInformation\n");
+            break;
+            
+        case FileRemoteProtocolInformation:
+            FIXME("STUB: FileRemoteProtocolInformation\n");
+            break;
+#endif
+            
+        default:
+            WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.SetFile.FileInformationClass);
+    }
+    
+    if (NT_SUCCESS(Status))
+        Status = consider_write(Vcb);
+    
+    if (NT_SUCCESS(Status))
+        clear_rollback(&rollback);
+    else
+        do_rollback(Vcb, &rollback);
+    
+    release_tree_lock(Vcb, TRUE);
+    
+end:
+    Irp->IoStatus.Status = Status;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+static NTSTATUS STDCALL fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb) {
+    RtlZeroMemory(fbi, sizeof(FILE_BASIC_INFORMATION));
+    
+    *length -= sizeof(FILE_BASIC_INFORMATION);
+    
+    fbi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
+    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;
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, LONG* length) {
+    INODE_ITEM* ii;
+    
+    if (*length < sizeof(FILE_NETWORK_OPEN_INFORMATION)) {
+        WARN("overflow\n");
+        return STATUS_BUFFER_OVERFLOW;
+    }
+    
+    RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION));
+    
+    *length -= sizeof(FILE_NETWORK_OPEN_INFORMATION);
+    
+    if (fcb->ads)
+        ii = &fcb->par->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);
+    fnoi->ChangeTime.QuadPart = 0;
+    
+    if (fcb->ads) {
+        fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adssize;
+        fnoi->FileAttributes = fcb->par->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;
+        fnoi->FileAttributes = fcb->atts;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, LONG* length) {
+    RtlZeroMemory(fsi, sizeof(FILE_STANDARD_INFORMATION));
+    
+    *length -= sizeof(FILE_STANDARD_INFORMATION);
+    
+    if (fcb->ads) {
+        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);
+    } 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;
+        fsi->NumberOfLinks = fcb->inode_item.st_nlink;
+        fsi->Directory = S_ISDIR(fcb->inode_item.st_mode);
+    }
+    
+    TRACE("length = %llu\n", fsi->EndOfFile.QuadPart);
+    
+    fsi->DeletePending = fcb->delete_on_close;
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_internal_information(FILE_INTERNAL_INFORMATION* fii, UINT64 inode, LONG* length) {
+    *length -= sizeof(FILE_INTERNAL_INFORMATION);
+    
+    fii->IndexNumber.QuadPart = inode;
+    
+    return STATUS_SUCCESS;
+}  
+    
+static NTSTATUS STDCALL fill_in_file_ea_information(FILE_EA_INFORMATION* eai, LONG* length) {
+    *length -= sizeof(FILE_EA_INFORMATION);
+    
+    eai->EaSize = 0;
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_access_information(FILE_ACCESS_INFORMATION* fai, LONG* length) {
+    *length -= sizeof(FILE_ACCESS_INFORMATION);
+    
+    fai->AccessFlags = GENERIC_READ;
+    
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS STDCALL fill_in_file_position_information(FILE_POSITION_INFORMATION* fpi, PFILE_OBJECT FileObject, LONG* length) {
+    RtlZeroMemory(fpi, sizeof(FILE_POSITION_INFORMATION));
+    
+    *length -= sizeof(FILE_POSITION_INFORMATION);
+    
+    fpi->CurrentByteOffset = FileObject->CurrentByteOffset;
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_mode_information(FILE_MODE_INFORMATION* fmi, ccb* ccb, LONG* length) {
+    RtlZeroMemory(fmi, sizeof(FILE_MODE_INFORMATION));
+    
+    *length -= sizeof(FILE_MODE_INFORMATION);
+    
+    if (ccb->options & FILE_WRITE_THROUGH)
+        fmi->Mode |= FILE_WRITE_THROUGH;
+    
+    if (ccb->options & FILE_SEQUENTIAL_ONLY)
+        fmi->Mode |= FILE_SEQUENTIAL_ONLY;
+    
+    if (ccb->options & FILE_NO_INTERMEDIATE_BUFFERING)
+        fmi->Mode |= FILE_NO_INTERMEDIATE_BUFFERING;
+    
+    if (ccb->options & FILE_SYNCHRONOUS_IO_ALERT)
+        fmi->Mode |= FILE_SYNCHRONOUS_IO_ALERT;
+    
+    if (ccb->options & FILE_SYNCHRONOUS_IO_NONALERT)
+        fmi->Mode |= FILE_SYNCHRONOUS_IO_NONALERT;
+    
+    if (ccb->options & FILE_DELETE_ON_CLOSE)
+        fmi->Mode |= FILE_DELETE_ON_CLOSE;
+        
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_alignment_information(FILE_ALIGNMENT_INFORMATION* fai, device_extension* Vcb, LONG* length) {
+    RtlZeroMemory(fai, sizeof(FILE_ALIGNMENT_INFORMATION));
+    
+    *length -= sizeof(FILE_ALIGNMENT_INFORMATION);
+    
+    fai->AlignmentRequirement = Vcb->devices[0].devobj->AlignmentRequirement;
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, LONG* length) {
+#ifdef _DEBUG
+    ULONG retlen = 0;
+#endif
+    static WCHAR datasuf[] = {':','$','D','A','T','A',0};
+    ULONG datasuflen = wcslen(datasuf) * sizeof(WCHAR);
+    
+    RtlZeroMemory(fni, sizeof(FILE_NAME_INFORMATION));
+    
+    *length -= (LONG)offsetof(FILE_NAME_INFORMATION, FileName[0]);
+    
+    TRACE("maximum length is %u\n", *length);
+    fni->FileNameLength = 0;
+    
+    fni->FileName[0] = 0;
+    
+    if (*length >= (LONG)fcb->full_filename.Length) {
+        RtlCopyMemory(fni->FileName, fcb->full_filename.Buffer, fcb->full_filename.Length);
+#ifdef _DEBUG
+        retlen = fcb->full_filename.Length;
+#endif
+        *length -= fcb->full_filename.Length;
+    } else {
+        if (*length > 0) {
+            RtlCopyMemory(fni->FileName, fcb->full_filename.Buffer, *length);
+#ifdef _DEBUG
+            retlen = *length;
+#endif
+        }
+        *length = -1;
+    }
+    
+    fni->FileNameLength = fcb->full_filename.Length;
+    
+    if (fcb->ads) {
+        if (*length >= (LONG)datasuflen) {
+            RtlCopyMemory(&fni->FileName[fcb->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);
+#ifdef _DEBUG
+                retlen += *length;
+#endif
+            }
+            *length = -1;
+        }
+    }
+    
+    TRACE("%.*S\n", retlen / sizeof(WCHAR), fni->FileName);
+
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, LONG* length) {
+    *length -= sizeof(FILE_ATTRIBUTE_TAG_INFORMATION);
+    
+    ati->FileAttributes = fcb->ads ? fcb->par->atts : fcb->atts;
+    ati->ReparseTag = 0;
+    
+    return STATUS_SUCCESS;
+}
+
+typedef struct {
+    UNICODE_STRING name;
+    UINT64 size;
+} stream_info;
+
+static NTSTATUS STDCALL fill_in_file_stream_information(FILE_STREAM_INFORMATION* fsi, fcb* fcb, LONG* length) {
+    ULONG reqsize;
+    UINT64 i, num_streams;
+    stream_info* streams;
+    FILE_STREAM_INFORMATION* entry;
+    NTSTATUS Status;
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    
+    static WCHAR datasuf[] = {':','$','D','A','T','A',0};
+    static char xapref[] = "user.";
+    UNICODE_STRING suf;
+    
+    suf.Buffer = datasuf;
+    suf.Length = suf.MaximumLength = wcslen(datasuf) * sizeof(WCHAR);
+    
+    num_streams = 1;
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_XATTR_ITEM;
+    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_XATTR_ITEM) {
+            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 {
+                ULONG len = tp.item->size;
+                DIR_ITEM* xa = (DIR_ITEM*)tp.item->data;
+                
+                do {
+                    if (len < sizeof(DIR_ITEM) || len < 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);
+                        break;
+                    }
+                    
+                    if (xa->n > strlen(xapref) && RtlCompareMemory(xa->name, xapref, strlen(xapref)) == strlen(xapref)) {
+                        if (tp.item->key.offset != EA_DOSATTRIB_HASH || xa->n != strlen(EA_DOSATTRIB) || RtlCompareMemory(xa->name, EA_DOSATTRIB, xa->n) != xa->n) {
+                            num_streams++;
+                        }
+                    }
+                    
+                    len -= sizeof(DIR_ITEM) - sizeof(char) + xa->n + xa->m;
+                    xa = (DIR_ITEM*)&xa->name[xa->n + xa->m]; // FIXME - test xattr hash collisions work
+                } while (len > 0);
+            }
+        }
+        
+        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)
+                break;
+        }
+    } 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);
+        return Status;
+    }
+    
+    streams = ExAllocatePoolWithTag(PagedPool, sizeof(stream_info) * num_streams, ALLOC_TAG);
+    if (!streams) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    reqsize = 0;
+    
+    streams[0].name.Length = streams[0].name.MaximumLength = 0;
+    streams[0].name.Buffer = NULL;
+    streams[0].size = fcb->inode_item.st_size;
+    reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + streams[0].name.Length;
+    
+    i = 1;
+    do {
+        if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM) {
+            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 {
+                ULONG len = tp.item->size;
+                DIR_ITEM* xa = (DIR_ITEM*)tp.item->data;
+                ULONG stringlen;
+                
+                do {
+                    if (len < sizeof(DIR_ITEM) || len < 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);
+                        break;
+                    }
+                    
+                    if (xa->n > strlen(xapref) && RtlCompareMemory(xa->name, xapref, strlen(xapref)) == strlen(xapref) &&
+                        (tp.item->key.offset != EA_DOSATTRIB_HASH || xa->n != strlen(EA_DOSATTRIB) || RtlCompareMemory(xa->name, EA_DOSATTRIB, xa->n) != xa->n)) {
+                        Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, &xa->name[strlen(xapref)], xa->n - strlen(xapref));
+                        if (!NT_SUCCESS(Status)) {
+                            UINT64 j;
+                            
+                            ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
+                            
+                            for (j = i; j < num_streams; j++)
+                                streams[j].name.Buffer = NULL;
+                            
+                            goto end;
+                        }
+                        
+                        streams[i].name.Buffer = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
+                        if (!streams[i].name.Buffer) {
+                            UINT64 j;
+                            
+                            ERR("out of memory\n");
+                            
+                            for (j = i+1; j < num_streams; j++)
+                                streams[j].name.Buffer = NULL;
+                            
+                            Status = STATUS_INSUFFICIENT_RESOURCES;
+                            goto end;
+                        }
+                            
+                        Status = RtlUTF8ToUnicodeN(streams[i].name.Buffer, stringlen, &stringlen, &xa->name[strlen(xapref)], xa->n - strlen(xapref));
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            UINT64 j;
+                            
+                            ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
+                            
+                            ExFreePool(streams[i].name.Buffer);
+                            for (j = i; j < num_streams; j++)
+                                streams[j].name.Buffer = NULL;
+                            
+                            goto end;
+                        }
+                        
+                        streams[i].name.Length = streams[i].name.MaximumLength = stringlen;
+                        
+                        streams[i].size = xa->m;
+                        reqsize = sector_align(reqsize, sizeof(LONGLONG));
+                        reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + streams[i].name.Length;
+                        
+                        TRACE("streams[%llu].name = %.*S (length = %u)\n", i, streams[i].name.Length / sizeof(WCHAR), streams[i].name.Buffer, streams[i].name.Length / sizeof(WCHAR));
+
+                        i++;
+                    }
+                    
+                    len -= sizeof(DIR_ITEM) - sizeof(char) + xa->n + xa->m;
+                    xa = (DIR_ITEM*)&xa->name[xa->n + xa->m]; // FIXME - test xattr hash collisions work
+                } while (len > 0);
+            }
+        }
+        
+        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)
+                break;
+        }
+    } while (b);    
+    
+    free_traverse_ptr(&tp);
+    
+    TRACE("length = %i, reqsize = %u\n", *length, reqsize);
+    
+    if (reqsize > *length) {
+        Status = STATUS_BUFFER_OVERFLOW;
+        goto end;
+    }
+    
+    entry = fsi;
+    for (i = 0; i < num_streams; i++) {
+        entry->StreamNameLength = streams[i].name.Length + suf.Length + sizeof(WCHAR);
+        entry->StreamSize.QuadPart = streams[i].size;
+        
+        if (i == 0)
+            entry->StreamAllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
+        else
+            entry->StreamAllocationSize.QuadPart = streams[i].size;
+        
+        entry->StreamName[0] = ':';
+        
+        if (streams[i].name.Length > 0)
+            RtlCopyMemory(&entry->StreamName[1], streams[i].name.Buffer, streams[i].name.Length);
+        
+        RtlCopyMemory(&entry->StreamName[1 + (streams[i].name.Length / sizeof(WCHAR))], suf.Buffer, suf.Length);
+        
+        if (i == num_streams - 1)
+            entry->NextEntryOffset = 0;
+        else {
+            entry->NextEntryOffset = sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + streams[i].name.Length, sizeof(LONGLONG));
+
+            entry = (FILE_STREAM_INFORMATION*)((UINT8*)entry + entry->NextEntryOffset);
+        }
+    }
+    
+    *length -= reqsize;
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    for (i = 0; i < num_streams; i++) {
+        if (streams[i].name.Buffer)
+            ExFreePool(streams[i].name.Buffer);
+    }
+    
+    ExFreePool(streams);
+    
+    return Status;
+}
+
+static NTSTATUS STDCALL fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, 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->Directory = fcb->type == BTRFS_TYPE_DIRECTORY ? TRUE : FALSE;
+    
+    *length -= sizeof(FILE_STANDARD_LINK_INFORMATION);
+    
+    return STATUS_SUCCESS;
+}
+
+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;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %p, %p)\n", Vcb, FileObject, Irp);
+    TRACE("fcb = %p\n", fcb);
+    
+    if (fcb == Vcb->volume_fcb)
+        return STATUS_INVALID_PARAMETER;
+    
+    switch (IrpSp->Parameters.QueryFile.FileInformationClass) {
+        case FileAllInformation:
+        {
+            FILE_ALL_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer;
+            INODE_ITEM* ii;
+            
+            TRACE("FileAllInformation\n");
+            
+            if (fcb->ads)
+                ii = &fcb->par->inode_item;
+            else
+                ii = &fcb->inode_item;
+            
+            if (length > 0)
+                fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb);
+            
+            if (length > 0)
+                fill_in_file_standard_information(&fai->StandardInformation, fcb, &length);
+            
+            if (length > 0)
+                fill_in_file_internal_information(&fai->InternalInformation, fcb->inode, &length);
+            
+            if (length > 0)
+                fill_in_file_ea_information(&fai->EaInformation, &length);
+            
+            if (length > 0)
+                fill_in_file_access_information(&fai->AccessInformation, &length);
+            
+            if (length > 0)
+                fill_in_file_position_information(&fai->PositionInformation, FileObject, &length);
+                
+            if (length > 0)
+                fill_in_file_mode_information(&fai->ModeInformation, ccb, &length);
+            
+            if (length > 0)
+                fill_in_file_alignment_information(&fai->AlignmentInformation, Vcb, &length);
+            
+            if (length > 0)
+                fill_in_file_name_information(&fai->NameInformation, fcb, &length);
+            
+            Status = STATUS_SUCCESS;
+
+            break;
+        }
+
+        case FileAttributeTagInformation:
+        {
+            FILE_ATTRIBUTE_TAG_INFORMATION* ati = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileAttributeTagInformation\n");
+            
+            Status = fill_in_file_attribute_information(ati, fcb, &length);
+            
+            break;
+        }
+
+        case FileBasicInformation:
+        {
+            FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;
+            INODE_ITEM* ii;
+            
+            TRACE("FileBasicInformation\n");
+            
+            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_BASIC_INFORMATION)) {
+                WARN("overflow\n");
+                Status = STATUS_BUFFER_OVERFLOW;
+                goto exit;
+            }
+            
+            if (fcb->ads)
+                ii = &fcb->par->inode_item;
+            else
+                ii = &fcb->inode_item;
+            
+            Status = fill_in_file_basic_information(fbi, ii, &length, fcb);
+            break;
+        }
+
+        case FileCompressionInformation:
+            FIXME("STUB: FileCompressionInformation\n");
+            Status = STATUS_INVALID_PARAMETER;
+            goto exit;
+
+        case FileEaInformation:
+        {
+            FILE_EA_INFORMATION* eai = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileEaInformation\n");
+            
+            Status = fill_in_file_ea_information(eai, &length);
+            
+            break;
+        }
+
+        case FileInternalInformation:
+        {
+            FILE_INTERNAL_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileInternalInformation\n");
+            
+            Status = fill_in_file_internal_information(fii, fcb->inode, &length);
+            
+            break;
+        }
+
+        case FileNameInformation:
+        {
+            FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileNameInformation\n");
+            
+            Status = fill_in_file_name_information(fni, fcb, &length);
+            
+            break;
+        }
+
+        case FileNetworkOpenInformation:
+        {
+            FILE_NETWORK_OPEN_INFORMATION* fnoi = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileNetworkOpenInformation\n");
+            
+            Status = fill_in_file_network_open_information(fnoi, fcb, &length);
+
+            break;
+        }
+
+        case FilePositionInformation:
+        {
+            FILE_POSITION_INFORMATION* fpi = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FilePositionInformation\n");
+            
+            Status = fill_in_file_position_information(fpi, FileObject, &length);
+            
+            break;
+        }
+
+        case FileStandardInformation:
+        {
+            FILE_STANDARD_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileStandardInformation\n");
+            
+            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STANDARD_INFORMATION)) {
+                WARN("overflow\n");
+                Status = STATUS_BUFFER_OVERFLOW;
+                goto exit;
+            }
+            
+            Status = fill_in_file_standard_information(fsi, fcb, &length);
+            
+            break;
+        }
+
+        case FileStreamInformation:
+        {
+            FILE_STREAM_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileStreamInformation\n");
+            
+            Status = fill_in_file_stream_information(fsi, fcb, &length);
+
+            break;
+        }
+
+#if (NTDDI_VERSION >= NTDDI_VISTA)
+        case FileHardLinkInformation:
+            FIXME("STUB: FileHardLinkInformation\n");
+            Status = STATUS_INVALID_PARAMETER;
+            goto exit;
+            
+        case FileNormalizedNameInformation:
+        {
+            FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileNormalizedNameInformation\n");
+            
+            Status = fill_in_file_name_information(fni, fcb, &length);
+            
+            break;
+        }
+#endif
+
+            
+#if (NTDDI_VERSION >= NTDDI_WIN7)
+        case FileStandardLinkInformation:
+        {
+            FILE_STANDARD_LINK_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer;
+            
+            TRACE("FileStandardLinkInformation\n");
+            
+            Status = fill_in_file_standard_link_information(fsli, fcb, &length);
+            
+            break;
+        }
+        
+        case FileRemoteProtocolInformation:
+            FIXME("STUB: FileRemoteProtocolInformation\n");
+            Status = STATUS_INVALID_PARAMETER;
+            goto exit;
+#endif
+        
+        default:
+            WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.QueryFile.FileInformationClass);
+            Status = STATUS_INVALID_PARAMETER;
+            goto exit;
+    }
+    
+    if (length < 0) {
+        length = 0;
+        Status = STATUS_BUFFER_OVERFLOW;
+    }
+    
+    Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - length;
+
+exit:    
+    TRACE("query_info returning %08x\n", Status);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp;
+    NTSTATUS Status;
+    fcb* fcb;
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    BOOL top_level;
+    
+    FsRtlEnterFileSystem();
+    
+    top_level = is_top_level(Irp);
+    
+    Irp->IoStatus.Information = 0;
+    
+    TRACE("query information\n");
+    
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    
+    acquire_tree_lock(Vcb, FALSE);
+    
+    fcb = IrpSp->FileObject->FsContext;
+    TRACE("fcb = %p\n", fcb);
+    TRACE("fcb->subvol = %p\n", fcb->subvol);
+    
+    Status = query_info(fcb->Vcb, IrpSp->FileObject, Irp);
+    
+    TRACE("returning %08x\n", Status);
+    
+    Irp->IoStatus.Status = Status;
+    
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    release_tree_lock(Vcb, FALSE);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+    
+    return Status;
+}
diff --git a/reactos/drivers/filesystems/btrfs/flushthread.c b/reactos/drivers/filesystems/btrfs/flushthread.c
new file mode 100644 (file)
index 0000000..c9708cd
--- /dev/null
@@ -0,0 +1,63 @@
+/* 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"
+
+#define INTERVAL 15000 // in milliseconds
+
+static void do_flush(device_extension* Vcb) {
+    LIST_ENTRY rollback;
+    
+    InitializeListHead(&rollback);
+    
+    FsRtlEnterFileSystem();
+
+    acquire_tree_lock(Vcb, TRUE);
+
+    if (Vcb->write_trees > 0)
+        do_write(Vcb, &rollback);
+    
+    free_tree_cache(&Vcb->tree_cache);
+    
+    clear_rollback(&rollback);
+
+    release_tree_lock(Vcb, TRUE);
+
+    FsRtlExitFileSystem();
+}
+
+void STDCALL flush_thread(void* context) {
+    device_extension* Vcb = context;
+    LARGE_INTEGER due_time;
+    
+    KeInitializeTimer(&Vcb->flush_thread_timer);
+    
+    due_time.QuadPart = -INTERVAL * 10000;
+    
+    KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);
+    
+    while (TRUE) {
+        KeWaitForSingleObject(&Vcb->flush_thread_timer, Executive, KernelMode, FALSE, NULL);        
+
+        do_flush(Vcb);
+        
+        KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);
+    }
+    
+    KeCancelTimer(&Vcb->flush_thread_timer);
+    PsTerminateSystemThread(STATUS_SUCCESS);
+}
diff --git a/reactos/drivers/filesystems/btrfs/fsctl.c b/reactos/drivers/filesystems/btrfs/fsctl.c
new file mode 100644 (file)
index 0000000..9405e72
--- /dev/null
@@ -0,0 +1,510 @@
+/* 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"
+
+#ifndef FSCTL_CSV_CONTROL
+#define FSCTL_CSV_CONTROL CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 181, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#endif
+
+NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP Irp, UINT32 type, BOOL user) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    NTSTATUS Status;
+    
+    switch (type) {
+        case FSCTL_REQUEST_OPLOCK_LEVEL_1:
+            WARN("STUB: FSCTL_REQUEST_OPLOCK_LEVEL_1\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_REQUEST_OPLOCK_LEVEL_2:
+            WARN("STUB: FSCTL_REQUEST_OPLOCK_LEVEL_2\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_REQUEST_BATCH_OPLOCK:
+            WARN("STUB: FSCTL_REQUEST_BATCH_OPLOCK\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE:
+            WARN("STUB: FSCTL_OPLOCK_BREAK_ACKNOWLEDGE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_OPBATCH_ACK_CLOSE_PENDING:
+            WARN("STUB: FSCTL_OPBATCH_ACK_CLOSE_PENDING\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_OPLOCK_BREAK_NOTIFY:
+            WARN("STUB: FSCTL_OPLOCK_BREAK_NOTIFY\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_LOCK_VOLUME:
+            WARN("STUB: FSCTL_LOCK_VOLUME\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_UNLOCK_VOLUME:
+            WARN("STUB: FSCTL_UNLOCK_VOLUME\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_DISMOUNT_VOLUME:
+            WARN("STUB: FSCTL_DISMOUNT_VOLUME\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_IS_VOLUME_MOUNTED:
+            WARN("STUB: FSCTL_IS_VOLUME_MOUNTED\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_IS_PATHNAME_VALID:
+            WARN("STUB: FSCTL_IS_PATHNAME_VALID\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_MARK_VOLUME_DIRTY:
+            WARN("STUB: FSCTL_MARK_VOLUME_DIRTY\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_RETRIEVAL_POINTERS:
+            WARN("STUB: FSCTL_QUERY_RETRIEVAL_POINTERS\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_COMPRESSION:
+            WARN("STUB: FSCTL_GET_COMPRESSION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_COMPRESSION:
+            WARN("STUB: FSCTL_SET_COMPRESSION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_BOOTLOADER_ACCESSED:
+            WARN("STUB: FSCTL_SET_BOOTLOADER_ACCESSED\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_OPLOCK_BREAK_ACK_NO_2:
+            WARN("STUB: FSCTL_OPLOCK_BREAK_ACK_NO_2\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_INVALIDATE_VOLUMES:
+            WARN("STUB: FSCTL_INVALIDATE_VOLUMES\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_FAT_BPB:
+            WARN("STUB: FSCTL_QUERY_FAT_BPB\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_REQUEST_FILTER_OPLOCK:
+            WARN("STUB: FSCTL_REQUEST_FILTER_OPLOCK\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_FILESYSTEM_GET_STATISTICS:
+            WARN("STUB: FSCTL_FILESYSTEM_GET_STATISTICS\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_NTFS_VOLUME_DATA:
+            WARN("STUB: FSCTL_GET_NTFS_VOLUME_DATA\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_NTFS_FILE_RECORD:
+            WARN("STUB: FSCTL_GET_NTFS_FILE_RECORD\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_VOLUME_BITMAP:
+            WARN("STUB: FSCTL_GET_VOLUME_BITMAP\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_RETRIEVAL_POINTERS:
+            WARN("STUB: FSCTL_GET_RETRIEVAL_POINTERS\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_MOVE_FILE:
+            WARN("STUB: FSCTL_MOVE_FILE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_IS_VOLUME_DIRTY:
+            WARN("STUB: FSCTL_IS_VOLUME_DIRTY\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_ALLOW_EXTENDED_DASD_IO:
+            WARN("STUB: FSCTL_ALLOW_EXTENDED_DASD_IO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_FIND_FILES_BY_SID:
+            WARN("STUB: FSCTL_FIND_FILES_BY_SID\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_OBJECT_ID:
+            WARN("STUB: FSCTL_SET_OBJECT_ID\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_OBJECT_ID:
+            WARN("STUB: FSCTL_GET_OBJECT_ID\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_DELETE_OBJECT_ID:
+            WARN("STUB: FSCTL_DELETE_OBJECT_ID\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_REPARSE_POINT:
+            Status = set_reparse_point(DeviceObject, Irp);
+            break;
+
+        case FSCTL_GET_REPARSE_POINT:
+            Status = get_reparse_point(DeviceObject, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,
+                                       IrpSp->Parameters.DeviceIoControl.OutputBufferLength, &Irp->IoStatus.Information);
+            break;
+
+        case FSCTL_DELETE_REPARSE_POINT:
+            WARN("STUB: FSCTL_DELETE_REPARSE_POINT\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_ENUM_USN_DATA:
+            WARN("STUB: FSCTL_ENUM_USN_DATA\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SECURITY_ID_CHECK:
+            WARN("STUB: FSCTL_SECURITY_ID_CHECK\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_READ_USN_JOURNAL:
+            WARN("STUB: FSCTL_READ_USN_JOURNAL\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_OBJECT_ID_EXTENDED:
+            WARN("STUB: FSCTL_SET_OBJECT_ID_EXTENDED\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_CREATE_OR_GET_OBJECT_ID:
+            WARN("STUB: FSCTL_CREATE_OR_GET_OBJECT_ID\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_SPARSE:
+            WARN("STUB: FSCTL_SET_SPARSE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_ZERO_DATA:
+            WARN("STUB: FSCTL_SET_ZERO_DATA\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_ALLOCATED_RANGES:
+            WARN("STUB: FSCTL_QUERY_ALLOCATED_RANGES\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_ENABLE_UPGRADE:
+            WARN("STUB: FSCTL_ENABLE_UPGRADE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_ENCRYPTION:
+            WARN("STUB: FSCTL_SET_ENCRYPTION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_ENCRYPTION_FSCTL_IO:
+            WARN("STUB: FSCTL_ENCRYPTION_FSCTL_IO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_WRITE_RAW_ENCRYPTED:
+            WARN("STUB: FSCTL_WRITE_RAW_ENCRYPTED\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_READ_RAW_ENCRYPTED:
+            WARN("STUB: FSCTL_READ_RAW_ENCRYPTED\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_CREATE_USN_JOURNAL:
+            WARN("STUB: FSCTL_CREATE_USN_JOURNAL\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_READ_FILE_USN_DATA:
+            WARN("STUB: FSCTL_READ_FILE_USN_DATA\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_WRITE_USN_CLOSE_RECORD:
+            WARN("STUB: FSCTL_WRITE_USN_CLOSE_RECORD\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_EXTEND_VOLUME:
+            WARN("STUB: FSCTL_EXTEND_VOLUME\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_USN_JOURNAL:
+            WARN("STUB: FSCTL_QUERY_USN_JOURNAL\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_DELETE_USN_JOURNAL:
+            WARN("STUB: FSCTL_DELETE_USN_JOURNAL\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_MARK_HANDLE:
+            WARN("STUB: FSCTL_MARK_HANDLE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SIS_COPYFILE:
+            WARN("STUB: FSCTL_SIS_COPYFILE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SIS_LINK_FILES:
+            WARN("STUB: FSCTL_SIS_LINK_FILES\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_RECALL_FILE:
+            WARN("STUB: FSCTL_RECALL_FILE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_READ_FROM_PLEX:
+            WARN("STUB: FSCTL_READ_FROM_PLEX\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_FILE_PREFETCH:
+            WARN("STUB: FSCTL_FILE_PREFETCH\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+#if WIN32_WINNT >= 0x0600
+        case FSCTL_MAKE_MEDIA_COMPATIBLE:
+            WARN("STUB: FSCTL_MAKE_MEDIA_COMPATIBLE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_DEFECT_MANAGEMENT:
+            WARN("STUB: FSCTL_SET_DEFECT_MANAGEMENT\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_SPARING_INFO:
+            WARN("STUB: FSCTL_QUERY_SPARING_INFO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_ON_DISK_VOLUME_INFO:
+            WARN("STUB: FSCTL_QUERY_ON_DISK_VOLUME_INFO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_VOLUME_COMPRESSION_STATE:
+            WARN("STUB: FSCTL_SET_VOLUME_COMPRESSION_STATE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_MODIFY_RM:
+            WARN("STUB: FSCTL_TXFS_MODIFY_RM\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_QUERY_RM_INFORMATION:
+            WARN("STUB: FSCTL_TXFS_QUERY_RM_INFORMATION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_ROLLFORWARD_REDO:
+            WARN("STUB: FSCTL_TXFS_ROLLFORWARD_REDO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_ROLLFORWARD_UNDO:
+            WARN("STUB: FSCTL_TXFS_ROLLFORWARD_UNDO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_START_RM:
+            WARN("STUB: FSCTL_TXFS_START_RM\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_SHUTDOWN_RM:
+            WARN("STUB: FSCTL_TXFS_SHUTDOWN_RM\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_READ_BACKUP_INFORMATION:
+            WARN("STUB: FSCTL_TXFS_READ_BACKUP_INFORMATION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_WRITE_BACKUP_INFORMATION:
+            WARN("STUB: FSCTL_TXFS_WRITE_BACKUP_INFORMATION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_CREATE_SECONDARY_RM:
+            WARN("STUB: FSCTL_TXFS_CREATE_SECONDARY_RM\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_GET_METADATA_INFO:
+            WARN("STUB: FSCTL_TXFS_GET_METADATA_INFO\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_GET_TRANSACTED_VERSION:
+            WARN("STUB: FSCTL_TXFS_GET_TRANSACTED_VERSION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_SAVEPOINT_INFORMATION:
+            WARN("STUB: FSCTL_TXFS_SAVEPOINT_INFORMATION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_CREATE_MINIVERSION:
+            WARN("STUB: FSCTL_TXFS_CREATE_MINIVERSION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_TRANSACTION_ACTIVE:
+            WARN("STUB: FSCTL_TXFS_TRANSACTION_ACTIVE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_ZERO_ON_DEALLOCATION:
+            WARN("STUB: FSCTL_SET_ZERO_ON_DEALLOCATION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_REPAIR:
+            WARN("STUB: FSCTL_SET_REPAIR\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_GET_REPAIR:
+            WARN("STUB: FSCTL_GET_REPAIR\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_WAIT_FOR_REPAIR:
+            WARN("STUB: FSCTL_WAIT_FOR_REPAIR\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_INITIATE_REPAIR:
+            WARN("STUB: FSCTL_INITIATE_REPAIR\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_CSC_INTERNAL:
+            WARN("STUB: FSCTL_CSC_INTERNAL\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SHRINK_VOLUME:
+            WARN("STUB: FSCTL_SHRINK_VOLUME\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_SET_SHORT_NAME_BEHAVIOR:
+            WARN("STUB: FSCTL_SET_SHORT_NAME_BEHAVIOR\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_DFSR_SET_GHOST_HANDLE_STATE:
+            WARN("STUB: FSCTL_DFSR_SET_GHOST_HANDLE_STATE\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES:
+            WARN("STUB: FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_LIST_TRANSACTIONS:
+            WARN("STUB: FSCTL_TXFS_LIST_TRANSACTIONS\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_QUERY_PAGEFILE_ENCRYPTION:
+            WARN("STUB: FSCTL_QUERY_PAGEFILE_ENCRYPTION\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_RESET_VOLUME_ALLOCATION_HINTS:
+            WARN("STUB: FSCTL_RESET_VOLUME_ALLOCATION_HINTS\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+
+        case FSCTL_TXFS_READ_BACKUP_INFORMATION2:
+            WARN("STUB: FSCTL_TXFS_READ_BACKUP_INFORMATION2\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+            
+        case FSCTL_CSV_CONTROL:
+            WARN("STUB: FSCTL_CSV_CONTROL\n");
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+#endif
+
+        default:
+            WARN("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);
+            Status = STATUS_NOT_IMPLEMENTED;
+            break;
+    }
+    
+    return Status;
+}
diff --git a/reactos/drivers/filesystems/btrfs/loader.c b/reactos/drivers/filesystems/btrfs/loader.c
new file mode 100644 (file)
index 0000000..b4014d9
--- /dev/null
@@ -0,0 +1,231 @@
+/* 
+ * 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/read.c b/reactos/drivers/filesystems/btrfs/read.c
new file mode 100644 (file)
index 0000000..1b46034
--- /dev/null
@@ -0,0 +1,723 @@
+#include "btrfs_drv.h"
+
+enum read_data_status {
+    ReadDataStatus_Pending,
+    ReadDataStatus_Success,
+    ReadDataStatus_Cancelling,
+    ReadDataStatus_Cancelled,
+    ReadDataStatus_Error,
+    ReadDataStatus_CRCError,
+    ReadDataStatus_MissingDevice
+};
+
+struct read_data_context;
+
+typedef struct {
+    struct read_data_context* context;
+    UINT8* buf;
+    PIRP Irp;
+    IO_STATUS_BLOCK iosb;
+    enum read_data_status status;
+} read_data_stripe;
+
+typedef struct {
+    KEVENT Event;
+    NTSTATUS Status;
+    chunk* c;
+    UINT32 buflen;
+    UINT64 num_stripes;
+    UINT64 type;
+    read_data_stripe* stripes;
+} read_data_context;
+
+static NTSTATUS STDCALL read_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
+    read_data_stripe* stripe = conptr;
+    read_data_context* context = (read_data_context*)stripe->context;
+    UINT64 i;
+    BOOL complete;
+    
+    if (stripe->status == ReadDataStatus_Cancelling) {
+        stripe->status = ReadDataStatus_Cancelled;
+        goto end;
+    }
+    
+    stripe->iosb = Irp->IoStatus;
+    
+    if (NT_SUCCESS(Irp->IoStatus.Status)) {
+        // FIXME - calculate and compare checksum
+        
+        stripe->status = ReadDataStatus_Success;
+            
+        for (i = 0; i < context->num_stripes; i++) {
+            if (context->stripes[i].status == ReadDataStatus_Pending) {
+                context->stripes[i].status = ReadDataStatus_Cancelling;
+                IoCancelIrp(context->stripes[i].Irp);
+            }
+        }
+            
+        goto end;
+    } else {
+        stripe->status = ReadDataStatus_Error;
+    }
+    
+end:
+    complete = TRUE;
+        
+    for (i = 0; i < context->num_stripes; i++) {
+        if (context->stripes[i].status == ReadDataStatus_Pending || context->stripes[i].status == ReadDataStatus_Cancelling) {
+            complete = FALSE;
+            break;
+        }
+    }
+    
+    if (complete)
+        KeSetEvent(&context->Event, 0, FALSE);
+
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS STDCALL read_data(device_extension* Vcb, UINT64 addr, UINT32 length, UINT8* buf) {
+    CHUNK_ITEM* ci;
+    CHUNK_ITEM_STRIPE* cis;
+    read_data_context* context;
+    UINT64 i/*, type*/, offset;
+    NTSTATUS Status;
+    device** devices;
+    
+    // FIXME - make this work with RAID
+    
+    if (Vcb->log_to_phys_loaded) {
+        chunk* c = get_chunk_from_address(Vcb, addr);
+        
+        if (!c) {
+            ERR("get_chunk_from_address failed\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        ci = c->chunk_item;
+        offset = c->offset;
+        devices = c->devices;
+    }
+    
+//     if (ci->type & BLOCK_FLAG_DUPLICATE) {
+//         type = BLOCK_FLAG_DUPLICATE;
+//     } else if (ci->type & BLOCK_FLAG_RAID0) {
+//         FIXME("RAID0 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID1) {
+//         FIXME("RAID1 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID10) {
+//         FIXME("RAID10 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID5) {
+//         FIXME("RAID5 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID6) {
+//         FIXME("RAID6 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else { // SINGLE
+//         type = 0;
+//     }
+
+    cis = (CHUNK_ITEM_STRIPE*)&ci[1];
+
+    context = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_data_context), ALLOC_TAG);
+    if (!context) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context, sizeof(read_data_context));
+    KeInitializeEvent(&context->Event, NotificationEvent, FALSE);
+    
+    context->stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_data_stripe) * ci->num_stripes, ALLOC_TAG);
+    if (!context->stripes) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context->stripes, sizeof(read_data_stripe) * ci->num_stripes);
+    
+    context->buflen = length;
+    context->num_stripes = ci->num_stripes;
+//     context->type = type;
+    
+    // FIXME - for RAID, check beforehand whether there's enough devices to satisfy request
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        PIO_STACK_LOCATION IrpSp;
+        
+        if (!devices[i]) {
+            context->stripes[i].status = ReadDataStatus_MissingDevice;
+            context->stripes[i].buf = NULL;
+        } else {
+            context->stripes[i].context = (struct read_data_context*)context;
+            context->stripes[i].buf = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);
+            
+            if (!context->stripes[i].buf) {
+                ERR("out of memory\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto exit;
+            }
+
+            context->stripes[i].Irp = IoAllocateIrp(devices[i]->devobj->StackSize, FALSE);
+            
+            if (!context->stripes[i].Irp) {
+                ERR("IoAllocateIrp failed\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto exit;
+            }
+            
+            IrpSp = IoGetNextIrpStackLocation(context->stripes[i].Irp);
+            IrpSp->MajorFunction = IRP_MJ_READ;
+            
+            if (devices[i]->devobj->Flags & DO_BUFFERED_IO) {
+                FIXME("FIXME - buffered IO\n");
+            } else if (devices[i]->devobj->Flags & DO_DIRECT_IO) {
+                context->stripes[i].Irp->MdlAddress = IoAllocateMdl(context->stripes[i].buf, length, FALSE, FALSE, NULL);
+                if (!context->stripes[i].Irp->MdlAddress) {
+                    ERR("IoAllocateMdl failed\n");
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    goto exit;
+                }
+                
+                MmProbeAndLockPages(context->stripes[i].Irp->MdlAddress, KernelMode, IoWriteAccess);
+            } else {
+                context->stripes[i].Irp->UserBuffer = context->stripes[i].buf;
+            }
+
+            IrpSp->Parameters.Read.Length = length;
+            IrpSp->Parameters.Read.ByteOffset.QuadPart = addr - offset + cis[i].offset;
+            
+            context->stripes[i].Irp->UserIosb = &context->stripes[i].iosb;
+            
+            IoSetCompletionRoutine(context->stripes[i].Irp, read_data_completion, &context->stripes[i], TRUE, TRUE, TRUE);
+
+            context->stripes[i].status = ReadDataStatus_Pending;
+        }
+    }
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status != ReadDataStatus_MissingDevice) {
+            IoCallDriver(devices[i]->devobj, context->stripes[i].Irp);
+        }
+    }
+
+    KeWaitForSingleObject(&context->Event, Executive, KernelMode, FALSE, NULL);
+    
+    // FIXME - if checksum error, write good data over bad
+    
+    // check if any of the stripes succeeded
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status == ReadDataStatus_Success) {
+            RtlCopyMemory(buf, context->stripes[i].buf, length);
+            Status = STATUS_SUCCESS;
+            goto exit;
+        }
+    }
+    
+    // if not, see if we got a checksum error
+    
+//     for (i = 0; i < ci->num_stripes; i++) {
+//         if (context->stripes[i].status == ReadDataStatus_CRCError) {
+//             WARN("stripe %llu had a checksum error\n", i);
+//             
+//             Status = STATUS_IMAGE_CHECKSUM_MISMATCH;
+//             goto exit;
+//         }
+//     }
+    
+    // failing that, return the first error we encountered
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status == ReadDataStatus_Error) {
+            Status = context->stripes[i].iosb.Status;
+            goto exit;
+        }
+    }
+    
+    // if we somehow get here, return STATUS_INTERNAL_ERROR
+    
+    Status = STATUS_INTERNAL_ERROR;
+
+exit:
+
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].Irp) {
+            if (devices[i]->devobj->Flags & DO_DIRECT_IO) {
+                MmUnlockPages(context->stripes[i].Irp->MdlAddress);
+                IoFreeMdl(context->stripes[i].Irp->MdlAddress);
+            }
+            IoFreeIrp(context->stripes[i].Irp);
+        }
+        
+        if (context->stripes[i].buf)
+            ExFreePool(context->stripes[i].buf);
+    }
+
+    ExFreePool(context->stripes);
+    ExFreePool(context);
+        
+    return Status;
+}
+
+static NTSTATUS STDCALL read_stream(fcb* fcb, UINT8* data, UINT64 start, ULONG length, ULONG* pbr) {
+    UINT8* xattrdata;
+    UINT16 xattrlen;
+    ULONG readlen;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %p, %llx, %llx, %p)\n", fcb, data, start, length, pbr);
+    
+    if (pbr) *pbr = 0;
+    
+    if (!get_xattr(fcb->Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &xattrdata, &xattrlen)) {
+        ERR("get_xattr failed\n");
+        return STATUS_OBJECT_NAME_NOT_FOUND;
+    }
+    
+    if (start >= xattrlen) {
+        TRACE("tried to read beyond end of stream\n");
+        Status = STATUS_END_OF_FILE;
+        goto end;
+    }
+    
+    if (length == 0) {
+        WARN("tried to read zero bytes\n");
+        Status = STATUS_SUCCESS;
+        goto end;
+    }
+    
+    if (start + length < xattrlen)
+        readlen = length;
+    else
+        readlen = (ULONG)xattrlen - (ULONG)start;
+    
+    RtlCopyMemory(data + start, xattrdata, readlen);
+    
+    if (pbr) *pbr = readlen;
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    ExFreePool(xattrdata);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL read_file(device_extension* Vcb, root* subvol, UINT64 inode, UINT8* data, UINT64 start, UINT64 length, ULONG* pbr) {
+    KEY searchkey;
+    NTSTATUS Status;
+    traverse_ptr tp, next_tp;
+    EXTENT_DATA* ed;
+    UINT64 bytes_read = 0;
+    
+    TRACE("(%p, %llx, %llx, %p, %llx, %llx, %p)\n", Vcb, subvol->id, inode, data, start, length, pbr);
+    
+    if (pbr)
+        *pbr = 0;
+    
+    searchkey.obj_id = inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    searchkey.offset = start;
+
+    Status = find_item(Vcb, 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) {
+        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 != 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;
+    }
+    
+    if (tp.item->key.offset > start) {
+        ERR("first EXTENT_DATA was after offset\n");
+        free_traverse_ptr(&tp);
+        Status = STATUS_INTERNAL_ERROR;
+        goto exit;
+    }
+    
+//     while (TRUE) {
+//         BOOL foundnext = find_next_item(Vcb, &tp, &next_tp, NULL, FALSE);
+//         
+//         if (!foundnext || next_tp.item->key.obj_id != inode ||
+//             next_tp.item->key.obj_type != TYPE_EXTENT_DATA || next_tp.item->key.offset > start) {
+//             if (foundnext)
+//                 free_traverse_ptr(&next_tp);
+//             
+//             break;
+//         }
+//         
+//         free_traverse_ptr(&tp);
+//         tp = next_tp;
+//     }
+    
+    do {
+        ed = (EXTENT_DATA*)tp.item->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);
+            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;
+        }
+        
+        if (tp.item->key.offset + ed->decoded_size < 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;
+        }
+        
+        switch (ed->type) {
+            case EXTENT_TYPE_INLINE:
+            {
+                UINT64 off = start + bytes_read - tp.item->key.offset;
+                UINT64 read;
+                
+                read = ed->decoded_size - off;
+                if (read > length) read = length;
+                
+                RtlCopyMemory(data + bytes_read, &ed->data[off], read);
+                
+                bytes_read += read;
+                length -= read;
+                break;
+            }
+            
+            case EXTENT_TYPE_REGULAR:
+            {
+                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
+                UINT64 off = start + bytes_read - tp.item->key.offset;
+                UINT32 to_read, read;
+                UINT8* buf;
+                
+                read = ed->decoded_size - off;
+                if (read > length) read = length;
+                
+                if (ed2->address == 0) {
+                    RtlZeroMemory(data + bytes_read, read);
+                } else {
+                    to_read = sector_align(read, Vcb->superblock.sector_size);
+                    
+                    buf = ExAllocatePoolWithTag(PagedPool, to_read, ALLOC_TAG);
+                    
+                    if (!buf) {
+                        ERR("out of memory\n");
+                        free_traverse_ptr(&tp);
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto exit;
+                    }
+                    
+                    // FIXME - load checksums
+                    
+                    Status = read_data(Vcb, ed2->address + ed2->offset + off, to_read, buf);
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("read_data returned %08x\n", Status);
+                        ExFreePool(buf);
+                        free_traverse_ptr(&tp);
+                        goto exit;
+                    }
+                    
+                    RtlCopyMemory(data + bytes_read, buf, read);
+                    
+                    ExFreePool(buf);
+                }
+                
+                bytes_read += read;
+                length -= read;
+                
+                break;
+            }
+           
+            case EXTENT_TYPE_PREALLOC:
+            {
+                UINT64 off = start + bytes_read - tp.item->key.offset;
+                UINT32 read;
+                
+                read = ed->decoded_size - off;
+                if (read > length) read = length;
+
+                RtlZeroMemory(data + bytes_read, read);
+
+                bytes_read += read;
+                length -= read;
+                
+                break;
+            }
+                
+            default:
+                WARN("Unsupported extent data type %u\n", ed->type);
+                free_traverse_ptr(&tp);
+                Status = STATUS_NOT_IMPLEMENTED;
+                goto exit;
+        }
+        
+        if (length > 0) {
+            BOOL foundnext = find_next_item(Vcb, &tp, &next_tp, FALSE);
+            
+            if (!foundnext)
+                break;
+            else if (next_tp.item->key.obj_id != inode ||
+                next_tp.item->key.obj_type != TYPE_EXTENT_DATA ||
+                next_tp.item->key.offset != tp.item->key.offset + ed->decoded_size
+            ) {
+                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;
+    
+exit:
+    return Status;
+}
+
+NTSTATUS STDCALL drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    UINT8* data;
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    fcb* fcb = FileObject->FsContext;
+    UINT64 start;
+    ULONG length, bytes_read;
+    NTSTATUS Status;
+    BOOL top_level;
+    
+    FsRtlEnterFileSystem();
+    
+    top_level = is_top_level(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);
+    
+    if (Irp->MdlAddress && !data) {
+        ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto exit;
+    }
+    
+    Irp->IoStatus.Information = 0;
+    
+    start = IrpSp->Parameters.Read.ByteOffset.QuadPart;
+    length = IrpSp->Parameters.Read.Length;
+    bytes_read = 0;
+    
+    if (!fcb || !fcb->Vcb || !fcb->subvol) {
+        Status = STATUS_INTERNAL_ERROR; // FIXME - invalid param error?
+        goto exit;
+    }
+    
+    TRACE("file = %.*S (fcb = %p)\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, 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 (!(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForReadAccess(&fcb->lock, Irp)) {
+        WARN("tried to read locked region\n");
+        Status = STATUS_FILE_LOCK_CONFLICT;
+        goto exit;
+    }
+    
+    if (length == 0) {
+        WARN("tried to read zero bytes\n");
+        Status = STATUS_SUCCESS;
+        goto exit;
+    }
+    
+    if (start >= fcb->Header.FileSize.QuadPart) {
+        TRACE("tried to read with offset after file end (%llx >= %llx)\n", start, fcb->Header.FileSize.QuadPart);
+        Status = STATUS_END_OF_FILE;
+        goto exit;
+    }
+    
+    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)) {
+        BOOL wait;
+        
+        Status = STATUS_SUCCESS;
+        
+//         try {
+            if (!FileObject->PrivateCacheMap) {
+                CC_FILE_SIZES ccfs;
+                
+                ccfs.AllocationSize = fcb->Header.AllocationSize;
+                ccfs.FileSize = fcb->Header.FileSize;
+                ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+                
+                TRACE("calling CcInitializeCacheMap (%llx, %llx, %llx)\n",
+                            ccfs.AllocationSize.QuadPart, ccfs.FileSize.QuadPart, ccfs.ValidDataLength.QuadPart);
+                CcInitializeCacheMap(FileObject, &ccfs, FALSE, cache_callbacks, FileObject);
+
+                CcSetReadAheadGranularity(FileObject, READ_AHEAD_GRANULARITY);
+            }
+            
+            // FIXME - uncomment this when async is working
+    //         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;
+            }
+            TRACE("CcCopyRead finished\n");
+//         } except (EXCEPTION_EXECUTE_HANDLER) {
+//             Status = GetExceptionCode();
+//         }
+        
+        if (NT_SUCCESS(Status)) {
+            Status = Irp->IoStatus.Status;
+            bytes_read = Irp->IoStatus.Information;
+        } else
+            ERR("EXCEPTION - %08x\n", Status);
+    } else {
+        if (!(Irp->Flags & IRP_PAGING_IO) && FileObject->SectionObjectPointer->DataSectionObject) {
+            IO_STATUS_BLOCK iosb;
+            
+            CcFlushCache(FileObject->SectionObjectPointer, &IrpSp->Parameters.Read.ByteOffset, length, &iosb);
+            
+            if (!NT_SUCCESS(iosb.Status)) {
+                ERR("CcFlushCache returned %08x\n", iosb.Status);
+                Status = iosb.Status;
+                goto exit;
+            }
+        }
+        
+        acquire_tree_lock(fcb->Vcb, FALSE);
+    
+        if (fcb->ads)
+            Status = read_stream(fcb, data, start, length, &bytes_read);
+        else
+            Status = read_file(fcb->Vcb, fcb->subvol, fcb->inode, data, start, length, &bytes_read);
+        
+        release_tree_lock(fcb->Vcb, FALSE);
+        
+        TRACE("read %u bytes\n", bytes_read);
+        
+        Irp->IoStatus.Information = bytes_read;
+    }
+    
+exit:
+    Irp->IoStatus.Status = Status;
+    
+    if (FileObject->Flags & FO_SYNCHRONOUS_IO && !(Irp->Flags & IRP_PAGING_IO))
+        FileObject->CurrentByteOffset.QuadPart = start + (NT_SUCCESS(Status) ? bytes_read : 0);
+    
+    TRACE("Irp->IoStatus.Status = %08x\n", Irp->IoStatus.Status);
+    TRACE("Irp->IoStatus.Information = %lu\n", Irp->IoStatus.Information);
+    TRACE("returning %08x\n", Status);
+    
+    if (Status != STATUS_PENDING)
+        IoCompleteRequest(Irp, IO_NO_INCREMENT);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+    
+    FsRtlExitFileSystem();
+    
+    return Status;
+}
diff --git a/reactos/drivers/filesystems/btrfs/reparse.c b/reactos/drivers/filesystems/btrfs/reparse.c
new file mode 100644 (file)
index 0000000..95078f2
--- /dev/null
@@ -0,0 +1,605 @@
+/* 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"
+
+BOOL follow_symlink(fcb* fcb, PFILE_OBJECT FileObject) {
+    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);
+    
+    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;
+    }
+    
+    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;
+    }
+    
+    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) {
+            ERR("out of memory\n");
+            ExFreePool(utf16);
+            goto end;
+        }
+        
+        RtlCopyMemory(abspath.Buffer, fcb->par->full_filename.Buffer, fcb->par->full_filename.Length);
+        abspath.Length = fcb->par->full_filename.Length;
+        
+        if (fcb->par->par) {
+            abspath.Buffer[abspath.Length / sizeof(WCHAR)] = '\\';
+            abspath.Length += sizeof(WCHAR);
+        }
+        
+        RtlCopyMemory(&abspath.Buffer[abspath.Length / sizeof(WCHAR)], utf16, stringlen);
+        abspath.Length += stringlen;
+        
+        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);
+        
+        if (!FileObject->FileName.Buffer) {
+            ERR("out of memory\n");
+            ExFreePool(abspath.Buffer);
+            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);
+
+    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);
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    release_tree_lock(fcb->Vcb, FALSE);
+
+    return Status;
+}
+
+static NTSTATUS change_file_type(device_extension* Vcb, UINT64 inode, root* subvol, UINT64 parinode, UINT64 index, PANSI_STRING utf8, UINT8 type, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    UINT32 crc32;
+    traverse_ptr tp;
+    NTSTATUS Status;
+    
+    crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8->Buffer, (ULONG)utf8->Length);
+
+    searchkey.obj_id = parinode;
+    searchkey.obj_type = TYPE_DIR_ITEM;
+    searchkey.offset = crc32;
+
+    Status = find_item(Vcb, subvol, &tp, &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 < 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 = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG), *di2;
+            BOOL found = FALSE;
+            ULONG len = tp.item->size;
+            
+            if (!di) {
+                ERR("out of memory\n");
+                free_traverse_ptr(&tp);
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            RtlCopyMemory(di, tp.item->data, tp.item->size);
+            
+            di2 = di;
+            do {
+                if (len < sizeof(DIR_ITEM) || len < sizeof(DIR_ITEM) - 1 + di2->m + di2->n) {
+                    ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+                    break;
+                }
+                
+                if (di2->n == utf8->Length && RtlCompareMemory(di2->name, utf8->Buffer, utf8->Length) == utf8->Length) {
+                    di2->type = type;
+                    found = TRUE;
+                    break;
+                }
+                
+                if (len > sizeof(DIR_ITEM) - sizeof(char) + di2->m + di2->n) {
+                    len -= sizeof(DIR_ITEM) - sizeof(char) + di2->m + di2->n;
+                    di2 = (DIR_ITEM*)&di2->name[di2->m + di2->n];
+                } else
+                    break;
+            } while (len > 0);
+            
+            if (found) {
+                delete_tree_item(Vcb, &tp, rollback);
+                insert_tree_item(Vcb, subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, di, tp.item->size, NULL, rollback);
+            } else
+                ExFreePool(di);
+        }
+    } else {
+        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;
+    
+    Status = find_item(Vcb, subvol, &tp, &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 < 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;
+            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;
+            }
+            
+            RtlCopyMemory(di2, di, tp.item->size);
+            di2->type = type;
+            
+            delete_tree_item(Vcb, &tp, rollback);
+            insert_tree_item(Vcb, subvol, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, di2, tp.item->size, NULL, rollback);
+        }
+    }
+    
+    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;
+    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;
+    }
+    
+    subname.Buffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)];
+    subname.MaximumLength = subname.Length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength;
+    
+    ERR("substitute name = %.*S\n", subname.Length / sizeof(WCHAR), subname.Buffer);
+    
+    fcb->type = BTRFS_TYPE_SYMLINK;
+    
+    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);
+        goto end;
+    }
+    
+    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_SYMLINK, &rollback);
+                    
+                    if (!NT_SUCCESS(Status)) {
+                        ERR("error - change_file_type returned %08x\n", Status);
+                        goto end;
+                    }
+                    
+                    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) {
+            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;
+        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);
+            goto end;
+        }
+        
+        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_SYMLINK, &rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("error - change_file_type returned %08x\n", Status);
+                            goto end;
+                        }
+                        
+                        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) {
+                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);
+    if (!NT_SUCCESS(Status)) {
+        ERR("truncate_file returned %08x\n", Status);
+        goto end;
+    }
+    
+    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;
+    }
+    
+    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;
+    }
+    
+    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;
+    }
+    
+    for (i = 0; i < target.Length; i++) {
+        if (target.Buffer[i] == '\\')
+            target.Buffer[i] = '/';
+    }
+    
+    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);
+    
+    ExFreePool(target.Buffer);
+    
+    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;
+}
diff --git a/reactos/drivers/filesystems/btrfs/resource.h b/reactos/drivers/filesystems/btrfs/resource.h
new file mode 100644 (file)
index 0000000..65e7d50
--- /dev/null
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by btrfs.rc
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        101
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
diff --git a/reactos/drivers/filesystems/btrfs/search.c b/reactos/drivers/filesystems/btrfs/search.c
new file mode 100644 (file)
index 0000000..4245e2e
--- /dev/null
@@ -0,0 +1,332 @@
+/* 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"
+
+#ifndef __REACTOS__
+#include <ntddk.h>
+#include <ntifs.h>
+#include <mountmgr.h>
+#include <windef.h>
+#endif
+
+#include <initguid.h>
+#ifndef __REACTOS__
+#include <winioctl.h>
+#endif
+#include <wdmguid.h>
+
+#ifndef __REACTOS__
+typedef struct _OBJECT_DIRECTORY_INFORMATION {
+    UNICODE_STRING Name;
+    UNICODE_STRING TypeName;
+} OBJECT_DIRECTORY_INFORMATION, *POBJECT_DIRECTORY_INFORMATION;
+#endif
+
+#if !defined (_GNU_NTIFS_) || defined(__REACTOS__)
+NTSTATUS WINAPI ZwQueryDirectoryObject(HANDLE DirectoryHandle, PVOID Buffer, ULONG Length,
+                                       BOOLEAN ReturnSingleEntry, BOOLEAN RestartScan, PULONG Context,
+                                       PULONG  ReturnLength);
+#endif
+
+VOID WINAPI IopNotifyPlugPlayNotification(
+    IN PDEVICE_OBJECT DeviceObject,
+    IN IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
+    IN LPCGUID Event,
+    IN PVOID EventCategoryData1,
+    IN PVOID EventCategoryData2
+);
+
+static const WCHAR devpath[] = {'\\','D','e','v','i','c','e',0};
+
+static void STDCALL add_volume(PDEVICE_OBJECT mountmgr, PUNICODE_STRING us) {
+    ULONG tnsize;
+    MOUNTMGR_TARGET_NAME* tn;
+    KEVENT Event;
+    IO_STATUS_BLOCK IoStatusBlock;
+    PIRP Irp;
+    NTSTATUS Status;
+    ULONG mmdltsize;
+    MOUNTMGR_DRIVE_LETTER_TARGET* mmdlt;
+    MOUNTMGR_DRIVE_LETTER_INFORMATION mmdli;
+    
+    TRACE("found BTRFS volume\n");
+    
+    tnsize = sizeof(MOUNTMGR_TARGET_NAME) - sizeof(WCHAR) + us->Length;
+    tn = ExAllocatePoolWithTag(NonPagedPool, tnsize, ALLOC_TAG);
+    if (!tn) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    tn->DeviceNameLength = us->Length;
+    RtlCopyMemory(tn->DeviceName, us->Buffer, tn->DeviceNameLength);
+    
+    KeInitializeEvent(&Event, NotificationEvent, FALSE);
+    Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION,
+                                        mountmgr, tn, tnsize, 
+                                        NULL, 0, FALSE, &Event, &IoStatusBlock);
+    if (!Irp) {
+        ERR("IoBuildDeviceIoControlRequest failed\n");
+        return;
+    }
+
+    Status = IoCallDriver(mountmgr, Irp);
+    if (Status == STATUS_PENDING) {
+        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
+        Status = IoStatusBlock.Status;
+    }
+
+    if (!NT_SUCCESS(Status))
+        ERR("IoCallDriver (1) returned %08x\n", Status);
+    
+    ExFreePool(tn);
+    
+    mmdltsize = sizeof(MOUNTMGR_DRIVE_LETTER_TARGET) - 1 + us->Length;
+    
+    mmdlt = ExAllocatePoolWithTag(NonPagedPool, mmdltsize, ALLOC_TAG);
+    if (!mmdlt) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    mmdlt->DeviceNameLength = us->Length;
+    RtlCopyMemory(&mmdlt->DeviceName, us->Buffer, us->Length);
+    TRACE("mmdlt = %.*S\n", mmdlt->DeviceNameLength / sizeof(WCHAR), mmdlt->DeviceName);
+    
+    KeInitializeEvent(&Event, NotificationEvent, FALSE);
+    Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER,
+                                        mountmgr, mmdlt, mmdltsize, 
+                                        &mmdli, sizeof(MOUNTMGR_DRIVE_LETTER_INFORMATION), FALSE, &Event, &IoStatusBlock);
+    if (!Irp) {
+        ERR("IoBuildDeviceIoControlRequest failed\n");
+        return;
+    }
+
+    Status = IoCallDriver(mountmgr, Irp);
+    if (Status == STATUS_PENDING) {
+        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
+        Status = IoStatusBlock.Status;
+    }
+
+    if (!NT_SUCCESS(Status))
+        ERR("IoCallDriver (2) returned %08x\n", Status);
+    else
+        TRACE("DriveLetterWasAssigned = %u, CurrentDriveLetter = %c\n", mmdli.DriveLetterWasAssigned, mmdli.CurrentDriveLetter);
+    
+    ExFreePool(mmdlt);
+}
+
+static void STDCALL test_vol(PDEVICE_OBJECT mountmgr, PUNICODE_STRING us, LIST_ENTRY* volumes) {
+    KEVENT Event;
+    PIRP Irp;
+    IO_STATUS_BLOCK IoStatusBlock;
+    NTSTATUS Status;
+    PFILE_OBJECT FileObject;
+    PDEVICE_OBJECT DeviceObject;
+    LARGE_INTEGER Offset;
+    ULONG toread;
+    UINT8* data;
+    UNICODE_STRING us2;
+    BOOL added_entry = FALSE;
+    
+    TRACE("%.*S\n", us->Length / sizeof(WCHAR), us->Buffer);
+    
+    us2.Length = ((wcslen(devpath) + 1) * sizeof(WCHAR)) + us->Length;
+    us2.MaximumLength = us2.Length;
+    us2.Buffer = ExAllocatePoolWithTag(PagedPool, us2.Length, ALLOC_TAG);
+    if (!us2.Buffer) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    RtlCopyMemory(us2.Buffer, devpath, wcslen(devpath) * sizeof(WCHAR));
+    us2.Buffer[wcslen(devpath)] = '\\';
+    RtlCopyMemory(&us2.Buffer[wcslen(devpath)+1], us->Buffer, us->Length);
+    
+    TRACE("%.*S\n", us2.Length / sizeof(WCHAR), us2.Buffer);
+    
+    Status = IoGetDeviceObjectPointer(&us2, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject);
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
+        goto exit;
+    }
+    
+    KeInitializeEvent(&Event, NotificationEvent, FALSE);
+
+    Offset.QuadPart = superblock_addrs[0];
+    
+    toread = sector_align(sizeof(superblock), DeviceObject->SectorSize);
+    data = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);
+    if (!data) {
+        ERR("out of memory\n");
+        goto deref;
+    }
+
+    Irp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, DeviceObject, data, toread, &Offset, &Event, &IoStatusBlock);
+    
+    if (!Irp) {
+        ERR("IoBuildSynchronousFsdRequest failed\n");
+        goto deref;
+    }
+
+    Status = IoCallDriver(DeviceObject, Irp);
+
+    if (Status == STATUS_PENDING) {
+        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
+        Status = IoStatusBlock.Status;
+    }
+
+    if (NT_SUCCESS(Status) && IoStatusBlock.Information > 0 && ((superblock*)data)->magic == BTRFS_MAGIC) {
+        superblock* sb = (superblock*)data;
+        volume* v = ExAllocatePoolWithTag(PagedPool, sizeof(volume), ALLOC_TAG);
+        if (!v) {
+            ERR("out of memory\n");
+            goto deref;
+        }
+        
+        v->devobj = DeviceObject;
+        RtlCopyMemory(&v->fsuuid, &sb->uuid, sizeof(BTRFS_UUID));
+        RtlCopyMemory(&v->devuuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID));
+        v->devnum = sb->dev_item.dev_id;
+        v->devpath = us2;
+        v->processed = FALSE;
+        InsertTailList(volumes, &v->list_entry);
+        
+        TRACE("volume found\n");
+        TRACE("FS uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+              v->fsuuid.uuid[0], v->fsuuid.uuid[1], v->fsuuid.uuid[2], v->fsuuid.uuid[3], v->fsuuid.uuid[4], v->fsuuid.uuid[5], v->fsuuid.uuid[6], v->fsuuid.uuid[7],
+              v->fsuuid.uuid[8], v->fsuuid.uuid[9], v->fsuuid.uuid[10], v->fsuuid.uuid[11], v->fsuuid.uuid[12], v->fsuuid.uuid[13], v->fsuuid.uuid[14], v->fsuuid.uuid[15]);
+        TRACE("device uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+              v->devuuid.uuid[0], v->devuuid.uuid[1], v->devuuid.uuid[2], v->devuuid.uuid[3], v->devuuid.uuid[4], v->devuuid.uuid[5], v->devuuid.uuid[6], v->devuuid.uuid[7],
+              v->devuuid.uuid[8], v->devuuid.uuid[9], v->devuuid.uuid[10], v->devuuid.uuid[11], v->devuuid.uuid[12], v->devuuid.uuid[13], v->devuuid.uuid[14], v->devuuid.uuid[15]);
+        TRACE("device number %llx\n", v->devnum);
+
+        added_entry = TRUE;
+    }
+    
+deref:
+    ExFreePool(data);
+    ObDereferenceObject(FileObject);
+    
+exit:
+    if (!added_entry)
+        ExFreePool(us2.Buffer);
+}
+
+void STDCALL look_for_vols(LIST_ENTRY* volumes) {
+    PFILE_OBJECT FileObject;
+    PDEVICE_OBJECT mountmgr;
+    OBJECT_ATTRIBUTES attr;
+    UNICODE_STRING mmdevpath, us;
+    HANDLE h;
+    OBJECT_DIRECTORY_INFORMATION* odi;
+    ULONG odisize;
+    ULONG context;
+    BOOL restart;
+    NTSTATUS Status;
+    LIST_ENTRY* le;
+    
+    static const WCHAR hdv[] = {'H','a','r','d','d','i','s','k','V','o','l','u','m','e',0};
+    static const WCHAR device[] = {'D','e','v','i','c','e',0};
+    
+    RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
+    Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
+        return;
+    }
+    
+    RtlInitUnicodeString(&us, devpath);
+    
+    attr.Length = sizeof(attr);
+    attr.RootDirectory = 0;
+    attr.Attributes = OBJ_CASE_INSENSITIVE;
+    attr.ObjectName = &us;
+    attr.SecurityDescriptor = NULL;
+    attr.SecurityQualityOfService = NULL;
+
+    Status = ZwOpenDirectoryObject(&h, DIRECTORY_TRAVERSE, &attr);
+
+    if (!NT_SUCCESS(Status)) {
+        ERR("ZwOpenDirectoryObject returned %08x\n", Status);
+        return;
+    }
+    
+    odisize = sizeof(OBJECT_DIRECTORY_INFORMATION) * 16;
+    odi = ExAllocatePoolWithTag(PagedPool, odisize, ALLOC_TAG);
+    if (!odi) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    restart = TRUE;
+    do {
+        Status = ZwQueryDirectoryObject(h, odi, odisize, FALSE, restart, &context, NULL/*&retlen*/);
+        restart = FALSE;
+        
+        if (!NT_SUCCESS(Status)) {
+            if (Status != STATUS_NO_MORE_ENTRIES)
+                ERR("ZwQueryDirectoryObject returned %08x\n", Status);
+        } else {
+            OBJECT_DIRECTORY_INFORMATION* odi2 = odi;
+            
+            while (odi2->Name.Buffer) {
+                if (odi2->TypeName.Length == wcslen(device) * sizeof(WCHAR) &&
+                    RtlCompareMemory(odi2->TypeName.Buffer, device, wcslen(device) * sizeof(WCHAR)) == wcslen(device) * sizeof(WCHAR) &&
+                    odi2->Name.Length > wcslen(hdv) * sizeof(WCHAR) &&
+                    RtlCompareMemory(odi2->Name.Buffer, hdv, wcslen(hdv) * sizeof(WCHAR)) == wcslen(hdv) * sizeof(WCHAR)) {
+                        test_vol(mountmgr, &odi2->Name, volumes);
+                }
+                odi2 = &odi2[1];
+            }
+        }
+    } while (NT_SUCCESS(Status));
+    
+    ZwClose(h);
+    
+    // FIXME - if Windows has already added the second device of a filesystem itself, delete it
+    
+    le = volumes->Flink;
+    while (le != volumes) {
+        volume* v = CONTAINING_RECORD(le, volume, list_entry);
+        
+        if (!v->processed) {
+            LIST_ENTRY* le2 = le;
+            volume* mountvol = v;
+            
+            while (le2 != volumes) {
+                volume* v2 = CONTAINING_RECORD(le2, volume, list_entry);
+                
+                if (RtlCompareMemory(&v2->fsuuid, &v->fsuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
+                    v2->processed = TRUE;
+                    
+                    if (v2->devnum < mountvol->devnum)
+                        mountvol = v2;
+                }
+                
+                le2 = le2->Flink;
+            }
+            
+            add_volume(mountmgr, &mountvol->devpath);
+        }
+        
+        le = le->Flink;
+    }
+    
+    ObDereferenceObject(FileObject);
+}
diff --git a/reactos/drivers/filesystems/btrfs/security.c b/reactos/drivers/filesystems/btrfs/security.c
new file mode 100644 (file)
index 0000000..0eb1c2a
--- /dev/null
@@ -0,0 +1,1034 @@
+/* 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"
+
+typedef struct {
+    UCHAR revision;
+    UCHAR elements;
+    UCHAR auth[6];
+    UINT32 nums[8];
+} sid_header;
+
+static sid_header sid_BA = { 1, 2, SECURITY_NT_AUTHORITY, {32, 544}}; // BUILTIN\Administrators
+static sid_header sid_SY = { 1, 1, SECURITY_NT_AUTHORITY, {18}};      // NT AUTHORITY\SYSTEM
+static sid_header sid_BU = { 1, 2, SECURITY_NT_AUTHORITY, {32, 545}}; // BUILTIN\Users
+static sid_header sid_AU = { 1, 1, SECURITY_NT_AUTHORITY, {11}};      // NT AUTHORITY\Authenticated Users
+
+typedef struct {
+    UCHAR flags;
+    ACCESS_MASK mask;
+    sid_header* sid;
+} dacl;
+
+static dacl def_dacls[] = {
+    { 0, FILE_ALL_ACCESS, &sid_BA },
+    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_ALL_ACCESS, &sid_BA },
+    { 0, FILE_ALL_ACCESS, &sid_SY },
+    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_ALL_ACCESS, &sid_SY },
+    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, &sid_BU },
+    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, &sid_AU },
+    { 0, FILE_ADD_SUBDIRECTORY, &sid_AU },
+    // FIXME - Mandatory Label\High Mandatory Level:(OI)(NP)(IO)(NW)
+    { 0, 0, NULL }
+};
+
+extern LIST_ENTRY uid_map_list;
+
+// UINT32 STDCALL get_uid() {
+//     PACCESS_TOKEN at = PsReferencePrimaryToken(PsGetCurrentProcess());
+//     HANDLE h;
+//     ULONG len, size;
+//     TOKEN_USER* tu;
+//     NTSTATUS Status;
+//     UINT32 uid = UID_NOBODY;
+//     LIST_ENTRY* le;
+//     uid_map* um;
+// //     char s[256];
+// 
+//     Status = ObOpenObjectByPointer(at, OBJ_KERNEL_HANDLE, NULL, GENERIC_READ, NULL, KernelMode, &h);
+//     if (!NT_SUCCESS(Status)) {
+//         ERR("ObOpenObjectByPointer returned %08x\n", Status);
+//         goto exit;
+//     }
+//     
+//     Status = ZwQueryInformationToken(h, TokenUser, NULL, 0, &len);
+//     if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {
+//         ERR("ZwQueryInformationToken(1) returned %08x (len = %u)\n", Status, len);
+//         goto exit2;
+//     }
+// 
+// //     TRACE("len = %u\n", len);
+//     
+//     tu = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
+//     
+//     Status = ZwQueryInformationToken(h, TokenUser, tu, len, &len);
+//     
+//     if (!NT_SUCCESS(Status)) {
+//         ERR("ZwQueryInformationToken(2) returned %08x\n", Status);
+//         goto exit3;
+//     }
+//     
+//     size = 8 + (4 * ((sid_header*)tu->User.Sid)->elements);
+//     
+//     le = uid_map_list.Flink;
+//     while (le != &uid_map_list) {
+//         um = CONTAINING_RECORD(le, uid_map, listentry);
+//         
+//         if (((sid_header*)um->sid)->elements == ((sid_header*)tu->User.Sid)->elements &&
+//             RtlCompareMemory(um->sid, tu->User.Sid, size) == size) {
+//             uid = um->uid;
+//             break;
+//         }
+//         
+//         le = le->Flink;
+//     }
+//     
+// //     sid_to_string(tu->User.Sid, s);
+//     
+// //     TRACE("got SID: %s\n", s);
+//     TRACE("uid = %u\n", uid);
+//     
+// exit3:
+//     ExFreePool(tu);
+//     
+// exit2:
+//     ZwClose(h);
+//     
+// exit:
+//     PsDereferencePrimaryToken(at);
+//     
+//     return uid;
+// }
+
+void add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, UINT32 uid) {
+    unsigned int i, np;
+    UINT8 numdashes;
+    UINT64 val;
+    ULONG sidsize;
+    sid_header* sid;
+    uid_map* um;
+//     char s[255];
+    
+    if (sidstringlength < 4 ||
+        sidstring[0] != 'S' ||
+        sidstring[1] != '-' ||
+        sidstring[2] != '1' ||
+        sidstring[3] != '-') {
+        ERR("invalid SID\n");
+        return;
+    }
+    
+    sidstring = &sidstring[4];
+    sidstringlength -= 4;
+    
+    numdashes = 0;
+    for (i = 0; i < sidstringlength; i++) {
+        if (sidstring[i] == '-') {
+            numdashes++;
+            sidstring[i] = 0;
+        }
+    }
+    
+    sidsize = 8 + (numdashes * 4);
+    sid = ExAllocatePoolWithTag(PagedPool, sidsize, ALLOC_TAG);
+    if (!sid) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    sid->revision = 0x01;
+    sid->elements = numdashes;
+    
+    np = 0;
+    while (sidstringlength > 0) {
+        val = 0;
+        i = 0;
+        while (sidstring[i] != '-' && i < sidstringlength) {
+            if (sidstring[i] >= '0' && sidstring[i] <= '9') {
+                val *= 10;
+                val += sidstring[i] - '0';
+            } else
+                break;
+            
+            i++;
+        }
+        
+        i++;
+        TRACE("val = %u, i = %u, ssl = %u\n", (UINT32)val, i, sidstringlength);
+        
+        if (np == 0) {
+            sid->auth[0] = (val & 0xff0000000000) >> 40;
+            sid->auth[1] = (val & 0xff00000000) >> 32;
+            sid->auth[2] = (val & 0xff000000) >> 24;
+            sid->auth[3] = (val & 0xff0000) >> 16;
+            sid->auth[4] = (val & 0xff00) >> 8;
+            sid->auth[5] = val & 0xff;
+        } else {
+            sid->nums[np-1] = (UINT32)val;
+        }
+        
+        np++;
+        
+        if (sidstringlength > i) {
+            sidstringlength -= i;
+
+            sidstring = &sidstring[i];
+        } else
+            break;
+    }
+    
+//     sid_to_string(sid, s);
+    
+//     TRACE("%s\n", s);
+    um = ExAllocatePoolWithTag(PagedPool, sizeof(uid_map), ALLOC_TAG);
+    if (!um) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    um->sid = sid;
+    um->uid = uid;
+    
+    InsertTailList(&uid_map_list, &um->listentry);
+}
+
+static void uid_to_sid(UINT32 uid, PSID* sid) {
+    LIST_ENTRY* le;
+    uid_map* um;
+    sid_header* sh;
+    UCHAR els;
+    
+    le = uid_map_list.Flink;
+    while (le != &uid_map_list) {
+        um = CONTAINING_RECORD(le, uid_map, listentry);
+        
+        if (um->uid == uid) {
+            *sid = ExAllocatePoolWithTag(PagedPool, RtlLengthSid(um->sid), ALLOC_TAG);
+            if (!*sid) {
+                ERR("out of memory\n");
+                return;
+            }
+            
+            RtlCopyMemory(*sid, um->sid, RtlLengthSid(um->sid));
+            return;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (uid == 0) { // root
+        // FIXME - find actual Administrator account, rather than SYSTEM (S-1-5-18)
+        // (of form S-1-5-21-...-500)
+        
+        els = 1;
+        
+        sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header) + ((els - 1) * sizeof(UINT32)), ALLOC_TAG);
+        if (!sh) {
+            ERR("out of memory\n");
+            *sid = NULL;
+            return;
+        }
+    
+        sh->revision = 1;
+        sh->elements = els;
+        
+        sh->auth[0] = 0;
+        sh->auth[1] = 0;
+        sh->auth[2] = 0;
+        sh->auth[3] = 0;
+        sh->auth[4] = 0;
+        sh->auth[5] = 5;
+        
+        sh->nums[0] = 18;
+    } else {    
+        // fallback to S-1-22-1-X, Samba's SID scheme
+        sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header), ALLOC_TAG);
+        if (!sh) {
+            ERR("out of memory\n");
+            *sid = NULL;
+            return;
+        }
+        
+        sh->revision = 1;
+        sh->elements = 2;
+        
+        sh->auth[0] = 0;
+        sh->auth[1] = 0;
+        sh->auth[2] = 0;
+        sh->auth[3] = 0;
+        sh->auth[4] = 0;
+        sh->auth[5] = 22;
+        
+        sh->nums[0] = 1;
+        sh->nums[1] = uid;
+    }
+
+    *sid = sh;
+}
+
+static UINT32 sid_to_uid(PSID sid) {
+    LIST_ENTRY* le;
+    uid_map* um;
+    sid_header* sh = sid;
+
+    le = uid_map_list.Flink;
+    while (le != &uid_map_list) {
+        um = CONTAINING_RECORD(le, uid_map, listentry);
+        
+        if (RtlEqualSid(sid, um->sid))
+            return um->uid;
+        
+        le = le->Flink;
+    }
+    
+    if (RtlEqualSid(sid, &sid_SY))
+        return 0; // root
+        
+    // Samba's SID scheme: S-1-22-1-X
+    if (sh->revision == 1 && sh->elements == 2 && sh->auth[0] == 0 && sh->auth[1] == 0 && sh->auth[2] == 0 && sh->auth[3] == 0 &&
+        sh->auth[4] == 0 && sh->auth[5] == 22 && sh->nums[0] == 1)
+        return sh->nums[1];
+
+    return UID_NOBODY;
+}
+
+static void gid_to_sid(UINT32 gid, PSID* sid) {
+    sid_header* sh;
+    UCHAR els;
+    
+    // FIXME - do this properly?
+    
+    // fallback to S-1-22-2-X, Samba's SID scheme
+    els = 2;
+    sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header) + ((els - 1) * sizeof(UINT32)), ALLOC_TAG);
+    if (!sh) {
+        ERR("out of memory\n");
+        *sid = NULL;
+        return;
+    }
+    
+    sh->revision = 1;
+    sh->elements = els;
+    
+    sh->auth[0] = 0;
+    sh->auth[1] = 0;
+    sh->auth[2] = 0;
+    sh->auth[3] = 0;
+    sh->auth[4] = 0;
+    sh->auth[5] = 22;
+    
+    sh->nums[0] = 2;
+    sh->nums[1] = gid;
+
+    *sid = sh;
+}
+
+static ACL* load_default_acl() {
+    ULONG size;
+    ACL* acl;
+    ACCESS_ALLOWED_ACE* aaa;
+    UINT32 i;
+    
+    size = sizeof(ACL);
+    i = 0;
+    while (def_dacls[i].sid) {
+        size += sizeof(ACCESS_ALLOWED_ACE);
+        size += 8 + (def_dacls[i].sid->elements * sizeof(UINT32)) - sizeof(ULONG);
+        i++;
+    }
+    
+    acl = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);
+    if (!acl) {
+        ERR("out of memory\n");
+        return NULL;
+    }
+    
+    acl->AclRevision = ACL_REVISION;
+    acl->Sbz1 = 0;
+    acl->AclSize = size;
+    acl->AceCount = i;
+    acl->Sbz2 = 0;
+    
+    aaa = (ACCESS_ALLOWED_ACE*)&acl[1];
+    i = 0;
+    while (def_dacls[i].sid) {
+        aaa->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
+        aaa->Header.AceFlags = def_dacls[i].flags;
+        aaa->Header.AceSize = sizeof(ACCESS_ALLOWED_ACE) - sizeof(ULONG) + 8 + (def_dacls[i].sid->elements * sizeof(UINT32));
+        aaa->Mask = def_dacls[i].mask;
+        
+        RtlCopyMemory(&aaa->SidStart, def_dacls[i].sid, 8 + (def_dacls[i].sid->elements * sizeof(UINT32)));
+        
+        aaa = (ACCESS_ALLOWED_ACE*)((UINT8*)aaa + aaa->Header.AceSize);
+        
+        i++;
+    }
+    
+    return acl;
+}
+
+static ACL* inherit_acl(SECURITY_DESCRIPTOR* parsd, BOOL file) {
+    ULONG size;
+    NTSTATUS Status;
+    ACL *paracl, *acl;
+    BOOLEAN parhasdacl, pardefaulted;
+    ACE_HEADER *ah, *parah;
+    UINT32 i;
+    USHORT num_aces;
+    
+    // FIXME - replace this with SeAssignSecurity
+    
+    Status = RtlGetDaclSecurityDescriptor(parsd, &parhasdacl, &paracl, &pardefaulted);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlGetDaclSecurityDescriptor returned %08x\n", Status);
+        return NULL;
+    }
+    
+    // FIXME - handle parhasdacl == FALSE
+
+//     OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE
+    num_aces = 0;
+    size = sizeof(ACL);
+    ah = (ACE_HEADER*)&paracl[1];
+    for (i = 0; i < paracl->AceCount; i++) {
+        if (!file && ah->AceFlags & CONTAINER_INHERIT_ACE) {
+            num_aces++;
+            size += ah->AceSize;
+            
+            if (ah->AceFlags & INHERIT_ONLY_ACE) {
+                num_aces++;
+                size += ah->AceSize;
+            }
+        }
+        
+        if (ah->AceFlags & OBJECT_INHERIT_ACE && (file || !(ah->AceFlags & CONTAINER_INHERIT_ACE))) {
+            num_aces++;
+            size += ah->AceSize;
+        }
+        
+        ah = (ACE_HEADER*)((UINT8*)ah + ah->AceSize);
+    }
+    
+    acl = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);
+    if (!acl) {
+        ERR("out of memory\n");
+        return NULL;
+    }
+    
+    acl->AclRevision = ACL_REVISION;
+    acl->Sbz1 = 0;
+    acl->AclSize = size;
+    acl->AceCount = num_aces;
+    acl->Sbz2 = 0;
+    
+    ah = (ACE_HEADER*)&acl[1];
+    parah = (ACE_HEADER*)&paracl[1];
+    for (i = 0; i < paracl->AceCount; i++) {
+        if (!file && parah->AceFlags & CONTAINER_INHERIT_ACE) {
+            if (parah->AceFlags & INHERIT_ONLY_ACE) {
+                RtlCopyMemory(ah, parah, parah->AceSize);
+                ah->AceFlags &= ~INHERIT_ONLY_ACE;
+                ah->AceFlags |= INHERITED_ACE;
+                ah->AceFlags &= ~(OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE);
+                
+                ah = (ACE_HEADER*)((UINT8*)ah + ah->AceSize);
+            }
+            
+            RtlCopyMemory(ah, parah, parah->AceSize);
+            ah->AceFlags |= INHERITED_ACE;
+            
+            if (ah->AceFlags & NO_PROPAGATE_INHERIT_ACE)
+                ah->AceFlags &= ~(OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE);
+            
+            ah = (ACE_HEADER*)((UINT8*)ah + ah->AceSize);
+        }
+        
+        if (parah->AceFlags & OBJECT_INHERIT_ACE && (file || !(parah->AceFlags & CONTAINER_INHERIT_ACE))) {
+            RtlCopyMemory(ah, parah, parah->AceSize);
+            ah->AceFlags |= INHERITED_ACE;
+            
+            if (file)
+                ah->AceFlags &= ~INHERIT_ONLY_ACE;
+            else
+                ah->AceFlags |= INHERIT_ONLY_ACE;
+            
+            if (ah->AceFlags & NO_PROPAGATE_INHERIT_ACE || file)
+                ah->AceFlags &= ~(OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE);
+            
+            ah = (ACE_HEADER*)((UINT8*)ah + ah->AceSize);
+        }
+        
+        parah = (ACE_HEADER*)((UINT8*)parah + parah->AceSize);
+    }
+    
+    return acl;
+}
+
+// static void STDCALL sid_to_string(PSID sid, char* s) {
+//     sid_header* sh = (sid_header*)sid;
+//     LARGE_INTEGER authnum;
+//     UINT8 i;
+//     
+//     authnum.LowPart = sh->auth[5] | (sh->auth[4] << 8) | (sh->auth[3] << 16) | (sh->auth[2] << 24);
+//     authnum.HighPart =  sh->auth[1] | (sh->auth[0] << 8);
+//     
+//     sprintf(s, "S-%u-%u", sh->revision, (UINT32)authnum.QuadPart);
+//     
+//     for (i = 0; i < sh->elements; i++) {
+//         sprintf(s, "%s-%u", s, sh->nums[i]);
+//     }
+// }
+
+static BOOL get_sd_from_xattr(fcb* fcb) {
+    ULONG buflen;
+    NTSTATUS Status;
+    PSID sid, usersid;
+    
+    if (!get_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_NTACL, EA_NTACL_HASH, (UINT8**)&fcb->sd, (UINT16*)&buflen))
+        return FALSE;
+    
+    TRACE("using xattr " EA_NTACL " for security descriptor\n");
+    
+    if (fcb->inode_item.st_uid != UID_NOBODY) {
+        BOOLEAN defaulted;
+        
+        Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &sid, &defaulted);
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlGetOwnerSecurityDescriptor returned %08x\n", Status);
+        } else {
+            uid_to_sid(fcb->inode_item.st_uid, &usersid);
+            
+            if (!usersid) {
+                ERR("out of memory\n");
+                return FALSE;
+            }
+            
+            if (!RtlEqualSid(sid, usersid)) {
+                SECURITY_DESCRIPTOR *newsd, *newsd2;
+                ULONG sdsize, daclsize, saclsize, ownersize, groupsize;
+                ACL *dacl, *sacl;
+                PSID owner, group;
+                
+                sdsize = daclsize = saclsize = ownersize = groupsize = 0;
+                
+                Status = RtlSelfRelativeToAbsoluteSD(fcb->sd, NULL, &sdsize, NULL, &daclsize, NULL, &saclsize, NULL, &ownersize, NULL, &groupsize);
+                
+                if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {
+                    ERR("RtlSelfRelativeToAbsoluteSD 1 returned %08x\n", Status);
+                }
+                
+                ERR("sdsize = %u, daclsize = %u, saclsize = %u, ownersize = %u, groupsize = %u\n", sdsize, daclsize, saclsize, ownersize, groupsize);
+                
+                newsd2 = sdsize == 0 ? NULL : ExAllocatePoolWithTag(PagedPool, sdsize, ALLOC_TAG);
+                dacl = daclsize == 0 ? NULL : ExAllocatePoolWithTag(PagedPool, daclsize, ALLOC_TAG);
+                sacl = saclsize == 0 ? NULL : ExAllocatePoolWithTag(PagedPool, saclsize, ALLOC_TAG);
+                owner = ownersize == 0 ? NULL : ExAllocatePoolWithTag(PagedPool, ownersize, ALLOC_TAG);
+                group = groupsize == 0 ? NULL : ExAllocatePoolWithTag(PagedPool, groupsize, ALLOC_TAG);
+                
+                if ((sdsize > 0 && !newsd2) || (daclsize > 0 && !dacl) || (saclsize > 0 && !sacl) || (ownersize > 0 && !owner) || (groupsize > 0 || !group)) {
+                    ERR("out of memory\n");
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    return FALSE;
+                }
+                
+                Status = RtlSelfRelativeToAbsoluteSD(fcb->sd, newsd2, &sdsize, dacl, &daclsize, sacl, &saclsize, owner, &ownersize, group, &groupsize);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("RtlSelfRelativeToAbsoluteSD returned %08x\n", Status);
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    return FALSE;
+                }
+                
+                Status = RtlSetOwnerSecurityDescriptor(newsd2, usersid, FALSE);
+                if (!NT_SUCCESS(Status)) {
+                    ERR("RtlSetOwnerSecurityDescriptor returned %08x\n", Status);
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    return FALSE;
+                }
+                
+                buflen = 0;
+                Status = RtlAbsoluteToSelfRelativeSD(newsd2, NULL, &buflen);
+                if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {
+                    ERR("RtlAbsoluteToSelfRelativeSD 1 returned %08x\n", Status);
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    return FALSE;
+                }
+                
+                if (buflen == 0 || NT_SUCCESS(Status)) {
+                    ERR("RtlAbsoluteToSelfRelativeSD said SD is zero-length\n");
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    return FALSE;
+                }
+                
+                newsd = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);
+                if (!newsd) {
+                    ERR("out of memory\n");
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    return FALSE;
+                }
+                
+                Status = RtlAbsoluteToSelfRelativeSD(newsd2, newsd, &buflen);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("RtlAbsoluteToSelfRelativeSD 2 returned %08x\n", Status);
+                    if (newsd2) ExFreePool(newsd2);
+                    if (dacl) ExFreePool(dacl);
+                    if (sacl) ExFreePool(sacl);
+                    if (owner) ExFreePool(owner);
+                    if (group) ExFreePool(group);
+                    ExFreePool(usersid);
+                    ExFreePool(newsd);
+                    return FALSE;
+                }
+                
+                ExFreePool(fcb->sd);
+                
+                fcb->sd = newsd;
+                
+                if (newsd2) ExFreePool(newsd2);
+                if (dacl) ExFreePool(dacl);
+                if (sacl) ExFreePool(sacl);
+                if (owner) ExFreePool(owner);
+                if (group) ExFreePool(group);
+            }
+            
+            ExFreePool(usersid);
+        }
+    }
+    
+    // FIXME - check GID here if not GID_NOBODY
+    
+    return TRUE;
+}
+
+void fcb_get_sd(fcb* fcb) {
+    NTSTATUS Status;
+    SECURITY_DESCRIPTOR sd;
+    ULONG buflen;
+    ACL* acl = NULL;
+    PSID usersid = NULL, groupsid = NULL;
+    
+    if (get_sd_from_xattr(fcb))
+        goto end;
+    
+    Status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlCreateSecurityDescriptor returned %08x\n", Status);
+        goto end;
+    }
+    
+//     if (fcb->inode_item.st_uid != UID_NOBODY) {
+        uid_to_sid(fcb->inode_item.st_uid, &usersid);
+        if (!usersid) {
+            ERR("out of memory\n");
+            goto end;
+        }
+        
+        RtlSetOwnerSecurityDescriptor(&sd, usersid, FALSE);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlSetOwnerSecurityDescriptor returned %08x\n", Status);
+            goto end;
+        }
+//     }
+    
+//     if (fcb->inode_item.st_gid != GID_NOBODY) {
+        gid_to_sid(fcb->inode_item.st_gid, &groupsid);
+        if (!groupsid) {
+            ERR("out of memory\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+        
+        RtlSetGroupSecurityDescriptor(&sd, groupsid, FALSE);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlSetGroupSecurityDescriptor returned %08x\n", Status);
+            goto end;
+        }
+//     }
+    
+    if (!fcb->par)
+        acl = load_default_acl();
+    else
+        acl = inherit_acl(fcb->par->sd, fcb->type != BTRFS_TYPE_DIRECTORY);
+    
+    if (!acl) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+
+    Status = RtlSetDaclSecurityDescriptor(&sd, TRUE, acl, FALSE);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlSetDaclSecurityDescriptor returned %08x\n", Status);
+        goto end;
+    }
+    
+    // FIXME - SACL_SECURITY_INFORMATION
+    
+    buflen = 0;
+    
+    // get sd size
+    Status = RtlAbsoluteToSelfRelativeSD(&sd, NULL, &buflen);
+    if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_TOO_SMALL) {
+        ERR("RtlAbsoluteToSelfRelativeSD 1 returned %08x\n", Status);
+        goto end;
+    }
+    
+//     fcb->sdlen = buflen;
+    
+    if (buflen == 0 || Status == STATUS_SUCCESS) {
+        TRACE("RtlAbsoluteToSelfRelativeSD said SD is zero-length\n");
+        goto end;
+    }
+    
+    fcb->sd = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);
+    if (!fcb->sd) {
+        ERR("out of memory\n");
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto end;
+    }
+    
+    Status = RtlAbsoluteToSelfRelativeSD(&sd, fcb->sd, &buflen);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlAbsoluteToSelfRelativeSD 2 returned %08x\n", Status);
+        goto end;
+    }
+    
+end:
+    if (acl)
+        ExFreePool(acl);
+    
+    if (usersid)
+        ExFreePool(usersid);
+    
+    if (groupsid)
+        ExFreePool(groupsid);
+}
+
+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;
+    
+//     TRACE("buflen = %u, fcb->sdlen = %u\n", *buflen, fcb->sdlen);
+
+    // Why (void**)? Is this a bug in mingw?
+    Status = SeQuerySecurityDescriptorInfo(&flags, relsd, buflen, (void**)&fcb->sd);
+    
+    if (Status == STATUS_BUFFER_TOO_SMALL)
+        TRACE("SeQuerySecurityDescriptorInfo returned %08x\n", Status);
+    else if (!NT_SUCCESS(Status))
+        ERR("SeQuerySecurityDescriptorInfo returned %08x\n", Status);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    SECURITY_DESCRIPTOR* sd;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    ULONG buflen;
+    BOOL top_level;
+
+    TRACE("query security\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_SUCCESS;
+    
+    Irp->IoStatus.Information = 0;
+    
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & OWNER_SECURITY_INFORMATION)
+        TRACE("OWNER_SECURITY_INFORMATION\n");
+
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & GROUP_SECURITY_INFORMATION)
+        TRACE("GROUP_SECURITY_INFORMATION\n");
+
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & DACL_SECURITY_INFORMATION)
+        TRACE("DACL_SECURITY_INFORMATION\n");
+
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & SACL_SECURITY_INFORMATION)
+        TRACE("SACL_SECURITY_INFORMATION\n");
+    
+    TRACE("length = %u\n", IrpSp->Parameters.QuerySecurity.Length);
+    
+    sd = map_user_buffer(Irp);
+//     sd = Irp->AssociatedIrp.SystemBuffer;
+    TRACE("sd = %p\n", sd);
+    
+    if (Irp->MdlAddress && !sd) {
+        ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    buflen = IrpSp->Parameters.QuerySecurity.Length;
+    
+    Status = get_file_security(DeviceObject->DeviceExtension, IrpSp->FileObject, sd, &buflen, IrpSp->Parameters.QuerySecurity.SecurityInformation);
+    
+    if (NT_SUCCESS(Status))
+        Irp->IoStatus.Information = IrpSp->Parameters.QuerySecurity.Length;
+    else if (Status == STATUS_BUFFER_TOO_SMALL) {
+        Irp->IoStatus.Information = buflen;
+        Status = STATUS_BUFFER_OVERFLOW;
+    } else
+        Irp->IoStatus.Information = 0;
+    
+    TRACE("Irp->IoStatus.Information = %u\n", Irp->IoStatus.Information);
+    
+    Irp->IoStatus.Status = Status;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);    
+    
+    FsRtlExitFileSystem();
+
+    TRACE("returning %08x\n", Status);
+    
+    return Status;
+}
+
+static NTSTATUS STDCALL set_file_security(device_extension* Vcb, PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* sd, SECURITY_INFORMATION flags) {
+    NTSTATUS Status;
+    fcb* fcb = FileObject->FsContext;
+    SECURITY_DESCRIPTOR* oldsd;
+    INODE_ITEM* ii;
+    KEY searchkey;
+    traverse_ptr tp;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    LIST_ENTRY rollback;
+    
+    TRACE("(%p, %p, %p, %x)\n", Vcb, FileObject, sd, flags);
+    
+    InitializeListHead(&rollback);
+    
+    if (Vcb->readonly)
+        return STATUS_MEDIA_WRITE_PROTECTED;
+    
+    acquire_tree_lock(Vcb, TRUE);
+    
+    if (fcb->ads)
+        fcb = fcb->par;
+    
+    if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
+        Status = STATUS_ACCESS_DENIED;
+        goto end;
+    }
+     
+    oldsd = fcb->sd;
+    
+    Status = SeSetSecurityDescriptorInfo(NULL, &flags, sd, (void**)&fcb->sd, PagedPool, IoGetFileObjectGenericMapping());
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("SeSetSecurityDescriptorInfo returned %08x\n", Status);
+        goto end;
+    }
+    
+    ExFreePool(oldsd);
+    
+    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(&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);
+    
+    fcb->inode_item.transid = Vcb->superblock.generation;
+    fcb->inode_item.st_ctime = now;
+    fcb->inode_item.sequence++;
+    
+    if (flags & OWNER_SECURITY_INFORMATION) {
+        PSID owner;
+        BOOLEAN defaulted;
+        
+        Status = RtlGetOwnerSecurityDescriptor(sd, &owner, &defaulted);
+        
+        if (!NT_SUCCESS(Status)) {
+            ERR("RtlGetOwnerSecurityDescriptor returned %08x\n", Status);
+            goto end;
+        }
+        
+        fcb->inode_item.st_uid = sid_to_uid(owner);
+    }
+    
+    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));
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, &rollback)) {
+        ERR("error - failed to insert INODE_ITEM\n");
+        Status = STATUS_INTERNAL_ERROR;
+        ExFreePool(ii);
+        goto end;
+    }
+    
+    Status = set_xattr(Vcb, fcb->subvol, fcb->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(ii);
+        goto end;
+    }
+    
+    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = now;
+    
+    Status = consider_write(Vcb);
+    
+end:
+    if (NT_SUCCESS(Status))
+        clear_rollback(&rollback);
+    else
+        do_rollback(Vcb, &rollback);
+
+    release_tree_lock(Vcb, TRUE);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
+    NTSTATUS Status;
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    BOOL top_level;
+
+    TRACE("set security\n");
+    
+    FsRtlEnterFileSystem();
+
+    top_level = is_top_level(Irp);
+    
+    Status = STATUS_SUCCESS;
+    
+    Irp->IoStatus.Information = 0;
+    
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & OWNER_SECURITY_INFORMATION)
+        TRACE("OWNER_SECURITY_INFORMATION\n");
+
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & GROUP_SECURITY_INFORMATION)
+        TRACE("GROUP_SECURITY_INFORMATION\n");
+
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & DACL_SECURITY_INFORMATION)
+        TRACE("DACL_SECURITY_INFORMATION\n");
+
+    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & SACL_SECURITY_INFORMATION)
+        TRACE("SACL_SECURITY_INFORMATION\n");
+    
+    Status = set_file_security(DeviceObject->DeviceExtension, IrpSp->FileObject, IrpSp->Parameters.SetSecurity.SecurityDescriptor,
+                               IrpSp->Parameters.SetSecurity.SecurityInformation);
+    
+    Irp->IoStatus.Status = Status;
+
+    IoCompleteRequest( Irp, IO_NO_INCREMENT );
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+
+    TRACE("returning %08x\n", Status);
+    
+    if (top_level) 
+        IoSetTopLevelIrp(NULL);
+
+    FsRtlExitFileSystem();
+    
+    return Status;
+}
+
+NTSTATUS fcb_get_new_sd(fcb* fcb, 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,
+                              &as->SubjectSecurityContext, IoGetFileObjectGenericMapping(), PagedPool);
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("SeAssignSecurity returned %08x\n", Status);
+        return Status;
+    }
+    
+    Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted);
+    if (!NT_SUCCESS(Status)) {
+        ERR("RtlGetOwnerSecurityDescriptor returned %08x\n", Status);
+        fcb->inode_item.st_uid = UID_NOBODY;
+    } else {
+        fcb->inode_item.st_uid = sid_to_uid(&owner);
+    }
+    
+    return STATUS_SUCCESS;
+}
diff --git a/reactos/drivers/filesystems/btrfs/treefuncs.c b/reactos/drivers/filesystems/btrfs/treefuncs.c
new file mode 100644 (file)
index 0000000..e5b5ac6
--- /dev/null
@@ -0,0 +1,1362 @@
+/* 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"
+
+// #define DEBUG_TREE_LOCKS
+
+enum read_tree_status {
+    ReadTreeStatus_Pending,
+    ReadTreeStatus_Success,
+    ReadTreeStatus_Cancelling,
+    ReadTreeStatus_Cancelled,
+    ReadTreeStatus_Error,
+    ReadTreeStatus_CRCError,
+    ReadTreeStatus_MissingDevice
+};
+
+struct read_tree_context;
+
+typedef struct {
+    struct read_tree_context* context;
+    UINT8* buf;
+    PIRP Irp;
+    IO_STATUS_BLOCK iosb;
+    enum read_tree_status status;
+} read_tree_stripe;
+
+typedef struct {
+    KEVENT Event;
+    NTSTATUS Status;
+    chunk* c;
+//     UINT8* buf;
+    UINT32 buflen;
+    UINT64 num_stripes;
+    LONG stripes_left;
+    UINT64 type;
+    read_tree_stripe* stripes;
+} read_tree_context;
+
+enum rollback_type {
+    ROLLBACK_INSERT_ITEM,
+    ROLLBACK_DELETE_ITEM
+};
+
+typedef struct {
+    enum rollback_type type;
+    void* ptr;
+    LIST_ENTRY list_entry;
+} rollback_item;
+
+static NTSTATUS STDCALL read_tree_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
+    read_tree_stripe* stripe = conptr;
+    read_tree_context* context = (read_tree_context*)stripe->context;
+    UINT64 i;
+    
+    if (stripe->status == ReadTreeStatus_Cancelling) {
+        stripe->status = ReadTreeStatus_Cancelled;
+        goto end;
+    }
+    
+    stripe->iosb = Irp->IoStatus;
+    
+    if (NT_SUCCESS(Irp->IoStatus.Status)) {
+        tree_header* th = (tree_header*)stripe->buf;
+        UINT32 crc32;
+        
+        crc32 = ~calc_crc32c(0xffffffff, (UINT8*)&th->fs_uuid, context->buflen - sizeof(th->csum));
+        
+        if (crc32 == *((UINT32*)th->csum)) {
+            stripe->status = ReadTreeStatus_Success;
+            
+            for (i = 0; i < context->num_stripes; i++) {
+                if (context->stripes[i].status == ReadTreeStatus_Pending) {
+                    context->stripes[i].status = ReadTreeStatus_Cancelling;
+                    IoCancelIrp(context->stripes[i].Irp);
+                }
+            }
+            
+            goto end;
+        } else
+            stripe->status = ReadTreeStatus_CRCError;
+    } else {
+        stripe->status = ReadTreeStatus_Error;
+    }
+    
+end:
+    if (InterlockedDecrement(&context->stripes_left) == 0)
+        KeSetEvent(&context->Event, 0, FALSE);
+    
+//     return STATUS_SUCCESS;
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS STDCALL read_tree(device_extension* Vcb, UINT64 addr, UINT8* buf) {
+    CHUNK_ITEM* ci;
+    CHUNK_ITEM_STRIPE* cis;
+    read_tree_context* context;
+    UINT64 i/*, type*/, offset;
+    NTSTATUS Status;
+    device** devices;
+    
+    // FIXME - make this work with RAID
+    
+    if (Vcb->log_to_phys_loaded) {
+        chunk* c = get_chunk_from_address(Vcb, addr);
+        
+        if (!c) {
+            ERR("get_chunk_from_address failed\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        ci = c->chunk_item;
+        offset = c->offset;
+        devices = c->devices;
+    } else {
+        LIST_ENTRY* le = Vcb->sys_chunks.Flink;
+        
+        ci = NULL;
+        
+        while (le != &Vcb->sys_chunks) {
+            sys_chunk* sc = CONTAINING_RECORD(le, sys_chunk, list_entry);
+            
+            if (sc->key.obj_id == 0x100 && sc->key.obj_type == TYPE_CHUNK_ITEM && sc->key.offset <= addr) {
+                CHUNK_ITEM* chunk_item = sc->data;
+                
+                if ((addr - sc->key.offset) < chunk_item->size && chunk_item->num_stripes > 0) {
+                    ci = chunk_item;
+                    offset = sc->key.offset;
+                    cis = (CHUNK_ITEM_STRIPE*)&chunk_item[1];
+                    
+                    devices = ExAllocatePoolWithTag(PagedPool, sizeof(device*) * ci->num_stripes, ALLOC_TAG);
+                    if (!devices) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    for (i = 0; i < ci->num_stripes; i++) {
+                        devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid);
+                    }
+                    
+                    break;
+                }
+            }
+            
+            le = le->Flink;
+        }
+        
+        if (!ci) {
+            ERR("could not find chunk for %llx in bootstrap\n", addr);
+            return STATUS_INTERNAL_ERROR;
+        }
+    }
+    
+//     if (ci->type & BLOCK_FLAG_DUPLICATE) {
+//         type = BLOCK_FLAG_DUPLICATE;
+//     } else if (ci->type & BLOCK_FLAG_RAID0) {
+//         FIXME("RAID0 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID1) {
+//         FIXME("RAID1 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID10) {
+//         FIXME("RAID10 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID5) {
+//         FIXME("RAID5 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else if (ci->type & BLOCK_FLAG_RAID6) {
+//         FIXME("RAID6 not yet supported\n");
+//         return STATUS_NOT_IMPLEMENTED;
+//     } else { // SINGLE
+//         type = 0;
+//     }
+
+    cis = (CHUNK_ITEM_STRIPE*)&ci[1];
+
+    context = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_tree_context), ALLOC_TAG);
+    if (!context) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context, sizeof(read_tree_context));
+    KeInitializeEvent(&context->Event, NotificationEvent, FALSE);
+    
+    context->stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_tree_stripe) * ci->num_stripes, ALLOC_TAG);
+    if (!context->stripes) {
+        ERR("out of memory\n");
+        ExFreePool(context);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context->stripes, sizeof(read_tree_stripe) * ci->num_stripes);
+    
+    context->buflen = Vcb->superblock.node_size;
+    context->num_stripes = ci->num_stripes;
+    context->stripes_left = context->num_stripes;
+//     context->type = type;
+    
+    // FIXME - for RAID, check beforehand whether there's enough devices to satisfy request
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        PIO_STACK_LOCATION IrpSp;
+        
+        if (!devices[i]) {
+            context->stripes[i].status = ReadTreeStatus_MissingDevice;
+            context->stripes[i].buf = NULL;
+            context->stripes_left--;
+        } else {
+            context->stripes[i].context = (struct read_tree_context*)context;
+            context->stripes[i].buf = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);
+            
+            if (!context->stripes[i].buf) {
+                ERR("out of memory\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto exit;
+            }
+
+            context->stripes[i].Irp = IoAllocateIrp(devices[i]->devobj->StackSize, FALSE);
+            
+            if (!context->stripes[i].Irp) {
+                ERR("IoAllocateIrp failed\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto exit;
+            }
+            
+            IrpSp = IoGetNextIrpStackLocation(context->stripes[i].Irp);
+            IrpSp->MajorFunction = IRP_MJ_READ;
+            
+            if (devices[i]->devobj->Flags & DO_BUFFERED_IO) {
+                FIXME("FIXME - buffered IO\n");
+            } else if (devices[i]->devobj->Flags & DO_DIRECT_IO) {
+                context->stripes[i].Irp->MdlAddress = IoAllocateMdl(context->stripes[i].buf, Vcb->superblock.node_size, FALSE, FALSE, NULL);
+                if (!context->stripes[i].Irp->MdlAddress) {
+                    ERR("IoAllocateMdl failed\n");
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    goto exit;
+                }
+                
+                MmProbeAndLockPages(context->stripes[i].Irp->MdlAddress, KernelMode, IoWriteAccess);
+            } else {
+                context->stripes[i].Irp->UserBuffer = context->stripes[i].buf;
+            }
+
+            IrpSp->Parameters.Read.Length = Vcb->superblock.node_size;
+            IrpSp->Parameters.Read.ByteOffset.QuadPart = addr - offset + cis[i].offset;
+            
+            context->stripes[i].Irp->UserIosb = &context->stripes[i].iosb;
+            
+            IoSetCompletionRoutine(context->stripes[i].Irp, read_tree_completion, &context->stripes[i], TRUE, TRUE, TRUE);
+
+            context->stripes[i].status = ReadTreeStatus_Pending;
+        }
+    }
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status != ReadTreeStatus_MissingDevice) {
+            IoCallDriver(devices[i]->devobj, context->stripes[i].Irp);
+        }
+    }
+
+    KeWaitForSingleObject(&context->Event, Executive, KernelMode, FALSE, NULL);
+    
+    // FIXME - if checksum error, write good data over bad
+    
+    // check if any of the stripes succeeded
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status == ReadTreeStatus_Success) {
+            RtlCopyMemory(buf, context->stripes[i].buf, Vcb->superblock.node_size);
+            Status = STATUS_SUCCESS;
+            goto exit;
+        }
+    }
+    
+    // if not, see if we got a checksum error
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status == ReadTreeStatus_CRCError) {
+#ifdef _DEBUG
+            tree_header* th = (tree_header*)context->stripes[i].buf;
+            UINT32 crc32 = ~calc_crc32c(0xffffffff, (UINT8*)&th->fs_uuid, context->buflen - sizeof(th->csum));
+//             UINT64 j;
+            
+            WARN("stripe %llu had a checksum error\n", i);
+            WARN("crc32 was %08x, expected %08x\n", crc32, *((UINT32*)th->csum));
+#endif
+            
+//             for (j = 0; j < ci->num_stripes; j++) {
+//                 WARN("stripe %llu: device = %p, status = %u\n", j, c->devices[j], context->stripes[j].status);
+//             }
+//             int3;
+            
+            Status = STATUS_IMAGE_CHECKSUM_MISMATCH;
+            goto exit;
+        }
+    }
+    
+    // failing that, return the first error we encountered
+    
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].status == ReadTreeStatus_Error) {
+            Status = context->stripes[i].iosb.Status;
+            goto exit;
+        }
+    }
+    
+    // if we somehow get here, return STATUS_INTERNAL_ERROR
+    
+    Status = STATUS_INTERNAL_ERROR;
+
+//     for (i = 0; i < ci->num_stripes; i++) {
+//         ERR("%llx: status = %u, NTSTATUS = %08x\n", i, context->stripes[i].status, context->stripes[i].iosb.Status);
+//     }
+exit:
+
+    for (i = 0; i < ci->num_stripes; i++) {
+        if (context->stripes[i].Irp) {
+            if (devices[i]->devobj->Flags & DO_DIRECT_IO) {
+                MmUnlockPages(context->stripes[i].Irp->MdlAddress);
+                IoFreeMdl(context->stripes[i].Irp->MdlAddress);
+            }
+            IoFreeIrp(context->stripes[i].Irp);
+        }
+        
+        if (context->stripes[i].buf)
+            ExFreePool(context->stripes[i].buf);
+    }
+
+    ExFreePool(context->stripes);
+    ExFreePool(context);
+    
+    if (!Vcb->log_to_phys_loaded)
+        ExFreePool(devices);
+    
+    return Status;
+}
+
+NTSTATUS STDCALL _load_tree(device_extension* Vcb, UINT64 addr, root* r, tree** pt, const char* func, const char* file, unsigned int line) {
+    UINT8* buf;
+    NTSTATUS Status;
+    tree_header* th;
+    tree* t;
+    tree_data* td;
+    chunk* c;
+    
+    TRACE("(%p, %llx)\n", Vcb, addr);
+    
+    buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);
+    if (!buf) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    Status = read_tree(Vcb, addr, buf);
+    if (!NT_SUCCESS(Status)) {
+        ERR("read_tree returned 0x%08x\n", Status);
+        ExFreePool(buf);
+        return Status;
+    }
+    
+    th = (tree_header*)buf;
+    
+    t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
+    if (!t) {
+        ERR("out of memory\n");
+        ExFreePool(buf);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(&t->header, th, sizeof(tree_header));
+//     t->address = addr;
+//     t->level = th->level;
+    t->refcount = 1;
+    t->Vcb = Vcb;
+    t->parent = NULL;
+    t->root = r;
+//     t->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);
+    t->paritem = NULL;
+    t->size = 0;
+    t->new_address = 0;
+#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
+    
+    c = get_chunk_from_address(Vcb, addr);
+    
+    if (c)
+        t->flags = c->chunk_item->type;
+    else
+        t->flags = 0;
+    
+//     ExInitializeResourceLite(&t->nonpaged->load_tree_lock);
+    
+//     t->items = ExAllocatePoolWithTag(PagedPool, num_items * sizeof(tree_data), ALLOC_TAG);
+    InitializeListHead(&t->itemlist);
+    
+    if (t->header.level == 0) { // leaf node
+        leaf_node* ln = (leaf_node*)(buf + sizeof(tree_header));
+        unsigned int i;
+        
+        for (i = 0; i < t->header.num_items; i++) {
+            td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
+            if (!td) {
+                ERR("out of memory\n");
+                ExFreePool(buf);
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            td->key = ln[i].key;
+//             TRACE("load_tree: leaf item %u (%x,%x,%x)\n", i, (UINT32)ln[i].key.obj_id, ln[i].key.obj_type, (UINT32)ln[i].key.offset);
+            
+            if (ln[i].size > 0) {
+                td->data = ExAllocatePoolWithTag(PagedPool, ln[i].size, ALLOC_TAG);
+                if (!td->data) {
+                    ERR("out of memory\n");
+                    ExFreePool(buf);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                RtlCopyMemory(td->data, buf + sizeof(tree_header) + ln[i].offset, ln[i].size);
+            } else
+                td->data = NULL;
+            
+            td->size = ln[i].size;
+            td->ignore = FALSE;
+            td->inserted = FALSE;
+            
+            InsertTailList(&t->itemlist, &td->list_entry);
+            
+            t->size += ln[i].size;
+        }
+        
+        t->size += t->header.num_items * sizeof(leaf_node);
+    } else {
+        internal_node* in = (internal_node*)(buf + sizeof(tree_header));
+        unsigned int i;
+        
+        for (i = 0; i < t->header.num_items; i++) {
+            td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
+            if (!td) {
+                ERR("out of memory\n");
+                ExFreePool(buf);
+                return STATUS_INSUFFICIENT_RESOURCES;
+            }
+            
+            td->key = in[i].key;
+//             TRACE("load_tree: internal item %u (%x,%x,%x)\n", i, (UINT32)in[i].key.obj_id, in[i].key.obj_type, (UINT32)in[i].key.offset);
+            
+            td->treeholder.address = in[i].address;
+            td->treeholder.generation = in[i].generation;
+            td->treeholder.tree = NULL;
+            init_tree_holder(&td->treeholder);
+//             td->treeholder.nonpaged->status = tree_holder_unloaded;
+            td->ignore = FALSE;
+            td->inserted = FALSE;
+            
+            InsertTailList(&t->itemlist, &td->list_entry);
+        }
+        
+        t->size = t->header.num_items * sizeof(internal_node);
+    }
+    
+    ExFreePool(buf);
+    
+    InterlockedIncrement(&Vcb->open_trees);
+    InsertTailList(&Vcb->trees, &t->list_entry);
+    
+    TRACE("returning %p\n", t);
+    
+    *pt = t;
+    
+    return STATUS_SUCCESS;
+}
+
+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
+    
+    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 (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;
+            
+//             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);
+            
+            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;
+//             ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+//             FsRtlExitFileSystem();
+        }
+        
+        ExFreePool(t);
+
+        return NULL;
+    } else {
+//         if (par) ExReleaseResourceLite(&par->nonpaged->load_tree_lock);
+    }
+    
+    return t;
+}
+
+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) {
+//     KIRQL irql;
+//     tree_holder_nonpaged* thnp = th->nonpaged;
+    BOOL ret;
+    
+//     ExAcquireResourceExclusiveLite(&thnp->lock, TRUE);
+    ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
+    
+//     KeAcquireSpinLock(&thnp->spin_lock, &irql);
+//     
+//     if (thnp->status == tree_header_loading) {
+//         KeReleaseSpinLock(&thnp->spin_lock, irql);
+//         
+//         // FIXME - wait for Event
+//     } else if (thnp->status == tree_header_unloaded || thnp->status == tree_header_unloading) {
+//         if (thnp->status == tree_header_unloading) {
+//             KeReleaseSpinLock(&thnp->spin_lock, irql);
+//             // FIXME - wait for Event
+//         }
+//         
+//         // FIXME - change status
+//         thnp->status = tree_header_loading;
+//         KeReleaseSpinLock(&thnp->spin_lock, irql);
+//         
+//         // FIXME - load
+//         // FIXME - change status
+//         // FIXME - trigger event
+//     } else if (thnp->status == tree_header_loaded) {
+//         _increase_tree_rc(th->tree, func, file, line);
+//         KeReleaseSpinLock(&thnp->spin_lock, irql);
+//         
+//         ret = FALSE;
+//     }
+
+    if (!th->tree) {
+        NTSTATUS Status;
+        
+        Status = _load_tree(Vcb, th->address, r, &th->tree, func, file, line);
+        if (!NT_SUCCESS(Status)) {
+            ERR("load_tree returned %08x\n", Status);
+            ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+            return Status;
+        }
+        
+        th->tree->parent = t;
+        th->tree->paritem = td;
+        
+        ret = TRUE;
+    } else {
+        _increase_tree_rc(th->tree, func, file, line);
+        
+        ret = FALSE;
+    }
+    
+//     KeReleaseSpinLock(&thnp->spin_lock, irql);
+    
+//     ExReleaseResourceLite(&thnp->lock);
+    ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+    
+    *loaded = ret;
+    
+    return STATUS_SUCCESS;
+}
+
+tree* STDCALL _free_tree(tree* t, const char* func, const char* file, unsigned int line) {
+    tree* ret;
+    root* r = t->root;
+    
+    ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
+
+    ret = free_tree2(t, func, file, line);
+
+    ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+    
+    return ret;
+}
+
+static __inline tree_data* first_item(tree* t) {
+    LIST_ENTRY* le = t->itemlist.Flink;
+    
+    if (le == &t->itemlist)
+        return NULL;
+    
+    return CONTAINING_RECORD(le, tree_data, list_entry);
+}
+
+static __inline tree_data* prev_item(tree* t, tree_data* td) {
+    LIST_ENTRY* le = td->list_entry.Blink;
+    
+    if (le == &t->itemlist)
+        return NULL;
+    
+    return CONTAINING_RECORD(le, tree_data, list_entry);
+}
+
+static __inline tree_data* next_item(tree* t, tree_data* td) {
+    LIST_ENTRY* le = td->list_entry.Flink;
+    
+    if (le == &t->itemlist)
+        return NULL;
+    
+    return CONTAINING_RECORD(le, tree_data, list_entry);
+}
+
+static NTSTATUS STDCALL find_item_in_tree(device_extension* Vcb, tree* t, traverse_ptr* tp, const KEY* searchkey, BOOL ignore, const char* func, const char* file, unsigned int line) {
+    int cmp;
+    tree_data *td, *lasttd;
+    
+    TRACE("(%p, %p, %p, %p, %u)\n", Vcb, t, tp, searchkey, ignore);
+    
+    cmp = 1;
+    td = first_item(t);
+    lasttd = NULL;
+    
+    if (!td) return STATUS_INTERNAL_ERROR;
+    
+    do {
+        cmp = keycmp(searchkey, &td->key);
+//         TRACE("(%u) comparing (%x,%x,%x) to (%x,%x,%x) - %i (ignore = %s)\n", t->header.level, (UINT32)searchkey->obj_id, searchkey->obj_type, (UINT32)searchkey->offset, (UINT32)td->key.obj_id, td->key.obj_type, (UINT32)td->key.offset, cmp, td->ignore ? "TRUE" : "FALSE");
+        if (cmp == 1) {
+            lasttd = td;
+            td = next_item(t, td);
+        }
+
+        if (t->header.level == 0 && cmp == 0 && !ignore && td && td->ignore) {
+            while (td && td->ignore)
+                td = next_item(t, td);
+            
+            if (td)
+                cmp = keycmp(searchkey, &td->key);
+        }
+    } while (td && cmp == 1);
+    
+    if ((cmp == -1 || !td) && lasttd)
+        td = lasttd;
+    
+    if (t->header.level == 0) {
+        if (td->ignore && !ignore) {
+            traverse_ptr oldtp;
+            
+            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;
+            }
+            
+            // if no valid entries before where item should be, look afterwards instead
+            
+            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;
+    } else {
+        NTSTATUS Status;
+        BOOL loaded;
+        
+        while (td && td->treeholder.tree && IsListEmpty(&td->treeholder.tree->itemlist)) {
+            td = prev_item(t, td);
+        }
+        
+        if (!td)
+            return STATUS_INTERNAL_ERROR;
+        
+//         if (i > 0)
+//             TRACE("entering tree from (%x,%x,%x) to (%x,%x,%x) (%p)\n", (UINT32)t->items[i].key.obj_id, t->items[i].key.obj_type, (UINT32)t->items[i].key.offset, (UINT32)t->items[i+1].key.obj_id, t->items[i+1].key.obj_type, (UINT32)t->items[i+1].key.offset, t->items[i].tree);
+        
+        Status = _do_load_tree(Vcb, &td->treeholder, t->root, t, td, &loaded, func, file, line);
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_load_tree returned %08x\n", Status);
+            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;
+    }
+}
+
+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) {
+    NTSTATUS Status;
+    BOOL loaded;
+//     KIRQL irql;
+    
+    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;
+    }
+
+    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) {
+//         ERR("error - returning ignored item\n");
+//         int3;
+//     }
+// #endif
+    
+    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;
+    NTSTATUS Status;
+    BOOL loaded;
+    
+    next = next_item(tp->tree, tp->item);
+    
+    if (!ignore) {
+        while (next && next->ignore)
+            next = next_item(tp->tree, next);
+    }
+    
+    if (next) {
+        next_tp->tree = tp->tree;
+        _increase_tree_rc(next_tp->tree, func, file, line);
+        next_tp->item = next;
+        
+#ifdef DEBUG_PARANOID
+        if (!ignore && next_tp->item->ignore) {
+            ERR("error - returning ignored item\n");
+            int3;
+        }
+#endif
+        
+        return TRUE;
+    }
+    
+    if (!tp->tree->parent)
+        return FALSE;
+    
+    t = tp->tree;
+    do {
+        if (t->parent) {
+            td = next_item(t->parent, t->paritem);
+            
+            if (td) break;
+        }
+        
+        t = t->parent;
+    } while (t);
+    
+    if (!t)
+        return FALSE;
+    
+    Status = _do_load_tree(Vcb, &td->treeholder, t->parent->root, t->parent, td, &loaded, func, file, line);
+    if (!NT_SUCCESS(Status)) {
+        ERR("do_load_tree returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (loaded)
+        _increase_tree_rc(t->parent, func, file, line);
+    
+    t = td->treeholder.tree;
+    
+    while (t->header.level != 0) {
+        tree_data* fi;
+       
+        fi = first_item(t);
+        
+        Status = _do_load_tree(Vcb, &fi->treeholder, t->parent->root, t, fi, &loaded, func, file, line);
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_load_tree returned %08x\n", Status);
+            return FALSE;
+        }
+        
+        t = fi->treeholder.tree;
+    }
+    
+    next_tp->tree = t;
+    next_tp->item = first_item(t);
+    
+    if (!ignore && next_tp->item->ignore) {
+        traverse_ptr ntp2;
+        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);
+            return FALSE;
+        }
+    }
+    
+    add_to_tree_cache(Vcb, t, FALSE);
+    
+#ifdef DEBUG_PARANOID
+    if (!ignore && next_tp->item->ignore) {
+        ERR("error - returning ignored item\n");
+        int3;
+    }
+#endif
+    
+    return TRUE;
+}
+
+static __inline tree_data* last_item(tree* t) {
+    LIST_ENTRY* le = t->itemlist.Blink;
+    
+    if (le == &t->itemlist)
+        return NULL;
+    
+    return CONTAINING_RECORD(le, tree_data, list_entry);
+}
+
+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) {
+    tree* t;
+    tree_data* td;
+    NTSTATUS Status;
+    BOOL loaded;
+    
+    // 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;
+    }
+    
+    if (!tp->tree->parent)
+        return FALSE;
+    
+    t = tp->tree;
+    while (t && (!t->parent || !prev_item(t->parent, t->paritem))) {
+        t = t->parent;
+    }
+    
+    if (!t)
+        return FALSE;
+    
+    td = prev_item(t->parent, t->paritem);
+    
+    Status = _do_load_tree(Vcb, &td->treeholder, t->parent->root, t, td, &loaded, func, file, line);
+    if (!NT_SUCCESS(Status)) {
+        ERR("do_load_tree returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (loaded)
+        _increase_tree_rc(t->parent, func, file, line);
+    
+    t = td->treeholder.tree;
+    
+    while (t->header.level != 0) {
+        tree_data* li;
+        
+        li = last_item(t);
+        
+        Status = _do_load_tree(Vcb, &li->treeholder, t->parent->root, t, li, &loaded, func, file, line);
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_load_tree returned %08x\n", Status);
+            return FALSE;
+        }
+        
+        t = li->treeholder.tree;
+    }
+    
+    add_to_tree_cache(Vcb, t, FALSE);
+    
+    prev_tp->tree = t;
+    prev_tp->item = last_item(t);
+    
+    return TRUE;
+}
+
+// static void free_tree_holder(tree_holder* th) {
+//     root* r = th->tree->root;
+//     
+// //     ExAcquireResourceExclusiveLite(&th->nonpaged->lock, TRUE);
+//     ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
+// 
+//     free_tree2(th->tree, funcname, __FILE__, __LINE__);
+// 
+// //     ExReleaseResourceLite(&th->nonpaged->lock);
+//     ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+// }
+
+void STDCALL free_tree_cache(LIST_ENTRY* tc) {
+    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;
+        
+        ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, TRUE);
+        
+        while (le != tc) {
+            LIST_ENTRY* nextle = le->Flink;
+            tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+            
+            if (tc2->tree->root == r) {
+                tree* nt;
+                BOOL top = !tc2->tree->paritem;
+                
+                nt = free_tree2(tc2->tree, funcname, __FILE__, __LINE__);
+                if (top && !nt && r->treeholder.tree == tc2->tree)
+                    r->treeholder.tree = NULL;
+                
+                RemoveEntryList(&tc2->list_entry);
+                ExFreePool(tc2);
+            }
+            
+            le = nextle;
+        }
+        
+        ExReleaseResourceLite(&r->nonpaged->load_tree_lock);
+    }
+}
+
+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);
+        
+        if (tc2->tree == t) {
+            if (write && !tc2->write) {
+                Vcb->write_trees++;
+                tc2->write = TRUE;
+            }
+            return;
+        }
+        
+        le = le->Flink;
+    }
+    
+    tc2 = ExAllocatePoolWithTag(PagedPool, sizeof(tree_cache), ALLOC_TAG);
+    if (!tc2) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    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) {
+    rollback_item* ri;
+    
+    ri = ExAllocatePoolWithTag(PagedPool, sizeof(rollback_item), ALLOC_TAG);
+    if (!ri) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    ri->type = type;
+    ri->ptr = ptr;
+    InsertTailList(rollback, &ri->list_entry);
+}
+
+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) {
+    traverse_ptr tp;
+    KEY searchkey;
+    int cmp;
+    tree_data *td, *paritem;
+    tree* t;
+#ifdef _DEBUG
+    LIST_ENTRY* le;
+    KEY firstitem = {0xcccccccccccccccc,0xcc,0xcccccccccccccccc};
+#endif
+    traverse_ptr* tp2;
+    BOOL success = FALSE;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %p, %llx, %x, %llx, %p, %x, %p, %p)\n", Vcb, r, obj_id, obj_type, offset, data, size, ptp, rollback);
+    
+    searchkey.obj_id = obj_id;
+    searchkey.obj_type = obj_type;
+    searchkey.offset = offset;
+    
+    Status = find_item(Vcb, r, &tp, &searchkey, TRUE);
+    if (!NT_SUCCESS(Status)) {
+        if (r) {
+            if (!r->treeholder.tree) {
+                BOOL loaded;
+                
+                Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, &loaded);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("do_load_tree returned %08x\n", Status);
+                    goto end;
+                }
+            }
+            
+            if (r->treeholder.tree && r->treeholder.tree->header.num_items == 0) {
+                tp.tree = r->treeholder.tree;
+                tp.item = NULL;
+            } else {
+                ERR("error: unable to load tree for root %llx\n", r->id);
+                goto end;
+            }
+        } else {
+            ERR("error: find_item returned %08x\n", Status);
+            goto end;
+        }
+    }
+    
+    TRACE("tp.item = %p\n", tp.item);
+    
+    if (tp.item) {
+        TRACE("tp.item->key = %p\n", &tp.item->key);
+        cmp = keycmp(&searchkey, &tp.item->key);
+        
+        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
+        cmp = -1;
+    
+    td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
+    if (!td) {
+        ERR("out of memory\n");
+        free_traverse_ptr(&tp);
+        goto end;
+    }
+    
+    td->key = searchkey;
+    td->size = size;
+    td->data = data;
+    td->ignore = FALSE;
+    td->inserted = TRUE;
+    
+#ifdef _DEBUG
+    le = tp.tree->itemlist.Flink;
+    while (le != &tp.tree->itemlist) {
+        tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);
+        firstitem = td2->key;
+        break;
+    }
+    
+    TRACE("inserting %llx,%x,%llx into tree beginning %llx,%x,%llx (num_items %x)\n", obj_id, obj_type, offset, firstitem.obj_id, firstitem.obj_type, firstitem.offset, tp.tree->header.num_items);
+#endif
+    
+    if (cmp == -1) { // very first key in root
+        InsertHeadList(&tp.tree->itemlist, &td->list_entry);
+
+        paritem = tp.tree->paritem;
+        while (paritem) {
+//             ERR("paritem = %llx,%x,%llx, tp.item->key = %llx,%x,%llx\n", paritem->key.obj_id, paritem->key.obj_type, paritem->key.offset, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+            if (!keycmp(&paritem->key, &tp.item->key)) {
+                paritem->key = searchkey;
+            } else
+                break;
+            
+            paritem = paritem->treeholder.tree->paritem;
+        }
+        
+    } else {          
+        InsertAfter(&tp.tree->itemlist, &td->list_entry, &tp.item->list_entry); // FIXME - we don't need this
+    }
+    
+    tp.tree->header.num_items++;
+    tp.tree->size += size + sizeof(leaf_node);
+//     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 (!ptp)
+        free_traverse_ptr(&tp);
+    else
+        *ptp = tp;
+    
+    t = tp.tree;
+    while (t) {
+        if (t->paritem && t->paritem->ignore) {
+            t->paritem->ignore = FALSE;
+            t->parent->header.num_items++;
+            t->parent->size += sizeof(internal_node);
+            
+            // FIXME - do we need to add a rollback entry here?
+        }
+
+        t->header.generation = Vcb->superblock.generation;
+        t = t->parent;
+    }
+    
+    // FIXME - free this correctly
+    
+    tp2 = ExAllocatePoolWithTag(PagedPool, sizeof(traverse_ptr), ALLOC_TAG);
+    if (!tp2) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    tp2->tree = tp.tree;
+    tp2->item = td;
+    
+    add_rollback(rollback, ROLLBACK_INSERT_ITEM, tp2);
+    
+    success = TRUE;
+
+end:
+    return success;
+}
+
+static __inline tree_data* first_valid_item(tree* t) {
+    LIST_ENTRY* le = t->itemlist.Flink;
+    
+    while (le != &t->itemlist) {
+        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+        
+        if (!td->ignore)
+            return td;
+        
+        le = le->Flink;
+    }
+        
+    return NULL;
+}
+
+void STDCALL delete_tree_item(device_extension* Vcb, traverse_ptr* tp, LIST_ENTRY* rollback) {
+    tree* t;
+    UINT64 gen;
+    traverse_ptr* tp2;
+
+    TRACE("deleting item %llx,%x,%llx (ignore = %s)\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->ignore ? "TRUE" : "FALSE");
+    
+#ifdef DEBUG_PARANOID
+    if (tp->item->ignore) {
+        ERR("trying to delete already-deleted item %llx,%x,%llx\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);
+        int3;
+    }
+#endif
+
+    tp->item->ignore = TRUE;
+    
+    add_to_tree_cache(Vcb, tp->tree, TRUE);
+    
+    tp->tree->header.num_items--;
+    
+    if (tp->tree->header.level == 0)
+        tp->tree->size -= sizeof(leaf_node) + tp->item->size;
+    else
+        tp->tree->size -= sizeof(internal_node);
+    
+    gen = tp->tree->Vcb->superblock.generation;
+    
+    t = tp->tree;
+    while (t) {
+        t->header.generation = gen;
+        t = t->parent;
+    }
+    
+    tp2 = ExAllocatePoolWithTag(PagedPool, sizeof(traverse_ptr), ALLOC_TAG);
+    if (!tp2) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    tp2->tree = tp->tree;
+    tp2->item = tp->item;
+
+    add_rollback(rollback, ROLLBACK_DELETE_ITEM, tp2);
+}
+
+void clear_rollback(LIST_ENTRY* rollback) {
+    rollback_item* ri;
+    
+    while (!IsListEmpty(rollback)) {
+        LIST_ENTRY* le = RemoveHeadList(rollback);
+        ri = CONTAINING_RECORD(le, rollback_item, list_entry);
+        
+        switch (ri->type) {
+            case ROLLBACK_INSERT_ITEM:
+            case ROLLBACK_DELETE_ITEM:
+                ExFreePool(ri->ptr);
+                break;
+        }
+        
+        ExFreePool(ri);
+    }
+}
+
+void do_rollback(device_extension* Vcb, LIST_ENTRY* rollback) {
+    rollback_item* ri;
+    
+    while (!IsListEmpty(rollback)) {
+        LIST_ENTRY* le = RemoveHeadList(rollback);
+        ri = CONTAINING_RECORD(le, rollback_item, list_entry);
+        
+        switch (ri->type) {
+            case ROLLBACK_INSERT_ITEM:
+            {
+                traverse_ptr* tp = ri->ptr;
+                
+                if (!tp->item->ignore) {
+                    tp->item->ignore = TRUE;
+                    tp->tree->header.num_items--;
+                
+                    if (tp->tree->header.level == 0)
+                        tp->tree->size -= sizeof(leaf_node) + tp->item->size;
+                    else
+                        tp->tree->size -= sizeof(internal_node);
+                }
+                
+                ExFreePool(tp);
+                break;
+            }
+                
+            case ROLLBACK_DELETE_ITEM:
+            {
+                traverse_ptr* tp = ri->ptr;
+                
+                if (tp->item->ignore) {
+                    tp->item->ignore = FALSE;
+                    tp->tree->header.num_items++;
+                
+                    if (tp->tree->header.level == 0)
+                        tp->tree->size += sizeof(leaf_node) + tp->item->size;
+                    else
+                        tp->tree->size += sizeof(internal_node);
+                }
+                
+                ExFreePool(tp);
+                break;
+            }
+        }
+        
+        ExFreePool(ri);
+    }
+}
diff --git a/reactos/drivers/filesystems/btrfs/write.c b/reactos/drivers/filesystems/btrfs/write.c
new file mode 100644 (file)
index 0000000..bff0518
--- /dev/null
@@ -0,0 +1,6572 @@
+/* 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"
+
+#define MAX_CSUM_SIZE (4096 - sizeof(tree_header) - sizeof(leaf_node))
+
+// BOOL did_split;
+BOOL chunk_test = FALSE;
+
+typedef struct {
+    KEVENT Event;
+    IO_STATUS_BLOCK iosb;
+} write_context;
+
+typedef struct {
+    EXTENT_ITEM ei;
+    UINT8 type;
+    EXTENT_DATA_REF edr;
+} EXTENT_ITEM_DATA_REF;
+
+typedef struct {
+    EXTENT_ITEM_TREE eit;
+    UINT8 type;
+    TREE_BLOCK_REF tbr;
+} EXTENT_ITEM_TREE2;
+
+typedef struct {
+    EXTENT_ITEM ei;
+    UINT8 type;
+    TREE_BLOCK_REF tbr;
+} EXTENT_ITEM_SKINNY_METADATA;
+
+typedef struct {
+    CHUNK_ITEM ci;
+    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 NTSTATUS STDCALL write_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
+    write_context* context = conptr;
+    
+    context->iosb = Irp->IoStatus;
+    KeSetEvent(&context->Event, 0, FALSE);
+    
+//     return STATUS_SUCCESS;
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS STDCALL write_data_phys(PDEVICE_OBJECT device, UINT64 address, void* data, UINT32 length) {
+    NTSTATUS Status;
+    LARGE_INTEGER offset;
+    PIRP Irp;
+    PIO_STACK_LOCATION IrpSp;
+    write_context* context = NULL;
+    
+    TRACE("(%p, %llx, %p, %x)\n", device, address, data, length);
+    
+    context = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_context), ALLOC_TAG);
+    if (!context) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlZeroMemory(context, sizeof(write_context));
+    
+    KeInitializeEvent(&context->Event, NotificationEvent, FALSE);
+    
+    offset.QuadPart = address;
+    
+//     Irp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE, Vcb->device, data, length, &offset, NULL, &context->iosb);
+    
+    Irp = IoAllocateIrp(device->StackSize, FALSE);
+    
+    if (!Irp) {
+        ERR("IoAllocateIrp failed\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto exit2;
+    }
+    
+    IrpSp = IoGetNextIrpStackLocation(Irp);
+    IrpSp->MajorFunction = IRP_MJ_WRITE;
+    
+    if (device->Flags & DO_BUFFERED_IO) {
+        Irp->AssociatedIrp.SystemBuffer = data;
+
+        Irp->Flags = IRP_BUFFERED_IO;
+    } else if (device->Flags & DO_DIRECT_IO) {
+        Irp->MdlAddress = IoAllocateMdl(data, length, FALSE, FALSE, NULL);
+        if (!Irp->MdlAddress) {
+            DbgPrint("IoAllocateMdl failed\n");
+            goto exit;
+        }
+        
+        MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoWriteAccess);
+    } else {
+        Irp->UserBuffer = data;
+    }
+
+    IrpSp->Parameters.Write.Length = length;
+    IrpSp->Parameters.Write.ByteOffset = offset;
+    
+    Irp->UserIosb = &context->iosb;
+
+    Irp->UserEvent = &context->Event;
+
+    IoSetCompletionRoutine(Irp, write_completion, context, TRUE, TRUE, TRUE);
+
+    // FIXME - support multiple devices
+    Status = IoCallDriver(device, Irp);
+    
+    if (Status == STATUS_PENDING) {
+        KeWaitForSingleObject(&context->Event, Executive, KernelMode, FALSE, NULL);
+        Status = context->iosb.Status;
+    }
+    
+    if (!NT_SUCCESS(Status)) {
+        ERR("IoCallDriver returned %08x\n", Status);
+    }
+    
+    if (device->Flags & DO_DIRECT_IO) {
+        MmUnlockPages(Irp->MdlAddress);
+        IoFreeMdl(Irp->MdlAddress);
+    }
+    
+exit:
+    IoFreeIrp(Irp);
+    
+exit2:
+    if (context)
+        ExFreePool(context);
+    
+    return Status;
+}
+
+static NTSTATUS STDCALL write_superblock(device_extension* Vcb, device* device) {
+    NTSTATUS Status;
+    unsigned int i = 0;
+    UINT32 crc32;
+
+#ifdef __REACTOS__
+    Status = STATUS_INTERNAL_ERROR;
+#endif
+    
+    // FIXME - work with RAID
+    
+    // FIXME - only write one superblock if on SSD (?)
+    while (superblock_addrs[i] > 0 && Vcb->length >= superblock_addrs[i] + sizeof(superblock)) {
+        TRACE("writing superblock %u\n", i);
+        
+        Vcb->superblock.sb_phys_addr = superblock_addrs[i];
+        RtlCopyMemory(&Vcb->superblock.dev_item, &device->devitem, sizeof(DEV_ITEM));
+        
+        crc32 = calc_crc32c(0xffffffff, (UINT8*)&Vcb->superblock.uuid, (ULONG)sizeof(superblock) - sizeof(Vcb->superblock.checksum));
+        crc32 = ~crc32;
+        TRACE("crc32 is %08x\n", crc32);
+        RtlCopyMemory(&Vcb->superblock.checksum, &crc32, sizeof(UINT32));
+        
+        Status = write_data_phys(device->devobj, superblock_addrs[i], &Vcb->superblock, sizeof(superblock));
+        
+        if (!NT_SUCCESS(Status))
+            break;
+        
+        i++;
+    }
+
+    return Status;
+}
+
+static BOOL find_address_in_chunk(device_extension* Vcb, chunk* c, UINT64 length, UINT64* address) {
+    LIST_ENTRY* le;
+    space *s, *bestfit = NULL;
+    
+    TRACE("(%p, %llx, %llx, %p)\n", Vcb, c->offset, length, address);
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        if (s->type == SPACE_TYPE_FREE) {
+            if (s->size == length) {
+                *address = s->offset;
+                TRACE("returning exact fit at %llx\n", s->offset);
+                return TRUE;
+            } else if (s->size > length && (!bestfit || bestfit->size > s->size)) {
+                bestfit = s;
+            }
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (bestfit) {
+        TRACE("returning best fit at %llx\n", bestfit->offset);
+        *address = bestfit->offset;
+        return TRUE;
+    }
+    
+    return FALSE;
+}
+
+void add_to_space_list(chunk* c, UINT64 offset, UINT64 size, UINT8 type) {
+    LIST_ENTRY *le = c->space.Flink, *nextle, *insbef;
+    space *s, *s2, *s3;
+#ifdef DEBUG_PARANOID
+    UINT64 lastaddr;
+#endif
+    
+    TRACE("(%p, %llx, %llx, %x)\n", c, offset, size, type);
+    
+#ifdef DEBUG_PARANOID
+    // TESTING
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        TRACE("%llx,%llx,%x\n", s->offset, s->size, s->type);
+        
+        le = le->Flink;
+    }
+#endif
+    
+    c->space_changed = TRUE;
+    
+    le = c->space.Flink;
+    insbef = &c->space;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        nextle = le->Flink;
+        
+        if (s->offset >= offset + size) {
+            insbef = le;
+            break;
+        }
+        
+        if (s->offset >= offset && s->offset + s->size <= offset + size) { // delete entirely
+            RemoveEntryList(&s->list_entry);
+            
+            if (s->offset + s->size == offset + size) {
+                insbef = s->list_entry.Flink;
+                RemoveEntryList(&s->list_entry);
+                ExFreePool(s);
+                break;
+            }
+            
+            RemoveEntryList(&s->list_entry);
+            ExFreePool(s);
+        } else if (s->offset < offset && s->offset + s->size > offset + size) { // split in two
+            s3 = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+            if (!s3) {
+                ERR("out of memory\n");
+                return;
+            }
+            
+            s3->offset = offset + size;
+            s3->size = s->size - size - offset + s->offset;
+            s3->type = s->type;
+            InsertHeadList(&s->list_entry, &s3->list_entry);
+            insbef = &s3->list_entry;
+            
+            s->size = offset - s->offset;
+            break;
+        } 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->offset = offset + size;
+            
+            insbef = le;
+            break;
+        }
+        
+        le = nextle;
+    }
+    
+    s2 = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+    if (!s2) {
+        ERR("out of memory\n");
+        return;
+    }
+    
+    s2->offset = offset;
+    s2->size = size;
+    s2->type = type;
+    InsertTailList(insbef, &s2->list_entry);
+    
+    // merge entries if same type
+   
+    if (s2->list_entry.Blink != &c->space) {
+        s = CONTAINING_RECORD(s2->list_entry.Blink, space, list_entry);
+        
+        if (s->type == type) {
+            s->size += s2->size;
+            
+            RemoveEntryList(&s2->list_entry);
+            ExFreePool(s2);
+            
+            s2 = s;
+        }
+    }
+    
+    if (s2->list_entry.Flink != &c->space) {
+        s = CONTAINING_RECORD(s2->list_entry.Flink, space, list_entry);
+        
+        if (s->type == type) {
+            s2->size += s->size;
+
+            RemoveEntryList(&s->list_entry);
+            ExFreePool(s);
+        }
+    }
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        TRACE("%llx,%llx,%x\n", s->offset, s->size, s->type);
+        
+        le = le->Flink;
+    }
+    
+#ifdef DEBUG_PARANOID
+    // TESTING
+    lastaddr = c->offset;
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        
+        if (s->offset != lastaddr) {
+            ERR("inconsistency detected!\n");
+            int3;
+        }
+        
+        lastaddr = s->offset + s->size;
+        
+        le = le->Flink;
+    }
+    
+    if (lastaddr != c->offset + c->chunk_item->size) {
+        ERR("inconsistency detected - space doesn't run all the way to end of chunk\n");
+        int3;
+    }
+#endif
+}
+
+chunk* get_chunk_from_address(device_extension* Vcb, UINT64 address) {
+    LIST_ENTRY* le2;
+    chunk* c;
+    
+    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 < c->offset + c->chunk_item->size)
+            return c;
+         
+        le2 = le2->Flink;
+    }
+    
+    return NULL;
+}
+
+typedef struct {
+    disk_hole* dh;
+    device* device;
+} stripe;
+
+static void add_provisional_disk_hole(device_extension* Vcb, stripe* s, UINT64 max_stripe_size) {
+//     LIST_ENTRY* le = s->device->disk_holes.Flink;
+//     disk_hole* dh;
+
+//     ERR("old holes:\n");
+//     while (le != &s->device->disk_holes) {
+//         dh = CONTAINING_RECORD(le, disk_hole, listentry);
+//         
+//         ERR("address %llx, size %llx, provisional %u\n", dh->address, dh->size, dh->provisional);
+//         
+//         le = le->Flink;
+//     }
+    
+    if (s->dh->size <= max_stripe_size) {
+        s->dh->provisional = TRUE;
+    } else {
+        disk_hole* newdh = ExAllocatePoolWithTag(PagedPool, sizeof(disk_hole), ALLOC_TAG);
+        if (!newdh) {
+            ERR("out of memory\n");
+            return;
+        }
+        
+        newdh->address = s->dh->address + max_stripe_size;
+        newdh->size = s->dh->size - max_stripe_size;
+        newdh->provisional = FALSE;
+        InsertTailList(&s->device->disk_holes, &newdh->listentry);
+        
+        s->dh->size = max_stripe_size;
+        s->dh->provisional = TRUE;
+    }
+    
+//     ERR("new holes:\n");
+//     le = s->device->disk_holes.Flink;
+//     while (le != &s->device->disk_holes) {
+//         dh = CONTAINING_RECORD(le, disk_hole, listentry);
+//         
+//         ERR("address %llx, size %llx, provisional %u\n", dh->address, dh->size, dh->provisional);
+//         
+//         le = le->Flink;
+//     }
+}
+
+static UINT64 find_new_chunk_address(device_extension* Vcb, UINT64 size) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    BOOL b;
+    UINT64 lastaddr;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = 0x100;
+    searchkey.obj_type = TYPE_CHUNK_ITEM;
+    searchkey.offset = 0;
+    
+    Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return 0xffffffffffffffff;
+    }
+    
+    lastaddr = 0;
+    
+    do {
+        if (tp.item->key.obj_type == TYPE_CHUNK_ITEM) {
+            if (tp.item->size < sizeof(CHUNK_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(CHUNK_ITEM));
+            } else {
+                CHUNK_ITEM* ci = (CHUNK_ITEM*)tp.item->data;
+                
+                if (tp.item->key.offset >= lastaddr + size) {
+                    free_traverse_ptr(&tp);
+                    return lastaddr;
+                }
+                
+                lastaddr = tp.item->key.offset + ci->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)
+                break;
+        }
+    } while (b);
+    
+    free_traverse_ptr(&tp);
+    
+    return lastaddr;
+}
+
+static BOOL increase_dev_item_used(device_extension* Vcb, device* device, UINT64 size, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    DEV_ITEM* di;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = 1;
+    searchkey.obj_type = TYPE_DEV_ITEM;
+    searchkey.offset = device->devitem.dev_id;
+    
+    Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    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);
+    if (!di) {
+        ERR("out of memory\n");
+        return FALSE;
+    }
+    
+    RtlCopyMemory(di, &device->devitem, sizeof(DEV_ITEM));
+    
+    if (!insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, device->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+
+static void reset_disk_holes(device* device, BOOL commit) {
+    LIST_ENTRY* le = device->disk_holes.Flink;
+    disk_hole* dh;
+
+//     ERR("old holes:\n");
+//     while (le != &device->disk_holes) {
+//         dh = CONTAINING_RECORD(le, disk_hole, listentry);
+//         
+//         ERR("address %llx, size %llx, provisional %u\n", dh->address, dh->size, dh->provisional);
+//         
+//         le = le->Flink;
+//     }
+    
+    le = device->disk_holes.Flink;
+    while (le != &device->disk_holes) {
+        LIST_ENTRY* le2 = le->Flink;
+        
+        dh = CONTAINING_RECORD(le, disk_hole, listentry);
+        
+        if (dh->provisional) {
+            if (commit) {
+                RemoveEntryList(le);
+                ExFreePool(dh);
+            } else {
+                dh->provisional = FALSE;
+            }
+        }
+        
+        le = le2;
+    }
+    
+    if (!commit) {
+        le = device->disk_holes.Flink;
+        while (le != &device->disk_holes) {
+            LIST_ENTRY* le2 = le->Flink;
+            
+            dh = CONTAINING_RECORD(le, disk_hole, listentry);
+            
+            while (le2 != &device->disk_holes) {
+                disk_hole* dh2 = CONTAINING_RECORD(le2, disk_hole, listentry);
+                
+                if (dh2->address == dh->address + dh->size) {
+                    LIST_ENTRY* le3 = le2->Flink;
+                    dh->size += dh2->size;
+                    
+                    RemoveEntryList(le2);
+                    ExFreePool(dh2);
+                    
+                    le2 = le3;
+                } else
+                    break;
+            }
+            
+            le = le->Flink;
+        }
+    }
+        
+//     ERR("new holes:\n");
+//     le = device->disk_holes.Flink;
+//     while (le != &device->disk_holes) {
+//         dh = CONTAINING_RECORD(le, disk_hole, listentry);
+//         
+//         ERR("address %llx, size %llx, provisional %u\n", dh->address, dh->size, dh->provisional);
+//         
+//         le = le->Flink;
+//     }
+}
+
+static NTSTATUS add_to_bootstrap(device_extension* Vcb, UINT64 obj_id, UINT8 obj_type, UINT64 offset, void* data, ULONG size) {
+    sys_chunk *sc, *sc2;
+    LIST_ENTRY* le;
+    USHORT i;
+    
+    if (Vcb->superblock.n + sizeof(KEY) + size > SYS_CHUNK_ARRAY_SIZE) {
+        ERR("error - bootstrap is full\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG);
+    if (!sc) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    sc->key.obj_id = obj_id;
+    sc->key.obj_type = obj_type;
+    sc->key.offset = offset;
+    sc->size = size;
+    sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG);
+    if (!sc->data) {
+        ERR("out of memory\n");
+        ExFreePool(sc);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(sc->data, data, sc->size);
+    
+    le = Vcb->sys_chunks.Flink;
+    while (le != &Vcb->sys_chunks) {
+        sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);
+        
+        if (keycmp(&sc2->key, &sc->key) == 1)
+            break;
+        
+        le = le->Flink;
+    }
+    InsertTailList(le, &sc->list_entry);
+    
+    Vcb->superblock.n += sizeof(KEY) + size;
+    
+    i = 0;
+    le = Vcb->sys_chunks.Flink;
+    while (le != &Vcb->sys_chunks) {
+        sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);
+        
+        TRACE("%llx,%x,%llx\n", sc2->key.obj_id, sc2->key.obj_type, sc2->key.offset);
+        
+        RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], &sc2->key, sizeof(KEY));
+        i += sizeof(KEY);
+        
+        RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], sc2->data, sc2->size);
+        i += sc2->size;
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static 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;
+    disk_hole* dh;
+    stripe* stripes;
+    ULONG cisize;
+    CHUNK_ITEM* ci;
+    CHUNK_ITEM_STRIPE* cis;
+    chunk* c = NULL;
+    space* s = NULL;
+    BOOL success = FALSE;
+    BLOCK_GROUP_ITEM* bgi;
+    
+    for (i = 0; i < Vcb->superblock.num_devices; i++) {
+        total_size += Vcb->devices[i].devitem.num_bytes;
+    }
+    TRACE("total_size = %llx\n", total_size);
+    
+    if (flags & BLOCK_FLAG_DATA) {
+        max_stripe_size = 0x40000000; // 1 GB
+        max_chunk_size = 10 * max_stripe_size;
+    } else if (flags & BLOCK_FLAG_METADATA) {
+        if (total_size > 0xC80000000) // 50 GB
+            max_stripe_size = 0x40000000; // 1 GB
+        else
+            max_stripe_size = 0x10000000; // 256 MB
+        
+        max_chunk_size = max_stripe_size;
+    } else if (flags & BLOCK_FLAG_SYSTEM) {
+        max_stripe_size = 0x2000000; // 32 MB
+        max_chunk_size = 2 * max_stripe_size;
+    }
+    
+    // FIXME - make sure whole number of sectors?
+    max_chunk_size = min(max_chunk_size, total_size / 10); // cap at 10%
+    
+    TRACE("would allocate a new chunk of %llx bytes and stripe %llx\n", max_chunk_size, max_stripe_size);
+    
+    if (flags & BLOCK_FLAG_DUPLICATE) {
+        num_stripes = 2;
+    } else if (flags & BLOCK_FLAG_RAID0) {
+        FIXME("RAID0 not yet supported\n");
+        return NULL;
+    } else if (flags & BLOCK_FLAG_RAID1) {
+        FIXME("RAID1 not yet supported\n");
+        return NULL;
+    } else if (flags & BLOCK_FLAG_RAID10) {
+        FIXME("RAID10 not yet supported\n");
+        return NULL;
+    } else if (flags & BLOCK_FLAG_RAID5) {
+        FIXME("RAID5 not yet supported\n");
+        return NULL;
+    } else if (flags & BLOCK_FLAG_RAID6) {
+        FIXME("RAID6 not yet supported\n");
+        return NULL;
+    } else { // SINGLE
+        num_stripes = 1;
+    }
+    
+    stripes = ExAllocatePoolWithTag(PagedPool, sizeof(stripe) * num_stripes, ALLOC_TAG);
+    if (!stripes) {
+        ERR("out of memory\n");
+        return NULL;
+    }
+    
+    for (i = 0; i < num_stripes; i++) {
+        stripes[i].dh = NULL;
+        
+        for (j = 0; j < Vcb->superblock.num_devices; j++) {
+            LIST_ENTRY* le = Vcb->devices[j].disk_holes.Flink;
+
+            while (le != &Vcb->devices[j].disk_holes) {
+                dh = CONTAINING_RECORD(le, disk_hole, listentry);
+                
+                if (!dh->provisional) {
+                    if (!stripes[i].dh || dh->size > stripes[i].dh->size) {
+                        stripes[i].dh = dh;
+                        stripes[i].device = &Vcb->devices[j];
+                        
+                        if (stripes[i].dh->size >= max_stripe_size)
+                            break;
+                    }
+                }
+
+                le = le->Flink;
+            }
+            
+            if (stripes[i].dh && stripes[i].dh->size >= max_stripe_size)
+                break;
+        }
+        
+        if (stripes[i].dh) {
+            TRACE("good DH: device %llx, address %llx, size %llx\n", stripes[i].device->devitem.dev_id, stripes[i].dh->address, stripes[i].dh->size);
+        } else {
+            TRACE("good DH not found\n");
+            goto end;
+        }
+        
+        add_provisional_disk_hole(Vcb, &stripes[i], max_stripe_size);
+    }
+    
+    stripe_size = min(stripes[0].dh->size, max_stripe_size);
+    for (i = 1; i < num_stripes; i++) {
+        stripe_size = min(stripe_size, stripes[1].dh->size);
+    }
+    // FIXME - make sure stripe_size aligned properly
+    // FIXME - obey max_chunk_size
+    
+    c = ExAllocatePoolWithTag(PagedPool, sizeof(chunk), ALLOC_TAG);
+    if (!c) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    // add CHUNK_ITEM to tree 3
+    
+    cisize = sizeof(CHUNK_ITEM) + (num_stripes * sizeof(CHUNK_ITEM_STRIPE));
+    ci = ExAllocatePoolWithTag(PagedPool, cisize, ALLOC_TAG);
+    if (!ci) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    ci->size = stripe_size; // FIXME for RAID
+    ci->root_id = Vcb->extent_root->id;
+    ci->stripe_length = 0x10000; // FIXME? BTRFS_STRIPE_LEN in kernel
+    ci->type = flags;
+    ci->opt_io_alignment = ci->stripe_length;
+    ci->opt_io_width = ci->stripe_length;
+    ci->sector_size = stripes[0].device->devitem.minimal_io_size;
+    ci->num_stripes = num_stripes;
+    ci->sub_stripes = 1;
+    
+    c->devices = ExAllocatePoolWithTag(PagedPool, sizeof(device*) * num_stripes, ALLOC_TAG);
+    if (!c->devices) {
+        ERR("out of memory\n");
+        ExFreePool(ci);
+        goto end;
+    }
+
+    for (i = 0; i < num_stripes; i++) {
+        if (i == 0)
+            cis = (CHUNK_ITEM_STRIPE*)&ci[1];
+        else
+            cis = &cis[1];
+        
+        cis->dev_id = stripes[i].device->devitem.dev_id;
+        cis->offset = stripes[i].dh->address;
+        cis->dev_uuid = stripes[i].device->devitem.device_uuid;
+        
+        c->devices[i] = stripes[i].device;
+    }
+    
+    logaddr = find_new_chunk_address(Vcb, ci->size);
+    if (logaddr == 0xffffffffffffffff) {
+        ERR("find_new_chunk_address failed\n");
+        ExFreePool(ci);
+        goto end;
+    }
+    
+    if (!insert_tree_item(Vcb, Vcb->chunk_root, 0x100, TYPE_CHUNK_ITEM, logaddr, ci, cisize, NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(ci);
+        goto end;
+    }
+    
+    if (flags & BLOCK_FLAG_SYSTEM) {
+        NTSTATUS Status = add_to_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, logaddr, ci, cisize);
+        if (!NT_SUCCESS(Status)) {
+            ERR("add_to_bootstrap returned %08x\n", Status);
+            goto end;
+        }
+    }
+
+    Vcb->superblock.chunk_root_generation = Vcb->superblock.generation;
+    
+    c->chunk_item = ExAllocatePoolWithTag(PagedPool, cisize, ALLOC_TAG);
+    if (!c->chunk_item) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    RtlCopyMemory(c->chunk_item, ci, cisize);
+    c->size = cisize;
+    c->offset = logaddr;
+    c->used = c->oldused = 0;
+    c->space_changed = FALSE;
+    InitializeListHead(&c->space);
+    
+    s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);
+    if (!s) {
+        ERR("out of memory\n");
+        goto end;
+    }
+    
+    s->offset = c->offset;
+    s->size = c->chunk_item->size;
+    s->type = SPACE_TYPE_FREE;
+    InsertTailList(&c->space, &s->list_entry);
+    
+    protect_superblocks(Vcb, c);
+    
+    // add BLOCK_GROUP_ITEM to tree 2
+    
+    bgi = ExAllocatePoolWithTag(PagedPool, sizeof(BLOCK_GROUP_ITEM), ALLOC_TAG);
+    if (!bgi) {
+        ERR("out of memory\n");
+        goto end;
+    }
+        
+    bgi->used = 0;
+    bgi->chunk_tree = 0x100;
+    bgi->flags = flags;
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, logaddr, TYPE_BLOCK_GROUP_ITEM, ci->size, bgi, sizeof(BLOCK_GROUP_ITEM), NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(bgi);
+        goto end;
+    }
+    
+    // add DEV_EXTENTs to tree 4
+    
+    for (i = 0; i < num_stripes; i++) {
+        DEV_EXTENT* de;
+        
+        de = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_EXTENT), ALLOC_TAG);
+        if (!de) {
+            ERR("out of memory\n");
+            goto end;
+        }
+        
+        de->chunktree = Vcb->chunk_root->id;
+        de->objid = 0x100;
+        de->address = logaddr;
+        de->length = ci->size;
+        de->chunktree_uuid = Vcb->chunk_root->treeholder.tree->header.chunk_tree_uuid;
+        
+        if (!insert_tree_item(Vcb, Vcb->dev_root, stripes[i].device->devitem.dev_id, TYPE_DEV_EXTENT, stripes[i].dh->address, de, sizeof(DEV_EXTENT), NULL, rollback)) {
+            ERR("insert_tree_item failed\n");
+            ExFreePool(de);
+            goto end;
+        }
+        
+        if (!increase_dev_item_used(Vcb, stripes[i].device, ci->size, rollback)) {
+            ERR("increase_dev_item_used failed\n");
+            goto end;
+        }
+    }
+    
+    for (i = 0; i < num_stripes; i++) {
+        BOOL b = FALSE;
+        for (j = 0; j < i; j++) {
+            if (stripes[j].device == stripes[i].device)
+                b = TRUE;
+        }
+        
+        if (!b)
+            reset_disk_holes(stripes[i].device, TRUE);
+    }
+    
+    success = TRUE;
+    
+end:
+    ExFreePool(stripes);
+    
+    if (!success) {
+        for (i = 0; i < num_stripes; i++) {
+            BOOL b = FALSE;
+            for (j = 0; j < i; j++) {
+                if (stripes[j].device == stripes[i].device)
+                    b = TRUE;
+            }
+            
+            if (!b)
+                reset_disk_holes(stripes[i].device, FALSE);
+        }
+        
+        if (c) ExFreePool(c);
+        if (s) ExFreePool(s);
+    } else
+        InsertTailList(&Vcb->chunks, &c->list_entry);
+
+    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) {
+    KEY searchkey;
+    traverse_ptr tp;
+    CHUNK_ITEM2* ci;
+    NTSTATUS Status;
+    UINT32 i;
+    
+    TRACE("(%p, %llx, %p, %x)\n", Vcb, address, data, length);
+    
+    // FIXME - use version cached in Vcb
+    
+    searchkey.obj_id = 0x100; // fixed?
+    searchkey.obj_type = TYPE_CHUNK_ITEM;
+    searchkey.offset = address;
+    
+    Status = find_item(Vcb, Vcb->chunk_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("error - unexpected item in chunk tree\n");
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    if (tp.item->size < sizeof(CHUNK_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(CHUNK_ITEM2));
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    ci = (CHUNK_ITEM2*)tp.item->data;
+    
+    if (tp.item->key.offset > address || tp.item->key.offset + ci->ci.size < address) {
+        ERR("error - address %llx was out of chunk bounds\n", address);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    // FIXME - only do this for chunks marked DUPLICATE?
+    // FIXME - for multiple writes, if PENDING do waits at the end
+    // FIXME - work with RAID
+    for (i = 0; i < ci->ci.num_stripes; i++) {
+        Status = write_data_phys(Vcb->devices[0].devobj, address - tp.item->key.offset + ci->stripes[i].offset, data, length);
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - write_data_phys failed\n");
+            goto end;
+        }
+    }
+    
+end:
+    free_traverse_ptr(&tp);
+    
+    return Status;
+}
+
+static void clean_space_cache_chunk(device_extension* Vcb, chunk* c) {
+    LIST_ENTRY *le, *nextle;
+    space *s, *s2;
+    
+//     // TESTING
+//     le = c->space.Flink;
+//     while (le != &c->space) {
+//         s = CONTAINING_RECORD(le, space, list_entry);
+//         
+//         TRACE("%x,%x,%x\n", (UINT32)s->offset, (UINT32)s->size, s->type);
+//         
+//         le = le->Flink;
+//     }
+    
+    le = c->space.Flink;
+    while (le != &c->space) {
+        s = CONTAINING_RECORD(le, space, list_entry);
+        nextle = le->Flink;
+        
+        if (s->type == SPACE_TYPE_DELETING)
+            s->type = SPACE_TYPE_FREE;
+        else if (s->type == SPACE_TYPE_WRITING)
+            s->type = SPACE_TYPE_USED;
+        
+        if (le->Blink != &c->space) {
+            s2 = CONTAINING_RECORD(le->Blink, space, list_entry);
+            
+            if (s2->type == s->type) { // do merge
+                s2->size += s->size;
+                
+                RemoveEntryList(&s->list_entry);
+                ExFreePool(s);
+            }
+        }
+
+        le = nextle;
+    }
+    
+//     le = c->space.Flink;
+//     while (le != &c->space) {
+//         s = CONTAINING_RECORD(le, space, list_entry);
+//         
+//         TRACE("%x,%x,%x\n", (UINT32)s->offset, (UINT32)s->size, s->type);
+//         
+//         le = le->Flink;
+//     }
+}
+
+static void clean_space_cache(device_extension* Vcb) {
+    LIST_ENTRY* le;
+    chunk* c;
+    
+    TRACE("(%p)\n", Vcb);
+    
+    le = Vcb->chunks.Flink;
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (c->space_changed) {
+            clean_space_cache_chunk(Vcb, c);
+            c->space_changed = FALSE;
+        }
+        
+        le = le->Flink;
+    }
+}
+
+static BOOL trees_consistent(device_extension* Vcb) {
+    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);
+        
+        if (tc2->write) {
+            if (tc2->tree->header.num_items == 0)
+                return FALSE;
+            
+            if (tc2->tree->size > maxsize)
+                return FALSE;
+            
+            if (tc2->tree->new_address == 0)
+                return FALSE;
+        }
+        
+        le = le->Flink;
+    }
+    
+    return TRUE;
+}
+
+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);
+        
+        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) {
+                KEY searchkey;
+                traverse_ptr tp;
+                
+                searchkey.obj_id = tc2->tree->root->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);
+                    return Status;
+                }
+                
+                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;
+                }
+                
+                if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, create new entry with new bits zeroed
+                    ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
+                    if (!ri) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    if (tp.item->size > 0)
+                        RtlCopyMemory(ri, tp.item->data, tp.item->size);
+                    
+                    RtlZeroMemory(((UINT8*)ri) + tp.item->size, sizeof(ROOT_ITEM) - tp.item->size);
+                    
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                } else {
+                    add_to_tree_cache(Vcb, tp.tree, TRUE);
+                }
+                
+                free_traverse_ptr(&tp);
+            }
+        }
+        
+        le = le->Flink;
+    }
+
+    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;
+    NTSTATUS Status;
+    
+    while (t->parent) {
+        t = t->parent;
+        
+        add_to_tree_cache(Vcb, t, TRUE);
+    }
+    
+    if (t->root == Vcb->root_root || t->root == Vcb->chunk_root)
+        return;
+    
+    searchkey.obj_id = t->root->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);
+        return;
+    }
+    
+    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);
+}
+
+static BOOL insert_tree_extent_skinny(device_extension* Vcb, tree* t, chunk* c, UINT64 address, LIST_ENTRY* rollback) {
+    EXTENT_ITEM_SKINNY_METADATA* eism;
+    traverse_ptr insert_tp;
+    
+    eism = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_SKINNY_METADATA), ALLOC_TAG);
+    if (!eism) {
+        ERR("out of memory\n");
+        return FALSE;
+    }
+    
+    eism->ei.refcount = 1;
+    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;
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, t->header.level, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(eism);
+        return FALSE;
+    }
+    
+    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;
+    
+    return TRUE;
+}
+
+static BOOL insert_tree_extent(device_extension* Vcb, tree* t, chunk* c, LIST_ENTRY* rollback) {
+    UINT64 address;
+    EXTENT_ITEM_TREE2* eit2;
+    traverse_ptr insert_tp;
+    
+    TRACE("(%p, %p, %p, %p)\n", Vcb, t, c);
+    
+    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);
+    
+    eit2 = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_TREE2), ALLOC_TAG);
+    if (!eit2) {
+        ERR("out of memory\n");
+        return FALSE;
+    }
+
+    eit2->eit.extent_item.refcount = 1;
+    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->type = TYPE_TREE_BLOCK_REF;
+    eit2->tbr.offset = t->header.tree_id;
+    
+// #ifdef DEBUG_PARANOID
+//     if (wt->firstitem.obj_type == 0xcc) { // TESTING
+//         ERR("error - firstitem not set (wt = %p, tree = %p, address = %x)\n", wt, wt->tree, (UINT32)address);
+//         ERR("num_items = %u, level = %u, root = %x, delete = %u\n", wt->tree->header.num_items, wt->tree->header.level, (UINT32)wt->tree->root->id, wt->delete);
+//         int3;
+//     }
+// #endif
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, eit2, sizeof(EXTENT_ITEM_TREE2), &insert_tp, rollback)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(eit2);
+        return FALSE;
+    }
+    
+    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;
+    
+    return TRUE;
+}
+
+static NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, LIST_ENTRY* rollback) {
+    chunk *origchunk = NULL, *c;
+    LIST_ENTRY* le;
+    UINT64 flags = t->flags;
+    
+    if (flags == 0)
+        flags = (t->root->id == BTRFS_ROOT_CHUNK ? BLOCK_FLAG_SYSTEM : BLOCK_FLAG_METADATA) | BLOCK_FLAG_DUPLICATE;
+    
+//     TRACE("flags = %x\n", (UINT32)wt->flags);
+    
+//     if (!chunk_test) { // TESTING
+//         if ((c = alloc_chunk(Vcb, flags))) {
+//             if ((c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {
+//                 if (insert_tree_extent(Vcb, t, c)) {
+//                     chunk_test = TRUE;
+//                     return STATUS_SUCCESS;
+//                 }
+//             }
+//         }
+//     }
+    
+    if (t->header.address != 0) {
+        origchunk = get_chunk_from_address(Vcb, t->header.address);
+        
+        if (insert_tree_extent(Vcb, t, origchunk, rollback))
+            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))
+                return STATUS_SUCCESS;
+        }
+
+        le = le->Flink;
+    }
+    
+    // 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))
+                return STATUS_SUCCESS;
+        }
+    }
+    
+    ERR("couldn't find any metadata chunks with %x bytes free\n", Vcb->superblock.node_size);
+
+    return STATUS_DISK_FULL;
+}
+
+static BOOL reduce_tree_extent_skinny(device_extension* Vcb, UINT64 address, tree* t, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp;
+    chunk* c;
+    EXTENT_ITEM_SKINNY_METADATA* eism;
+    NTSTATUS Status;
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_METADATA_ITEM;
+    searchkey.offset = t->header.level;
+    
+    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+    if (!NT_SUCCESS(Status)) {
+        ERR("error - find_item returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (keycmp(&tp.item->key, &searchkey)) {
+        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) {
+        // convert shared data extents
+        
+        LIST_ENTRY* le = t->itemlist.Flink;
+        while (le != &t->itemlist) {
+            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+            
+            TRACE("%llx,%x,%llx\n", td->key.obj_id, td->key.obj_type, td->key.offset);
+            
+            if (!td->ignore && !td->inserted) {
+                if (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 != 0) {
+                            TRACE("trying to convert shared data extent %llx,%llx\n", ed2->address, ed2->size);
+                            convert_shared_data_extent(Vcb, ed2->address, ed2->size, rollback);
+                        }
+                    }
+                }
+            }
+
+            le = le->Flink;
+        }
+        
+        t->header.flags &= ~HEADER_FLAG_SHARED_BACKREF;
+    }
+
+    c = get_chunk_from_address(Vcb, address);
+    
+    if (c) {
+        decrease_chunk_usage(c, Vcb->superblock.node_size);
+        
+        add_to_space_list(c, address, Vcb->superblock.node_size, SPACE_TYPE_DELETING);
+    } else
+        ERR("could not find chunk for address %llx\n", address);
+    
+    free_traverse_ptr(&tp);
+    
+    return TRUE;
+}
+
+// TESTING
+// static void check_tree_num_items(tree* t) {
+//     LIST_ENTRY* le2;
+//     UINT32 ni;
+//     
+//     le2 = t->itemlist.Flink;
+//     ni = 0;
+//     while (le2 != &t->itemlist) {
+//         tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
+//         if (!td->ignore)
+//             ni++;
+//         le2 = le2->Flink;
+//     }
+//     
+//     if (t->header.num_items != ni) {
+//         ERR("tree %p not okay: num_items was %x, expecting %x\n", t, ni, t->header.num_items);
+//         int3;
+//     } else {
+//         ERR("tree %p okay\n", t);
+//     }
+// }
+// 
+// static void check_trees_num_items(LIST_ENTRY* tc) {
+//     LIST_ENTRY* le = tc->Flink;
+//     while (le != tc) {
+//         tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+//         
+//         check_tree_num_items(tc2->tree);
+//         
+//         le = le->Flink;
+//     }    
+// }
+
+static void convert_old_tree_extent(device_extension* Vcb, tree_data* td, tree* t, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp, tp2, insert_tp;
+    EXTENT_REF_V0* erv0;
+    NTSTATUS Status;
+    
+    TRACE("(%p, %p, %p)\n", Vcb, td, t);
+    
+    searchkey.obj_id = td->treeholder.address;
+    searchkey.obj_type = TYPE_EXTENT_REF_V0;
+    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;
+    }
+    
+    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;
+    }
+    
+    searchkey.obj_id = td->treeholder.address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = Vcb->superblock.node_size;
+    
+    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;
+    }
+    
+    erv0 = (EXTENT_REF_V0*)tp.item->data;
+    
+    delete_tree_item(Vcb, &tp, rollback);
+    delete_tree_item(Vcb, &tp2, rollback);
+    
+    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {
+        EXTENT_ITEM_SKINNY_METADATA* eism = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_SKINNY_METADATA), ALLOC_TAG);
+        
+        if (!eism) {
+            ERR("out of memory\n");
+            free_traverse_ptr(&tp2);
+            free_traverse_ptr(&tp);
+            return;
+        }
+        
+        eism->ei.refcount = 1;
+        eism->ei.generation = erv0->gen;
+        eism->ei.flags = EXTENT_ITEM_TREE_BLOCK;
+        eism->type = TYPE_TREE_BLOCK_REF;
+        eism->tbr.offset = t->header.tree_id;
+        
+        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 {
+        EXTENT_ITEM_TREE2* eit2 = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_TREE2), ALLOC_TAG);
+        
+        if (!eit2) {
+            ERR("out of memory\n");
+            free_traverse_ptr(&tp2);
+            free_traverse_ptr(&tp);
+            return;
+        }
+        
+        eit2->eit.extent_item.refcount = 1;
+        eit2->eit.extent_item.generation = erv0->gen;
+        eit2->eit.extent_item.flags = EXTENT_ITEM_TREE_BLOCK;
+        eit2->eit.firstitem = td->key;
+        eit2->eit.level = t->header.level - 1;
+        eit2->type = TYPE_TREE_BLOCK_REF;
+        eit2->tbr.offset = t->header.tree_id;
+
+        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) {
+    KEY searchkey;
+    traverse_ptr tp;
+    EXTENT_ITEM* ei;
+    EXTENT_ITEM_V0* eiv0;
+    chunk* c;
+    NTSTATUS Status;
+    
+    // FIXME - deal with refcounts > 1
+    
+    TRACE("(%p, %llx, %p)\n", Vcb, address, t);
+    
+    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {
+        if (reduce_tree_extent_skinny(Vcb, address, t, rollback)) {
+            return STATUS_SUCCESS;
+        }
+    }
+    
+    searchkey.obj_id = address;
+    searchkey.obj_type = TYPE_EXTENT_ITEM;
+    searchkey.offset = Vcb->superblock.node_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)) {
+        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(EXTENT_ITEM_V0)) {
+        eiv0 = (EXTENT_ITEM_V0*)tp.item->data;
+        
+        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;
+        }
+        
+        ei = (EXTENT_ITEM*)tp.item->data;
+        
+        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) {
+            // convert shared data extents
+            
+            LIST_ENTRY* le = t->itemlist.Flink;
+            while (le != &t->itemlist) {
+                tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+                
+                TRACE("%llx,%x,%llx\n", td->key.obj_id, td->key.obj_type, td->key.offset);
+                
+                if (!td->ignore && !td->inserted) {
+                    if (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 != 0) {
+                                TRACE("trying to convert shared data extent %llx,%llx\n", ed2->address, ed2->size);
+                                convert_shared_data_extent(Vcb, ed2->address, ed2->size, rollback);
+                            }
+                        }
+                    }
+                }
+    
+                le = le->Flink;
+            }
+            
+            t->header.flags &= ~HEADER_FLAG_SHARED_BACKREF;
+        }
+    }
+    
+    delete_tree_item(Vcb, &tp, rollback);
+    
+    // if EXTENT_ITEM_V0, delete corresponding B4 item
+    if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {
+        traverse_ptr tp2;
+        
+        searchkey.obj_id = address;
+        searchkey.obj_type = TYPE_EXTENT_REF_V0;
+        searchkey.offset = 0xffffffffffffffff;
+        
+        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)) {
+        LIST_ENTRY* le;
+        
+        // when writing old internal trees, convert related extents
+        
+        le = t->itemlist.Flink;
+        while (le != &t->itemlist) {
+            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+            
+//             ERR("%llx,%x,%llx\n", td->key.obj_id, td->key.obj_type, td->key.offset);
+            
+            if (!td->ignore && !td->inserted) {
+                if (t->header.level > 0) {
+                    convert_old_tree_extent(Vcb, td, t, rollback);
+                } else if (td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA)) {
+                    EXTENT_DATA* ed = (EXTENT_DATA*)td->data;
+                    
+                    if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
+                        
+                        if (ed2->address != 0) {
+                            TRACE("trying to convert old data extent %llx,%llx\n", ed2->address, ed2->size);
+                            convert_old_data_extent(Vcb, ed2->address, ed2->size, rollback);
+                        }
+                    }
+                }
+            }
+
+            le = le->Flink;
+        }
+    }
+
+    c = get_chunk_from_address(Vcb, address);
+    
+    if (c) {
+        decrease_chunk_usage(c, tp.item->key.offset);
+        
+        add_to_space_list(c, address, tp.item->key.offset, SPACE_TYPE_DELETING);
+    } else
+        ERR("could not find chunk for address %llx\n", address);
+    
+    free_traverse_ptr(&tp);
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS allocate_tree_extents(device_extension* Vcb, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le;
+    NTSTATUS Status;
+    
+    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->new_address == 0) {
+            chunk* c;
+            
+            Status = get_tree_new_address(Vcb, tc2->tree, 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);
+            
+            if (tc2->tree->header.address != 0) {
+                Status = reduce_tree_extent(Vcb, tc2->tree->header.address, tc2->tree, rollback);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("reduce_tree_extent returned %08x\n", Status);
+                    return Status;
+                }
+            }
+
+            c = get_chunk_from_address(Vcb, tc2->tree->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);
+                return STATUS_INTERNAL_ERROR;
+            }
+        }
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS update_root_root(device_extension* Vcb, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le;
+    NTSTATUS Status;
+    
+    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 && tc2->tree->root != Vcb->chunk_root) {
+                KEY searchkey;
+                traverse_ptr tp;
+                
+                searchkey.obj_id = tc2->tree->root->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);
+                    return Status;
+                }
+                
+                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);
+                
+                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;
+                
+                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);
+                    
+                    if (!ri) {
+                        ERR("out of memory\n");
+                        return STATUS_INSUFFICIENT_RESOURCES;
+                    }
+                    
+                    RtlCopyMemory(ri, &tc2->tree->root->root_item, sizeof(ROOT_ITEM));
+                    
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                } else
+                    RtlCopyMemory(tp.item->data, &tc2->tree->root->root_item, sizeof(ROOT_ITEM));
+                
+                free_traverse_ptr(&tp);
+            }
+            
+            tc2->tree->root->treeholder.address = tc2->tree->new_address;
+        }
+        
+        le = le->Flink;
+    }
+    
+    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;
+    LIST_ENTRY* le;
+    BOOL complete;
+    
+    if (stripe->status == WriteTreeStatus_Cancelling) {
+        stripe->status = WriteTreeStatus_Cancelled;
+        goto end;
+    }
+    
+    stripe->iosb = Irp->IoStatus;
+    
+    if (NT_SUCCESS(Irp->IoStatus.Status)) {
+        stripe->status = WriteTreeStatus_Success;
+    } else {
+        le = context->stripes.Flink;
+        
+        stripe->status = WriteTreeStatus_Error;
+        
+        while (le != &context->stripes) {
+            write_tree_stripe* s2 = CONTAINING_RECORD(le, write_tree_stripe, list_entry);
+            
+            if (s2->status == WriteTreeStatus_Pending) {
+                s2->status = WriteTreeStatus_Cancelling;
+                IoCancelIrp(s2->Irp);
+            }
+            
+            le = le->Flink;
+        }
+    }
+    
+end:
+    le = context->stripes.Flink;
+    complete = TRUE;
+        
+    while (le != &context->stripes) {
+        write_tree_stripe* s2 = CONTAINING_RECORD(le, write_tree_stripe, list_entry);
+        
+        if (s2->status == WriteTreeStatus_Pending || s2->status == WriteTreeStatus_Cancelling) {
+            complete = FALSE;
+            break;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (complete)
+        KeSetEvent(&context->Event, 0, FALSE);
+
+    return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS write_tree(device_extension* Vcb, UINT64 addr, UINT8* data, write_tree_context* wtc) {
+    chunk* c;
+    CHUNK_ITEM_STRIPE* cis;
+    write_tree_stripe* stripe;
+    UINT64 i;
+    
+    c = get_chunk_from_address(Vcb, addr);
+    
+    if (!c) {
+        ERR("get_chunk_from_address failed\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
+    
+    // FIXME - make this work with RAID
+    
+    for (i = 0; i < c->chunk_item->num_stripes; i++) {
+        PIO_STACK_LOCATION IrpSp;
+        
+        // FIXME - handle missing devices
+        
+        stripe = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_tree_stripe), ALLOC_TAG);
+        if (!stripe) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        
+        stripe->context = (struct write_tree_context*)wtc;
+        stripe->buf = data;
+        stripe->device = c->devices[i];
+        RtlZeroMemory(&stripe->iosb, sizeof(IO_STATUS_BLOCK));
+        stripe->status = WriteTreeStatus_Pending;
+        
+        stripe->Irp = IoAllocateIrp(stripe->device->devobj->StackSize, FALSE);
+    
+        if (!stripe->Irp) {
+            ERR("IoAllocateIrp failed\n");
+            return STATUS_INTERNAL_ERROR;
+        }
+        
+        IrpSp = IoGetNextIrpStackLocation(stripe->Irp);
+        IrpSp->MajorFunction = IRP_MJ_WRITE;
+        
+        if (stripe->device->devobj->Flags & DO_BUFFERED_IO) {
+            stripe->Irp->AssociatedIrp.SystemBuffer = data;
+
+            stripe->Irp->Flags = IRP_BUFFERED_IO;
+        } else if (stripe->device->devobj->Flags & DO_DIRECT_IO) {
+            stripe->Irp->MdlAddress = IoAllocateMdl(data, Vcb->superblock.node_size, FALSE, FALSE, NULL);
+            if (!stripe->Irp->MdlAddress) {
+                ERR("IoAllocateMdl failed\n");
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            MmProbeAndLockPages(stripe->Irp->MdlAddress, KernelMode, IoWriteAccess);
+        } else {
+            stripe->Irp->UserBuffer = data;
+        }
+
+        IrpSp->Parameters.Write.Length = Vcb->superblock.node_size;
+        IrpSp->Parameters.Write.ByteOffset.QuadPart = addr - c->offset + cis[i].offset;
+        
+        stripe->Irp->UserIosb = &stripe->iosb;
+
+        IoSetCompletionRoutine(stripe->Irp, write_tree_completion, stripe, TRUE, TRUE, TRUE);
+
+        InsertTailList(&wtc->stripes, &stripe->list_entry);
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static void free_stripes(write_tree_context* wtc) {
+    LIST_ENTRY *le, *le2, *nextle;
+    
+    le = wtc->stripes.Flink;
+    while (le != &wtc->stripes) {
+        write_tree_stripe* stripe = CONTAINING_RECORD(le, write_tree_stripe, list_entry);
+        
+        if (stripe->device->devobj->Flags & DO_DIRECT_IO) {
+            MmUnlockPages(stripe->Irp->MdlAddress);
+            IoFreeMdl(stripe->Irp->MdlAddress);
+        }
+        
+        le = le->Flink;
+    }
+    
+    le = wtc->stripes.Flink;
+    while (le != &wtc->stripes) {
+        write_tree_stripe* stripe = CONTAINING_RECORD(le, write_tree_stripe, list_entry);
+        
+        nextle = le->Flink;
+
+        if (stripe->buf) {
+            ExFreePool(stripe->buf);
+            
+            le2 = le->Flink;
+            while (le2 != &wtc->stripes) {
+                write_tree_stripe* s2 = CONTAINING_RECORD(le2, write_tree_stripe, list_entry);
+                
+                if (s2->buf == stripe->buf)
+                    s2->buf = NULL;
+                
+                le2 = le2->Flink;
+            }
+            
+        }
+        
+        ExFreePool(stripe);
+        
+        le = nextle;
+    }
+}
+
+static NTSTATUS write_trees(device_extension* Vcb) {
+    UINT8 level;
+    UINT8 *data, *body;
+    UINT32 crc32;
+    NTSTATUS Status;
+    LIST_ENTRY* le;
+    write_tree_context* wtc;
+    
+    TRACE("(%p)\n", Vcb);
+    
+    for (level = 0; level <= 255; level++) {
+        BOOL nothing_found = TRUE;
+        
+        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);
+            
+            if (tc2->write && tc2->tree->header.level == level) {
+                KEY firstitem, searchkey;
+                LIST_ENTRY* le2;
+                traverse_ptr tp;
+                EXTENT_ITEM_TREE* eit;
+                
+                if (tc2->tree->new_address == 0) {
+                    ERR("error - tried to write tree with no new address\n");
+                    int3;
+                }
+                
+                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;
+                        break;
+                    }
+                    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 (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {
+                    searchkey.obj_id = tc2->tree->new_address;
+                    searchkey.obj_type = TYPE_EXTENT_ITEM;
+                    searchkey.offset = Vcb->superblock.node_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(&searchkey, &tp.item->key)) {
+//                         traverse_ptr next_tp;
+//                         BOOL b;
+//                         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;
+//                         searchkey.offset = 0;
+//                         
+//                         find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE);
+//                         
+//                         paritem = NULL;
+//                         do {
+//                             if (tp.tree->paritem != paritem) {
+//                                 paritem = tp.tree->paritem;
+//                                 ERR("paritem: %llx,%x,%llx\n", paritem->key.obj_id, paritem->key.obj_type, paritem->key.offset);
+//                             }
+//                             
+//                             ERR("%llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+//                             
+//                             b = find_next_item(Vcb, &tp, &next_tp, NULL, FALSE);
+//                             if (b) {
+//                                 free_traverse_ptr(&tp);
+//                                 tp = next_tp;
+//                             }
+//                         } while (b);
+//                         
+//                         free_traverse_ptr(&tp);
+                        
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                    
+                    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;
+            }
+            
+            le = le->Flink;
+        }
+        
+        if (nothing_found)
+            break;
+    }
+    
+    TRACE("allocated tree extents\n");
+    
+    wtc = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_tree_context), ALLOC_TAG);
+    if (!wtc) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    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);
+#ifdef DEBUG_PARANOID
+        UINT32 num_items = 0, size = 0;
+        LIST_ENTRY* le2;
+        BOOL crash = FALSE;
+#endif
+
+        if (tc2->write) {
+#ifdef DEBUG_PARANOID
+            le2 = tc2->tree->itemlist.Flink;
+            while (le2 != &tc2->tree->itemlist) {
+                tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
+                if (!td->ignore) {
+                    num_items++;
+                    
+                    if (tc2->tree->header.level == 0)
+                        size += td->size;
+                }
+                le2 = le2->Flink;
+            }
+            
+            if (tc2->tree->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);
+                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);
+                crash = TRUE;
+            }
+            
+            if (tc2->tree->new_address == 0) {
+                ERR("tree %llx, level %x: tried to write tree to address 0\n", tc2->tree->root->id, tc2->tree->header.level);
+                crash = TRUE;
+            }
+            
+            if (tc2->tree->header.num_items == 0) {
+                ERR("tree %llx, level %x: tried to write empty tree\n", tc2->tree->root->id, tc2->tree->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));
+                crash = TRUE;
+            }
+            
+            if (crash) {
+                ERR("tree %p\n", tc2->tree);
+                le2 = tc2->tree->itemlist.Flink;
+                while (le2 != &tc2->tree->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);
+                    }
+                    le2 = le2->Flink;
+                }
+                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;
+            
+            data = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);
+            if (!data) {
+                ERR("out of memory\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+            
+            body = data + sizeof(tree_header);
+            
+            RtlCopyMemory(data, &tc2->tree->header, sizeof(tree_header));
+            RtlZeroMemory(body, Vcb->superblock.node_size - sizeof(tree_header));
+            
+            if (tc2->tree->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) {
+                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
+                    if (!td->ignore) {
+                        dataptr = dataptr - td->size;
+                        
+                        itemptr[i].key = td->key;
+                        itemptr[i].offset = (UINT8*)dataptr - (UINT8*)body;
+                        itemptr[i].size = td->size;
+                        i++;
+                        
+                        if (td->size > 0)
+                            RtlCopyMemory(dataptr, td->data, td->size);
+                    }
+                    
+                    le2 = le2->Flink;
+                }
+            } else {
+                internal_node* itemptr = (internal_node*)body;
+                int i = 0;
+                LIST_ENTRY* le2;
+                
+                le2 = tc2->tree->itemlist.Flink;
+                while (le2 != &tc2->tree->itemlist) {
+                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
+                    if (!td->ignore) {
+                        itemptr[i].key = td->key;
+                        itemptr[i].address = td->treeholder.address;
+                        itemptr[i].generation = td->treeholder.generation;
+                        i++;
+                    }
+                    
+                    le2 = le2->Flink;
+                }
+            }
+            
+            crc32 = calc_crc32c(0xffffffff, (UINT8*)&((tree_header*)data)->fs_uuid, Vcb->superblock.node_size - sizeof(((tree_header*)data)->csum));
+            crc32 = ~crc32;
+            *((UINT32*)data) = crc32;
+            TRACE("setting crc32 to %08x\n", crc32);
+            
+            Status = write_tree(Vcb, tc2->tree->new_address, data, wtc);
+            if (!NT_SUCCESS(Status)) {
+                ERR("write_tree returned %08x\n", Status);
+                goto end;
+            }
+        }
+
+        le = le->Flink;
+    }
+    
+    Status = STATUS_SUCCESS;
+    
+    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_stripes(wtc);
+    }
+    
+end:
+    ExFreePool(wtc);
+    
+    return Status;
+}
+
+static NTSTATUS write_superblocks(device_extension* Vcb) {
+    UINT64 i;
+    NTSTATUS Status;
+    LIST_ENTRY* le;
+    
+    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 = le->Flink;
+    }
+    
+    for (i = 0; i < Vcb->superblock.num_devices; i++) {
+        if (Vcb->devices[i].devobj) {
+            Status = write_superblock(Vcb, &Vcb->devices[i]);
+            if (!NT_SUCCESS(Status)) {
+                ERR("write_superblock returned %08x\n", Status);
+                return Status;
+            }
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS update_chunk_usage(device_extension* Vcb, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le = Vcb->chunks.Flink;
+    chunk* c;
+    KEY searchkey;
+    traverse_ptr tp;
+    BLOCK_GROUP_ITEM* bgi;
+    NTSTATUS Status;
+    
+    TRACE("(%p)\n", Vcb);
+    
+    while (le != &Vcb->chunks) {
+        c = CONTAINING_RECORD(le, chunk, list_entry);
+        
+        if (c->used != c->oldused) {
+            searchkey.obj_id = c->offset;
+            searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;
+            searchkey.offset = c->chunk_item->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(&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;
+            }
+    
+            RtlCopyMemory(bgi, tp.item->data, tp.item->size);
+            bgi->used = c->used;
+            
+            TRACE("adjusting usage of chunk %llx to %llx\n", c->offset, c->used);
+            
+            delete_tree_item(Vcb, &tp, rollback);
+            
+            if (!insert_tree_item(Vcb, Vcb->extent_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, bgi, tp.item->size, NULL, rollback)) {
+                ERR("insert_tree_item failed\n");
+                ExFreePool(bgi);
+                return STATUS_INTERNAL_ERROR;
+            }
+            
+            TRACE("bytes_used = %llx\n", Vcb->superblock.bytes_used);
+            TRACE("chunk_item type = %llx\n", c->chunk_item->type);
+            
+            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;
+        }
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static void get_first_item(tree* t, KEY* key) {
+    LIST_ENTRY* le;
+    
+    le = t->itemlist.Flink;
+    while (le != &t->itemlist) {
+        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+
+        *key = td->key;
+        return;
+    }
+}
+
+static NTSTATUS STDCALL split_tree_at(device_extension* Vcb, tree* t, tree_data* newfirstitem, UINT32 numitems, UINT32 size) {
+    tree *nt, *pt;
+    tree_data* td;
+    tree_data* oldlastitem;
+//     write_tree* wt2;
+// //     tree_data *firsttd, *lasttd;
+// //     LIST_ENTRY* le;
+// #ifdef DEBUG_PARANOID
+//     KEY lastkey1, lastkey2;
+//     traverse_ptr tp, next_tp;
+//     ULONG numitems1, numitems2;
+// #endif
+    
+    TRACE("splitting tree in %llx at (%llx,%x,%llx)\n", t->root->id, newfirstitem->key.obj_id, newfirstitem->key.obj_type, newfirstitem->key.offset);
+    
+// #ifdef DEBUG_PARANOID
+//     lastkey1.obj_id = 0xffffffffffffffff;
+//     lastkey1.obj_type = 0xff;
+//     lastkey1.offset = 0xffffffffffffffff;
+//     
+//     if (!find_item(Vcb, t->root, &tp, &lastkey1, NULL, FALSE))
+//         ERR("error - find_item failed\n");
+//     else {
+//         lastkey1 = tp.item->key;
+//         numitems1 = 0;
+//         while (find_prev_item(Vcb, &tp, &next_tp, NULL, FALSE)) {
+//             free_traverse_ptr(&tp);
+//             tp = next_tp;
+//             numitems1++;
+//         }
+//         free_traverse_ptr(&tp);
+//     }
+// #endif
+    
+    nt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
+    if (!nt) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(&nt->header, &t->header, sizeof(tree_header));
+    nt->header.address = 0;
+    nt->header.generation = Vcb->superblock.generation;
+    nt->header.num_items = t->header.num_items - numitems;
+    nt->header.flags = HEADER_FLAG_MIXED_BACKREF;
+    
+    nt->refcount = 0;
+    nt->Vcb = Vcb;
+    nt->parent = t->parent;
+    nt->root = t->root;
+//     nt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);
+    nt->new_address = 0;
+    nt->flags = t->flags;
+    InitializeListHead(&nt->itemlist);
+    
+//     ExInitializeResourceLite(&nt->nonpaged->load_tree_lock);
+    
+    oldlastitem = CONTAINING_RECORD(newfirstitem->list_entry.Blink, tree_data, list_entry);
+
+// //     firsttd = CONTAINING_RECORD(wt->tree->itemlist.Flink, tree_data, list_entry);
+// //     lasttd = CONTAINING_RECORD(wt->tree->itemlist.Blink, tree_data, list_entry);
+// //     
+// //     TRACE("old tree in %x was from (%x,%x,%x) to (%x,%x,%x)\n",
+// //                   (UINT32)wt->tree->root->id, (UINT32)firsttd->key.obj_id, firsttd->key.obj_type, (UINT32)firsttd->key.offset,
+// //                   (UINT32)lasttd->key.obj_id, lasttd->key.obj_type, (UINT32)lasttd->key.offset);
+// //     
+// //     le = wt->tree->itemlist.Flink;
+// //     while (le != &wt->tree->itemlist) {
+// //         td = CONTAINING_RECORD(le, tree_data, list_entry);
+// //         TRACE("old tree item was (%x,%x,%x)\n", (UINT32)td->key.obj_id, td->key.obj_type, (UINT32)td->key.offset);
+// //         le = le->Flink;
+// //     }
+    
+    nt->itemlist.Flink = &newfirstitem->list_entry;
+    nt->itemlist.Blink = t->itemlist.Blink;
+    nt->itemlist.Flink->Blink = &nt->itemlist;
+    nt->itemlist.Blink->Flink = &nt->itemlist;
+    
+    t->itemlist.Blink = &oldlastitem->list_entry;
+    t->itemlist.Blink->Flink = &t->itemlist;
+    
+// //     le = wt->tree->itemlist.Flink;
+// //     while (le != &wt->tree->itemlist) {
+// //         td = CONTAINING_RECORD(le, tree_data, list_entry);
+// //         TRACE("old tree item now (%x,%x,%x)\n", (UINT32)td->key.obj_id, td->key.obj_type, (UINT32)td->key.offset);
+// //         le = le->Flink;
+// //     }
+// //     
+// //     firsttd = CONTAINING_RECORD(wt->tree->itemlist.Flink, tree_data, list_entry);
+// //     lasttd = CONTAINING_RECORD(wt->tree->itemlist.Blink, tree_data, list_entry);
+// //     
+// //     TRACE("old tree in %x is now from (%x,%x,%x) to (%x,%x,%x)\n",
+// //                   (UINT32)wt->tree->root->id, (UINT32)firsttd->key.obj_id, firsttd->key.obj_type, (UINT32)firsttd->key.offset,
+// //                   (UINT32)lasttd->key.obj_id, lasttd->key.obj_type, (UINT32)lasttd->key.offset);
+    
+    nt->size = t->size - size;
+    t->size = size;
+    t->header.num_items = numitems;
+    add_to_tree_cache(Vcb, nt, TRUE);
+    
+    InterlockedIncrement(&Vcb->open_trees);
+#ifdef DEBUG_TREE_REFCOUNTS
+    TRACE("created new split tree %p\n", nt);
+#endif
+    InsertTailList(&Vcb->trees, &nt->list_entry);
+    
+// //     // TESTING
+// //     td = wt->tree->items;
+// //     while (td) {
+// //         if (!td->ignore) {
+// //             TRACE("old tree item: (%x,%x,%x)\n", (UINT32)td->key.obj_id, td->key.obj_type, (UINT32)td->key.offset);
+// //         }
+// //         td = td->next;
+// //     }
+    
+// //     oldlastitem->next = NULL;
+// //     wt->tree->lastitem = oldlastitem;
+    
+// //     TRACE("last item is now (%x,%x,%x)\n", (UINT32)oldlastitem->key.obj_id, oldlastitem->key.obj_type, (UINT32)oldlastitem->key.offset);
+    
+    if (nt->parent) {
+        increase_tree_rc(nt->parent);
+        
+        td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
+        if (!td) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+    
+        td->key = newfirstitem->key;
+        
+        InsertAfter(&t->itemlist, &td->list_entry, &t->paritem->list_entry);
+        
+        td->ignore = FALSE;
+        td->inserted = TRUE;
+        td->treeholder.tree = nt;
+        init_tree_holder(&td->treeholder);
+//         td->treeholder.nonpaged->status = tree_holder_loaded;
+        nt->paritem = td;
+        
+        nt->parent->header.num_items++;
+        nt->parent->size += sizeof(internal_node);
+
+        goto end;
+    }
+    
+    TRACE("adding new tree parent\n");
+    
+    if (nt->header.level == 255) {
+        ERR("cannot add parent to tree at level 255\n");
+        return STATUS_INTERNAL_ERROR;
+    }
+    
+    pt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
+    if (!pt) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    RtlCopyMemory(&pt->header, &nt->header, sizeof(tree_header));
+    pt->header.address = 0;
+    pt->header.num_items = 2;
+    pt->header.level = nt->header.level + 1;
+    pt->header.flags = HEADER_FLAG_MIXED_BACKREF;
+    
+    pt->refcount = 2;
+    pt->Vcb = Vcb;
+    pt->parent = NULL;
+    pt->paritem = NULL;
+    pt->root = t->root;
+    pt->new_address = 0;
+//     pt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);
+    pt->size = pt->header.num_items * sizeof(internal_node);
+    pt->flags = t->flags;
+    InitializeListHead(&pt->itemlist);
+    
+//     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);
+    if (!td) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    get_first_item(t, &td->key);
+    td->ignore = FALSE;
+    td->inserted = FALSE;
+    td->treeholder.address = 0;
+    td->treeholder.generation = Vcb->superblock.generation;
+    td->treeholder.tree = t;
+    init_tree_holder(&td->treeholder);
+//     td->treeholder.nonpaged->status = tree_holder_loaded;
+    InsertTailList(&pt->itemlist, &td->list_entry);
+    t->paritem = td;
+    
+    td = ExAllocatePoolWithTag(PagedPool, sizeof(tree_data), ALLOC_TAG);
+    if (!td) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    
+    td->key = newfirstitem->key;
+    td->ignore = FALSE;
+    td->inserted = FALSE;
+    td->treeholder.address = 0;
+    td->treeholder.generation = Vcb->superblock.generation;
+    td->treeholder.tree = nt;
+    init_tree_holder(&td->treeholder);
+//     td->treeholder.nonpaged->status = tree_holder_loaded;
+    InsertTailList(&pt->itemlist, &td->list_entry);
+    nt->paritem = td;
+    
+    add_to_tree_cache(Vcb, pt, TRUE);
+
+    t->root->treeholder.tree = pt;
+    
+    t->parent = pt;
+    nt->parent = pt;
+    
+end:
+
+// #ifdef DEBUG_PARANOID
+//     lastkey2.obj_id = 0xffffffffffffffff;
+//     lastkey2.obj_type = 0xff;
+//     lastkey2.offset = 0xffffffffffffffff;
+//     
+//     if (!find_item(Vcb, wt->tree->root, &tp, &lastkey2, NULL, FALSE))
+//         ERR("error - find_item failed\n");
+//     else {    
+//         lastkey2 = tp.item->key;
+//         
+//         numitems2 = 0;
+//         while (find_prev_item(Vcb, &tp, &next_tp, NULL, FALSE)) {
+//             free_traverse_ptr(&tp);
+//             tp = next_tp;
+//             numitems2++;
+//         }
+//         free_traverse_ptr(&tp);
+//     }
+//     
+//     ERR("lastkey1 = %llx,%x,%llx\n", lastkey1.obj_id, lastkey1.obj_type, lastkey1.offset);
+//     ERR("lastkey2 = %llx,%x,%llx\n", lastkey2.obj_id, lastkey2.obj_type, lastkey2.offset);
+//     ERR("numitems1 = %u\n", numitems1);
+//     ERR("numitems2 = %u\n", numitems2);
+// #endif
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL split_tree(device_extension* Vcb, tree* t) {
+    LIST_ENTRY* le;
+    UINT32 size, ds, numitems;
+    
+    size = 0;
+    numitems = 0;
+    
+    // FIXME - naïve implementation: maximizes number of filled trees
+    
+    le = t->itemlist.Flink;
+    while (le != &t->itemlist) {
+        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+        
+        if (!td->ignore) {
+            if (t->header.level == 0)
+                ds = sizeof(leaf_node) + td->size;
+            else
+                ds = sizeof(internal_node);
+            
+            // FIXME - move back if previous item was deleted item with same key
+            if (size + ds > Vcb->superblock.node_size - sizeof(tree_header))
+                return split_tree_at(Vcb, t, td, numitems, size);
+
+            size += ds;
+            numitems++;
+        }
+        
+        le = le->Flink;
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, LIST_ENTRY* rollback) {
+    LIST_ENTRY* le;
+    tree_data* nextparitem = NULL;
+    NTSTATUS Status;
+    tree *next_tree, *par;
+    BOOL loaded;
+    
+    TRACE("trying to amalgamate tree in root %llx, level %x (size %u)\n", t->root->id, t->header.level, t->size);
+    
+    // FIXME - doesn't capture everything, as it doesn't ascend
+    // FIXME - write proper function and put it in treefuncs.c
+    le = t->paritem->list_entry.Flink;
+    while (le != &t->parent->itemlist) {
+        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+        
+        if (!td->ignore) {
+            nextparitem = td;
+            break;
+        }
+        
+        le = le->Flink;
+    }
+    
+    if (!nextparitem)
+        return STATUS_SUCCESS;
+    
+    // FIXME - loop, and capture more than one tree if we can
+    
+    TRACE("nextparitem: key = %llx,%x,%llx\n", nextparitem->key.obj_id, nextparitem->key.obj_type, nextparitem->key.offset);
+//     nextparitem = t->paritem;
+    
+//     ExAcquireResourceExclusiveLite(&t->parent->nonpaged->load_tree_lock, TRUE);
+    
+    Status = do_load_tree(Vcb, &nextparitem->treeholder, t->root, t->parent, nextparitem, &loaded);
+    if (!NT_SUCCESS(Status)) {
+        ERR("do_load_tree returned %08x\n", Status);
+        return Status;
+    }
+    
+    if (loaded)
+        increase_tree_rc(t->parent);
+        
+//     ExReleaseResourceLite(&t->parent->nonpaged->load_tree_lock);
+    
+    next_tree = nextparitem->treeholder.tree;
+    
+    if (t->size + next_tree->size <= Vcb->superblock.node_size - sizeof(tree_header)) {
+        // merge two trees into one
+        
+        t->header.num_items += next_tree->header.num_items;
+        t->size += next_tree->size;
+        
+        t->itemlist.Blink->Flink = next_tree->itemlist.Flink;
+        t->itemlist.Blink->Flink->Blink = t->itemlist.Blink;
+        t->itemlist.Blink = next_tree->itemlist.Blink;
+        t->itemlist.Blink->Flink = &t->itemlist;
+        
+//         // TESTING
+//         le = t->itemlist.Flink;
+//         while (le != &t->itemlist) {
+//             tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+//             if (!td->ignore) {
+//                 ERR("key: %llx,%x,%llx\n", td->key.obj_id, td->key.obj_type, td->key.offset);
+//             }
+//             le = le->Flink;
+//         }
+        
+        next_tree->itemlist.Flink = next_tree->itemlist.Blink = &next_tree->itemlist;
+        
+        next_tree->header.num_items = 0;
+        next_tree->size = 0;
+        
+        if (next_tree->new_address != 0) { // delete associated EXTENT_ITEM
+            Status = reduce_tree_extent(Vcb, next_tree->new_address, next_tree, rollback);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("reduce_tree_extent returned %08x\n", Status);
+                free_tree(next_tree);
+                return Status;
+            }
+        } else if (next_tree->header.address != 0) {
+            Status = reduce_tree_extent(Vcb, next_tree->header.address, next_tree, rollback);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("reduce_tree_extent returned %08x\n", Status);
+                free_tree(next_tree);
+                return Status;
+            }
+        }
+        
+        if (!nextparitem->ignore) {
+            nextparitem->ignore = TRUE;
+            next_tree->parent->header.num_items--;
+            next_tree->parent->size -= sizeof(internal_node);
+        }
+        
+        par = next_tree->parent;
+        while (par) {
+            add_to_tree_cache(Vcb, par, TRUE);
+            par = par->parent;
+        }
+        
+        RemoveEntryList(&nextparitem->list_entry);
+        ExFreePool(next_tree->paritem);
+        next_tree->paritem = NULL;
+        
+        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;
+        KEY firstitem = {0, 0, 0};
+        
+        TRACE("attempting rebalance\n");
+        
+        le = next_tree->itemlist.Flink;
+        while (le != &next_tree->itemlist && t->size < avg_size && next_tree->header.num_items > 1) {
+            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+            ULONG size;
+            
+            if (!td->ignore) {
+                if (next_tree->header.level == 0)
+                    size = sizeof(leaf_node) + td->size;
+                else
+                    size = sizeof(internal_node);
+            } else
+                size = 0;
+            
+            if (t->size + size < Vcb->superblock.node_size - sizeof(tree_header)) {
+                RemoveEntryList(&td->list_entry);
+                InsertTailList(&t->itemlist, &td->list_entry);
+                
+                if (!td->ignore) {
+                    next_tree->size -= size;
+                    t->size += size;
+                    next_tree->header.num_items--;
+                    t->header.num_items++;
+                }
+            } else
+                break;
+            
+            le = next_tree->itemlist.Flink;
+        }
+        
+        le = next_tree->itemlist.Flink;
+        while (le != &next_tree->itemlist) {
+            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+            
+            if (!td->ignore) {
+                firstitem = td->key;
+                break;
+            }
+            
+            le = le->Flink;
+        }
+        
+//         ERR("firstitem = %llx,%x,%llx\n", firstitem.obj_id, firstitem.obj_type, firstitem.offset);
+        
+        // FIXME - once ascension is working, make this work with parent's parent, etc.
+        if (next_tree->paritem)
+            next_tree->paritem->key = firstitem;
+        
+        par = next_tree;
+        while (par) {
+            add_to_tree_cache(Vcb, par, TRUE);
+            par = par->parent;
+        }
+        
+        free_tree(next_tree);
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS STDCALL do_splits(device_extension* Vcb, LIST_ENTRY* rollback) {
+//     LIST_ENTRY *le, *le2;
+//     write_tree* wt;
+//     tree_data* td;
+    UINT8 level, max_level;
+    UINT32 min_size;
+    BOOL empty, done_deletions = FALSE;
+    NTSTATUS Status;
+    tree_cache* tc2;
+    
+    TRACE("(%p)\n", Vcb);
+    
+    max_level = 0;
+    
+    for (level = 0; level <= 255; level++) {
+        LIST_ENTRY *le, *nextle;
+        
+        empty = TRUE;
+        
+        TRACE("doing level %u\n", level);
+        
+        le = Vcb->tree_cache.Flink;
+    
+        while (le != &Vcb->tree_cache) {
+            tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
+            
+            nextle = le->Flink;
+            
+            if (tc2->write && tc2->tree->header.level == level) {
+                empty = FALSE;
+                
+                if (tc2->tree->header.num_items == 0) {
+                    LIST_ENTRY* le2;
+                    KEY firstitem = {0xcccccccccccccccc,0xcc,0xcccccccccccccccc};
+                    
+                    done_deletions = TRUE;
+        
+                    le2 = tc2->tree->itemlist.Flink;
+                    while (le2 != &tc2->tree->itemlist) {
+                        tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);
+                        firstitem = td->key;
+                        break;
+                    }
+                    
+                    ERR("deleting tree in root %llx (first item was %llx,%x,%llx)\n",
+                        tc2->tree->root->id, firstitem.obj_id, firstitem.obj_type, firstitem.offset);
+                    
+                    if (tc2->tree->new_address != 0) { // delete associated EXTENT_ITEM
+                        Status = reduce_tree_extent(Vcb, tc2->tree->new_address, tc2->tree, rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("reduce_tree_extent returned %08x\n", Status);
+                            return Status;
+                        }
+                    } else if (tc2->tree->header.address != 0) {
+                        Status = reduce_tree_extent(Vcb,tc2->tree->header.address, tc2->tree, rollback);
+                        
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("reduce_tree_extent returned %08x\n", Status);
+                            return Status;
+                        }
+                    }
+                    
+                    if (tc2->tree->parent) {
+                        if (!tc2->tree->paritem->ignore) {
+                            tc2->tree->paritem->ignore = TRUE;
+                            tc2->tree->parent->header.num_items--;
+                            tc2->tree->parent->size -= sizeof(internal_node);
+                        }
+                        
+                        RemoveEntryList(&tc2->tree->paritem->list_entry);
+                        ExFreePool(tc2->tree->paritem);
+                        tc2->tree->paritem = NULL;
+                        
+                        free_tree(tc2->tree);
+                        
+                        RemoveEntryList(le);
+                        ExFreePool(tc2);
+                    } else {
+                        FIXME("trying to delete top root, not sure what to do here\n"); // FIXME
+                        return STATUS_INTERNAL_ERROR;
+                    }
+                } 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);
+
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("split_tree returned %08x\n", Status);
+                            return Status;
+                        }
+                    }
+                }
+            }
+            
+            le = nextle;
+        }
+        
+        if (!empty) {
+            max_level = level;
+        } else {
+            TRACE("nothing found for level %u\n", level);
+            break;
+        }
+    }
+    
+    min_size = (Vcb->superblock.node_size - sizeof(tree_header)) / 2;
+    
+    for (level = 0; level <= max_level; level++) {
+        LIST_ENTRY* le;
+        
+        le = Vcb->tree_cache.Flink;
+    
+        while (le != &Vcb->tree_cache) {
+            tc2 = CONTAINING_RECORD(le, tree_cache, 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 (!NT_SUCCESS(Status)) {
+                    ERR("try_tree_amalgamate returned %08x\n", Status);
+                    return Status;
+                }
+            }
+            
+            le = le->Flink;
+        }
+    }
+    
+    // simplify trees if top tree only has one entry
+    
+    if (done_deletions) {
+        for (level = max_level; level > 0; level--) {
+            LIST_ENTRY *le, *nextle;
+            
+            le = Vcb->tree_cache.Flink;
+            while (le != &Vcb->tree_cache) {
+                nextle = le->Flink;
+                tc2 = CONTAINING_RECORD(le, tree_cache, 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;
+                        tree_data* td;
+                        tree* child_tree = NULL;
+
+                        while (le2 != &tc2->tree->itemlist) {
+                            td = CONTAINING_RECORD(le2, tree_data, list_entry);
+                            if (!td->ignore)
+                                break;
+                            le2 = le2->Flink;
+                        }
+                        
+                        ERR("deleting top-level tree in root %llx with one item\n", tc2->tree->root->id);
+                        
+                        if (tc2->tree->new_address != 0) { // delete associated EXTENT_ITEM
+                            Status = reduce_tree_extent(Vcb, tc2->tree->new_address, tc2->tree, rollback);
+                            
+                            if (!NT_SUCCESS(Status)) {
+                                ERR("reduce_tree_extent returned %08x\n", Status);
+                                return Status;
+                            }
+                        } else if (tc2->tree->header.address != 0) {
+                            Status = reduce_tree_extent(Vcb,tc2->tree->header.address, tc2->tree, rollback);
+                            
+                            if (!NT_SUCCESS(Status)) {
+                                ERR("reduce_tree_extent returned %08x\n", Status);
+                                return Status;
+                            }
+                        }
+                        
+                        if (!td->treeholder.tree) { // load first item if not already loaded
+                            KEY searchkey = {0,0,0};
+                            traverse_ptr tp;
+                            
+                            Status = find_item(Vcb, tc2->tree->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;
+                        
+                        if (child_tree) {
+                            child_tree->parent = NULL;
+                            free_tree(tc2->tree);
+                        }
+
+                        free_tree(tc2->tree);
+                        
+                        if (child_tree)
+                            child_tree->root->treeholder.tree = child_tree;
+                        
+                        RemoveEntryList(le);
+                        ExFreePool(tc2);
+                    }
+                }
+                
+                le = nextle;
+            }
+        }
+    }
+    
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS STDCALL do_write(device_extension* Vcb, LIST_ENTRY* rollback) {
+    NTSTATUS Status;
+    LIST_ENTRY* le;
+    
+    TRACE("(%p)\n", Vcb);
+    
+    // If only changing superblock, e.g. changing label, we still need to rewrite
+    // the root tree so the generations mach. Otherwise you won't be able to mount on Linux.
+    if (Vcb->write_trees > 0) {
+        KEY searchkey;
+        traverse_ptr tp;
+        
+        searchkey.obj_id = 0;
+        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;
+        }
+        
+        add_to_tree_cache(Vcb, Vcb->root_root->treeholder.tree, TRUE);
+        
+        free_traverse_ptr(&tp);
+    }
+    
+    do {
+        Status = add_parents(Vcb, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("add_parents returned %08x\n", Status);
+            goto end;
+        }
+        
+        Status = do_splits(Vcb, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("do_splits returned %08x\n", Status);
+            goto end;
+        }
+        
+        Status = allocate_tree_extents(Vcb, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("add_parents returned %08x\n", Status);
+            goto end;
+        }
+        
+        Status = update_chunk_usage(Vcb, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("update_chunk_usage returned %08x\n", Status);
+            goto end;
+        }
+    } while (!trees_consistent(Vcb));
+    
+    TRACE("trees consistent\n");
+    
+    Status = update_root_root(Vcb, rollback);
+    if (!NT_SUCCESS(Status)) {
+        ERR("update_root_root returned %08x\n", Status);
+        goto end;
+    }
+    
+    Status = write_trees(Vcb);
+    if (!NT_SUCCESS(Status)) {
+        ERR("write_trees returned %08x\n", Status);
+        goto end;
+    }
+    
+    Status = write_superblocks(Vcb);
+    if (!NT_SUCCESS(Status)) {
+        ERR("write_superblocks returned %08x\n", Status);
+        goto end;
+    }
+    
+    clean_space_cache(Vcb);
+    
+    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);
+        
+        tc2->write = FALSE;
+        
+        le = le->Flink;
+    }
+    
+    Vcb->write_trees = 0;
+    
+end:
+    TRACE("do_write returning %08x\n", Status);
+    
+    return Status;
+}
+
+NTSTATUS consider_write(device_extension* Vcb) {
+    // FIXME - call do_write if Vcb->write_trees high
+#if 0
+    LIST_ENTRY rollback;
+    NTSTATUS Status = STATUS_SUCCESS;
+    
+    InitializeListHead(&rollback);
+    
+    if (Vcb->write_trees > 0)
+        Status = do_write(Vcb, &rollback);
+    
+    free_tree_cache(&Vcb->tree_cache);
+    
+    if (!NT_SUCCESS(Status))
+        do_rollback(Vcb, &rollback);
+    else
+        clear_rollback(&rollback);
+    
+    return Status;
+#else
+    return STATUS_SUCCESS;
+#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);
+    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);
+        free_traverse_ptr(&tp);
+        
+        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);
+            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 (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);
+    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);
+        }
+        
+        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 (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);
+        
+        if (er->allocated)
+            ExFreePool(er->data);
+        
+        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;
+        }
+
+        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;
+        
+        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 (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;
+        
+        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);
+        
+        if (tp.item->key.offset < end_data && tp.item->key.offset + ed->decoded_size >= 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->encoding != BTRFS_ENCODING_NONE) {
+                WARN("other encodings not supported\n");
+                Status = STATUS_NOT_SUPPORTED;
+                goto end;
+            }
+            
+            // FIXME - is ed->decoded_size the size of the whole extent, or just this bit of it?
+            
+            if (ed->type == EXTENT_TYPE_INLINE) {
+                if (start_data <= tp.item->key.offset && end_data >= tp.item->key.offset + ed->decoded_size) { // remove all
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    fcb->inode_item.st_blocks -= ed->decoded_size;
+                } else if (start_data <= tp.item->key.offset && end_data < tp.item->key.offset + ed->decoded_size) { // remove beginning
+                    EXTENT_DATA* ned;
+                    UINT64 size;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    size = ed->decoded_size - (end_data - tp.item->key.offset);
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + size, ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = size;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    
+                    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)) {
+                        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;
+                } else if (start_data > tp.item->key.offset && end_data >= tp.item->key.offset + ed->decoded_size) { // remove end
+                    EXTENT_DATA* ned;
+                    UINT64 size;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    size = start_data - tp.item->key.offset;
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + size, ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = size;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto end;
+                    }
+                    
+                    fcb->inode_item.st_blocks -= tp.item->key.offset + ed->decoded_size - start_data;
+                } else if (start_data > tp.item->key.offset && end_data < tp.item->key.offset + ed->decoded_size) { // remove middle
+                    EXTENT_DATA* ned;
+                    UINT64 size;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    size = start_data - tp.item->key.offset;
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + size, ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = size;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto end;
+                    }
+                    
+                    size = tp.item->key.offset + ed->decoded_size - end_data;
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + size, ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = size;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto end;
+                    }
+                    
+                    fcb->inode_item.st_blocks -= end_data - start_data;
+                }
+            } else if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {
+                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0];
+                
+                if (start_data <= tp.item->key.offset && end_data >= tp.item->key.offset + ed->decoded_size) { // remove all
+                    if (ed2->address != 0) {
+                        Status = remove_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset, changed_sector_list, rollback);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("remove_extent_ref returned %08x\n", Status);
+                            goto end;
+                        }
+                        
+                        fcb->inode_item.st_blocks -= ed->decoded_size;
+                    }
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                } else if (start_data <= tp.item->key.offset && end_data < tp.item->key.offset + ed->decoded_size) { // remove beginning
+                    EXTENT_DATA* ned;
+                    EXTENT_DATA2* ned2;
+                    
+                    if (ed2->address != 0) {
+                        Status = add_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, end_data, rollback);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("add_extent_ref returned %08x\n", Status);
+                            goto end;
+                        }
+                        
+                        Status = remove_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset, changed_sector_list, rollback);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("remove_extent_ref returned %08x\n", Status);
+                            goto end;
+                        }
+                        
+                        fcb->inode_item.st_blocks -= end_data - tp.item->key.offset;
+                    }
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned2 = (EXTENT_DATA2*)&ned->data[0];
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = ed->decoded_size - (end_data - tp.item->key.offset);
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    ned2->address = ed2->address;
+                    ned2->size = ed2->size;
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto end;
+                    }
+                } else if (start_data > tp.item->key.offset && end_data >= tp.item->key.offset + ed->decoded_size) { // remove end
+                    EXTENT_DATA* ned;
+                    EXTENT_DATA2* ned2;
+                    
+                    if (ed2->address != 0)
+                        fcb->inode_item.st_blocks -= tp.item->key.offset + ed->decoded_size - start_data;
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned2 = (EXTENT_DATA2*)&ned->data[0];
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = start_data - tp.item->key.offset;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    ned2->address = ed2->address;
+                    ned2->size = ed2->size;
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto end;
+                    }
+                } else if (start_data > tp.item->key.offset && end_data < tp.item->key.offset + ed->decoded_size) { // remove middle
+                    EXTENT_DATA* ned;
+                    EXTENT_DATA2* ned2;
+                    
+                    if (ed2->address != 0) {
+                        Status = add_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, end_data, rollback);
+                        if (!NT_SUCCESS(Status)) {
+                            ERR("add_extent_ref returned %08x\n", Status);
+                            goto end;
+                        }
+                        
+                        fcb->inode_item.st_blocks -= end_data - start_data;
+                    }
+                    
+                    delete_tree_item(Vcb, &tp, rollback);
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned2 = (EXTENT_DATA2*)&ned->data[0];
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = start_data - tp.item->key.offset;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    ned2->address = ed2->address;
+                    ned2->size = ed2->size;
+                    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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto end;
+                    }
+                    
+                    ned = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);
+                    if (!ned) {
+                        ERR("out of memory\n");
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        goto end;
+                    }
+                    
+                    ned2 = (EXTENT_DATA2*)&ned->data[0];
+                    
+                    ned->generation = Vcb->superblock.generation;
+                    ned->decoded_size = tp.item->key.offset + ed->decoded_size - end_data;
+                    ned->compression = ed->compression;
+                    ned->encryption = ed->encryption;
+                    ned->encoding = ed->encoding;
+                    ned->type = ed->type;
+                    ned2->address = ed2->address;
+                    ned2->size = ed2->size;
+                    ned2->offset = ed2->address == 0 ? 0 : (ed2->offset + (end_data - tp.item->key.offset));
+                    ned2->num_bytes = tp.item->key.offset + ed->decoded_size - 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)) {
+                        ERR("insert_tree_item failed\n");
+                        ExFreePool(ned);
+                        Status = STATUS_INTERNAL_ERROR;
+                        goto 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 >= end_data)
+                break;
+        }
+    } while (b);
+    
+    // FIXME - do bitmap analysis of changed extents, and free what we can
+    
+    Status = STATUS_SUCCESS;
+    
+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 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;
+    
+    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;
+    }
+    
+    tp.tree->header.generation = eidr->ei.generation;
+    
+    free_traverse_ptr(&tp);
+    
+    Status = write_data(Vcb, address, data, length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("write_data returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (changed_sector_list) {
+        sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
+        if (!sc) {
+            ERR("out of memory\n");
+            return FALSE;
+        }
+        
+        sc->ol.key = address;
+        sc->length = length / Vcb->superblock.sector_size;
+        sc->deleted = FALSE;
+        
+        sc->checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * sc->length, ALLOC_TAG);
+        if (!sc->checksums) {
+            ERR("out of memory\n");
+            ExFreePool(sc);
+            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
+    ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);
+    if (!ed) {
+        ERR("out of memory\n");
+        return FALSE;
+    }
+    
+    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 = address;
+    ed2->size = length;
+    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)) {
+        ERR("insert_tree_item failed\n");
+        ExFreePool(ed);
+        return FALSE;
+    }
+    
+    increase_chunk_usage(c, length);
+    add_to_space_list(c, address, length, SPACE_TYPE_WRITING);
+    
+    fcb->inode_item.st_blocks += length;
+    
+    return TRUE;
+}
+
+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;
+    EXTENT_DATA2* ed2;
+    EXTENT_ITEM* ei;
+    NTSTATUS Status;
+    changed_sector* sc;
+    chunk* c;
+    int i;
+    
+    TRACE("(%p, (%llx, %llx), %llx, %llx, %p, %p, %p, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data,
+                                                                  length, data, changed_sector_list, edtp, eitp);
+    
+    ed = ExAllocatePoolWithTag(PagedPool, edtp->item->size, ALLOC_TAG);
+    if (!ed) {
+        ERR("out of memory\n");
+        return FALSE;
+    }
+    
+    RtlCopyMemory(ed, edtp->item->data, edtp->item->size);
+    
+    ed->decoded_size += length;
+    ed2 = (EXTENT_DATA2*)ed->data;
+    ed2->size += length;
+    ed2->num_bytes += length;
+    
+    delete_tree_item(Vcb, edtp, rollback);
+    
+    if (!insert_tree_item(Vcb, fcb->subvol, edtp->item->key.obj_id, edtp->item->key.obj_type, edtp->item->key.offset, ed, edtp->item->size, NULL, rollback)) {
+        TRACE("insert_tree_item failed\n");
+
+        ExFreePool(ed);
+        return FALSE;
+    }
+    
+    ei = ExAllocatePoolWithTag(PagedPool, eitp->item->size, ALLOC_TAG);
+    if (!ei) {
+        ERR("out of memory\n");
+        ExFreePool(ed);
+        return FALSE;
+    }
+    
+    RtlCopyMemory(ei, eitp->item->data, eitp->item->size);
+    
+    if (!insert_tree_item(Vcb, Vcb->extent_root, eitp->item->key.obj_id, eitp->item->key.obj_type, eitp->item->key.offset + length, ei, eitp->item->size, NULL, rollback)) {
+        ERR("insert_tree_item failed\n");
+
+        ExFreePool(ei);
+        return FALSE;
+    }
+    
+    delete_tree_item(Vcb, eitp, rollback);
+    
+    Status = write_data(Vcb, eitp->item->key.obj_id + eitp->item->key.offset, data, length);
+    if (!NT_SUCCESS(Status)) {
+        ERR("write_data returned %08x\n", Status);
+        return FALSE;
+    }
+    
+    if (changed_sector_list) {
+        sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
+        if (!sc) {
+            ERR("out of memory\n");
+            return FALSE;
+        }
+        
+        sc->ol.key = eitp->item->key.obj_id + eitp->item->key.offset;
+        sc->length = length / Vcb->superblock.sector_size;
+        sc->deleted = FALSE;
+        
+        sc->checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * sc->length, ALLOC_TAG);
+        if (!sc->checksums) {
+            ERR("out of memory\n");
+            ExFreePool(sc);
+            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);
+    }
+    
+    c = get_chunk_from_address(Vcb, eitp->item->key.obj_id);
+    
+    if (c) {
+        increase_chunk_usage(c, length);
+        
+        add_to_space_list(c, eitp->item->key.obj_id + eitp->item->key.offset, length, SPACE_TYPE_WRITING);
+    }
+    
+    fcb->inode_item.st_blocks += length;
+    
+    return TRUE;
+}
+
+static BOOL try_extend_data(device_extension* Vcb, fcb* fcb, UINT64 start_data, UINT64 length, void* data,
+                            LIST_ENTRY* changed_sector_list, LIST_ENTRY* rollback) {
+    KEY searchkey;
+    traverse_ptr tp, tp2;
+    BOOL success = FALSE;
+    EXTENT_DATA* ed;
+    EXTENT_DATA2* ed2;
+    EXTENT_ITEM* ei;
+    chunk* c;
+    LIST_ENTRY* le;
+    space* s;
+    NTSTATUS Status;
+    
+    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 FALSE;
+    }
+    
+    if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || tp.item->key.offset >= start_data) {
+        WARN("previous EXTENT_DATA not found\n");
+        goto end;
+    }
+    
+    ed = (EXTENT_DATA*)tp.item->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));
+        goto end;
+    }
+    
+    if (ed->type != EXTENT_TYPE_REGULAR) {
+        TRACE("not extending extent which is not EXTENT_TYPE_REGULAR\n");
+        goto end;
+    }
+
+    if (tp.item->key.offset + ed->decoded_size != start_data) {
+        TRACE("last EXTENT_DATA does not run up to start_data (%llx + %llx != %llx)\n", tp.item->key.offset, ed->decoded_size, start_data);
+        goto end;
+    }
+    
+    if (ed->compression != BTRFS_COMPRESSION_NONE) {
+        FIXME("FIXME: compression not yet supported\n");
+        goto end;
+    }
+    
+    if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
+        WARN("encryption not supported\n");
+        goto end;
+    }
+    
+    if (ed->encoding != BTRFS_ENCODING_NONE) {
+        WARN("other encodings not supported\n");
+        goto end;
+    }
+    
+    ed2 = (EXTENT_DATA2*)ed->data;
+    
+    if (tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
+        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_DATA) - 1 + sizeof(EXTENT_DATA2));
+        goto end;
+    }
+    
+    if (ed2->size - ed2->offset != ed->decoded_size) {
+        TRACE("last EXTENT_DATA does not run all the way to the end of the extent\n");
+        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 end2;
+    }
+    
+    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);
+        
+        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 end2;
+        }
+    }
+    
+    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 end2;
+    }
+    
+    // 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;
+        }
+        
+        if (keycmp(&tp2.item->key, &searchkey)) {
+            WARN("extent item not found for address %llx, size %llx\n", ed2->address, ed2->size);
+            goto end2;
+        }
+        
+        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 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);
+        
+        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;
+    }
+    
+end2:
+    free_traverse_ptr(&tp2);
+    
+end:
+    free_traverse_ptr(&tp);
+        
+    return success;
+}
+
+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;
+        
+        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))
+            ed = (EXTENT_DATA*)tp.item->data;
+        else
+            ed = NULL;
+        
+        if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || !ed || tp.item->key.offset + ed->decoded_size < 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 + ed->decoded_size,
+                                              start_data - tp.item->key.offset - ed->decoded_size, rollback);
+            }
+            if (!NT_SUCCESS(Status)) {
+                ERR("insert_sparse_extent returned %08x\n", Status);
+                free_traverse_ptr(&tp);
+                return Status;
+            }
+        }
+        
+        free_traverse_ptr(&tp);
+    }
+    
+    // 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 ((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;
+        }
+    }
+    
+    // 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 (!Vcb->checksum_root) {
+        ERR("no checksum root\n");
+        goto exit;
+    }
+    
+    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;
+            }
+            
+            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)) {
+                    free_traverse_ptr(&tp);
+                    tp = next_tp;
+                } else
+                    break;
+            }
+    //         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);
+            }
+            
+            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);
+            }
+            
+            ExFreePool(bmparr);
+            ExFreePool(checksums);
+        }
+        
+        le = le->Flink;
+    }
+    
+exit:
+    while (!IsListEmpty(changed_sector_list)) {
+        le = RemoveHeadList(changed_sector_list);
+        cs = (changed_sector*)le;
+        
+        if (cs->checksums)
+            ExFreePool(cs->checksums);
+        
+        ExFreePool(cs);
+    }
+}
+
+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;
+    
+    if (!nocsum)
+        InitializeListHead(&changed_sector_list);
+    
+    // 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;
+    }
+    
+    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;
+    
+    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;
+        
+        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) {
+            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;
+            }
+            
+            oldalloc = tp.item->key.offset + ((EXTENT_DATA*)tp.item->data)->decoded_size;
+            cur_inline = ((EXTENT_DATA*)tp.item->data)->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 = ((EXTENT_DATA*)tp.item->data)->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);
+                    free_traverse_ptr(&tp);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+                
+                if (length > origlength)
+                    RtlZeroMemory(data + origlength, length - origlength);
+                
+                RtlCopyMemory(data, ((EXTENT_DATA*)tp.item->data)->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);
+                    free_traverse_ptr(&tp);
+                    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) {
+                EXTENT_DATA* ed;
+                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");
+                        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;
+                    
+                    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);
+                        free_traverse_ptr(&tp);
+                        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) {
+                    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);
+                        free_traverse_ptr(&tp);
+                        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);
+            
+                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);
+                    free_traverse_ptr(&tp);
+                    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");
+                    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;
+                
+                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);
+                    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;
+            }
+        }
+        
+        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 (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 (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;
+    }
+    
+    ei = (EXTENT_ITEM*)tp.item->data;
+    rc = ei->refcount;
+    
+    free_traverse_ptr(&tp);
+    
+    return rc;
+}
+
+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) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    NTSTATUS Status;
+    EXTENT_DATA* ed;
+    BOOL b, do_cow;
+    EXTENT_DATA2* eds;
+    UINT64 size, new_start, new_end, last_write = 0;
+    
+    TRACE("(%p, (%llx, %llx), %llx, %llx, %p, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data, length, 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;
+    }
+    
+    if (tp.item->key.obj_id != fcb->inode || tp.item->key.obj_type != TYPE_EXTENT_DATA || tp.item->key.offset > start_data) {
+        ERR("previous EXTENT_DATA not found (found %llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
+        Status = STATUS_INTERNAL_ERROR;
+        goto end;
+    }
+    
+    do {
+        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;
+        }
+        
+        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));
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+        
+        eds = (EXTENT_DATA2*)&ed->data[0];
+        
+        b = find_next_item(Vcb, &tp, &next_tp, TRUE);
+        
+        switch (ed->type) {
+            case EXTENT_TYPE_REGULAR:
+            {
+                UINT64 rc = get_extent_item_refcount(Vcb, eds->address);
+                
+                if (rc == 0) {
+                    ERR("get_extent_item_refcount failed\n");
+                    Status = STATUS_INTERNAL_ERROR;
+                    goto end;
+                }
+                
+                do_cow = rc > 1;
+                break;
+            }
+                
+            case EXTENT_TYPE_INLINE:
+                do_cow = TRUE;
+                break;
+                
+            case EXTENT_TYPE_PREALLOC:
+                FIXME("FIXME - handle prealloc extents\n"); // FIXME
+                Status = STATUS_NOT_SUPPORTED;
+                goto end;
+                
+            default:
+                ERR("error - unknown extent type %x\n", ed->type);
+                Status = STATUS_NOT_SUPPORTED;
+                goto end;
+        }
+        
+        if (ed->compression != BTRFS_COMPRESSION_NONE) {
+            FIXME("FIXME: compression not yet supported\n");
+            Status = STATUS_NOT_SUPPORTED;
+            goto end;
+        }
+        
+        if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
+            WARN("encryption not supported\n");
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+        
+        if (ed->encoding != BTRFS_ENCODING_NONE) {
+            WARN("other encodings not supported\n");
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+        
+        size = ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : eds->num_bytes;
+        
+        TRACE("extent: start = %llx, length = %llx\n", tp.item->key.offset, size);
+        
+        new_start = tp.item->key.offset < start_data ? start_data : tp.item->key.offset;
+        new_end = tp.item->key.offset + size > start_data + length ? (start_data + length) : (tp.item->key.offset + size);
+        
+        TRACE("new_start = %llx\n", new_start);
+        TRACE("new_end = %llx\n", new_end);
+        
+        if (do_cow) {
+            TRACE("doing COW write\n");
+            
+            Status = excise_extents(Vcb, fcb, new_start, new_start + new_end, changed_sector_list, rollback);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - excise_extents returned %08x\n", Status);
+                goto end;
+            }
+            
+            Status = insert_extent(Vcb, fcb, new_start, new_end - new_start, (UINT8*)data + new_start - start_data, changed_sector_list, rollback);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - insert_extent returned %08x\n", Status);
+                goto end;
+            }
+        } else {
+            UINT64 writeaddr;
+            
+            writeaddr = eds->address + eds->offset + new_start - tp.item->key.offset;
+            TRACE("doing non-COW write to %llx\n", writeaddr);
+            
+            Status = write_data(Vcb, writeaddr, (UINT8*)data + new_start - start_data, new_end - new_start);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - write_data returned %08x\n", Status);
+                goto end;
+            }
+            
+            if (changed_sector_list) {
+                unsigned int i;
+                changed_sector* sc;
+                
+                sc = ExAllocatePoolWithTag(PagedPool, sizeof(changed_sector), ALLOC_TAG);
+                if (!sc) {
+                    ERR("out of memory\n");
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    goto end;
+                }
+                
+                sc->ol.key = writeaddr;
+                sc->length = (new_end - new_start) / Vcb->superblock.sector_size;
+                sc->deleted = FALSE;
+                
+                sc->checksums = ExAllocatePoolWithTag(PagedPool, sizeof(UINT32) * sc->length, ALLOC_TAG);
+                if (!sc->checksums) {
+                    ERR("out of memory\n");
+                    ExFreePool(sc);
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    goto end;
+                }
+                
+                for (i = 0; i < sc->length; i++) {
+                    sc->checksums[i] = ~calc_crc32c(0xffffffff, (UINT8*)data + new_start - start_data + (i * Vcb->superblock.sector_size), Vcb->superblock.sector_size);
+                }
+
+                insert_into_ordered_list(changed_sector_list, &sc->ol);
+            }
+        }
+        
+        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)
+                b = FALSE;
+        }
+    } while (b);
+    
+    if (last_write < start_data + length) {
+        new_start = last_write;
+        new_end = start_data + length;
+        
+        TRACE("new_start = %llx\n", new_start);
+        TRACE("new_end = %llx\n", new_end);
+        
+        Status = insert_extent(Vcb, fcb, new_start, new_end - new_start, (UINT8*)data + new_start - start_data, changed_sector_list, rollback);
+            
+        if (!NT_SUCCESS(Status)) {
+            ERR("error - insert_extent returned %08x\n", Status);
+            goto end;
+        }
+    }
+
+    Status = STATUS_SUCCESS;
+    
+end:
+    free_traverse_ptr(&tp);
+    
+    return Status;
+}
+
+#ifdef DEBUG_PARANOID
+static void print_loaded_trees(tree* t, int spaces) {
+    char pref[10];
+    int i;
+    LIST_ENTRY* le;
+    
+    for (i = 0; i < spaces; i++) {
+        pref[i] = ' ';
+    }
+    pref[spaces] = 0;
+    
+    if (!t) {
+        ERR("%s(not loaded)\n", pref);
+        return;
+    }
+    
+    le = t->itemlist.Flink;
+    while (le != &t->itemlist) {
+        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);
+        
+        ERR("%s%llx,%x,%llx ignore=%s\n", pref, td->key.obj_id, td->key.obj_type, td->key.offset, td->ignore ? "TRUE" : "FALSE");
+        
+        if (t->header.level > 0) {
+            print_loaded_trees(td->treeholder.tree, spaces+1);
+        }
+        
+        le = le->Flink;
+    }
+}
+
+static void check_extents_consistent(device_extension* Vcb, fcb* fcb) {
+    KEY searchkey;
+    traverse_ptr tp, next_tp;
+    UINT64 length, oldlength, lastoff, alloc;
+    NTSTATUS Status;
+    EXTENT_DATA* ed;
+    EXTENT_DATA2* ed2;
+    
+    if (fcb->ads || fcb->inode_item.st_size == 0 || fcb->deleted)
+        return;
+    
+    TRACE("inode = %llx, subvol = %llx\n", fcb->inode, fcb->subvol->id);
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_EXTENT_DATA;
+    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 failure2;
+    }
+    
+    if (keycmp(&searchkey, &tp.item->key)) {
+        ERR("could not find EXTENT_DATA at offset 0\n");
+        goto failure;
+    }
+    
+    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));
+        goto failure;
+    }
+    
+    ed = (EXTENT_DATA*)tp.item->data;
+    ed2 = (EXTENT_DATA2*)&ed->data[0];
+    
+    length = oldlength = ed->decoded_size;
+    lastoff = tp.item->key.offset;
+    
+    TRACE("(%llx,%x,%llx) length = %llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, length);
+    
+    alloc = 0;
+    if (ed->type != EXTENT_TYPE_REGULAR || ed2->address != 0) {
+        alloc += length;
+    }
+    
+    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);
+            break;
+        }
+        
+        free_traverse_ptr(&tp);
+        tp = next_tp;
+        
+        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));
+            goto failure;
+        }
+        
+        ed = (EXTENT_DATA*)tp.item->data;
+        ed2 = (EXTENT_DATA2*)&ed->data[0];
+    
+        length = ed->decoded_size;
+    
+        TRACE("(%llx,%x,%llx) length = %llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, length);
+        
+        if (tp.item->key.offset != lastoff + oldlength) {
+            ERR("EXTENT_DATA in %llx,%llx was at %llx, expected %llx\n", fcb->subvol->id, fcb->inode, tp.item->key.offset, lastoff + oldlength);
+            goto failure;
+        }
+        
+        if (ed->type != EXTENT_TYPE_REGULAR || ed2->address != 0) {
+            alloc += length;
+        }
+        
+        oldlength = length;
+        lastoff = tp.item->key.offset;
+    }
+    
+    if (alloc != fcb->inode_item.st_blocks) {
+        ERR("allocation size was %llx, expected %llx\n", alloc, fcb->inode_item.st_blocks);
+        goto failure;
+    }
+    
+//     if (fcb->inode_item.st_blocks != lastoff + oldlength) {
+//         ERR("extents finished at %x, expected %x\n", (UINT32)(lastoff + oldlength), (UINT32)fcb->inode_item.st_blocks);
+//         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);
+
+    int3;
+}
+
+// static void check_extent_tree_consistent(device_extension* Vcb) {
+//     KEY searchkey;
+//     traverse_ptr tp, next_tp;
+//     UINT64 lastaddr;
+//     BOOL b, inconsistency;
+//     
+//     searchkey.obj_id = 0;
+//     searchkey.obj_type = 0;
+//     searchkey.offset = 0;
+//     
+//     if (!find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE)) {
+//         ERR("error - could not find any entries in extent_root\n");
+//         int3;
+//     }
+//     
+//     lastaddr = 0;
+//     inconsistency = FALSE;
+//     
+//     do {
+//         if (tp.item->key.obj_type == TYPE_EXTENT_ITEM) {
+// //             ERR("%x,%x,%x\n", (UINT32)tp.item->key.obj_id, tp.item->key.obj_type, (UINT32)tp.item->key.offset);
+//             
+//             if (tp.item->key.obj_id < lastaddr) {
+// //                 ERR("inconsistency!\n");
+// //                 int3;
+//                 inconsistency = TRUE;
+//             }
+//             
+//             lastaddr = tp.item->key.obj_id + tp.item->key.offset;
+//         }
+//         
+//         b = find_next_item(Vcb, &tp, &next_tp, NULL, FALSE);
+//         if (b) {
+//             free_traverse_ptr(&tp);
+//             tp = next_tp;
+//         }
+//     } while (b);
+//     
+//     free_traverse_ptr(&tp);
+//     
+//     if (!inconsistency)
+//         return;
+//     
+//     ERR("Inconsistency detected:\n");
+//     
+//     if (!find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE)) {
+//         ERR("error - could not find any entries in extent_root\n");
+//         int3;
+//     }
+//     
+//     do {
+//         if (tp.item->key.obj_type == TYPE_EXTENT_ITEM) {
+//             ERR("%x,%x,%x\n", (UINT32)tp.item->key.obj_id, tp.item->key.obj_type, (UINT32)tp.item->key.offset);
+//             
+//             if (tp.item->key.obj_id < lastaddr) {
+//                 ERR("inconsistency!\n");
+//             }
+//             
+//             lastaddr = tp.item->key.obj_id + tp.item->key.offset;
+//         }
+//         
+//         b = find_next_item(Vcb, &tp, &next_tp, NULL, FALSE);
+//         if (b) {
+//             free_traverse_ptr(&tp);
+//             tp = next_tp;
+//         }
+//     } while (b);
+//     
+//     free_traverse_ptr(&tp);
+//     
+//     int3;
+// }
+#endif
+
+NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void* buf, ULONG* length, BOOL paging_io, BOOL no_cache, LIST_ENTRY* rollback) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    KEY searchkey;
+    traverse_ptr tp;
+    EXTENT_DATA* ed2;
+    UINT64 newlength, start_data, end_data;
+    UINT32 bufhead;
+    BOOL make_inline;
+    UINT8* data;
+    LIST_ENTRY changed_sector_list;
+    INODE_ITEM *ii, *origii;
+    BOOL changed_length = FALSE, nocsum, nocow/*, lazy_writer = FALSE, write_eof = FALSE*/;
+    NTSTATUS Status;
+    LARGE_INTEGER time;
+    BTRFS_TIME now;
+    fcb* fcb;
+    BOOL paging_lock = FALSE;
+    
+    TRACE("(%p, %p, %llx, %p, %x, %u, %u)\n", Vcb, FileObject, offset.QuadPart, buf, *length, paging_io, no_cache);
+    
+    if (*length == 0) {
+        WARN("returning success for zero-length write\n");
+        return STATUS_SUCCESS;
+    }
+    
+    if (!FileObject) {
+        ERR("error - FileObject was NULL\n");
+        return STATUS_ACCESS_DENIED;
+    }
+    
+    fcb = FileObject->FsContext;
+    
+    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;
+    }
+    
+    if (offset.LowPart == FILE_WRITE_TO_END_OF_FILE && offset.HighPart == -1) {
+        offset = fcb->Header.FileSize;
+//         write_eof = TRUE;
+    }
+    
+    TRACE("fcb->Header.Flags = %x\n", fcb->Header.Flags);
+    
+    if (no_cache && !paging_io && FileObject->SectionObjectPointer->DataSectionObject) {
+        IO_STATUS_BLOCK iosb;
+        
+        ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, TRUE);
+
+        CcFlushCache(FileObject->SectionObjectPointer, &offset, *length, &iosb);
+
+        if (!NT_SUCCESS(iosb.Status)) {
+            ExReleaseResourceLite(fcb->Header.PagingIoResource);
+            ERR("CcFlushCache returned %08x\n", iosb.Status);
+            return iosb.Status;
+        }
+        
+        paging_lock = TRUE;
+
+        CcPurgeCacheSection(FileObject->SectionObjectPointer, &offset, *length, FALSE);
+    }
+    
+    if (paging_io) {
+        ExAcquireResourceSharedLite(fcb->Header.PagingIoResource, TRUE);
+        paging_lock = TRUE;
+    }
+    
+    nocsum = fcb->ads ? TRUE : fcb->inode_item.flags & BTRFS_INODE_NODATASUM;
+    nocow = fcb->ads ? TRUE : fcb->inode_item.flags & BTRFS_INODE_NODATACOW;
+    
+    newlength = fcb->ads ? fcb->adssize : fcb->inode_item.st_size;
+    
+    if (fcb->deleted)
+        newlength = 0;
+    
+    TRACE("newlength = %llx\n", newlength);
+    
+//     if (KeGetCurrentThread() == fcb->lazy_writer_thread) {
+//         ERR("lazy writer on the TV\n");
+//         lazy_writer = TRUE;
+//     }
+    
+    if (offset.QuadPart + *length > newlength) {
+        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("FileObject: AllocationSize = %llx, FileSize = %llx, ValidDataLength = %llx\n",
+                    fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
+                Status = STATUS_SUCCESS;
+                goto end;
+            }
+            
+            *length = newlength - offset.QuadPart;
+        } else {
+            newlength = offset.QuadPart + *length;
+            changed_length = TRUE;
+            
+            TRACE("extending length to %llx\n", newlength);
+        }
+    }
+    
+    make_inline = fcb->ads ? FALSE : newlength <= fcb->Vcb->max_inline;
+    
+    if (changed_length) {
+        if (newlength > fcb->Header.AllocationSize.QuadPart) {
+            Status = extend_file(fcb, newlength, rollback);
+            if (!NT_SUCCESS(Status)) {
+                ERR("extend_file returned %08x\n", Status);
+                goto end;
+            }
+        } else if (fcb->ads)
+            fcb->adssize = newlength;
+        else
+            fcb->inode_item.st_size = newlength;
+        
+        fcb->Header.FileSize.QuadPart = newlength;
+        fcb->Header.ValidDataLength.QuadPart = newlength;
+        
+        TRACE("AllocationSize = %llx\n", fcb->Header.AllocationSize.QuadPart);
+        TRACE("FileSize = %llx\n", fcb->Header.FileSize.QuadPart);
+        TRACE("ValidDataLength = %llx\n", fcb->Header.ValidDataLength.QuadPart);
+    }
+    
+    if (!no_cache) {
+        BOOL wait;
+        
+        if (!FileObject->PrivateCacheMap || changed_length) {
+            CC_FILE_SIZES ccfs;
+            
+            ccfs.AllocationSize = fcb->Header.AllocationSize;
+            ccfs.FileSize = fcb->Header.FileSize;
+            ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+            
+            if (!FileObject->PrivateCacheMap) {
+                TRACE("calling CcInitializeCacheMap...\n");
+                CcInitializeCacheMap(FileObject, &ccfs, FALSE, cache_callbacks, FileObject);
+                
+                CcSetReadAheadGranularity(FileObject, READ_AHEAD_GRANULARITY);
+            } else {
+                CcSetFileSizes(FileObject, &ccfs);
+            }
+        }
+        
+        // FIXME - uncomment this when async is working
+//             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;
+            goto end;
+        }
+        TRACE("CcCopyWrite finished\n");
+        
+        Status = STATUS_SUCCESS;
+        goto end;
+    }
+    
+    if (fcb->ads) {
+        UINT16 datalen;
+        UINT8* data2;
+        UINT32 maxlen;
+        
+        if (!get_xattr(fcb->Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &data, &datalen)) {
+            ERR("get_xattr failed\n");
+            Status = STATUS_INTERNAL_ERROR;
+            goto end;
+        }
+        
+        if (changed_length) {
+            // find maximum length of xattr
+            maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
+            
+            searchkey.obj_id = fcb->inode;
+            searchkey.obj_type = TYPE_XATTR_ITEM;
+            searchkey.offset = fcb->adshash;
+
+            Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - find_item returned %08x\n", Status);
+                goto end;
+            }
+            
+            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;
+                goto end;
+            }
+            
+            fcb->adssize = newlength;
+
+            data2 = ExAllocatePoolWithTag(PagedPool, newlength, ALLOC_TAG);
+            if (!data2) {
+                ERR("out of memory\n");
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+            
+            RtlCopyMemory(data2, data, datalen);
+            
+            if (offset.QuadPart > datalen)
+                RtlZeroMemory(&data2[datalen], offset.QuadPart - datalen);
+        } else
+            data2 = data;
+        
+        if (*length > 0)
+            RtlCopyMemory(&data2[offset.QuadPart], buf, *length);
+        
+        Status = set_xattr(fcb->Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data2, newlength, rollback);
+        if (!NT_SUCCESS(Status)) {
+            ERR("set_xattr returned %08x\n", Status);
+            goto end;
+        }
+        
+        if (data) ExFreePool(data);
+        if (data2 != data) ExFreePool(data2);
+        
+        fcb->Header.ValidDataLength.QuadPart = newlength;
+    } else {
+        if (make_inline) {
+            start_data = 0;
+            end_data = sector_align(newlength, fcb->Vcb->superblock.sector_size);
+            bufhead = sizeof(EXTENT_DATA) - 1;
+        } else {
+            start_data = offset.QuadPart & ~(fcb->Vcb->superblock.sector_size - 1);
+            end_data = sector_align(offset.QuadPart + *length, fcb->Vcb->superblock.sector_size);
+            bufhead = 0;
+        }
+            
+        fcb->Header.ValidDataLength.QuadPart = newlength;
+        TRACE("fcb %p FileSize = %llx\n", fcb, fcb->Header.FileSize.QuadPart);
+    
+        data = ExAllocatePoolWithTag(PagedPool, end_data - start_data + bufhead, ALLOC_TAG);
+        if (!data) {
+            ERR("out of memory\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+        
+        RtlZeroMemory(data + bufhead, end_data - start_data);
+        
+        TRACE("start_data = %llx\n", start_data);
+        TRACE("end_data = %llx\n", end_data);
+        
+        if (offset.QuadPart > start_data || offset.QuadPart + *length < end_data) {
+            if (changed_length) {
+                if (fcb->inode_item.st_size > start_data) 
+                    Status = read_file(Vcb, fcb->subvol, fcb->inode, data + bufhead, start_data, fcb->inode_item.st_size - start_data, NULL);
+                else
+                    Status = STATUS_SUCCESS;
+            } else
+                Status = read_file(Vcb, fcb->subvol, fcb->inode, data + bufhead, start_data, end_data - start_data, NULL);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("read_file returned %08x\n", Status);
+                ExFreePool(data);
+                goto end;
+            }
+        }
+        
+        RtlCopyMemory(data + bufhead + offset.QuadPart - start_data, buf, *length);
+        
+        if (!nocsum)
+            InitializeListHead(&changed_sector_list);
+
+        if (make_inline || !nocow) {
+            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);
+                ExFreePool(data);
+                goto end;
+            }
+            
+            if (!make_inline) {
+                Status = insert_extent(fcb->Vcb, fcb, start_data, end_data - start_data, data, nocsum ? NULL : &changed_sector_list, rollback);
+                
+                if (!NT_SUCCESS(Status)) {
+                    ERR("error - insert_extent 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);
+                
+                fcb->inode_item.st_blocks += newlength;
+            }
+        } else {
+            Status = do_nocow_write(fcb->Vcb, fcb, start_data, end_data - start_data, data, nocsum ? NULL : &changed_sector_list, rollback);
+            
+            if (!NT_SUCCESS(Status)) {
+                ERR("error - do_nocow_write returned %08x\n", Status);
+                ExFreePool(data);
+                goto end;
+            }
+            
+            ExFreePool(data);
+        }
+    }
+    
+    KeQuerySystemTime(&time);
+    win_time_to_unix(time, &now);
+    
+//     ERR("no_cache = %s, FileObject->PrivateCacheMap = %p\n", no_cache ? "TRUE" : "FALSE", FileObject->PrivateCacheMap);
+    
+//     if (!no_cache) {
+//         if (!FileObject->PrivateCacheMap) {
+//             CC_FILE_SIZES ccfs;
+//             
+//             ccfs.AllocationSize = fcb->Header.AllocationSize;
+//             ccfs.FileSize = fcb->Header.FileSize;
+//             ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+//             
+//             TRACE("calling CcInitializeCacheMap...\n");
+//             CcInitializeCacheMap(FileObject, &ccfs, FALSE, cache_callbacks, fcb);
+//             
+//             changed_length = FALSE;
+//         }
+//     }
+    
+    if (fcb->ads)
+        origii = &fcb->par->inode_item;
+    else
+        origii = &fcb->inode_item;
+    
+    origii->transid = Vcb->superblock.generation;
+    origii->sequence++;
+    origii->st_ctime = now;
+    
+    if (!fcb->ads) {
+        TRACE("setting st_size to %llx\n", newlength);
+        origii->st_size = newlength;
+        origii->st_mtime = now;
+    }
+    
+    searchkey.obj_id = fcb->inode;
+    searchkey.obj_type = TYPE_INODE_ITEM;
+    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);
+        goto end;
+    }
+    
+    if (!keycmp(&tp.item->key, &searchkey))
+        delete_tree_item(Vcb, &tp, rollback);
+    else
+        WARN("couldn't find existing INODE_ITEM\n");
+
+    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;
+    }
+    
+    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)
+        update_checksum_tree(Vcb, &changed_sector_list, rollback);
+    
+    if (changed_length) {
+        CC_FILE_SIZES ccfs;
+            
+        ccfs.AllocationSize = fcb->Header.AllocationSize;
+        ccfs.FileSize = fcb->Header.FileSize;
+        ccfs.ValidDataLength = fcb->Header.ValidDataLength;
+
+        CcSetFileSizes(FileObject, &ccfs);
+    }
+    
+    // FIXME - make sure this still called if STATUS_PENDING and async
+//     if (!no_cache) {
+//         if (!CcCopyWrite(FileObject, &offset, *length, TRUE, buf)) {
+//             ERR("CcCopyWrite failed.\n");
+//         }
+//     }
+    
+    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
+    fcb->subvol->root_item.ctime = now;
+    
+    Status = STATUS_SUCCESS;
+    
+end:
+    if (FileObject->Flags & FO_SYNCHRONOUS_IO && !paging_io) {
+        TRACE("CurrentByteOffset was: %llx\n", FileObject->CurrentByteOffset.QuadPart);
+        FileObject->CurrentByteOffset.QuadPart = offset.QuadPart + (NT_SUCCESS(Status) ? *length : 0);
+        TRACE("CurrentByteOffset now: %llx\n", FileObject->CurrentByteOffset.QuadPart);
+    }
+    
+    if (paging_lock)
+        ExReleaseResourceLite(fcb->Header.PagingIoResource);
+
+    return Status;
+}
+
+NTSTATUS write_file(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
+    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+    device_extension* Vcb = DeviceObject->DeviceExtension;
+    void* buf;
+    NTSTATUS Status;
+    LARGE_INTEGER offset = IrpSp->Parameters.Write.ByteOffset;
+    PFILE_OBJECT FileObject = IrpSp->FileObject;
+    fcb* fcb = FileObject ? FileObject->FsContext : NULL;
+    BOOL locked = FALSE;
+//     LARGE_INTEGER freq, time1, time2;
+    LIST_ENTRY rollback;
+    
+    InitializeListHead(&rollback);
+    
+    if (Vcb->readonly)
+        return STATUS_MEDIA_WRITE_PROTECTED;
+    
+    if (fcb && fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY)
+        return STATUS_ACCESS_DENIED;
+    
+//     time1 = KeQueryPerformanceCounter(&freq);
+    
+    TRACE("write\n");
+    
+    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);
+    
+    if (!Irp->AssociatedIrp.SystemBuffer) {
+        buf = map_user_buffer(Irp);
+        
+        if (Irp->MdlAddress && !buf) {
+            ERR("MmGetSystemAddressForMdlSafe returned NULL\n");
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto exit;
+        }   
+    } else
+        buf = Irp->AssociatedIrp.SystemBuffer;
+    
+    TRACE("buf = %p\n", buf);
+    
+    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");
+        Status = STATUS_FILE_LOCK_CONFLICT;
+        goto exit;
+    }
+    
+//     ERR("Irp->Flags = %x\n", Irp->Flags);
+    Status = write_file2(Vcb, Irp, offset, buf, &IrpSp->Parameters.Write.Length, Irp->Flags & IRP_PAGING_IO, Irp->Flags & IRP_NOCACHE, &rollback);
+    if (!NT_SUCCESS(Status)) {
+        if (Status != STATUS_PENDING)
+            ERR("write_file2 returned %08x\n", Status);
+        goto exit;
+    }
+    
+    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
+    
+//         check_extent_tree_consistent(Vcb);
+#endif
+    }
+    
+exit:
+    if (locked) {
+        if (NT_SUCCESS(Status))
+            clear_rollback(&rollback);
+        else
+            do_rollback(Vcb, &rollback);
+        
+        release_tree_lock(Vcb, TRUE);
+    }
+    
+//     time2 = KeQueryPerformanceCounter(NULL);
+    
+//     ERR("time = %u (freq = %u)\n", (UINT32)(time2.QuadPart - time1.QuadPart), (UINT32)freq.QuadPart);
+    
+    return Status;
+}