mirror of
https://github.com/reactos/reactos.git
synced 2025-07-08 01:07:53 +00:00
2950 lines
101 KiB
C
2950 lines
101 KiB
C
/* Copyright (c) Mark Harmstone 2016
|
|
*
|
|
* This file is part of WinBtrfs.
|
|
*
|
|
* WinBtrfs is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public Licence as published by
|
|
* the Free Software Foundation, either version 3 of the Licence, or
|
|
* (at your option) any later version.
|
|
*
|
|
* WinBtrfs is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public Licence for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public Licence
|
|
* along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "btrfs_drv.h"
|
|
|
|
static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, UINT32 oldcrc32, BTRFS_TIME* now, BOOL ReplaceIfExists, LIST_ENTRY* rollback);
|
|
|
|
static NTSTATUS STDCALL set_basic_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, LIST_ENTRY* rollback) {
|
|
FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ULONG defda;
|
|
BOOL inode_item_changed = FALSE;
|
|
NTSTATUS Status;
|
|
|
|
if (fcb->ads)
|
|
fcb = fcb->par;
|
|
|
|
TRACE("file = %.*S, attributes = %x\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fbi->FileAttributes);
|
|
|
|
if (fbi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY && fcb->type != BTRFS_TYPE_DIRECTORY) {
|
|
WARN("attempted to set FILE_ATTRIBUTE_DIRECTORY on non-directory\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// FIXME - what if FCB is volume or root?
|
|
// FIXME - what about subvol roots?
|
|
|
|
// FIXME - link FILE_ATTRIBUTE_READONLY to st_mode
|
|
// FIXME - handle times == -1
|
|
|
|
// FileAttributes == 0 means don't set - undocumented, but seen in fastfat
|
|
if (fbi->FileAttributes != 0) {
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
defda = get_file_attributes(Vcb, &fcb->inode_item, fcb->subvol, fcb->inode, fcb->type, fcb->filepart.Length > 0 && fcb->filepart.Buffer[0] == '.', TRUE);
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY)
|
|
fbi->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
|
else if (fcb->type == BTRFS_TYPE_SYMLINK)
|
|
fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
// create new xattr
|
|
if (defda != fbi->FileAttributes) {
|
|
char val[64];
|
|
|
|
TRACE("inserting new DOSATTRIB xattr\n");
|
|
sprintf(val, "0x%lx", fbi->FileAttributes);
|
|
|
|
Status = set_xattr(Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8*)val, strlen(val), rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("set_xattr returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
} else
|
|
delete_xattr(Vcb, fcb->subvol, fcb->inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, rollback);
|
|
|
|
fcb->atts = fbi->FileAttributes;
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.st_ctime = now;
|
|
fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
|
|
inode_item_changed = TRUE;
|
|
}
|
|
|
|
// FIXME - CreationTime
|
|
// FIXME - LastAccessTime
|
|
// FIXME - LastWriteTime
|
|
// FIXME - ChangeTime
|
|
|
|
if (inode_item_changed) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
INODE_ITEM* ii;
|
|
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
else
|
|
WARN("couldn't find old INODE_ITEM\n");
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
|
|
|
|
if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
ExFreePool(ii);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL set_disposition_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
|
|
FILE_DISPOSITION_INFORMATION* fdi = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ULONG atts;
|
|
|
|
TRACE("changing delete_on_close to %s for %.*S (fcb %p)\n", fdi->DeleteFile ? "TRUE" : "FALSE", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fcb);
|
|
|
|
atts = fcb->ads ? fcb->par->atts : fcb->atts;
|
|
TRACE("atts = %x\n", atts);
|
|
|
|
if (atts & FILE_ATTRIBUTE_READONLY)
|
|
return STATUS_CANNOT_DELETE;
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0)
|
|
return STATUS_DIRECTORY_NOT_EMPTY;
|
|
|
|
if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForDelete)) {
|
|
WARN("trying to delete file which is being mapped as an image\n");
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE) {
|
|
FIXME("FIXME - subvol deletion not yet supported\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
fcb->delete_on_close = fdi->DeleteFile;
|
|
// FIXME - should this fail if file opened with FILE_DELETE_ON_CLOSE?
|
|
FileObject->DeletePending = fdi->DeleteFile;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS add_inode_extref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, UINT64 index, PANSI_STRING utf8, LIST_ENTRY* rollback) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
INODE_EXTREF* ier;
|
|
NTSTATUS Status;
|
|
|
|
searchkey.obj_id = inode;
|
|
searchkey.obj_type = TYPE_INODE_EXTREF;
|
|
searchkey.offset = calc_crc32c((UINT32)parinode, (UINT8*)utf8->Buffer, utf8->Length);
|
|
|
|
Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
ULONG iersize = tp.item->size + sizeof(INODE_EXTREF) - 1 + utf8->Length;
|
|
UINT8* ier2;
|
|
UINT32 maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
|
|
|
|
if (iersize > maxlen) {
|
|
ERR("item would be too long (%u > %u)\n", iersize, maxlen);
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
ier2 = ExAllocatePoolWithTag(PagedPool, iersize, ALLOC_TAG);
|
|
if (!ier2) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (tp.item->size > 0)
|
|
RtlCopyMemory(ier2, tp.item->data, tp.item->size);
|
|
|
|
ier = (INODE_EXTREF*)&ier2[tp.item->size];
|
|
ier->dir = parinode;
|
|
ier->index = index;
|
|
ier->n = utf8->Length;
|
|
RtlCopyMemory(ier->name, utf8->Buffer, utf8->Length);
|
|
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ier2, iersize, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
} else {
|
|
ier = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_EXTREF) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!ier) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
ier->dir = parinode;
|
|
ier->index = index;
|
|
ier->n = utf8->Length;
|
|
RtlCopyMemory(ier->name, utf8->Buffer, utf8->Length);
|
|
|
|
if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ier, sizeof(INODE_EXTREF) - 1 + utf8->Length, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS add_inode_ref(device_extension* Vcb, root* subvol, UINT64 inode, UINT64 parinode, UINT64 index, PANSI_STRING utf8, LIST_ENTRY* rollback) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
INODE_REF* ir;
|
|
NTSTATUS Status;
|
|
|
|
searchkey.obj_id = inode;
|
|
searchkey.obj_type = TYPE_INODE_REF;
|
|
searchkey.offset = parinode;
|
|
|
|
Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
ULONG irsize = tp.item->size + sizeof(INODE_REF) - 1 + utf8->Length;
|
|
UINT8* ir2;
|
|
UINT32 maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
|
|
|
|
if (irsize > maxlen) {
|
|
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
|
|
TRACE("INODE_REF too long, creating INODE_EXTREF\n");
|
|
free_traverse_ptr(&tp);
|
|
return add_inode_extref(Vcb, subvol, inode, parinode, index, utf8, rollback);
|
|
} else {
|
|
ERR("item would be too long (%u > %u)\n", irsize, maxlen);
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
ir2 = ExAllocatePoolWithTag(PagedPool, irsize, ALLOC_TAG);
|
|
if (!ir2) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (tp.item->size > 0)
|
|
RtlCopyMemory(ir2, tp.item->data, tp.item->size);
|
|
|
|
ir = (INODE_REF*)&ir2[tp.item->size];
|
|
ir->index = index;
|
|
ir->n = utf8->Length;
|
|
RtlCopyMemory(ir->name, utf8->Buffer, utf8->Length);
|
|
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ir2, irsize, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
} else {
|
|
ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!ir) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
ir->index = index;
|
|
ir->n = utf8->Length;
|
|
RtlCopyMemory(ir->name, utf8->Buffer, utf8->Length);
|
|
|
|
if (!insert_tree_item(Vcb, subvol, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ir, sizeof(INODE_REF) - 1 + ir->n, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS get_fcb_from_dir_item(device_extension* Vcb, fcb** pfcb, fcb* parent, root* subvol, DIR_ITEM* di) {
|
|
LIST_ENTRY* le;
|
|
fcb* sf2;
|
|
struct _fcb* c;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
|
|
le = parent->children.Flink;
|
|
|
|
while (le != &parent->children) {
|
|
c = CONTAINING_RECORD(le, struct _fcb, list_entry);
|
|
|
|
if (c->refcount > 0 && c->inode == di->key.obj_id && c->subvol == subvol) {
|
|
c->refcount++;
|
|
#ifdef DEBUG_FCB_REFCOUNTS
|
|
WARN("fcb %p: refcount now %i (%.*S)\n", c, c->refcount, c->full_filename.Length / sizeof(WCHAR), c->full_filename.Buffer);
|
|
#endif
|
|
*pfcb = c;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
sf2 = create_fcb();
|
|
if (!sf2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
sf2->Vcb = Vcb;
|
|
|
|
sf2->utf8.Length = sf2->utf8.MaximumLength = di->n;
|
|
sf2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, di->n, ALLOC_TAG);
|
|
if (!sf2->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(sf2->utf8.Buffer, di->name, di->n);
|
|
|
|
sf2->par = parent;
|
|
|
|
parent->refcount++;
|
|
#ifdef DEBUG_FCB_REFCOUNTS
|
|
WARN("fcb %p: refcount now %i (%.*S)\n", parent, parent->refcount, parent->full_filename.Length / sizeof(WCHAR), parent->full_filename.Buffer);
|
|
#endif
|
|
|
|
if (di->key.obj_type == TYPE_ROOT_ITEM) {
|
|
root* fcbroot = Vcb->roots;
|
|
while (fcbroot && fcbroot->id != di->key.obj_id)
|
|
fcbroot = fcbroot->next;
|
|
|
|
sf2->subvol = fcbroot;
|
|
sf2->inode = SUBVOL_ROOT_INODE;
|
|
} else {
|
|
sf2->subvol = subvol;
|
|
sf2->inode = di->key.obj_id;
|
|
}
|
|
|
|
sf2->type = di->type;
|
|
|
|
if (Vcb->fcbs)
|
|
Vcb->fcbs->prev = sf2;
|
|
|
|
sf2->next = Vcb->fcbs;
|
|
Vcb->fcbs = sf2;
|
|
|
|
sf2->name_offset = parent->full_filename.Length / sizeof(WCHAR);
|
|
|
|
if (parent != Vcb->root_fcb)
|
|
sf2->name_offset++;
|
|
|
|
InsertTailList(&parent->children, &sf2->list_entry);
|
|
|
|
searchkey.obj_id = sf2->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, sf2->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
free_fcb(sf2);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
|
|
ERR("couldn't find INODE_ITEM for inode %llx in subvol %llx\n", sf2->inode, sf2->subvol->id);
|
|
free_traverse_ptr(&tp);
|
|
free_fcb(sf2);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (tp.item->size > 0)
|
|
RtlCopyMemory(&sf2->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
// This is just a quick function for the sake of move_across_subvols. As such, we don't bother
|
|
// with sf2->atts, sf2->sd, or sf2->full_filename.
|
|
|
|
*pfcb = sf2;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// static LONG get_tree_count(device_extension* Vcb, LIST_ENTRY* tc) {
|
|
// LONG rc = 0;
|
|
// LIST_ENTRY* le = Vcb->trees.Flink;
|
|
//
|
|
// while (le != &Vcb->trees) {
|
|
// tree* t = CONTAINING_RECORD(le, tree, list_entry);
|
|
//
|
|
// rc += t->refcount;
|
|
//
|
|
// le = le->Flink;
|
|
// }
|
|
//
|
|
// le = tc->Flink;
|
|
// while (le != tc) {
|
|
// tree_cache* tc2 = CONTAINING_RECORD(le, tree_cache, list_entry);
|
|
// tree* t;
|
|
//
|
|
// rc--;
|
|
//
|
|
// t = tc2->tree->parent;
|
|
// while (t) {
|
|
// rc--;
|
|
// t = t->parent;
|
|
// }
|
|
//
|
|
// le = le->Flink;
|
|
// }
|
|
//
|
|
// return rc;
|
|
// }
|
|
|
|
static NTSTATUS STDCALL move_inode_across_subvols(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, UINT64 inode, UINT64 oldparinode, PANSI_STRING utf8, UINT32 crc32, BTRFS_TIME* now, LIST_ENTRY* rollback) {
|
|
UINT64 oldindex, index;
|
|
UINT32 oldcrc32;
|
|
INODE_ITEM* ii;
|
|
BOOL has_hardlink = FALSE;
|
|
DIR_ITEM* di;
|
|
KEY searchkey;
|
|
traverse_ptr tp, next_tp;
|
|
NTSTATUS Status;
|
|
BOOL b;
|
|
|
|
// move INODE_ITEM
|
|
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
fcb->inode_item.st_ctime = *now;
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
if (fcb->inode_item.st_nlink > 1) {
|
|
fcb->inode_item.st_nlink--;
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
|
|
|
|
if (!insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
has_hardlink = TRUE;
|
|
}
|
|
} else {
|
|
WARN("couldn't find old INODE_ITEM\n");
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
fcb->inode_item.st_nlink = 1;
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
|
|
|
|
if (!insert_tree_item(Vcb, destsubvol, inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fcb->utf8.Buffer, (ULONG)fcb->utf8.Length);
|
|
|
|
// delete old DIR_ITEM
|
|
|
|
Status = delete_dir_item(Vcb, fcb->subvol, oldparinode, oldcrc32, &fcb->utf8, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_dir_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// create new DIR_ITEM
|
|
|
|
di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
di->key.obj_id = inode;
|
|
di->key.obj_type = TYPE_INODE_ITEM;
|
|
di->key.offset = 0;
|
|
di->transid = Vcb->superblock.generation;
|
|
di->m = 0;
|
|
di->n = utf8->Length;
|
|
di->type = fcb->type;
|
|
RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
|
|
|
|
Status = add_dir_item(Vcb, destsubvol, destinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8->Length, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_dir_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, oldparinode, &fcb->utf8, &oldindex, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_inode_ref returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// delete DIR_INDEX
|
|
|
|
if (oldindex == 0) {
|
|
WARN("couldn't find old INODE_REF\n");
|
|
} else {
|
|
searchkey.obj_id = oldparinode;
|
|
searchkey.obj_type = TYPE_DIR_INDEX;
|
|
searchkey.offset = oldindex;
|
|
|
|
Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key))
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
else
|
|
WARN("couldn't find old DIR_INDEX\n");
|
|
|
|
free_traverse_ptr(&tp);
|
|
}
|
|
|
|
// get new index
|
|
|
|
searchkey.obj_id = destinode;
|
|
searchkey.obj_type = TYPE_DIR_INDEX + 1;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(Vcb, destsubvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
if (find_prev_item(Vcb, &tp, &next_tp, FALSE)) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
|
|
TRACE("moving back to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
}
|
|
}
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_DIR_INDEX) {
|
|
index = tp.item->key.offset + 1;
|
|
} else
|
|
index = 2;
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
// create INODE_REF
|
|
|
|
Status = add_inode_ref(Vcb, destsubvol, inode, destinode, index, utf8, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_inode_ref returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// create DIR_INDEX
|
|
|
|
di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
di->key.obj_id = inode;
|
|
di->key.obj_type = TYPE_INODE_ITEM;
|
|
di->key.offset = 0;
|
|
di->transid = Vcb->superblock.generation;
|
|
di->m = 0;
|
|
di->n = utf8->Length;
|
|
di->type = fcb->type;
|
|
RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
|
|
|
|
if (!insert_tree_item(Vcb, destsubvol, destinode, TYPE_DIR_INDEX, index, di, sizeof(DIR_ITEM) - 1 + utf8->Length, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
// move XATTR_ITEMs
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_XATTR_ITEM;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
do {
|
|
if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM && tp.item->size > 0) {
|
|
di = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(di, tp.item->data, tp.item->size);
|
|
|
|
if (!insert_tree_item(Vcb, destsubvol, inode, TYPE_XATTR_ITEM, tp.item->key.offset, di, tp.item->size, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (!has_hardlink)
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
}
|
|
|
|
b = find_next_item(Vcb, &tp, &next_tp, FALSE);
|
|
if (b) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
|
|
if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
|
|
break;
|
|
}
|
|
} while (b);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
// do extents
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_EXTENT_DATA;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
do {
|
|
if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_EXTENT_DATA) {
|
|
if (tp.item->size < sizeof(EXTENT_DATA)) {
|
|
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
|
|
} else {
|
|
EXTENT_DATA* ed = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);
|
|
|
|
if (!ed) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ed, tp.item->data, tp.item->size);
|
|
|
|
// FIXME - update ed's generation
|
|
|
|
if (!insert_tree_item(Vcb, destsubvol, inode, TYPE_EXTENT_DATA, tp.item->key.offset, ed, tp.item->size, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && tp.item->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {
|
|
EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
|
|
|
|
if (ed2->address != 0) {
|
|
Status = add_extent_ref(Vcb, ed2->address, ed2->size, destsubvol, inode, tp.item->key.offset, rollback);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_extent_ref returned %08x\n", Status);
|
|
free_traverse_ptr(&tp);
|
|
return Status;
|
|
}
|
|
|
|
if (!has_hardlink) {
|
|
Status = remove_extent_ref(Vcb, ed2->address, ed2->size, fcb->subvol, fcb->inode, tp.item->key.offset, NULL, rollback);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("remove_extent_ref returned %08x\n", Status);
|
|
free_traverse_ptr(&tp);
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!has_hardlink)
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
}
|
|
}
|
|
|
|
b = find_next_item(Vcb, &tp, &next_tp, FALSE);
|
|
if (b) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
|
|
if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_EXTENT_DATA)
|
|
break;
|
|
}
|
|
} while (b);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
typedef struct {
|
|
fcb* fcb;
|
|
UINT8 level;
|
|
UINT32 crc32;
|
|
UINT64 newinode;
|
|
UINT64 newparinode;
|
|
BOOL subvol;
|
|
ANSI_STRING utf8;
|
|
LIST_ENTRY list_entry;
|
|
} dir_list;
|
|
|
|
static NTSTATUS add_to_dir_list(fcb* fcb, UINT8 level, LIST_ENTRY* dl, UINT64 newparinode, BOOL* empty) {
|
|
KEY searchkey;
|
|
traverse_ptr tp, next_tp;
|
|
BOOL b;
|
|
NTSTATUS Status;
|
|
|
|
*empty = TRUE;
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_DIR_INDEX;
|
|
searchkey.offset = 2;
|
|
|
|
Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
do {
|
|
if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_DIR_INDEX) {
|
|
if (tp.item->size < sizeof(DIR_ITEM)) {
|
|
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
|
|
} else {
|
|
DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
|
|
struct _fcb* child;
|
|
dir_list* dl2;
|
|
|
|
if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->n + di->m) {
|
|
ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
} else {
|
|
if (di->key.obj_type == TYPE_INODE_ITEM || di->key.obj_type == TYPE_ROOT_ITEM) {
|
|
if (di->key.obj_type == TYPE_ROOT_ITEM)
|
|
TRACE("moving subvol %llx\n", di->key.obj_id);
|
|
else
|
|
TRACE("moving inode %llx\n", di->key.obj_id);
|
|
|
|
*empty = FALSE;
|
|
|
|
Status = get_fcb_from_dir_item(fcb->Vcb, &child, fcb, fcb->subvol, di);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("get_fcb_from_dir_item returned %08x\n", Status);
|
|
free_traverse_ptr(&tp);
|
|
return Status;
|
|
}
|
|
|
|
dl2 = ExAllocatePoolWithTag(PagedPool, sizeof(dir_list), ALLOC_TAG);
|
|
if (!dl2) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
dl2->fcb = child;
|
|
dl2->level = level;
|
|
dl2->newparinode = newparinode;
|
|
dl2->subvol = di->key.obj_type == TYPE_ROOT_ITEM;
|
|
|
|
dl2->utf8.Length = dl2->utf8.MaximumLength = di->n;
|
|
dl2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, dl2->utf8.MaximumLength, ALLOC_TAG);
|
|
if (!dl2->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(dl2->utf8.Buffer, di->name, dl2->utf8.Length);
|
|
dl2->crc32 = calc_crc32c(0xfffffffe, (UINT8*)dl2->utf8.Buffer, (ULONG)dl2->utf8.Length);
|
|
|
|
InsertTailList(dl, &dl2->list_entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
|
|
if (b) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
|
|
if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
|
|
break;
|
|
}
|
|
} while (b);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL move_across_subvols(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, BTRFS_TIME* now, LIST_ENTRY* rollback) {
|
|
UINT64 inode, oldparinode;
|
|
NTSTATUS Status;
|
|
LIST_ENTRY dl;
|
|
|
|
if (destsubvol->lastinode == 0)
|
|
get_last_inode(Vcb, destsubvol);
|
|
|
|
inode = destsubvol->lastinode + 1;
|
|
destsubvol->lastinode++;
|
|
|
|
oldparinode = fcb->subvol == fcb->par->subvol ? fcb->par->inode : SUBVOL_ROOT_INODE;
|
|
|
|
Status = move_inode_across_subvols(Vcb, fcb, destsubvol, destinode, inode, oldparinode, utf8, crc32, now, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("move_inode_across_subvols returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) {
|
|
BOOL b, empty;
|
|
UINT8 level, max_level;
|
|
LIST_ENTRY* le;
|
|
|
|
InitializeListHead(&dl);
|
|
|
|
add_to_dir_list(fcb, 0, &dl, inode, &b);
|
|
|
|
level = 0;
|
|
do {
|
|
empty = TRUE;
|
|
|
|
le = dl.Flink;
|
|
while (le != &dl) {
|
|
dir_list* dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
|
|
|
|
if (dl2->level == level && !dl2->subvol) {
|
|
inode++;
|
|
destsubvol->lastinode++;
|
|
|
|
dl2->newinode = inode;
|
|
|
|
if (dl2->fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
add_to_dir_list(dl2->fcb, level+1, &dl, dl2->newinode, &b);
|
|
if (!b) empty = FALSE;
|
|
}
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (!empty) level++;
|
|
} while (!empty);
|
|
|
|
max_level = level;
|
|
|
|
for (level = 0; level <= max_level; level++) {
|
|
TRACE("level %u\n", level);
|
|
|
|
le = dl.Flink;
|
|
while (le != &dl) {
|
|
dir_list* dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
|
|
|
|
if (dl2->level == level) {
|
|
if (dl2->subvol) {
|
|
TRACE("subvol %llx\n", dl2->fcb->subvol->id);
|
|
|
|
Status = move_subvol(Vcb, dl2->fcb, destsubvol, dl2->newparinode, &dl2->utf8, dl2->crc32, dl2->crc32, now, FALSE, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("move_subvol returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
} else {
|
|
TRACE("inode %llx\n", dl2->fcb->inode);
|
|
|
|
Status = move_inode_across_subvols(Vcb, dl2->fcb, destsubvol, dl2->newparinode, dl2->newinode, dl2->fcb->par->inode, &dl2->utf8, dl2->crc32, now, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("move_inode_across_subvols returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
while (!IsListEmpty(&dl)) {
|
|
dir_list* dl2;
|
|
|
|
le = RemoveHeadList(&dl);
|
|
dl2 = CONTAINING_RECORD(le, dir_list, list_entry);
|
|
|
|
ExFreePool(dl2->utf8.Buffer);
|
|
free_fcb(dl2->fcb);
|
|
|
|
ExFreePool(dl2);
|
|
}
|
|
}
|
|
|
|
fcb->inode = inode;
|
|
fcb->subvol = destsubvol;
|
|
|
|
fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = *now;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS delete_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, UINT64 parinode, PANSI_STRING utf8, UINT64* index, LIST_ENTRY* rollback) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
|
|
searchkey.obj_id = parsubvolid;
|
|
searchkey.obj_type = TYPE_ROOT_REF;
|
|
searchkey.offset = subvolid;
|
|
|
|
Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
if (tp.item->size < sizeof(ROOT_REF)) {
|
|
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF));
|
|
} else {
|
|
ROOT_REF* rr;
|
|
ULONG len;
|
|
|
|
rr = (ROOT_REF*)tp.item->data;
|
|
len = tp.item->size;
|
|
|
|
do {
|
|
ULONG itemlen;
|
|
|
|
if (len < sizeof(ROOT_REF) || len < sizeof(ROOT_REF) - 1 + rr->n) {
|
|
ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
break;
|
|
}
|
|
|
|
itemlen = sizeof(ROOT_REF) - sizeof(char) + rr->n;
|
|
|
|
if (rr->dir == parinode && rr->n == utf8->Length && RtlCompareMemory(rr->name, utf8->Buffer, rr->n) == rr->n) {
|
|
ULONG newlen = tp.item->size - itemlen;
|
|
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
if (newlen == 0) {
|
|
TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
} else {
|
|
UINT8 *newrr = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *rroff;
|
|
|
|
if (!newrr) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
TRACE("modifying (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
|
|
if ((UINT8*)rr > tp.item->data) {
|
|
RtlCopyMemory(newrr, tp.item->data, (UINT8*)rr - tp.item->data);
|
|
rroff = newrr + ((UINT8*)rr - tp.item->data);
|
|
} else {
|
|
rroff = newrr;
|
|
}
|
|
|
|
if ((UINT8*)&rr->name[rr->n] - tp.item->data < tp.item->size)
|
|
RtlCopyMemory(rroff, &rr->name[rr->n], tp.item->size - ((UINT8*)&rr->name[rr->n] - tp.item->data));
|
|
|
|
insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newrr, newlen, NULL, rollback);
|
|
}
|
|
|
|
if (index)
|
|
*index = rr->index;
|
|
|
|
break;
|
|
}
|
|
|
|
if (len > itemlen) {
|
|
len -= itemlen;
|
|
rr = (ROOT_REF*)&rr->name[rr->n];
|
|
} else
|
|
break;
|
|
} while (len > 0);
|
|
}
|
|
} else {
|
|
WARN("could not find ROOT_REF entry for subvol %llx in %llx\n", searchkey.offset, searchkey.obj_id);
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS add_root_ref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, ROOT_REF* rr, LIST_ENTRY* rollback) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
|
|
searchkey.obj_id = parsubvolid;
|
|
searchkey.obj_type = TYPE_ROOT_REF;
|
|
searchkey.offset = subvolid;
|
|
|
|
Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
ULONG rrsize = tp.item->size + sizeof(ROOT_REF) - 1 + rr->n;
|
|
UINT8* rr2;
|
|
|
|
rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);
|
|
if (!rr2) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (tp.item->size > 0)
|
|
RtlCopyMemory(rr2, tp.item->data, tp.item->size);
|
|
|
|
RtlCopyMemory(rr2 + tp.item->size, rr, sizeof(ROOT_REF) - 1 + rr->n);
|
|
ExFreePool(rr);
|
|
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
if (!insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr2, rrsize, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
ExFreePool(rr2);
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
} else {
|
|
if (!insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr, sizeof(ROOT_REF) - 1 + rr->n, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
ExFreePool(rr);
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL update_root_backref(device_extension* Vcb, UINT64 subvolid, UINT64 parsubvolid, LIST_ENTRY* rollback) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
UINT8* data;
|
|
ULONG datalen;
|
|
NTSTATUS Status;
|
|
|
|
searchkey.obj_id = parsubvolid;
|
|
searchkey.obj_type = TYPE_ROOT_REF;
|
|
searchkey.offset = subvolid;
|
|
|
|
Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&tp.item->key, &searchkey) && tp.item->size > 0) {
|
|
datalen = tp.item->size;
|
|
|
|
data = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);
|
|
if (!data) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(data, tp.item->data, datalen);
|
|
} else {
|
|
datalen = 0;
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
searchkey.obj_id = subvolid;
|
|
searchkey.obj_type = TYPE_ROOT_BACKREF;
|
|
searchkey.offset = parsubvolid;
|
|
|
|
Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&tp.item->key, &searchkey))
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
if (datalen > 0) {
|
|
if (!insert_tree_item(Vcb, Vcb->root_root, subvolid, TYPE_ROOT_BACKREF, parsubvolid, data, datalen, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
ExFreePool(data);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL move_subvol(device_extension* Vcb, fcb* fcb, root* destsubvol, UINT64 destinode, PANSI_STRING utf8, UINT32 crc32, UINT32 oldcrc32, BTRFS_TIME* now, BOOL ReplaceIfExists, LIST_ENTRY* rollback) {
|
|
DIR_ITEM* di;
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
UINT64 oldindex, index;
|
|
ROOT_REF* rr;
|
|
|
|
// delete old DIR_ITEM
|
|
|
|
Status = delete_dir_item(Vcb, fcb->par->subvol, fcb->par->inode, oldcrc32, &fcb->utf8, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_dir_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// create new DIR_ITEM
|
|
|
|
di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
di->key.obj_id = fcb->subvol->id;
|
|
di->key.obj_type = TYPE_ROOT_ITEM;
|
|
di->key.offset = 0;
|
|
di->transid = Vcb->superblock.generation;
|
|
di->m = 0;
|
|
di->n = utf8->Length;
|
|
di->type = fcb->type;
|
|
RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
|
|
|
|
Status = add_dir_item(Vcb, destsubvol, destinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8->Length, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_dir_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// delete old ROOT_REF
|
|
|
|
oldindex = 0;
|
|
|
|
Status = delete_root_ref(Vcb, fcb->subvol->id, fcb->par->subvol->id, fcb->par->inode, &fcb->utf8, &oldindex, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_root_ref returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
TRACE("root index = %llx\n", oldindex);
|
|
|
|
// delete old DIR_INDEX
|
|
|
|
if (oldindex != 0) {
|
|
searchkey.obj_id = fcb->par->inode;
|
|
searchkey.obj_type = TYPE_DIR_INDEX;
|
|
searchkey.offset = oldindex;
|
|
|
|
Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&searchkey, &tp.item->key)) {
|
|
TRACE("deleting (%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
} else {
|
|
WARN("could not find old DIR_INDEX entry\n");
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
}
|
|
|
|
// create new DIR_INDEX
|
|
|
|
if (fcb->par->subvol == destsubvol && fcb->par->inode == destinode) {
|
|
index = oldindex;
|
|
} else {
|
|
index = find_next_dir_index(Vcb, destsubvol, destinode);
|
|
}
|
|
|
|
di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
di->key.obj_id = fcb->subvol->id;
|
|
di->key.obj_type = TYPE_ROOT_ITEM;
|
|
di->key.offset = 0;
|
|
di->transid = Vcb->superblock.generation;
|
|
di->m = 0;
|
|
di->n = utf8->Length;
|
|
di->type = fcb->type;
|
|
RtlCopyMemory(di->name, utf8->Buffer, utf8->Length);
|
|
|
|
if (!insert_tree_item(Vcb, destsubvol, destinode, TYPE_DIR_INDEX, index, di, sizeof(DIR_ITEM) - 1 + utf8->Length, NULL, rollback)) {
|
|
ERR("error - failed to insert item\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
// create new ROOT_REF
|
|
|
|
rr = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_REF) - 1 + utf8->Length, ALLOC_TAG);
|
|
if (!rr) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
rr->dir = destinode;
|
|
rr->index = index;
|
|
rr->n = utf8->Length;
|
|
RtlCopyMemory(rr->name, utf8->Buffer, utf8->Length);
|
|
|
|
Status = add_root_ref(Vcb, fcb->subvol->id, destsubvol->id, rr, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_root_ref returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = update_root_backref(Vcb, fcb->subvol->id, fcb->par->subvol->id, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("update_root_backref 1 returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (fcb->par->subvol != destsubvol) {
|
|
Status = update_root_backref(Vcb, fcb->subvol->id, destsubvol->id, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("update_root_backref 1 returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
fcb->par->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fcb->par->subvol->root_item.ctime = *now;
|
|
}
|
|
|
|
destsubvol->root_item.ctransid = Vcb->superblock.generation;
|
|
destsubvol->root_item.ctime = *now;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static BOOL has_open_children(fcb* fcb) {
|
|
LIST_ENTRY* le = fcb->children.Flink;
|
|
struct _fcb* c;
|
|
|
|
while (le != &fcb->children) {
|
|
c = CONTAINING_RECORD(le, struct _fcb, list_entry);
|
|
|
|
if (c->refcount > 0) {
|
|
if (c->open_count > 0)
|
|
return TRUE;
|
|
|
|
if (has_open_children(c))
|
|
return TRUE;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static NTSTATUS STDCALL set_rename_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, BOOL ReplaceIfExists, LIST_ENTRY* rollback) {
|
|
FILE_RENAME_INFORMATION* fri = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb *fcb = FileObject->FsContext, *tfofcb, *oldparfcb, *oldfcb;
|
|
root* parsubvol;
|
|
UINT64 parinode, dirpos;
|
|
WCHAR* fn;
|
|
UNICODE_STRING fnus;
|
|
ULONG fnlen, utf8len, disize;
|
|
NTSTATUS Status;
|
|
ANSI_STRING utf8;
|
|
UINT32 crc32, oldcrc32;
|
|
KEY searchkey;
|
|
traverse_ptr tp, next_tp;
|
|
DIR_ITEM* di;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
BOOL across_directories;
|
|
INODE_ITEM* ii;
|
|
|
|
// FIXME - MSDN says we should be able to rename streams here, but I can't get it to work.
|
|
|
|
TRACE(" tfo = %p\n", tfo);
|
|
TRACE(" ReplaceIfExists = %u\n", ReplaceIfExists);
|
|
TRACE(" RootDirectory = %p\n", fri->RootDirectory);
|
|
TRACE(" FileName = %.*S\n", fri->FileNameLength / sizeof(WCHAR), fri->FileName);
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
utf8.Buffer = NULL;
|
|
|
|
if (!fcb->par) {
|
|
ERR("error - tried to rename file with no parent\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
fn = fri->FileName;
|
|
fnlen = fri->FileNameLength / sizeof(WCHAR);
|
|
|
|
if (!tfo) {
|
|
parsubvol = fcb->par->subvol;
|
|
parinode = fcb->par->inode;
|
|
tfofcb = NULL;
|
|
|
|
across_directories = FALSE;
|
|
} else {
|
|
LONG i;
|
|
|
|
tfofcb = tfo->FsContext;
|
|
parsubvol = tfofcb->subvol;
|
|
parinode = tfofcb->inode;
|
|
|
|
for (i = fnlen - 1; i >= 0; i--) {
|
|
if (fri->FileName[i] == '\\' || fri->FileName[i] == '/') {
|
|
fn = &fri->FileName[i+1];
|
|
fnlen = (fri->FileNameLength / sizeof(WCHAR)) - i - 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
across_directories = parsubvol != fcb->par->subvol || parinode != fcb->par->inode;
|
|
}
|
|
|
|
fnus.Buffer = fn;
|
|
fnus.Length = fnus.MaximumLength = fnlen * sizeof(WCHAR);
|
|
|
|
TRACE("fnus = %.*S\n", fnus.Length / sizeof(WCHAR), fnus.Buffer);
|
|
|
|
Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
utf8.MaximumLength = utf8.Length = utf8len;
|
|
utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);
|
|
if (!utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = RtlUnicodeToUTF8N(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
crc32 = calc_crc32c(0xfffffffe, (UINT8*)utf8.Buffer, (ULONG)utf8.Length);
|
|
|
|
// FIXME - set to crc32 if utf8 and oldutf8 are identical
|
|
oldcrc32 = calc_crc32c(0xfffffffe, (UINT8*)fcb->utf8.Buffer, (ULONG)fcb->utf8.Length);
|
|
|
|
// TRACE("utf8 fn = %s (%08x), old utf8 fn = %s (%08x)\n", utf8, crc32, oldutf8, oldcrc32);
|
|
|
|
oldfcb = NULL;
|
|
|
|
Status = get_fcb(Vcb, &oldfcb, &fnus, tfo ? tfo->FsContext : NULL, FALSE);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
WARN("destination file %.*S already exists\n", oldfcb->full_filename.Length / sizeof(WCHAR), oldfcb->full_filename.Buffer);
|
|
|
|
if (fcb != oldfcb && !(oldfcb->open_count == 0 && oldfcb->deleted)) {
|
|
if (!ReplaceIfExists) {
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto end;
|
|
} else if (oldfcb->open_count >= 1 && !oldfcb->deleted) {
|
|
WARN("trying to overwrite open file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (oldfcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
WARN("trying to overwrite directory\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (has_open_children(fcb)) {
|
|
WARN("trying to rename file with open children\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (oldfcb) {
|
|
Status = delete_fcb(oldfcb, NULL, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fcb returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE) {
|
|
UNICODE_STRING filename;
|
|
|
|
filename.Buffer = fn;
|
|
filename.MaximumLength = filename.Length = fnlen * sizeof(WCHAR);
|
|
|
|
Status = move_subvol(Vcb, fcb, tfofcb->subvol, tfofcb->inode, &utf8, crc32, oldcrc32, &now, ReplaceIfExists, rollback);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("move_subvol returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
} else if (parsubvol != fcb->subvol) {
|
|
UNICODE_STRING filename;
|
|
|
|
filename.Buffer = fn;
|
|
filename.MaximumLength = filename.Length = fnlen * sizeof(WCHAR);
|
|
|
|
Status = move_across_subvols(Vcb, fcb, tfofcb->subvol, tfofcb->inode, &utf8, crc32, &now, rollback);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("move_across_subvols returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
} else {
|
|
UINT64 oldindex;
|
|
INODE_ITEM* ii;
|
|
|
|
// delete old DIR_ITEM entry
|
|
|
|
Status = delete_dir_item(Vcb, fcb->subvol, fcb->par->inode, oldcrc32, &fcb->utf8, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_dir_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// FIXME - make sure fcb's filepart matches the case on disk
|
|
|
|
// create new DIR_ITEM entry
|
|
|
|
di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + utf8.Length, ALLOC_TAG);
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
di->key.obj_id = fcb->inode;
|
|
di->key.obj_type = TYPE_INODE_ITEM;
|
|
di->key.offset = 0;
|
|
di->transid = Vcb->superblock.generation;
|
|
di->m = 0;
|
|
di->n = utf8.Length;
|
|
di->type = fcb->type;
|
|
RtlCopyMemory(di->name, utf8.Buffer, utf8.Length);
|
|
|
|
Status = add_dir_item(Vcb, parsubvol, parinode, crc32, di, sizeof(DIR_ITEM) - 1 + utf8.Length, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_dir_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
oldindex = 0;
|
|
|
|
Status = delete_inode_ref(Vcb, fcb->subvol, fcb->inode, fcb->par->inode, &fcb->utf8, &oldindex, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_inode_ref returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
// delete old DIR_INDEX entry
|
|
|
|
if (oldindex != 0) {
|
|
searchkey.obj_id = fcb->par->inode;
|
|
searchkey.obj_type = TYPE_DIR_INDEX;
|
|
searchkey.offset = oldindex;
|
|
|
|
Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
if (!keycmp(&tp.item->key, &searchkey))
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
else {
|
|
WARN("couldn't find DIR_INDEX\n");
|
|
}
|
|
|
|
free_traverse_ptr(&tp);
|
|
} else {
|
|
WARN("couldn't get index from INODE_REF\n");
|
|
}
|
|
|
|
// create new DIR_INDEX entry
|
|
|
|
if (parsubvol != fcb->par->subvol || parinode != fcb->par->inode) {
|
|
searchkey.obj_id = parinode;
|
|
searchkey.obj_type = TYPE_DIR_INDEX + 1;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(Vcb, parsubvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
dirpos = 2;
|
|
|
|
do {
|
|
TRACE("%llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_DIR_INDEX) {
|
|
dirpos = tp.item->key.offset + 1;
|
|
break;
|
|
}
|
|
|
|
if (find_prev_item(Vcb, &tp, &next_tp, FALSE)) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
} else
|
|
break;
|
|
} while (tp.item->key.obj_id >= parinode && tp.item->key.obj_type >= TYPE_DIR_INDEX);
|
|
|
|
free_traverse_ptr(&tp);
|
|
} else
|
|
dirpos = oldindex;
|
|
|
|
disize = (ULONG)(sizeof(DIR_ITEM) - 1 + utf8.Length);
|
|
di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);
|
|
if (!di) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
di->key.obj_id = fcb->inode;
|
|
di->key.obj_type = TYPE_INODE_ITEM;
|
|
di->key.offset = 0;
|
|
di->transid = Vcb->superblock.generation;
|
|
di->m = 0;
|
|
di->n = (UINT16)utf8.Length;
|
|
di->type = fcb->type;
|
|
RtlCopyMemory(di->name, utf8.Buffer, utf8.Length);
|
|
|
|
if (!insert_tree_item(Vcb, parsubvol, parinode, TYPE_DIR_INDEX, dirpos, di, disize, NULL, rollback))
|
|
ERR("error - failed to insert item\n");
|
|
|
|
// create new INODE_REF entry
|
|
|
|
Status = add_inode_ref(Vcb, parsubvol, fcb->inode, parinode, dirpos, &utf8, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_inode_ref returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, parsubvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
|
|
|
|
if (!insert_tree_item(Vcb, parsubvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback)) {
|
|
WARN("insert_tree_item failed\n");
|
|
}
|
|
}
|
|
|
|
// update directory INODE_ITEMs
|
|
|
|
fcb->par->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->par->inode_item.sequence++;
|
|
fcb->par->inode_item.st_ctime = now;
|
|
fcb->par->inode_item.st_mtime = now;
|
|
|
|
TRACE("fcb->par->inode_item.st_size was %llx\n", fcb->par->inode_item.st_size);
|
|
if (!tfofcb || (fcb->par->inode == tfofcb->inode && fcb->par->subvol == tfofcb->subvol)) {
|
|
fcb->par->inode_item.st_size += 2 * (utf8.Length - fcb->utf8.Length);
|
|
} else {
|
|
fcb->par->inode_item.st_size -= 2 * fcb->utf8.Length;
|
|
TRACE("tfofcb->inode_item.st_size was %llx\n", tfofcb->inode_item.st_size);
|
|
tfofcb->inode_item.st_size += 2 * utf8.Length;
|
|
TRACE("tfofcb->inode_item.st_size now %llx\n", tfofcb->inode_item.st_size);
|
|
tfofcb->inode_item.transid = Vcb->superblock.generation;
|
|
tfofcb->inode_item.sequence++;
|
|
tfofcb->inode_item.st_ctime = now;
|
|
tfofcb->inode_item.st_mtime = now;
|
|
}
|
|
TRACE("fcb->par->inode_item.st_size now %llx\n", fcb->par->inode_item.st_size);
|
|
|
|
if (oldfcb && oldfcb->par != fcb->par) {
|
|
TRACE("oldfcb->par->inode_item.st_size was %llx\n", oldfcb->par->inode_item.st_size);
|
|
oldfcb->par->inode_item.st_size -= 2 * oldfcb->utf8.Length;
|
|
TRACE("oldfcb->par->inode_item.st_size now %llx\n", oldfcb->par->inode_item.st_size);
|
|
}
|
|
|
|
searchkey.obj_id = fcb->par->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, fcb->par->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->par->inode_item, sizeof(INODE_ITEM));
|
|
|
|
if (!insert_tree_item(Vcb, fcb->par->subvol, fcb->par->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback))
|
|
WARN("insert_tree_item failed\n");
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
if (tfofcb && (fcb->par->inode != tfofcb->inode || fcb->par->subvol != tfofcb->subvol)) {
|
|
searchkey.obj_id = tfofcb->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0xffffffffffffffff;
|
|
|
|
Status = find_item(Vcb, tfofcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &tfofcb->inode_item, sizeof(INODE_ITEM));
|
|
|
|
if (!insert_tree_item(Vcb, tfofcb->subvol, tfofcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback))
|
|
WARN("insert_tree_item failed\n");
|
|
|
|
free_traverse_ptr(&tp);
|
|
}
|
|
|
|
fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
|
|
// FIXME - handle overwrite by rename here
|
|
FsRtlNotifyFullReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fcb->full_filename, fcb->name_offset * sizeof(WCHAR), NULL, NULL,
|
|
fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
|
|
across_directories ? FILE_ACTION_REMOVED : FILE_ACTION_RENAMED_OLD_NAME, NULL);
|
|
|
|
// FIXME - change full_filename and name_offset of open children
|
|
|
|
if (fnlen != fcb->filepart.Length / sizeof(WCHAR) || RtlCompareMemory(fn, fcb->filepart.Buffer, fcb->filepart.Length) != fcb->filepart.Length) {
|
|
RtlFreeUnicodeString(&fcb->filepart);
|
|
fcb->filepart.Length = fcb->filepart.MaximumLength = (USHORT)(fnlen * sizeof(WCHAR));
|
|
fcb->filepart.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->filepart.Length, ALLOC_TAG);
|
|
|
|
if (!fcb->filepart.Buffer) {
|
|
ERR("out of memory\n");
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->filepart.Buffer, fn, fcb->filepart.Length);
|
|
}
|
|
|
|
if (tfo && tfofcb != fcb->par) {
|
|
oldparfcb = fcb->par;
|
|
fcb->par = tfofcb;
|
|
|
|
fcb->par->refcount++;
|
|
|
|
RemoveEntryList(&fcb->list_entry);
|
|
InsertTailList(&fcb->par->children, &fcb->list_entry);
|
|
|
|
#ifdef DEBUG_FCB_REFCOUNTS
|
|
WARN("fcb %p: refcount now %i (%.*S)\n", fcb->par, fcb->par->refcount, fcb->par->full_filename.Length / sizeof(WCHAR), fcb->par->full_filename.Buffer);
|
|
#endif
|
|
free_fcb(oldparfcb);
|
|
}
|
|
|
|
ExFreePool(fcb->utf8.Buffer);
|
|
fcb->utf8 = utf8;
|
|
utf8.Buffer = NULL;
|
|
|
|
// change fcb->full_filename
|
|
|
|
fcb->full_filename.MaximumLength = fcb->par->full_filename.Length + fcb->filepart.Length;
|
|
if (fcb->par->par) fcb->full_filename.MaximumLength += sizeof(WCHAR);
|
|
ExFreePool(fcb->full_filename.Buffer);
|
|
|
|
fcb->full_filename.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->full_filename.MaximumLength, ALLOC_TAG);
|
|
if (!fcb->full_filename.Buffer) {
|
|
ERR("out of memory\n");
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->full_filename.Buffer, fcb->par->full_filename.Buffer, fcb->par->full_filename.Length);
|
|
fcb->full_filename.Length = fcb->par->full_filename.Length;
|
|
|
|
if (fcb->par->par) {
|
|
fcb->full_filename.Buffer[fcb->full_filename.Length / sizeof(WCHAR)] = '\\';
|
|
fcb->full_filename.Length += sizeof(WCHAR);
|
|
}
|
|
fcb->name_offset = fcb->full_filename.Length / sizeof(WCHAR);
|
|
|
|
RtlAppendUnicodeStringToString(&fcb->full_filename, &fcb->filepart);
|
|
|
|
FsRtlNotifyFullReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fcb->full_filename, fcb->name_offset * sizeof(WCHAR), NULL, NULL,
|
|
fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
|
|
across_directories ? FILE_ACTION_ADDED : FILE_ACTION_RENAMED_NEW_NAME, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (utf8.Buffer)
|
|
ExFreePool(utf8.Buffer);
|
|
|
|
if (oldfcb)
|
|
free_fcb(oldfcb);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS STDCALL stream_set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, BOOL advance_only, LIST_ENTRY* rollback) {
|
|
FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
INODE_ITEM* ii;
|
|
CC_FILE_SIZES ccfs;
|
|
UINT8* data = NULL;
|
|
UINT16 datalen;
|
|
NTSTATUS Status;
|
|
|
|
TRACE("setting new end to %llx bytes (currently %x)\n", feofi->EndOfFile.QuadPart, fcb->adssize);
|
|
|
|
if (feofi->EndOfFile.QuadPart < fcb->adssize) {
|
|
if (advance_only)
|
|
return STATUS_SUCCESS;
|
|
|
|
TRACE("truncating stream to %llx bytes\n", feofi->EndOfFile.QuadPart);
|
|
|
|
if (feofi->EndOfFile.QuadPart > 0) {
|
|
if (!get_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &data, &datalen)) {
|
|
ERR("get_xattr failed\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data, feofi->EndOfFile.QuadPart, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("set_xattr returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
fcb->adssize = feofi->EndOfFile.QuadPart;
|
|
|
|
if (data)
|
|
ExFreePool(data);
|
|
} else if (feofi->EndOfFile.QuadPart > fcb->adssize) {
|
|
UINT16 maxlen;
|
|
UINT8* data2;
|
|
|
|
TRACE("extending stream to %llx bytes\n", feofi->EndOfFile.QuadPart);
|
|
|
|
// find maximum length of xattr
|
|
maxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node);
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_XATTR_ITEM;
|
|
searchkey.offset = fcb->adshash;
|
|
|
|
Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (keycmp(&tp.item->key, &searchkey)) {
|
|
ERR("error - could not find key for xattr\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
#ifdef __REACTOS__
|
|
// BUGBUG: FIXME!!
|
|
ERR("BUGBUG: datalen is uninitialized! Set it to zero temporarily...\n");
|
|
datalen = 0;
|
|
#endif
|
|
|
|
if (tp.item->size < datalen) {
|
|
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, datalen);
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
maxlen -= tp.item->size - datalen; // subtract XATTR_ITEM overhead
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
if (feofi->EndOfFile.QuadPart > maxlen) {
|
|
ERR("error - xattr too long (%llu > %u)\n", feofi->EndOfFile.QuadPart, maxlen);
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
|
|
if (!get_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, &data, &datalen)) {
|
|
ERR("get_xattr failed\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
data2 = ExAllocatePoolWithTag(PagedPool, feofi->EndOfFile.QuadPart, ALLOC_TAG);
|
|
if (!data2) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(data);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(data2, data, datalen);
|
|
ExFreePool(data);
|
|
|
|
RtlZeroMemory(&data2[datalen], feofi->EndOfFile.QuadPart - datalen);
|
|
|
|
Status = set_xattr(Vcb, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adshash, data2, feofi->EndOfFile.QuadPart, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("set_xattr returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
fcb->adssize = feofi->EndOfFile.QuadPart;
|
|
|
|
ExFreePool(data2);
|
|
}
|
|
|
|
ccfs.AllocationSize = fcb->Header.AllocationSize;
|
|
ccfs.FileSize = fcb->Header.FileSize;
|
|
ccfs.ValidDataLength = fcb->Header.ValidDataLength;
|
|
|
|
CcSetFileSizes(FileObject, &ccfs);
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->par->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->par->inode_item.sequence++;
|
|
fcb->par->inode_item.st_ctime = now;
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&tp.item->key, &searchkey))
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
else
|
|
WARN("couldn't find existing INODE_ITEM\n");
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
free_traverse_ptr(&tp);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->par->inode_item, sizeof(INODE_ITEM));
|
|
insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
fcb->par->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fcb->par->subvol->root_item.ctime = now;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, BOOL advance_only, LIST_ENTRY* rollback) {
|
|
FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER time;
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
INODE_ITEM* ii;
|
|
CC_FILE_SIZES ccfs;
|
|
|
|
if (fcb->deleted)
|
|
return STATUS_FILE_CLOSED;
|
|
|
|
if (fcb->ads)
|
|
return stream_set_end_of_file_information(Vcb, Irp, FileObject, advance_only, rollback);
|
|
|
|
TRACE("filename %.*S\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
|
|
TRACE("paging IO: %s\n", Irp->Flags & IRP_PAGING_IO ? "TRUE" : "FALSE");
|
|
TRACE("FileObject: AllocationSize = %llx, FileSize = %llx, ValidDataLength = %llx\n",
|
|
fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
|
|
|
|
// int3;
|
|
TRACE("setting new end to %llx bytes (currently %llx)\n", feofi->EndOfFile.QuadPart, fcb->inode_item.st_size);
|
|
|
|
// if (feofi->EndOfFile.QuadPart==0x36c000)
|
|
// int3;
|
|
|
|
if (feofi->EndOfFile.QuadPart < fcb->inode_item.st_size) {
|
|
if (advance_only)
|
|
return STATUS_SUCCESS;
|
|
|
|
TRACE("truncating file to %llx bytes\n", feofi->EndOfFile.QuadPart);
|
|
|
|
Status = truncate_file(fcb, feofi->EndOfFile.QuadPart, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - truncate_file failed\n");
|
|
return Status;
|
|
}
|
|
} else if (feofi->EndOfFile.QuadPart > fcb->inode_item.st_size) {
|
|
if (Irp->Flags & IRP_PAGING_IO) {
|
|
TRACE("paging IO tried to extend file size\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
TRACE("extending file to %llx bytes\n", feofi->EndOfFile.QuadPart);
|
|
|
|
// FIXME - pass flag to say that new extents should be prealloc rather than sparse
|
|
Status = extend_file(fcb, feofi->EndOfFile.QuadPart, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - extend_file failed\n");
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
ccfs.AllocationSize = fcb->Header.AllocationSize;
|
|
ccfs.FileSize = fcb->Header.FileSize;
|
|
ccfs.ValidDataLength = fcb->Header.ValidDataLength;
|
|
|
|
CcSetFileSizes(FileObject, &ccfs);
|
|
TRACE("setting FileSize for %.*S to %llx\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, ccfs.FileSize);
|
|
|
|
KeQuerySystemTime(&time);
|
|
|
|
win_time_to_unix(time, &fcb->inode_item.st_mtime);
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_INODE_ITEM;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!keycmp(&tp.item->key, &searchkey))
|
|
delete_tree_item(Vcb, &tp, rollback);
|
|
else
|
|
WARN("couldn't find existing INODE_ITEM\n");
|
|
|
|
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
|
|
if (!ii) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));
|
|
insert_tree_item(Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, rollback);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// static NTSTATUS STDCALL set_allocation_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
|
|
// FILE_ALLOCATION_INFORMATION* fai = (FILE_ALLOCATION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
|
|
// fcb* fcb = FileObject->FsContext;
|
|
//
|
|
// FIXME("FIXME\n");
|
|
// ERR("fcb = %p (%.*S)\n", fcb, fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer);
|
|
// ERR("AllocationSize = %llx\n", fai->AllocationSize.QuadPart);
|
|
//
|
|
// return STATUS_NOT_IMPLEMENTED;
|
|
// }
|
|
|
|
static NTSTATUS STDCALL set_position_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
|
|
FILE_POSITION_INFORMATION* fpi = (FILE_POSITION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
|
|
#ifdef DEBUG_LONG_MESSAGES
|
|
fcb* fcb = FileObject->FsContext;
|
|
|
|
TRACE("setting the position on %.*S to %llx\n", fcb->full_filename.Length / sizeof(WCHAR), fcb->full_filename.Buffer, fpi->CurrentByteOffset.QuadPart);
|
|
#endif
|
|
|
|
// FIXME - make sure aligned for FO_NO_INTERMEDIATE_BUFFERING
|
|
|
|
FileObject->CurrentByteOffset = fpi->CurrentByteOffset;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS STDCALL drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
|
|
NTSTATUS Status;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
fcb* fcb = IrpSp->FileObject->FsContext;
|
|
BOOL top_level;
|
|
LIST_ENTRY rollback;
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
if (Vcb->readonly) {
|
|
Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
|
|
TRACE("set information\n");
|
|
|
|
acquire_tree_lock(Vcb, TRUE);
|
|
|
|
switch (IrpSp->Parameters.SetFile.FileInformationClass) {
|
|
case FileAllocationInformation:
|
|
TRACE("FileAllocationInformation\n");
|
|
Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, FALSE, &rollback);
|
|
break;
|
|
|
|
case FileBasicInformation:
|
|
TRACE("FileBasicInformation\n");
|
|
Status = set_basic_information(Vcb, Irp, IrpSp->FileObject, &rollback);
|
|
break;
|
|
|
|
case FileDispositionInformation:
|
|
TRACE("FileDispositionInformation\n");
|
|
Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject);
|
|
break;
|
|
|
|
case FileEndOfFileInformation:
|
|
TRACE("FileEndOfFileInformation\n");
|
|
Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.AdvanceOnly, &rollback);
|
|
break;
|
|
|
|
case FileLinkInformation:
|
|
FIXME("STUB: FileLinkInformation\n");
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
TRACE("FilePositionInformation\n");
|
|
Status = set_position_information(Vcb, Irp, IrpSp->FileObject);
|
|
break;
|
|
|
|
case FileRenameInformation:
|
|
TRACE("FileRenameInformation\n");
|
|
// FIXME - make this work with streams
|
|
Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, IrpSp->Parameters.SetFile.ReplaceIfExists, &rollback);
|
|
break;
|
|
|
|
case FileValidDataLengthInformation:
|
|
FIXME("STUB: FileValidDataLengthInformation\n");
|
|
break;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
|
case FileNormalizedNameInformation:
|
|
FIXME("STUB: FileNormalizedNameInformation\n");
|
|
break;
|
|
#endif
|
|
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN7)
|
|
case FileStandardLinkInformation:
|
|
FIXME("STUB: FileStandardLinkInformation\n");
|
|
break;
|
|
|
|
case FileRemoteProtocolInformation:
|
|
TRACE("FileRemoteProtocolInformation\n");
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.SetFile.FileInformationClass);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
Status = consider_write(Vcb);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(Vcb, &rollback);
|
|
|
|
release_tree_lock(Vcb, TRUE);
|
|
|
|
end:
|
|
Irp->IoStatus.Status = Status;
|
|
|
|
IoCompleteRequest( Irp, IO_NO_INCREMENT );
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb) {
|
|
RtlZeroMemory(fbi, sizeof(FILE_BASIC_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_BASIC_INFORMATION);
|
|
|
|
fbi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
|
|
fbi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
|
|
fbi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
|
|
fbi->ChangeTime.QuadPart = 0;
|
|
fbi->FileAttributes = fcb->ads ? fcb->par->atts : fcb->atts;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, LONG* length) {
|
|
INODE_ITEM* ii;
|
|
|
|
if (*length < sizeof(FILE_NETWORK_OPEN_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_NETWORK_OPEN_INFORMATION);
|
|
|
|
if (fcb->ads)
|
|
ii = &fcb->par->inode_item;
|
|
else
|
|
ii = &fcb->inode_item;
|
|
|
|
|
|
fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
|
|
fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
|
|
fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
|
|
fnoi->ChangeTime.QuadPart = 0;
|
|
|
|
if (fcb->ads) {
|
|
fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adssize;
|
|
fnoi->FileAttributes = fcb->par->atts;
|
|
} else {
|
|
fnoi->AllocationSize.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
|
|
fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
|
|
fnoi->FileAttributes = fcb->atts;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, LONG* length) {
|
|
RtlZeroMemory(fsi, sizeof(FILE_STANDARD_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_STANDARD_INFORMATION);
|
|
|
|
if (fcb->ads) {
|
|
fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adssize;
|
|
fsi->NumberOfLinks = fcb->par->inode_item.st_nlink;
|
|
fsi->Directory = S_ISDIR(fcb->par->inode_item.st_mode);
|
|
} else {
|
|
fsi->AllocationSize.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
|
|
fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
|
|
fsi->NumberOfLinks = fcb->inode_item.st_nlink;
|
|
fsi->Directory = S_ISDIR(fcb->inode_item.st_mode);
|
|
}
|
|
|
|
TRACE("length = %llu\n", fsi->EndOfFile.QuadPart);
|
|
|
|
fsi->DeletePending = fcb->delete_on_close;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_internal_information(FILE_INTERNAL_INFORMATION* fii, UINT64 inode, LONG* length) {
|
|
*length -= sizeof(FILE_INTERNAL_INFORMATION);
|
|
|
|
fii->IndexNumber.QuadPart = inode;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_ea_information(FILE_EA_INFORMATION* eai, LONG* length) {
|
|
*length -= sizeof(FILE_EA_INFORMATION);
|
|
|
|
eai->EaSize = 0;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_access_information(FILE_ACCESS_INFORMATION* fai, LONG* length) {
|
|
*length -= sizeof(FILE_ACCESS_INFORMATION);
|
|
|
|
fai->AccessFlags = GENERIC_READ;
|
|
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_position_information(FILE_POSITION_INFORMATION* fpi, PFILE_OBJECT FileObject, LONG* length) {
|
|
RtlZeroMemory(fpi, sizeof(FILE_POSITION_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_POSITION_INFORMATION);
|
|
|
|
fpi->CurrentByteOffset = FileObject->CurrentByteOffset;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_mode_information(FILE_MODE_INFORMATION* fmi, ccb* ccb, LONG* length) {
|
|
RtlZeroMemory(fmi, sizeof(FILE_MODE_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_MODE_INFORMATION);
|
|
|
|
if (ccb->options & FILE_WRITE_THROUGH)
|
|
fmi->Mode |= FILE_WRITE_THROUGH;
|
|
|
|
if (ccb->options & FILE_SEQUENTIAL_ONLY)
|
|
fmi->Mode |= FILE_SEQUENTIAL_ONLY;
|
|
|
|
if (ccb->options & FILE_NO_INTERMEDIATE_BUFFERING)
|
|
fmi->Mode |= FILE_NO_INTERMEDIATE_BUFFERING;
|
|
|
|
if (ccb->options & FILE_SYNCHRONOUS_IO_ALERT)
|
|
fmi->Mode |= FILE_SYNCHRONOUS_IO_ALERT;
|
|
|
|
if (ccb->options & FILE_SYNCHRONOUS_IO_NONALERT)
|
|
fmi->Mode |= FILE_SYNCHRONOUS_IO_NONALERT;
|
|
|
|
if (ccb->options & FILE_DELETE_ON_CLOSE)
|
|
fmi->Mode |= FILE_DELETE_ON_CLOSE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_alignment_information(FILE_ALIGNMENT_INFORMATION* fai, device_extension* Vcb, LONG* length) {
|
|
RtlZeroMemory(fai, sizeof(FILE_ALIGNMENT_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_ALIGNMENT_INFORMATION);
|
|
|
|
fai->AlignmentRequirement = Vcb->devices[0].devobj->AlignmentRequirement;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, LONG* length) {
|
|
#ifdef _DEBUG
|
|
ULONG retlen = 0;
|
|
#endif
|
|
static WCHAR datasuf[] = {':','$','D','A','T','A',0};
|
|
ULONG datasuflen = wcslen(datasuf) * sizeof(WCHAR);
|
|
|
|
RtlZeroMemory(fni, sizeof(FILE_NAME_INFORMATION));
|
|
|
|
*length -= (LONG)offsetof(FILE_NAME_INFORMATION, FileName[0]);
|
|
|
|
TRACE("maximum length is %u\n", *length);
|
|
fni->FileNameLength = 0;
|
|
|
|
fni->FileName[0] = 0;
|
|
|
|
if (*length >= (LONG)fcb->full_filename.Length) {
|
|
RtlCopyMemory(fni->FileName, fcb->full_filename.Buffer, fcb->full_filename.Length);
|
|
#ifdef _DEBUG
|
|
retlen = fcb->full_filename.Length;
|
|
#endif
|
|
*length -= fcb->full_filename.Length;
|
|
} else {
|
|
if (*length > 0) {
|
|
RtlCopyMemory(fni->FileName, fcb->full_filename.Buffer, *length);
|
|
#ifdef _DEBUG
|
|
retlen = *length;
|
|
#endif
|
|
}
|
|
*length = -1;
|
|
}
|
|
|
|
fni->FileNameLength = fcb->full_filename.Length;
|
|
|
|
if (fcb->ads) {
|
|
if (*length >= (LONG)datasuflen) {
|
|
RtlCopyMemory(&fni->FileName[fcb->full_filename.Length / sizeof(WCHAR)], datasuf, datasuflen);
|
|
#ifdef _DEBUG
|
|
retlen += datasuflen;
|
|
#endif
|
|
*length -= datasuflen;
|
|
} else {
|
|
if (*length > 0) {
|
|
RtlCopyMemory(&fni->FileName[fcb->full_filename.Length / sizeof(WCHAR)], datasuf, *length);
|
|
#ifdef _DEBUG
|
|
retlen += *length;
|
|
#endif
|
|
}
|
|
*length = -1;
|
|
}
|
|
}
|
|
|
|
TRACE("%.*S\n", retlen / sizeof(WCHAR), fni->FileName);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, LONG* length) {
|
|
*length -= sizeof(FILE_ATTRIBUTE_TAG_INFORMATION);
|
|
|
|
ati->FileAttributes = fcb->ads ? fcb->par->atts : fcb->atts;
|
|
ati->ReparseTag = 0;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
typedef struct {
|
|
UNICODE_STRING name;
|
|
UINT64 size;
|
|
} stream_info;
|
|
|
|
static NTSTATUS STDCALL fill_in_file_stream_information(FILE_STREAM_INFORMATION* fsi, fcb* fcb, LONG* length) {
|
|
ULONG reqsize;
|
|
UINT64 i, num_streams;
|
|
stream_info* streams;
|
|
FILE_STREAM_INFORMATION* entry;
|
|
NTSTATUS Status;
|
|
KEY searchkey;
|
|
traverse_ptr tp, next_tp;
|
|
BOOL b;
|
|
|
|
static WCHAR datasuf[] = {':','$','D','A','T','A',0};
|
|
static char xapref[] = "user.";
|
|
UNICODE_STRING suf;
|
|
|
|
suf.Buffer = datasuf;
|
|
suf.Length = suf.MaximumLength = wcslen(datasuf) * sizeof(WCHAR);
|
|
|
|
num_streams = 1;
|
|
|
|
searchkey.obj_id = fcb->inode;
|
|
searchkey.obj_type = TYPE_XATTR_ITEM;
|
|
searchkey.offset = 0;
|
|
|
|
Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
do {
|
|
if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM) {
|
|
if (tp.item->size < sizeof(DIR_ITEM)) {
|
|
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
|
|
} else {
|
|
ULONG len = tp.item->size;
|
|
DIR_ITEM* xa = (DIR_ITEM*)tp.item->data;
|
|
|
|
do {
|
|
if (len < sizeof(DIR_ITEM) || len < sizeof(DIR_ITEM) - 1 + xa->m + xa->n) {
|
|
ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
break;
|
|
}
|
|
|
|
if (xa->n > strlen(xapref) && RtlCompareMemory(xa->name, xapref, strlen(xapref)) == strlen(xapref)) {
|
|
if (tp.item->key.offset != EA_DOSATTRIB_HASH || xa->n != strlen(EA_DOSATTRIB) || RtlCompareMemory(xa->name, EA_DOSATTRIB, xa->n) != xa->n) {
|
|
num_streams++;
|
|
}
|
|
}
|
|
|
|
len -= sizeof(DIR_ITEM) - sizeof(char) + xa->n + xa->m;
|
|
xa = (DIR_ITEM*)&xa->name[xa->n + xa->m]; // FIXME - test xattr hash collisions work
|
|
} while (len > 0);
|
|
}
|
|
}
|
|
|
|
b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
|
|
if (b) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
|
|
if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
|
|
break;
|
|
}
|
|
} while (b);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, FALSE);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - find_item returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
streams = ExAllocatePoolWithTag(PagedPool, sizeof(stream_info) * num_streams, ALLOC_TAG);
|
|
if (!streams) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
reqsize = 0;
|
|
|
|
streams[0].name.Length = streams[0].name.MaximumLength = 0;
|
|
streams[0].name.Buffer = NULL;
|
|
streams[0].size = fcb->inode_item.st_size;
|
|
reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + streams[0].name.Length;
|
|
|
|
i = 1;
|
|
do {
|
|
if (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type == TYPE_XATTR_ITEM) {
|
|
if (tp.item->size < sizeof(DIR_ITEM)) {
|
|
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
|
|
} else {
|
|
ULONG len = tp.item->size;
|
|
DIR_ITEM* xa = (DIR_ITEM*)tp.item->data;
|
|
ULONG stringlen;
|
|
|
|
do {
|
|
if (len < sizeof(DIR_ITEM) || len < sizeof(DIR_ITEM) - 1 + xa->m + xa->n) {
|
|
ERR("(%llx,%x,%llx) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
|
|
break;
|
|
}
|
|
|
|
if (xa->n > strlen(xapref) && RtlCompareMemory(xa->name, xapref, strlen(xapref)) == strlen(xapref) &&
|
|
(tp.item->key.offset != EA_DOSATTRIB_HASH || xa->n != strlen(EA_DOSATTRIB) || RtlCompareMemory(xa->name, EA_DOSATTRIB, xa->n) != xa->n)) {
|
|
Status = RtlUTF8ToUnicodeN(NULL, 0, &stringlen, &xa->name[strlen(xapref)], xa->n - strlen(xapref));
|
|
if (!NT_SUCCESS(Status)) {
|
|
UINT64 j;
|
|
|
|
ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status);
|
|
|
|
for (j = i; j < num_streams; j++)
|
|
streams[j].name.Buffer = NULL;
|
|
|
|
goto end;
|
|
}
|
|
|
|
streams[i].name.Buffer = ExAllocatePoolWithTag(PagedPool, stringlen, ALLOC_TAG);
|
|
if (!streams[i].name.Buffer) {
|
|
UINT64 j;
|
|
|
|
ERR("out of memory\n");
|
|
|
|
for (j = i+1; j < num_streams; j++)
|
|
streams[j].name.Buffer = NULL;
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = RtlUTF8ToUnicodeN(streams[i].name.Buffer, stringlen, &stringlen, &xa->name[strlen(xapref)], xa->n - strlen(xapref));
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
UINT64 j;
|
|
|
|
ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status);
|
|
|
|
ExFreePool(streams[i].name.Buffer);
|
|
for (j = i; j < num_streams; j++)
|
|
streams[j].name.Buffer = NULL;
|
|
|
|
goto end;
|
|
}
|
|
|
|
streams[i].name.Length = streams[i].name.MaximumLength = stringlen;
|
|
|
|
streams[i].size = xa->m;
|
|
reqsize = sector_align(reqsize, sizeof(LONGLONG));
|
|
reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + streams[i].name.Length;
|
|
|
|
TRACE("streams[%llu].name = %.*S (length = %u)\n", i, streams[i].name.Length / sizeof(WCHAR), streams[i].name.Buffer, streams[i].name.Length / sizeof(WCHAR));
|
|
|
|
i++;
|
|
}
|
|
|
|
len -= sizeof(DIR_ITEM) - sizeof(char) + xa->n + xa->m;
|
|
xa = (DIR_ITEM*)&xa->name[xa->n + xa->m]; // FIXME - test xattr hash collisions work
|
|
} while (len > 0);
|
|
}
|
|
}
|
|
|
|
b = find_next_item(fcb->Vcb, &tp, &next_tp, FALSE);
|
|
if (b) {
|
|
free_traverse_ptr(&tp);
|
|
tp = next_tp;
|
|
|
|
if (next_tp.item->key.obj_id > fcb->inode || next_tp.item->key.obj_type > TYPE_XATTR_ITEM)
|
|
break;
|
|
}
|
|
} while (b);
|
|
|
|
free_traverse_ptr(&tp);
|
|
|
|
TRACE("length = %i, reqsize = %u\n", *length, reqsize);
|
|
|
|
if (reqsize > *length) {
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto end;
|
|
}
|
|
|
|
entry = fsi;
|
|
for (i = 0; i < num_streams; i++) {
|
|
entry->StreamNameLength = streams[i].name.Length + suf.Length + sizeof(WCHAR);
|
|
entry->StreamSize.QuadPart = streams[i].size;
|
|
|
|
if (i == 0)
|
|
entry->StreamAllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);
|
|
else
|
|
entry->StreamAllocationSize.QuadPart = streams[i].size;
|
|
|
|
entry->StreamName[0] = ':';
|
|
|
|
if (streams[i].name.Length > 0)
|
|
RtlCopyMemory(&entry->StreamName[1], streams[i].name.Buffer, streams[i].name.Length);
|
|
|
|
RtlCopyMemory(&entry->StreamName[1 + (streams[i].name.Length / sizeof(WCHAR))], suf.Buffer, suf.Length);
|
|
|
|
if (i == num_streams - 1)
|
|
entry->NextEntryOffset = 0;
|
|
else {
|
|
entry->NextEntryOffset = sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + streams[i].name.Length, sizeof(LONGLONG));
|
|
|
|
entry = (FILE_STREAM_INFORMATION*)((UINT8*)entry + entry->NextEntryOffset);
|
|
}
|
|
}
|
|
|
|
*length -= reqsize;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
for (i = 0; i < num_streams; i++) {
|
|
if (streams[i].name.Buffer)
|
|
ExFreePool(streams[i].name.Buffer);
|
|
}
|
|
|
|
ExFreePool(streams);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS STDCALL fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, LONG* length) {
|
|
TRACE("FileStandardLinkInformation\n");
|
|
|
|
// FIXME - NumberOfAccessibleLinks should subtract open links which have been marked as delete_on_close
|
|
|
|
fsli->NumberOfAccessibleLinks = fcb->inode_item.st_nlink;
|
|
fsli->TotalNumberOfLinks = fcb->inode_item.st_nlink;
|
|
fsli->DeletePending = fcb->delete_on_close;
|
|
fsli->Directory = fcb->type == BTRFS_TYPE_DIRECTORY ? TRUE : FALSE;
|
|
|
|
*length -= sizeof(FILE_STANDARD_LINK_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS STDCALL query_info(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) {
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
LONG length = IrpSp->Parameters.QueryFile.Length;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
NTSTATUS Status;
|
|
|
|
TRACE("(%p, %p, %p)\n", Vcb, FileObject, Irp);
|
|
TRACE("fcb = %p\n", fcb);
|
|
|
|
if (fcb == Vcb->volume_fcb)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
switch (IrpSp->Parameters.QueryFile.FileInformationClass) {
|
|
case FileAllInformation:
|
|
{
|
|
FILE_ALL_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer;
|
|
INODE_ITEM* ii;
|
|
|
|
TRACE("FileAllInformation\n");
|
|
|
|
if (fcb->ads)
|
|
ii = &fcb->par->inode_item;
|
|
else
|
|
ii = &fcb->inode_item;
|
|
|
|
if (length > 0)
|
|
fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb);
|
|
|
|
if (length > 0)
|
|
fill_in_file_standard_information(&fai->StandardInformation, fcb, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_internal_information(&fai->InternalInformation, fcb->inode, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_ea_information(&fai->EaInformation, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_access_information(&fai->AccessInformation, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_position_information(&fai->PositionInformation, FileObject, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_mode_information(&fai->ModeInformation, ccb, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_alignment_information(&fai->AlignmentInformation, Vcb, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_name_information(&fai->NameInformation, fcb, &length);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
break;
|
|
}
|
|
|
|
case FileAttributeTagInformation:
|
|
{
|
|
FILE_ATTRIBUTE_TAG_INFORMATION* ati = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileAttributeTagInformation\n");
|
|
|
|
Status = fill_in_file_attribute_information(ati, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileBasicInformation:
|
|
{
|
|
FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;
|
|
INODE_ITEM* ii;
|
|
|
|
TRACE("FileBasicInformation\n");
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_BASIC_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
if (fcb->ads)
|
|
ii = &fcb->par->inode_item;
|
|
else
|
|
ii = &fcb->inode_item;
|
|
|
|
Status = fill_in_file_basic_information(fbi, ii, &length, fcb);
|
|
break;
|
|
}
|
|
|
|
case FileCompressionInformation:
|
|
FIXME("STUB: FileCompressionInformation\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
|
|
case FileEaInformation:
|
|
{
|
|
FILE_EA_INFORMATION* eai = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileEaInformation\n");
|
|
|
|
Status = fill_in_file_ea_information(eai, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileInternalInformation:
|
|
{
|
|
FILE_INTERNAL_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileInternalInformation\n");
|
|
|
|
Status = fill_in_file_internal_information(fii, fcb->inode, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileNameInformation:
|
|
{
|
|
FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileNameInformation\n");
|
|
|
|
Status = fill_in_file_name_information(fni, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileNetworkOpenInformation:
|
|
{
|
|
FILE_NETWORK_OPEN_INFORMATION* fnoi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileNetworkOpenInformation\n");
|
|
|
|
Status = fill_in_file_network_open_information(fnoi, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FilePositionInformation:
|
|
{
|
|
FILE_POSITION_INFORMATION* fpi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FilePositionInformation\n");
|
|
|
|
Status = fill_in_file_position_information(fpi, FileObject, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStandardInformation:
|
|
{
|
|
FILE_STANDARD_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileStandardInformation\n");
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STANDARD_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
Status = fill_in_file_standard_information(fsi, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStreamInformation:
|
|
{
|
|
FILE_STREAM_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileStreamInformation\n");
|
|
|
|
Status = fill_in_file_stream_information(fsi, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
|
case FileHardLinkInformation:
|
|
FIXME("STUB: FileHardLinkInformation\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
|
|
case FileNormalizedNameInformation:
|
|
{
|
|
FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileNormalizedNameInformation\n");
|
|
|
|
Status = fill_in_file_name_information(fni, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN7)
|
|
case FileStandardLinkInformation:
|
|
{
|
|
FILE_STANDARD_LINK_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileStandardLinkInformation\n");
|
|
|
|
Status = fill_in_file_standard_link_information(fsli, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileRemoteProtocolInformation:
|
|
TRACE("FileRemoteProtocolInformation\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
#endif
|
|
|
|
default:
|
|
WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.QueryFile.FileInformationClass);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
}
|
|
|
|
if (length < 0) {
|
|
length = 0;
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - length;
|
|
|
|
exit:
|
|
TRACE("query_info returning %08x\n", Status);
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS STDCALL drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
|
|
PIO_STACK_LOCATION IrpSp;
|
|
NTSTATUS Status;
|
|
fcb* fcb;
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
BOOL top_level;
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
TRACE("query information\n");
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
acquire_tree_lock(Vcb, FALSE);
|
|
|
|
fcb = IrpSp->FileObject->FsContext;
|
|
TRACE("fcb = %p\n", fcb);
|
|
TRACE("fcb->subvol = %p\n", fcb->subvol);
|
|
|
|
Status = query_info(fcb->Vcb, IrpSp->FileObject, Irp);
|
|
|
|
TRACE("returning %08x\n", Status);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
|
|
IoCompleteRequest( Irp, IO_NO_INCREMENT );
|
|
|
|
release_tree_lock(Vcb, FALSE);
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|