[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);
+