/* 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 . */ #include #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; }