mirror of
https://github.com/reactos/reactos.git
synced 2024-08-26 13:18:51 +00:00
2310 lines
79 KiB
C
2310 lines
79 KiB
C
/* Copyright (c) Mark Harmstone 2016-17
|
|
*
|
|
* This file is part of WinBtrfs.
|
|
*
|
|
* WinBtrfs is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public Licence as published by
|
|
* the Free Software Foundation, either version 3 of the Licence, or
|
|
* (at your option) any later version.
|
|
*
|
|
* WinBtrfs is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public Licence for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public Licence
|
|
* along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "btrfs_drv.h"
|
|
#include "crc32c.h"
|
|
|
|
typedef struct {
|
|
uint8_t type;
|
|
|
|
union {
|
|
EXTENT_DATA_REF edr;
|
|
SHARED_DATA_REF sdr;
|
|
TREE_BLOCK_REF tbr;
|
|
SHARED_BLOCK_REF sbr;
|
|
};
|
|
|
|
uint64_t hash;
|
|
LIST_ENTRY list_entry;
|
|
} extent_ref;
|
|
|
|
uint64_t get_extent_data_ref_hash2(uint64_t root, uint64_t objid, uint64_t offset) {
|
|
uint32_t high_crc = 0xffffffff, low_crc = 0xffffffff;
|
|
|
|
high_crc = calc_crc32c(high_crc, (uint8_t*)&root, sizeof(uint64_t));
|
|
low_crc = calc_crc32c(low_crc, (uint8_t*)&objid, sizeof(uint64_t));
|
|
low_crc = calc_crc32c(low_crc, (uint8_t*)&offset, sizeof(uint64_t));
|
|
|
|
return ((uint64_t)high_crc << 31) ^ (uint64_t)low_crc;
|
|
}
|
|
|
|
static __inline uint64_t get_extent_data_ref_hash(EXTENT_DATA_REF* edr) {
|
|
return get_extent_data_ref_hash2(edr->root, edr->objid, edr->offset);
|
|
}
|
|
|
|
static uint64_t get_extent_hash(uint8_t type, void* data) {
|
|
if (type == TYPE_EXTENT_DATA_REF) {
|
|
return get_extent_data_ref_hash((EXTENT_DATA_REF*)data);
|
|
} else if (type == TYPE_SHARED_BLOCK_REF) {
|
|
SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data;
|
|
return sbr->offset;
|
|
} else if (type == TYPE_SHARED_DATA_REF) {
|
|
SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;
|
|
return sdr->offset;
|
|
} else if (type == TYPE_TREE_BLOCK_REF) {
|
|
TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data;
|
|
return tbr->offset;
|
|
} else {
|
|
ERR("unhandled extent type %x\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void free_extent_refs(LIST_ENTRY* extent_refs) {
|
|
while (!IsListEmpty(extent_refs)) {
|
|
LIST_ENTRY* le = RemoveHeadList(extent_refs);
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
|
|
ExFreePool(er);
|
|
}
|
|
}
|
|
|
|
static NTSTATUS add_shared_data_extent_ref(LIST_ENTRY* extent_refs, uint64_t parent, uint32_t count) {
|
|
extent_ref* er2;
|
|
LIST_ENTRY* le;
|
|
|
|
if (!IsListEmpty(extent_refs)) {
|
|
le = extent_refs->Flink;
|
|
|
|
while (le != extent_refs) {
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
|
|
if (er->type == TYPE_SHARED_DATA_REF && er->sdr.offset == parent) {
|
|
er->sdr.count += count;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
|
|
if (!er2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
er2->type = TYPE_SHARED_DATA_REF;
|
|
er2->sdr.offset = parent;
|
|
er2->sdr.count = count;
|
|
|
|
InsertTailList(extent_refs, &er2->list_entry);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS add_shared_block_extent_ref(LIST_ENTRY* extent_refs, uint64_t parent) {
|
|
extent_ref* er2;
|
|
LIST_ENTRY* le;
|
|
|
|
if (!IsListEmpty(extent_refs)) {
|
|
le = extent_refs->Flink;
|
|
|
|
while (le != extent_refs) {
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
|
|
if (er->type == TYPE_SHARED_BLOCK_REF && er->sbr.offset == parent)
|
|
return STATUS_SUCCESS;
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
|
|
if (!er2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
er2->type = TYPE_SHARED_BLOCK_REF;
|
|
er2->sbr.offset = parent;
|
|
|
|
InsertTailList(extent_refs, &er2->list_entry);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS add_tree_block_extent_ref(LIST_ENTRY* extent_refs, uint64_t root) {
|
|
extent_ref* er2;
|
|
LIST_ENTRY* le;
|
|
|
|
if (!IsListEmpty(extent_refs)) {
|
|
le = extent_refs->Flink;
|
|
|
|
while (le != extent_refs) {
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
|
|
if (er->type == TYPE_TREE_BLOCK_REF && er->tbr.offset == root)
|
|
return STATUS_SUCCESS;
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);
|
|
if (!er2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
er2->type = TYPE_TREE_BLOCK_REF;
|
|
er2->tbr.offset = root;
|
|
|
|
InsertTailList(extent_refs, &er2->list_entry);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static void sort_extent_refs(LIST_ENTRY* extent_refs) {
|
|
LIST_ENTRY newlist;
|
|
|
|
if (IsListEmpty(extent_refs))
|
|
return;
|
|
|
|
// insertion sort
|
|
|
|
InitializeListHead(&newlist);
|
|
|
|
while (!IsListEmpty(extent_refs)) {
|
|
extent_ref* er = CONTAINING_RECORD(RemoveHeadList(extent_refs), extent_ref, list_entry);
|
|
LIST_ENTRY* le;
|
|
bool inserted = false;
|
|
|
|
le = newlist.Flink;
|
|
while (le != &newlist) {
|
|
extent_ref* er2 = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
|
|
if (er->type < er2->type || (er->type == er2->type && er->hash > er2->hash)) {
|
|
InsertHeadList(le->Blink, &er->list_entry);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (!inserted)
|
|
InsertTailList(&newlist, &er->list_entry);
|
|
}
|
|
|
|
newlist.Flink->Blink = extent_refs;
|
|
newlist.Blink->Flink = extent_refs;
|
|
extent_refs->Flink = newlist.Flink;
|
|
extent_refs->Blink = newlist.Blink;
|
|
}
|
|
|
|
static NTSTATUS construct_extent_item(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t flags, LIST_ENTRY* extent_refs,
|
|
KEY* firstitem, uint8_t level, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
LIST_ENTRY *le, *next_le;
|
|
uint64_t refcount;
|
|
uint16_t inline_len;
|
|
bool all_inline = true;
|
|
extent_ref* first_noninline = NULL;
|
|
EXTENT_ITEM* ei;
|
|
uint8_t* siptr;
|
|
|
|
// FIXME - write skinny extents if is tree and incompat flag set
|
|
|
|
if (IsListEmpty(extent_refs)) {
|
|
WARN("no extent refs found\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
refcount = 0;
|
|
inline_len = sizeof(EXTENT_ITEM);
|
|
|
|
if (flags & EXTENT_ITEM_TREE_BLOCK)
|
|
inline_len += sizeof(EXTENT_ITEM2);
|
|
|
|
le = extent_refs->Flink;
|
|
while (le != extent_refs) {
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
uint64_t rc;
|
|
|
|
next_le = le->Flink;
|
|
|
|
rc = get_extent_data_refcount(er->type, &er->edr);
|
|
|
|
if (rc == 0) {
|
|
RemoveEntryList(&er->list_entry);
|
|
|
|
ExFreePool(er);
|
|
} else {
|
|
uint16_t extlen = get_extent_data_len(er->type);
|
|
|
|
refcount += rc;
|
|
|
|
er->hash = get_extent_hash(er->type, &er->edr);
|
|
|
|
if (all_inline) {
|
|
if ((uint16_t)(inline_len + 1 + extlen) > Vcb->superblock.node_size >> 2) {
|
|
all_inline = false;
|
|
first_noninline = er;
|
|
} else
|
|
inline_len += extlen + 1;
|
|
}
|
|
}
|
|
|
|
le = next_le;
|
|
}
|
|
|
|
ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG);
|
|
if (!ei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
ei->refcount = refcount;
|
|
ei->generation = Vcb->superblock.generation;
|
|
ei->flags = flags;
|
|
|
|
if (flags & EXTENT_ITEM_TREE_BLOCK) {
|
|
EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)&ei[1];
|
|
|
|
if (firstitem) {
|
|
ei2->firstitem.obj_id = firstitem->obj_id;
|
|
ei2->firstitem.obj_type = firstitem->obj_type;
|
|
ei2->firstitem.offset = firstitem->offset;
|
|
} else {
|
|
ei2->firstitem.obj_id = 0;
|
|
ei2->firstitem.obj_type = 0;
|
|
ei2->firstitem.offset = 0;
|
|
}
|
|
|
|
ei2->level = level;
|
|
|
|
siptr = (uint8_t*)&ei2[1];
|
|
} else
|
|
siptr = (uint8_t*)&ei[1];
|
|
|
|
sort_extent_refs(extent_refs);
|
|
|
|
le = extent_refs->Flink;
|
|
while (le != extent_refs) {
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
ULONG extlen = get_extent_data_len(er->type);
|
|
|
|
if (!all_inline && er == first_noninline)
|
|
break;
|
|
|
|
*siptr = er->type;
|
|
siptr++;
|
|
|
|
if (extlen > 0) {
|
|
RtlCopyMemory(siptr, &er->edr, extlen);
|
|
siptr += extlen;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, inline_len, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
ExFreePool(ei);
|
|
return Status;
|
|
}
|
|
|
|
if (!all_inline) {
|
|
le = &first_noninline->list_entry;
|
|
|
|
while (le != extent_refs) {
|
|
extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);
|
|
uint16_t len;
|
|
uint8_t* data;
|
|
|
|
if (er->type == TYPE_EXTENT_DATA_REF) {
|
|
len = sizeof(EXTENT_DATA_REF);
|
|
|
|
data = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
|
|
|
|
if (!data) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(data, &er->edr, len);
|
|
} else if (er->type == TYPE_SHARED_DATA_REF) {
|
|
len = sizeof(uint32_t);
|
|
|
|
data = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
|
|
|
|
if (!data) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
*((uint32_t*)data) = er->sdr.count;
|
|
} else {
|
|
len = 0;
|
|
data = NULL;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, address, er->type, er->hash, data, len, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
if (data) ExFreePool(data);
|
|
return Status;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS convert_old_extent(device_extension* Vcb, uint64_t address, bool tree, KEY* firstitem, uint8_t level, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp, next_tp;
|
|
LIST_ENTRY extent_refs;
|
|
uint64_t size;
|
|
|
|
InitializeListHead(&extent_refs);
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("find_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
|
|
ERR("old-style extent %I64x not found\n", address);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
size = tp.item->key.offset;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {
|
|
tp = next_tp;
|
|
|
|
if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_REF_V0 && tp.item->size >= sizeof(EXTENT_REF_V0)) {
|
|
EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)tp.item->data;
|
|
|
|
if (tree) {
|
|
if (tp.item->key.offset == tp.item->key.obj_id) { // top of the tree
|
|
Status = add_tree_block_extent_ref(&extent_refs, erv0->root);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_tree_block_extent_ref returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
} else {
|
|
Status = add_shared_block_extent_ref(&extent_refs, tp.item->key.offset);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_shared_block_extent_ref returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
} else {
|
|
Status = add_shared_data_extent_ref(&extent_refs, tp.item->key.offset, erv0->count);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_shared_data_extent_ref returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (tp.item->key.obj_id > address || tp.item->key.obj_type > TYPE_EXTENT_REF_V0)
|
|
break;
|
|
}
|
|
|
|
Status = construct_extent_item(Vcb, address, size, tree ? (EXTENT_ITEM_TREE_BLOCK | EXTENT_ITEM_SHARED_BACKREFS) : EXTENT_ITEM_DATA,
|
|
&extent_refs, firstitem, level, Irp);
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("construct_extent_item returned %08lx\n", Status);
|
|
|
|
end:
|
|
free_extent_refs(&extent_refs);
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS increase_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
ULONG len, max_extent_item_size;
|
|
uint16_t datalen = get_extent_data_len(type);
|
|
EXTENT_ITEM* ei;
|
|
uint8_t* ptr;
|
|
uint64_t inline_rc, offset;
|
|
uint8_t* data2;
|
|
EXTENT_ITEM* newei;
|
|
bool skinny;
|
|
bool is_tree = type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF;
|
|
|
|
if (datalen == 0) {
|
|
ERR("unrecognized extent type %x\n", type);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// If entry doesn't exist yet, create new inline extent item
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM)) {
|
|
uint16_t eisize;
|
|
|
|
eisize = sizeof(EXTENT_ITEM);
|
|
if (is_tree && !(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) eisize += sizeof(EXTENT_ITEM2);
|
|
eisize += sizeof(uint8_t);
|
|
eisize += datalen;
|
|
|
|
ei = ExAllocatePoolWithTag(PagedPool, eisize, ALLOC_TAG);
|
|
if (!ei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
ei->refcount = get_extent_data_refcount(type, data);
|
|
ei->generation = Vcb->superblock.generation;
|
|
ei->flags = is_tree ? EXTENT_ITEM_TREE_BLOCK : EXTENT_ITEM_DATA;
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
if (is_tree && !(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {
|
|
EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)ptr;
|
|
ei2->firstitem = *firstitem;
|
|
ei2->level = level;
|
|
ptr = (uint8_t*)&ei2[1];
|
|
}
|
|
|
|
*ptr = type;
|
|
RtlCopyMemory(ptr + 1, data, datalen);
|
|
|
|
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && is_tree)
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, ei, eisize, NULL, Irp);
|
|
else
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, eisize, NULL, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} else if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset != size) {
|
|
ERR("extent %I64x exists, but with size %I64x rather than %I64x expected\n", tp.item->key.obj_id, tp.item->key.offset, size);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
skinny = tp.item->key.obj_type == TYPE_METADATA_ITEM;
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0) && !skinny) {
|
|
Status = convert_old_extent(Vcb, address, is_tree, firstitem, level, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("convert_old_extent returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return increase_extent_refcount(Vcb, address, size, type, data, firstitem, level, Irp);
|
|
}
|
|
|
|
if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) {
|
|
if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
len -= sizeof(EXTENT_ITEM2);
|
|
ptr += sizeof(EXTENT_ITEM2);
|
|
}
|
|
|
|
inline_rc = 0;
|
|
|
|
// Loop through existing inline extent entries
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
ULONG sectlen = get_extent_data_len(secttype);
|
|
uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
|
|
|
|
len--;
|
|
|
|
if (sectlen > len) {
|
|
ERR("(%I64x,%x,%I64x): %lx bytes left, expecting at least %lx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (sectlen == 0) {
|
|
ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
// If inline extent already present, increase refcount and return
|
|
|
|
if (secttype == type) {
|
|
if (type == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
|
|
|
|
if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {
|
|
uint32_t rc = get_extent_data_refcount(type, data);
|
|
EXTENT_DATA_REF* sectedr2;
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount += rc;
|
|
|
|
sectedr2 = (EXTENT_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectedr - tp.item->data));
|
|
sectedr2->count += rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else if (type == TYPE_TREE_BLOCK_REF) {
|
|
TREE_BLOCK_REF* secttbr = (TREE_BLOCK_REF*)(ptr + sizeof(uint8_t));
|
|
TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data;
|
|
|
|
if (secttbr->offset == tbr->offset) {
|
|
TRACE("trying to increase refcount of non-shared tree extent\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else if (type == TYPE_SHARED_BLOCK_REF) {
|
|
SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t));
|
|
SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data;
|
|
|
|
if (sectsbr->offset == sbr->offset)
|
|
return STATUS_SUCCESS;
|
|
} else if (type == TYPE_SHARED_DATA_REF) {
|
|
SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;
|
|
|
|
if (sectsdr->offset == sdr->offset) {
|
|
uint32_t rc = get_extent_data_refcount(type, data);
|
|
SHARED_DATA_REF* sectsdr2;
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount += rc;
|
|
|
|
sectsdr2 = (SHARED_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectsdr - tp.item->data));
|
|
sectsdr2->count += rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else {
|
|
ERR("unhandled extent type %x\n", type);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
len -= sectlen;
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
inline_rc += sectcount;
|
|
}
|
|
|
|
offset = get_extent_hash(type, data);
|
|
|
|
max_extent_item_size = (Vcb->superblock.node_size >> 4) - sizeof(leaf_node);
|
|
|
|
// If we can, add entry as inline extent item
|
|
|
|
if (inline_rc == ei->refcount && tp.item->size + sizeof(uint8_t) + datalen < max_extent_item_size) {
|
|
len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) {
|
|
len -= sizeof(EXTENT_ITEM2);
|
|
ptr += sizeof(EXTENT_ITEM2);
|
|
}
|
|
|
|
// Confusingly, it appears that references are sorted forward by type (i.e. EXTENT_DATA_REFs before
|
|
// SHARED_DATA_REFs), but then backwards by hash...
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
ULONG sectlen = get_extent_data_len(secttype);
|
|
|
|
if (secttype > type)
|
|
break;
|
|
|
|
if (secttype == type) {
|
|
uint64_t sectoff = get_extent_hash(secttype, ptr + 1);
|
|
|
|
if (sectoff < offset)
|
|
break;
|
|
}
|
|
|
|
len -= sectlen + sizeof(uint8_t);
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(uint8_t) + datalen, ALLOC_TAG);
|
|
RtlCopyMemory(newei, tp.item->data, ptr - tp.item->data);
|
|
|
|
newei->refcount += get_extent_data_refcount(type, data);
|
|
|
|
if (len > 0)
|
|
RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data) + sizeof(uint8_t) + datalen, ptr, len);
|
|
|
|
ptr = (ptr - tp.item->data) + (uint8_t*)newei;
|
|
|
|
*ptr = type;
|
|
RtlCopyMemory(ptr + 1, data, datalen);
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size + sizeof(uint8_t) + datalen, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Look for existing non-inline entry, and increase refcount if found
|
|
|
|
if (inline_rc != ei->refcount) {
|
|
traverse_ptr tp2;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = type;
|
|
searchkey.offset = offset;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(tp2.item->key, searchkey)) {
|
|
if (type == TYPE_SHARED_DATA_REF && tp2.item->size < sizeof(uint32_t)) {
|
|
ERR("(%I64x,%x,%I64x) was %x bytes, expecting %Ix\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, sizeof(uint32_t));
|
|
return STATUS_INTERNAL_ERROR;
|
|
} else if (type != TYPE_SHARED_DATA_REF && tp2.item->size < datalen) {
|
|
ERR("(%I64x,%x,%I64x) was %x bytes, expecting %x\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, datalen);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
data2 = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);
|
|
if (!data2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(data2, tp2.item->data, tp2.item->size);
|
|
|
|
if (type == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data2;
|
|
|
|
edr->count += get_extent_data_refcount(type, data);
|
|
} else if (type == TYPE_TREE_BLOCK_REF) {
|
|
TRACE("trying to increase refcount of non-shared tree extent\n");
|
|
return STATUS_SUCCESS;
|
|
} else if (type == TYPE_SHARED_BLOCK_REF)
|
|
return STATUS_SUCCESS;
|
|
else if (type == TYPE_SHARED_DATA_REF) {
|
|
uint32_t* sdr = (uint32_t*)data2;
|
|
|
|
*sdr += get_extent_data_refcount(type, data);
|
|
} else {
|
|
ERR("unhandled extent type %x\n", type);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, data2, tp2.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount += get_extent_data_refcount(type, data);
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// Otherwise, add new non-inline entry
|
|
|
|
if (type == TYPE_SHARED_DATA_REF) {
|
|
SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;
|
|
|
|
data2 = ExAllocatePoolWithTag(PagedPool, sizeof(uint32_t), ALLOC_TAG);
|
|
if (!data2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
datalen = sizeof(uint32_t);
|
|
|
|
*((uint32_t*)data2) = sdr->count;
|
|
} else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) {
|
|
data2 = NULL;
|
|
datalen = 0;
|
|
} else {
|
|
data2 = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);
|
|
if (!data2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(data2, data, datalen);
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, address, type, offset, data2, datalen, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount += get_extent_data_refcount(type, data);
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS increase_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode, uint64_t offset, uint32_t refcount, PIRP Irp) {
|
|
EXTENT_DATA_REF edr;
|
|
|
|
edr.root = root;
|
|
edr.objid = inode;
|
|
edr.offset = offset;
|
|
edr.count = refcount;
|
|
|
|
return increase_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp);
|
|
}
|
|
|
|
NTSTATUS decrease_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem,
|
|
uint8_t level, uint64_t parent, bool superseded, PIRP Irp) {
|
|
KEY searchkey;
|
|
NTSTATUS Status;
|
|
traverse_ptr tp, tp2;
|
|
EXTENT_ITEM* ei;
|
|
ULONG len;
|
|
uint64_t inline_rc;
|
|
uint8_t* ptr;
|
|
uint32_t rc = data ? get_extent_data_refcount(type, data) : 1;
|
|
ULONG datalen = get_extent_data_len(type);
|
|
bool is_tree = (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF), skinny = false;
|
|
|
|
if (is_tree && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_METADATA_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
|
|
skinny = true;
|
|
}
|
|
|
|
if (!skinny) {
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
|
|
ERR("could not find EXTENT_ITEM for address %I64x\n", address);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (tp.item->key.offset != size) {
|
|
ERR("extent %I64x had length %I64x, not %I64x as expected\n", address, tp.item->key.offset, size);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {
|
|
Status = convert_old_extent(Vcb, address, is_tree, firstitem, level, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("convert_old_extent returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return decrease_extent_refcount(Vcb, address, size, type, data, firstitem, level, parent, superseded, Irp);
|
|
}
|
|
}
|
|
|
|
if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) {
|
|
if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
len -= sizeof(EXTENT_ITEM2);
|
|
ptr += sizeof(EXTENT_ITEM2);
|
|
}
|
|
|
|
if (ei->refcount < rc) {
|
|
ERR("error - extent has refcount %I64x, trying to reduce by %x\n", ei->refcount, rc);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
inline_rc = 0;
|
|
|
|
// Loop through inline extent entries
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
uint16_t sectlen = get_extent_data_len(secttype);
|
|
uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
|
|
|
|
len--;
|
|
|
|
if (sectlen > len) {
|
|
ERR("(%I64x,%x,%I64x): %lx bytes left, expecting at least %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (sectlen == 0) {
|
|
ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (secttype == type) {
|
|
if (type == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
|
|
|
|
if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {
|
|
uint16_t neweilen;
|
|
EXTENT_ITEM* newei;
|
|
|
|
if (ei->refcount == edr->count) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!superseded)
|
|
add_checksum_entry(Vcb, address, (ULONG)(size / Vcb->superblock.sector_size), NULL, Irp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (sectedr->count < edr->count) {
|
|
ERR("error - extent section has refcount %x, trying to reduce by %x\n", sectedr->count, edr->count);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (sectedr->count > edr->count) // reduce section refcount
|
|
neweilen = tp.item->size;
|
|
else // remove section entirely
|
|
neweilen = tp.item->size - sizeof(uint8_t) - sectlen;
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (sectedr->count > edr->count) {
|
|
EXTENT_DATA_REF* newedr = (EXTENT_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectedr - tp.item->data));
|
|
|
|
RtlCopyMemory(newei, ei, neweilen);
|
|
|
|
newedr->count -= rc;
|
|
} else {
|
|
RtlCopyMemory(newei, ei, ptr - tp.item->data);
|
|
|
|
if (len > sectlen)
|
|
RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);
|
|
}
|
|
|
|
newei->refcount -= rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else if (type == TYPE_SHARED_DATA_REF) {
|
|
SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;
|
|
|
|
if (sectsdr->offset == sdr->offset) {
|
|
EXTENT_ITEM* newei;
|
|
uint16_t neweilen;
|
|
|
|
if (ei->refcount == sectsdr->count) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!superseded)
|
|
add_checksum_entry(Vcb, address, (ULONG)(size / Vcb->superblock.sector_size), NULL, Irp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (sectsdr->count < sdr->count) {
|
|
ERR("error - SHARED_DATA_REF has refcount %x, trying to reduce by %x\n", sectsdr->count, sdr->count);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (sectsdr->count > sdr->count) // reduce section refcount
|
|
neweilen = tp.item->size;
|
|
else // remove section entirely
|
|
neweilen = tp.item->size - sizeof(uint8_t) - sectlen;
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (sectsdr->count > sdr->count) {
|
|
SHARED_DATA_REF* newsdr = (SHARED_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectsdr - tp.item->data));
|
|
|
|
RtlCopyMemory(newei, ei, neweilen);
|
|
|
|
newsdr->count -= rc;
|
|
} else {
|
|
RtlCopyMemory(newei, ei, ptr - tp.item->data);
|
|
|
|
if (len > sectlen)
|
|
RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);
|
|
}
|
|
|
|
newei->refcount -= rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else if (type == TYPE_TREE_BLOCK_REF) {
|
|
TREE_BLOCK_REF* secttbr = (TREE_BLOCK_REF*)(ptr + sizeof(uint8_t));
|
|
TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data;
|
|
|
|
if (secttbr->offset == tbr->offset) {
|
|
EXTENT_ITEM* newei;
|
|
uint16_t neweilen;
|
|
|
|
if (ei->refcount == 1) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
neweilen = tp.item->size - sizeof(uint8_t) - sectlen;
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, ei, ptr - tp.item->data);
|
|
|
|
if (len > sectlen)
|
|
RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);
|
|
|
|
newei->refcount--;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else if (type == TYPE_SHARED_BLOCK_REF) {
|
|
SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t));
|
|
SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data;
|
|
|
|
if (sectsbr->offset == sbr->offset) {
|
|
EXTENT_ITEM* newei;
|
|
uint16_t neweilen;
|
|
|
|
if (ei->refcount == 1) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
neweilen = tp.item->size - sizeof(uint8_t) - sectlen;
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, ei, ptr - tp.item->data);
|
|
|
|
if (len > sectlen)
|
|
RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);
|
|
|
|
newei->refcount--;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else {
|
|
ERR("unhandled extent type %x\n", type);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
len -= sectlen;
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
inline_rc += sectcount;
|
|
}
|
|
|
|
if (inline_rc == ei->refcount) {
|
|
ERR("entry not found in inline extent item for address %I64x\n", address);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (type == TYPE_SHARED_DATA_REF)
|
|
datalen = sizeof(uint32_t);
|
|
else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF)
|
|
datalen = 0;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = type;
|
|
searchkey.offset = (type == TYPE_SHARED_DATA_REF || type == TYPE_EXTENT_REF_V0) ? parent : get_extent_hash(type, data);
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (keycmp(tp2.item->key, searchkey)) {
|
|
ERR("(%I64x,%x,%I64x) not found\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (tp2.item->size < datalen) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %lu\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, datalen);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (type == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)tp2.item->data;
|
|
EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;
|
|
|
|
if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {
|
|
EXTENT_ITEM* newei;
|
|
|
|
if (ei->refcount == edr->count) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!superseded)
|
|
add_checksum_entry(Vcb, address, (ULONG)(size / Vcb->superblock.sector_size), NULL, Irp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (sectedr->count < edr->count) {
|
|
ERR("error - extent section has refcount %x, trying to reduce by %x\n", sectedr->count, edr->count);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (sectedr->count > edr->count) {
|
|
EXTENT_DATA_REF* newedr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);
|
|
|
|
if (!newedr) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newedr, sectedr, tp2.item->size);
|
|
|
|
newedr->count -= edr->count;
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, newedr, tp2.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount -= rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
ERR("error - hash collision?\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
} else if (type == TYPE_SHARED_DATA_REF) {
|
|
SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;
|
|
|
|
if (tp2.item->key.offset == sdr->offset) {
|
|
uint32_t* sectsdrcount = (uint32_t*)tp2.item->data;
|
|
EXTENT_ITEM* newei;
|
|
|
|
if (ei->refcount == sdr->count) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!superseded)
|
|
add_checksum_entry(Vcb, address, (ULONG)(size / Vcb->superblock.sector_size), NULL, Irp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (*sectsdrcount < sdr->count) {
|
|
ERR("error - extent section has refcount %x, trying to reduce by %x\n", *sectsdrcount, sdr->count);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (*sectsdrcount > sdr->count) {
|
|
uint32_t* newsdr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);
|
|
|
|
if (!newsdr) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
*newsdr = *sectsdrcount - sdr->count;
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, newsdr, tp2.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount -= rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
ERR("error - collision?\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
} else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) {
|
|
EXTENT_ITEM* newei;
|
|
|
|
if (ei->refcount == 1) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount -= rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} else if (type == TYPE_EXTENT_REF_V0) {
|
|
EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)tp2.item->data;
|
|
EXTENT_ITEM* newei;
|
|
|
|
if (ei->refcount == erv0->count) {
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!superseded)
|
|
add_checksum_entry(Vcb, address, (ULONG)(size / Vcb->superblock.sector_size), NULL, Irp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
Status = delete_tree_item(Vcb, &tp2);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
if (!newei) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newei, tp.item->data, tp.item->size);
|
|
|
|
newei->refcount -= rc;
|
|
|
|
Status = delete_tree_item(Vcb, &tp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("insert_tree_item returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
ERR("unhandled extent type %x\n", type);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
NTSTATUS decrease_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode,
|
|
uint64_t offset, uint32_t refcount, bool superseded, PIRP Irp) {
|
|
EXTENT_DATA_REF edr;
|
|
|
|
edr.root = root;
|
|
edr.objid = inode;
|
|
edr.offset = offset;
|
|
edr.count = refcount;
|
|
|
|
return decrease_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, 0, superseded, Irp);
|
|
}
|
|
|
|
NTSTATUS decrease_extent_refcount_tree(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root,
|
|
uint8_t level, PIRP Irp) {
|
|
TREE_BLOCK_REF tbr;
|
|
|
|
tbr.offset = root;
|
|
|
|
return decrease_extent_refcount(Vcb, address, size, TYPE_TREE_BLOCK_REF, &tbr, NULL/*FIXME*/, level, 0, false, Irp);
|
|
}
|
|
|
|
static uint32_t find_extent_data_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
|
|
TRACE("could not find address %I64x in extent tree\n", address);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->key.offset != size) {
|
|
ERR("extent %I64x had size %I64x, not %I64x as expected\n", address, tp.item->key.offset, size);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->size >= sizeof(EXTENT_ITEM)) {
|
|
EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;
|
|
uint32_t len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
uint8_t* ptr = (uint8_t*)&ei[1];
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
ULONG sectlen = get_extent_data_len(secttype);
|
|
uint32_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
|
|
|
|
len--;
|
|
|
|
if (sectlen > len) {
|
|
ERR("(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
|
|
return 0;
|
|
}
|
|
|
|
if (sectlen == 0) {
|
|
ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
|
|
return 0;
|
|
}
|
|
|
|
if (secttype == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
|
|
if (sectedr->root == root && sectedr->objid == objid && sectedr->offset == offset)
|
|
return sectcount;
|
|
}
|
|
|
|
len -= sectlen;
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
}
|
|
}
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_EXTENT_DATA_REF;
|
|
searchkey.offset = get_extent_data_ref_hash2(root, objid, offset);
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (!keycmp(searchkey, tp.item->key)) {
|
|
if (tp.item->size < sizeof(EXTENT_DATA_REF))
|
|
ERR("(%I64x,%x,%I64x) has size %u, not %Iu as expected\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA_REF));
|
|
else {
|
|
EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)tp.item->data;
|
|
|
|
return edr->count;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t get_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
EXTENT_ITEM* ei;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address &&
|
|
tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
return ei->refcount;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) {
|
|
ERR("couldn't find (%I64x,%x,%I64x) in extent tree\n", address, TYPE_EXTENT_ITEM, size);
|
|
return 0;
|
|
} else if (tp.item->key.offset != size) {
|
|
ERR("extent %I64x had size %I64x, not %I64x as expected\n", address, tp.item->key.offset, size);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {
|
|
EXTENT_ITEM_V0* eiv0 = (EXTENT_ITEM_V0*)tp.item->data;
|
|
|
|
return eiv0->refcount;
|
|
} else if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\n", tp.item->key.obj_id, tp.item->key.obj_type,
|
|
tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return 0;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
return ei->refcount;
|
|
}
|
|
|
|
bool is_extent_unique(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp) {
|
|
KEY searchkey;
|
|
traverse_ptr tp, next_tp;
|
|
NTSTATUS Status;
|
|
uint64_t rc, rcrun, root = 0, inode = 0, offset = 0;
|
|
uint32_t len;
|
|
EXTENT_ITEM* ei;
|
|
uint8_t* ptr;
|
|
bool b;
|
|
|
|
rc = get_extent_refcount(Vcb, address, size, Irp);
|
|
|
|
if (rc == 1)
|
|
return true;
|
|
|
|
if (rc == 0)
|
|
return false;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_EXTENT_ITEM;
|
|
searchkey.offset = size;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
WARN("error - find_item returned %08lx\n", Status);
|
|
return false;
|
|
}
|
|
|
|
if (keycmp(tp.item->key, searchkey)) {
|
|
WARN("could not find (%I64x,%x,%I64x)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
|
|
return false;
|
|
}
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0))
|
|
return false;
|
|
|
|
if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
WARN("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return false;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
if (ei->flags & EXTENT_ITEM_TREE_BLOCK) {
|
|
if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {
|
|
WARN("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
|
|
return false;
|
|
}
|
|
|
|
len -= sizeof(EXTENT_ITEM2);
|
|
ptr += sizeof(EXTENT_ITEM2);
|
|
}
|
|
|
|
rcrun = 0;
|
|
|
|
// Loop through inline extent entries
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
ULONG sectlen = get_extent_data_len(secttype);
|
|
uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
|
|
|
|
len--;
|
|
|
|
if (sectlen > len) {
|
|
WARN("(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
|
|
return false;
|
|
}
|
|
|
|
if (sectlen == 0) {
|
|
WARN("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
|
|
return false;
|
|
}
|
|
|
|
if (secttype == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
|
|
if (root == 0 && inode == 0) {
|
|
root = sectedr->root;
|
|
inode = sectedr->objid;
|
|
offset = sectedr->offset;
|
|
} else if (root != sectedr->root || inode != sectedr->objid || offset != sectedr->offset)
|
|
return false;
|
|
} else
|
|
return false;
|
|
|
|
len -= sectlen;
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
rcrun += sectcount;
|
|
}
|
|
|
|
if (rcrun == rc)
|
|
return true;
|
|
|
|
// Loop through non-inlines if some refs still unaccounted for
|
|
|
|
do {
|
|
b = find_next_item(Vcb, &tp, &next_tp, false, Irp);
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_EXTENT_DATA_REF) {
|
|
EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)tp.item->data;
|
|
|
|
if (tp.item->size < sizeof(EXTENT_DATA_REF)) {
|
|
WARN("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
|
|
tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
|
|
return false;
|
|
}
|
|
|
|
if (root == 0 && inode == 0) {
|
|
root = edr->root;
|
|
inode = edr->objid;
|
|
offset = edr->offset;
|
|
} else if (root != edr->root || inode != edr->objid || offset != edr->offset)
|
|
return false;
|
|
|
|
rcrun += edr->count;
|
|
}
|
|
|
|
if (rcrun == rc)
|
|
return true;
|
|
|
|
if (b) {
|
|
tp = next_tp;
|
|
|
|
if (tp.item->key.obj_id > searchkey.obj_id)
|
|
break;
|
|
}
|
|
} while (b);
|
|
|
|
// If we reach this point, there's still some refs unaccounted for somewhere.
|
|
// Return false in case we mess things up elsewhere.
|
|
|
|
return false;
|
|
}
|
|
|
|
uint64_t get_extent_flags(device_extension* Vcb, uint64_t address, PIRP Irp) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
EXTENT_ITEM* ei;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address &&
|
|
tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
return ei->flags;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) {
|
|
ERR("couldn't find %I64x in extent tree\n", address);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0))
|
|
return 0;
|
|
else if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\n", tp.item->key.obj_id, tp.item->key.obj_type,
|
|
tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return 0;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
return ei->flags;
|
|
}
|
|
|
|
void update_extent_flags(device_extension* Vcb, uint64_t address, uint64_t flags, PIRP Irp) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
EXTENT_ITEM* ei;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return;
|
|
}
|
|
|
|
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address &&
|
|
tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
ei->flags = flags;
|
|
return;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) {
|
|
ERR("couldn't find %I64x in extent tree\n", address);
|
|
return;
|
|
}
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0))
|
|
return;
|
|
else if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\n", tp.item->key.obj_id, tp.item->key.obj_type,
|
|
tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
ei->flags = flags;
|
|
}
|
|
|
|
static changed_extent* get_changed_extent_item(chunk* c, uint64_t address, uint64_t size, bool no_csum) {
|
|
LIST_ENTRY* le;
|
|
changed_extent* ce;
|
|
|
|
le = c->changed_extents.Flink;
|
|
while (le != &c->changed_extents) {
|
|
ce = CONTAINING_RECORD(le, changed_extent, list_entry);
|
|
|
|
if (ce->address == address && ce->size == size)
|
|
return ce;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
ce = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent), ALLOC_TAG);
|
|
if (!ce) {
|
|
ERR("out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
ce->address = address;
|
|
ce->size = size;
|
|
ce->old_size = size;
|
|
ce->count = 0;
|
|
ce->old_count = 0;
|
|
ce->no_csum = no_csum;
|
|
ce->superseded = false;
|
|
InitializeListHead(&ce->refs);
|
|
InitializeListHead(&ce->old_refs);
|
|
|
|
InsertTailList(&c->changed_extents, &ce->list_entry);
|
|
|
|
return ce;
|
|
}
|
|
|
|
NTSTATUS update_changed_extent_ref(device_extension* Vcb, chunk* c, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, int32_t count,
|
|
bool no_csum, bool superseded, PIRP Irp) {
|
|
LIST_ENTRY* le;
|
|
changed_extent* ce;
|
|
changed_extent_ref* cer;
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
uint32_t old_count;
|
|
|
|
ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true);
|
|
|
|
ce = get_changed_extent_item(c, address, size, no_csum);
|
|
|
|
if (!ce) {
|
|
ERR("get_changed_extent_item failed\n");
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto end;
|
|
}
|
|
|
|
if (IsListEmpty(&ce->refs) && IsListEmpty(&ce->old_refs)) { // new entry
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
|
|
ERR("could not find address %I64x in extent tree\n", address);
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto end;
|
|
}
|
|
|
|
if (tp.item->key.offset != size) {
|
|
ERR("extent %I64x had size %I64x, not %I64x as expected\n", address, tp.item->key.offset, size);
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto end;
|
|
}
|
|
|
|
if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {
|
|
EXTENT_ITEM_V0* eiv0 = (EXTENT_ITEM_V0*)tp.item->data;
|
|
|
|
ce->count = ce->old_count = eiv0->refcount;
|
|
} else if (tp.item->size >= sizeof(EXTENT_ITEM)) {
|
|
EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;
|
|
|
|
ce->count = ce->old_count = ei->refcount;
|
|
} else {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
le = ce->refs.Flink;
|
|
while (le != &ce->refs) {
|
|
cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);
|
|
|
|
if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == root && cer->edr.objid == objid && cer->edr.offset == offset) {
|
|
ce->count += count;
|
|
cer->edr.count += count;
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if (superseded)
|
|
ce->superseded = true;
|
|
|
|
goto end;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
old_count = find_extent_data_refcount(Vcb, address, size, root, objid, offset, Irp);
|
|
|
|
if (old_count > 0) {
|
|
cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);
|
|
|
|
if (!cer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
cer->type = TYPE_EXTENT_DATA_REF;
|
|
cer->edr.root = root;
|
|
cer->edr.objid = objid;
|
|
cer->edr.offset = offset;
|
|
cer->edr.count = old_count;
|
|
|
|
InsertTailList(&ce->old_refs, &cer->list_entry);
|
|
}
|
|
|
|
cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);
|
|
|
|
if (!cer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
cer->type = TYPE_EXTENT_DATA_REF;
|
|
cer->edr.root = root;
|
|
cer->edr.objid = objid;
|
|
cer->edr.offset = offset;
|
|
cer->edr.count = old_count + count;
|
|
|
|
InsertTailList(&ce->refs, &cer->list_entry);
|
|
|
|
ce->count += count;
|
|
|
|
if (superseded)
|
|
ce->superseded = true;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
ExReleaseResourceLite(&c->changed_extents_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
void add_changed_extent_ref(chunk* c, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, uint32_t count, bool no_csum) {
|
|
changed_extent* ce;
|
|
changed_extent_ref* cer;
|
|
LIST_ENTRY* le;
|
|
|
|
ce = get_changed_extent_item(c, address, size, no_csum);
|
|
|
|
if (!ce) {
|
|
ERR("get_changed_extent_item failed\n");
|
|
return;
|
|
}
|
|
|
|
le = ce->refs.Flink;
|
|
while (le != &ce->refs) {
|
|
cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);
|
|
|
|
if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == root && cer->edr.objid == objid && cer->edr.offset == offset) {
|
|
ce->count += count;
|
|
cer->edr.count += count;
|
|
return;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);
|
|
|
|
if (!cer) {
|
|
ERR("out of memory\n");
|
|
return;
|
|
}
|
|
|
|
cer->type = TYPE_EXTENT_DATA_REF;
|
|
cer->edr.root = root;
|
|
cer->edr.objid = objid;
|
|
cer->edr.offset = offset;
|
|
cer->edr.count = count;
|
|
|
|
InsertTailList(&ce->refs, &cer->list_entry);
|
|
|
|
ce->count += count;
|
|
}
|
|
|
|
uint64_t find_extent_shared_tree_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
uint64_t inline_rc;
|
|
EXTENT_ITEM* ei;
|
|
uint32_t len;
|
|
uint8_t* ptr;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM)) {
|
|
TRACE("could not find address %I64x in extent tree\n", address);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset != Vcb->superblock.node_size) {
|
|
ERR("extent %I64x had size %I64x, not %x as expected\n", address, tp.item->key.offset, Vcb->superblock.node_size);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x): size was %u, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return 0;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
inline_rc = 0;
|
|
|
|
len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
if (searchkey.obj_type == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) {
|
|
if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {
|
|
ERR("(%I64x,%x,%I64x): size was %u, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
|
|
tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));
|
|
return 0;
|
|
}
|
|
|
|
len -= sizeof(EXTENT_ITEM2);
|
|
ptr += sizeof(EXTENT_ITEM2);
|
|
}
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
ULONG sectlen = get_extent_data_len(secttype);
|
|
uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
|
|
|
|
len--;
|
|
|
|
if (sectlen > len) {
|
|
ERR("(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
|
|
return 0;
|
|
}
|
|
|
|
if (sectlen == 0) {
|
|
ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
|
|
return 0;
|
|
}
|
|
|
|
if (secttype == TYPE_SHARED_BLOCK_REF) {
|
|
SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t));
|
|
|
|
if (sectsbr->offset == parent)
|
|
return 1;
|
|
}
|
|
|
|
len -= sectlen;
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
inline_rc += sectcount;
|
|
}
|
|
|
|
// FIXME - what if old?
|
|
|
|
if (inline_rc == ei->refcount)
|
|
return 0;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_SHARED_BLOCK_REF;
|
|
searchkey.offset = parent;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (!keycmp(searchkey, tp.item->key))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t find_extent_shared_data_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
uint64_t inline_rc;
|
|
EXTENT_ITEM* ei;
|
|
uint32_t len;
|
|
uint8_t* ptr;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM)) {
|
|
TRACE("could not find address %I64x in extent tree\n", address);
|
|
return 0;
|
|
}
|
|
|
|
if (tp.item->size < sizeof(EXTENT_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x): size was %u, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
|
|
return 0;
|
|
}
|
|
|
|
ei = (EXTENT_ITEM*)tp.item->data;
|
|
inline_rc = 0;
|
|
|
|
len = tp.item->size - sizeof(EXTENT_ITEM);
|
|
ptr = (uint8_t*)&ei[1];
|
|
|
|
while (len > 0) {
|
|
uint8_t secttype = *ptr;
|
|
ULONG sectlen = get_extent_data_len(secttype);
|
|
uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
|
|
|
|
len--;
|
|
|
|
if (sectlen > len) {
|
|
ERR("(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
|
|
return 0;
|
|
}
|
|
|
|
if (sectlen == 0) {
|
|
ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
|
|
return 0;
|
|
}
|
|
|
|
if (secttype == TYPE_SHARED_DATA_REF) {
|
|
SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t));
|
|
|
|
if (sectsdr->offset == parent)
|
|
return sectsdr->count;
|
|
}
|
|
|
|
len -= sectlen;
|
|
ptr += sizeof(uint8_t) + sectlen;
|
|
inline_rc += sectcount;
|
|
}
|
|
|
|
// FIXME - what if old?
|
|
|
|
if (inline_rc == ei->refcount)
|
|
return 0;
|
|
|
|
searchkey.obj_id = address;
|
|
searchkey.obj_type = TYPE_SHARED_DATA_REF;
|
|
searchkey.offset = parent;
|
|
|
|
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08lx\n", Status);
|
|
return 0;
|
|
}
|
|
|
|
if (!keycmp(searchkey, tp.item->key)) {
|
|
if (tp.item->size < sizeof(uint32_t))
|
|
ERR("(%I64x,%x,%I64x) has size %u, not %Iu as expected\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(uint32_t));
|
|
else {
|
|
uint32_t* count = (uint32_t*)tp.item->data;
|
|
return *count;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|