/* 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;
    ULONG rc;
    
    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 {
                rc = InterlockedIncrement(&c->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
                WARN("fcb %p: refcount now %i (%.*S)\n", c, rc, c->full_filename.Length / sizeof(WCHAR), c->full_filename.Buffer);
#endif
                return c;
            }
        }
        
        le = le->Flink;
    }
    
    if (deleted) {
        rc = InterlockedIncrement(&deleted->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
        WARN("fcb %p: refcount now %i (%.*S)\n", deleted, rc, deleted->full_filename.Length / sizeof(WCHAR), deleted->full_filename.Buffer);
#endif
    }
    
    return deleted;
}

// #ifdef DEBUG_FCB_REFCOUNTS
// static void print_fcbs(device_extension* Vcb) {
//     fcb* fcb = Vcb->fcbs;
//     
//     while (fcb) {
//         ERR("fcb %p (%.*S): refcount %u\n", fcb, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb->refcount);
//         
//         fcb = fcb->next;
//     }
// }
// #endif

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)) {
            LONG rc;
            
            *pfcb = Vcb->root_fcb;
            rc = InterlockedIncrement(&Vcb->root_fcb->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
            WARN("fcb %p: refcount now %i (root)\n", Vcb->root_fcb, rc);
#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;
    LONG rc;
    
    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;
    rc = InterlockedIncrement(&parfcb->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
    WARN("fcb %p: refcount now %i (%.*S)\n", parfcb, rc, parfcb->full_filename.Length / sizeof(WCHAR), parfcb->full_filename.Buffer);
#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;
    LONG oc;
            
    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;
        LONG rc;
        
        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;
        rc = InterlockedIncrement(&parfcb->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
        WARN("fcb %p: refcount now %i (%.*S)\n", parfcb, rc, parfcb->full_filename.Length / sizeof(WCHAR), parfcb->full_filename.Buffer);
#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;
    
    oc = InterlockedIncrement(&fcb->open_count);
#ifdef DEBUG_FCB_REFCOUNTS
    ERR("fcb %p: open_count now %i\n", fcb, oc);
#endif
    
    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;
    LONG oc;
    
    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;
        goto exit;
    }
    
    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;
            }
        }
        
        oc = InterlockedIncrement(&fcb->open_count);
#ifdef DEBUG_FCB_REFCOUNTS
        ERR("fcb %p: open_count now %i\n", fcb, oc);
#endif
    } else {
        Status = file_create(Irp, DeviceObject->DeviceExtension, FileObject, &FileObject->FileName, RequestedDisposition, options, rollback);
        Irp->IoStatus.Information = NT_SUCCESS(Status) ? FILE_CREATED : 0;
        
//         if (!NT_SUCCESS(Status))
//             free_fcb(fcb);
    }
    
    if (NT_SUCCESS(Status) && !(options & FILE_NO_INTERMEDIATE_BUFFERING))
        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;
        LONG rc, oc;
        
        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;
        }

        rc = InterlockedIncrement(&Vcb->volume_fcb->refcount);
        oc = InterlockedIncrement(&Vcb->volume_fcb->open_count);
#ifdef DEBUG_FCB_REFCOUNTS
        WARN("fcb %p: refcount now %i (volume)\n", Vcb->volume_fcb, rc);
        WARN("fcb %p: open_count now %i (volume)\n", Vcb->volume_fcb, oc);
#endif
        attach_fcb_to_fileobject(Vcb, Vcb->volume_fcb, IrpSp->FileObject);
// //         NtfsAttachFCBToFileObject(DeviceExt, DeviceExt->VolumeFcb, FileObject);
// //         DeviceExt->VolumeFcb->RefCount++;
        
        IrpSp->FileObject->SectionObjectPointer = &Vcb->volume_fcb->nonpaged->segment_object;

        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;
}
