reactos/drivers/filesystems/btrfs/btrfs.c
Pierre Schweitzer c13ccc92f6
[BTRFS] Leak the root stream file object on mount.
For whatever (unknown) reason yet, this stream file object
seems to be deleted when still in use while installing
ReactOS on BtrFS partition, leading to use after free.

So, quick and dirty hack: leak it to prevent deletion.

CORE-13769
2018-08-15 16:38:56 +02:00

5647 lines
179 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/>. */
#ifdef _DEBUG
#define DEBUG
#endif
#include "btrfs_drv.h"
#ifndef __REACTOS__
#ifndef _MSC_VER
#include <cpuid.h>
#else
#include <intrin.h>
#endif
#endif
#include <ntddscsi.h>
#include "btrfs.h"
#include <ata.h>
#ifndef _MSC_VER
#include <initguid.h>
#include <ntddstor.h>
#undef INITGUID
#endif
#include <ntdddisk.h>
#include <ntddvol.h>
#ifdef _MSC_VER
#include <initguid.h>
#include <ntddstor.h>
#undef INITGUID
#endif
#define INCOMPAT_SUPPORTED (BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF | BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL | BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS | \
BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO | BTRFS_INCOMPAT_FLAGS_BIG_METADATA | BTRFS_INCOMPAT_FLAGS_RAID56 | \
BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA | BTRFS_INCOMPAT_FLAGS_NO_HOLES)
#define COMPAT_RO_SUPPORTED (BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE | BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID)
static WCHAR device_name[] = {'\\','B','t','r','f','s',0};
static WCHAR dosdevice_name[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\','B','t','r','f','s',0};
DEFINE_GUID(BtrfsBusInterface, 0x4d414874, 0x6865, 0x6761, 0x6d, 0x65, 0x83, 0x69, 0x17, 0x9a, 0x7d, 0x1d);
PDRIVER_OBJECT drvobj;
PDEVICE_OBJECT master_devobj;
#ifndef __REACTOS__
BOOL have_sse42 = FALSE, have_sse2 = FALSE;
#endif
UINT64 num_reads = 0;
LIST_ENTRY uid_map_list, gid_map_list;
LIST_ENTRY VcbList;
ERESOURCE global_loading_lock;
UINT32 debug_log_level = 0;
UINT32 mount_compress = 0;
UINT32 mount_compress_force = 0;
UINT32 mount_compress_type = 0;
UINT32 mount_zlib_level = 3;
UINT32 mount_flush_interval = 30;
UINT32 mount_max_inline = 2048;
UINT32 mount_skip_balance = 0;
UINT32 mount_no_barrier = 0;
UINT32 mount_no_trim = 0;
UINT32 mount_clear_cache = 0;
UINT32 mount_allow_degraded = 0;
UINT32 mount_readonly = 0;
UINT32 no_pnp = 0;
BOOL log_started = FALSE;
UNICODE_STRING log_device, log_file, registry_path;
tPsUpdateDiskCounters fPsUpdateDiskCounters;
tCcCopyReadEx fCcCopyReadEx;
tCcCopyWriteEx fCcCopyWriteEx;
tCcSetAdditionalCacheAttributesEx fCcSetAdditionalCacheAttributesEx;
tFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters;
BOOL diskacc = FALSE;
void *notification_entry = NULL, *notification_entry2 = NULL, *notification_entry3 = NULL;
ERESOURCE pdo_list_lock, mapping_lock;
LIST_ENTRY pdo_list;
BOOL finished_probing = FALSE;
HANDLE degraded_wait_handle = NULL, mountmgr_thread_handle = NULL;
BOOL degraded_wait = TRUE;
KEVENT mountmgr_thread_event;
BOOL shutting_down = FALSE;
#ifdef _DEBUG
PFILE_OBJECT comfo = NULL;
PDEVICE_OBJECT comdo = NULL;
HANDLE log_handle = NULL;
ERESOURCE log_lock;
HANDLE serial_thread_handle = NULL;
static void init_serial(BOOL first_time);
#endif
static NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp);
typedef struct {
KEVENT Event;
IO_STATUS_BLOCK iosb;
} read_context;
#ifdef _DEBUG
_Function_class_(IO_COMPLETION_ROUTINE)
static NTSTATUS dbg_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) {
read_context* context = conptr;
UNUSED(DeviceObject);
context->iosb = Irp->IoStatus;
KeSetEvent(&context->Event, 0, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
#ifdef DEBUG_LONG_MESSAGES
void _debug_message(_In_ const char* func, _In_ const char* file, _In_ unsigned int line, _In_ char* s, ...) {
#else
void _debug_message(_In_ const char* func, _In_ char* s, ...) {
#endif
LARGE_INTEGER offset;
PIO_STACK_LOCATION IrpSp;
NTSTATUS Status;
PIRP Irp;
va_list ap;
char *buf2, *buf;
read_context context;
UINT32 length;
buf2 = ExAllocatePoolWithTag(NonPagedPool, 1024, ALLOC_TAG);
if (!buf2) {
DbgPrint("Couldn't allocate buffer in debug_message\n");
return;
}
#ifdef DEBUG_LONG_MESSAGES
sprintf(buf2, "%p:%s:%s:%u:", PsGetCurrentThread(), func, file, line);
#else
sprintf(buf2, "%p:%s:", PsGetCurrentThread(), func);
#endif
buf = &buf2[strlen(buf2)];
va_start(ap, s);
vsprintf(buf, s, ap);
ExAcquireResourceSharedLite(&log_lock, TRUE);
if (!log_started || (log_device.Length == 0 && log_file.Length == 0)) {
DbgPrint(buf2);
} else if (log_device.Length > 0) {
if (!comdo) {
DbgPrint("comdo is NULL :-(\n");
DbgPrint(buf2);
goto exit2;
}
length = (UINT32)strlen(buf2);
offset.u.LowPart = 0;
offset.u.HighPart = 0;
RtlZeroMemory(&context, sizeof(read_context));
KeInitializeEvent(&context.Event, NotificationEvent, FALSE);
Irp = IoAllocateIrp(comdo->StackSize, FALSE);
if (!Irp) {
DbgPrint("IoAllocateIrp failed\n");
goto exit2;
}
IrpSp = IoGetNextIrpStackLocation(Irp);
IrpSp->MajorFunction = IRP_MJ_WRITE;
if (comdo->Flags & DO_BUFFERED_IO) {
Irp->AssociatedIrp.SystemBuffer = buf2;
Irp->Flags = IRP_BUFFERED_IO;
} else if (comdo->Flags & DO_DIRECT_IO) {
Irp->MdlAddress = IoAllocateMdl(buf2, length, FALSE, FALSE, NULL);
if (!Irp->MdlAddress) {
DbgPrint("IoAllocateMdl failed\n");
goto exit;
}
MmBuildMdlForNonPagedPool(Irp->MdlAddress);
} else {
Irp->UserBuffer = buf2;
}
IrpSp->Parameters.Write.Length = length;
IrpSp->Parameters.Write.ByteOffset = offset;
Irp->UserIosb = &context.iosb;
Irp->UserEvent = &context.Event;
IoSetCompletionRoutine(Irp, dbg_completion, &context, TRUE, TRUE, TRUE);
Status = IoCallDriver(comdo, Irp);
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(&context.Event, Executive, KernelMode, FALSE, NULL);
Status = context.iosb.Status;
}
if (comdo->Flags & DO_DIRECT_IO)
IoFreeMdl(Irp->MdlAddress);
if (!NT_SUCCESS(Status)) {
DbgPrint("failed to write to COM1 - error %08x\n", Status);
goto exit;
}
exit:
IoFreeIrp(Irp);
} else if (log_handle != NULL) {
IO_STATUS_BLOCK iosb;
length = (UINT32)strlen(buf2);
Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, buf2, length, NULL, NULL);
if (!NT_SUCCESS(Status)) {
DbgPrint("failed to write to file - error %08x\n", Status);
}
}
exit2:
ExReleaseResourceLite(&log_lock);
va_end(ap);
if (buf2)
ExFreePool(buf2);
}
#endif
BOOL is_top_level(_In_ PIRP Irp) {
if (!IoGetTopLevelIrp()) {
IoSetTopLevelIrp(Irp);
return TRUE;
}
return FALSE;
}
_Function_class_(DRIVER_UNLOAD)
#ifdef __REACTOS__
static void NTAPI DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {
#else
static void DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {
#endif
UNICODE_STRING dosdevice_nameW;
ERR("DriverUnload\n");
free_cache();
IoUnregisterFileSystem(DriverObject->DeviceObject);
if (notification_entry2)
#ifdef __REACTOS__
IoUnregisterPlugPlayNotification(notification_entry2);
#else
IoUnregisterPlugPlayNotificationEx(notification_entry2);
#endif
if (notification_entry3)
#ifdef __REACTOS__
IoUnregisterPlugPlayNotification(notification_entry3);
#else
IoUnregisterPlugPlayNotificationEx(notification_entry3);
#endif
if (notification_entry)
#ifdef __REACTOS__
IoUnregisterPlugPlayNotification(notification_entry);
#else
IoUnregisterPlugPlayNotificationEx(notification_entry);
#endif
dosdevice_nameW.Buffer = dosdevice_name;
dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = (USHORT)wcslen(dosdevice_name) * sizeof(WCHAR);
IoDeleteSymbolicLink(&dosdevice_nameW);
IoDeleteDevice(DriverObject->DeviceObject);
while (!IsListEmpty(&uid_map_list)) {
LIST_ENTRY* le = RemoveHeadList(&uid_map_list);
uid_map* um = CONTAINING_RECORD(le, uid_map, listentry);
ExFreePool(um->sid);
ExFreePool(um);
}
while (!IsListEmpty(&gid_map_list)) {
gid_map* gm = CONTAINING_RECORD(RemoveHeadList(&gid_map_list), gid_map, listentry);
ExFreePool(gm->sid);
ExFreePool(gm);
}
// FIXME - free volumes and their devpaths
#ifdef _DEBUG
if (comfo)
ObDereferenceObject(comfo);
if (log_handle)
ZwClose(log_handle);
#endif
ExDeleteResourceLite(&global_loading_lock);
ExDeleteResourceLite(&pdo_list_lock);
if (log_device.Buffer)
ExFreePool(log_device.Buffer);
if (log_file.Buffer)
ExFreePool(log_file.Buffer);
if (registry_path.Buffer)
ExFreePool(registry_path.Buffer);
#ifdef _DEBUG
ExDeleteResourceLite(&log_lock);
#endif
ExDeleteResourceLite(&mapping_lock);
}
static BOOL get_last_inode(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_opt_ PIRP Irp) {
KEY searchkey;
traverse_ptr tp, prev_tp;
NTSTATUS Status;
// get last entry
searchkey.obj_id = 0xffffffffffffffff;
searchkey.obj_type = 0xff;
searchkey.offset = 0xffffffffffffffff;
Status = find_item(Vcb, r, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
return FALSE;
}
if (tp.item->key.obj_type == TYPE_INODE_ITEM || (tp.item->key.obj_type == TYPE_ROOT_ITEM && !(tp.item->key.obj_id & 0x8000000000000000))) {
r->lastinode = tp.item->key.obj_id;
TRACE("last inode for tree %llx is %llx\n", r->id, r->lastinode);
return TRUE;
}
while (find_prev_item(Vcb, &tp, &prev_tp, Irp)) {
tp = prev_tp;
TRACE("moving on to %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
if (tp.item->key.obj_type == TYPE_INODE_ITEM || (tp.item->key.obj_type == TYPE_ROOT_ITEM && !(tp.item->key.obj_id & 0x8000000000000000))) {
r->lastinode = tp.item->key.obj_id;
TRACE("last inode for tree %llx is %llx\n", r->id, r->lastinode);
return TRUE;
}
}
r->lastinode = SUBVOL_ROOT_INODE;
WARN("no INODE_ITEMs in tree %llx\n", r->id);
return TRUE;
}
_Success_(return)
static BOOL extract_xattr(_In_reads_bytes_(size) void* item, _In_ USHORT size, _In_z_ char* name, _Out_ UINT8** data, _Out_ UINT16* datalen) {
DIR_ITEM* xa = (DIR_ITEM*)item;
USHORT xasize;
while (TRUE) {
if (size < sizeof(DIR_ITEM) || size < (sizeof(DIR_ITEM) - 1 + xa->m + xa->n)) {
WARN("DIR_ITEM is truncated\n");
return FALSE;
}
if (xa->n == strlen(name) && RtlCompareMemory(name, xa->name, xa->n) == xa->n) {
TRACE("found xattr %s\n", name);
*datalen = xa->m;
if (xa->m > 0) {
*data = ExAllocatePoolWithTag(PagedPool, xa->m, ALLOC_TAG);
if (!*data) {
ERR("out of memory\n");
return FALSE;
}
RtlCopyMemory(*data, &xa->name[xa->n], xa->m);
} else
*data = NULL;
return TRUE;
}
xasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n;
if (size > xasize) {
size -= xasize;
xa = (DIR_ITEM*)&xa->name[xa->m + xa->n];
} else
break;
}
TRACE("xattr %s not found\n", name);
return FALSE;
}
_Success_(return)
BOOL get_xattr(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* subvol, _In_ UINT64 inode, _In_z_ char* name, _In_ UINT32 crc32,
_Out_ UINT8** data, _Out_ UINT16* datalen, _In_opt_ PIRP Irp) {
KEY searchkey;
traverse_ptr tp;
NTSTATUS Status;
TRACE("(%p, %llx, %llx, %s, %08x, %p, %p)\n", Vcb, subvol->id, inode, name, crc32, data, datalen);
searchkey.obj_id = inode;
searchkey.obj_type = TYPE_XATTR_ITEM;
searchkey.offset = crc32;
Status = find_item(Vcb, subvol, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
return FALSE;
}
if (keycmp(tp.item->key, searchkey)) {
TRACE("could not find item (%llx,%x,%llx)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
return FALSE;
}
if (tp.item->size < sizeof(DIR_ITEM)) {
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
return FALSE;
}
return extract_xattr(tp.item->data, tp.item->size, name, data, datalen);
}
_Dispatch_type_(IRP_MJ_CLOSE)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_close(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
TRACE("close\n");
top_level = is_top_level(Irp);
if (DeviceObject == master_devobj) {
TRACE("Closing file system\n");
Status = STATUS_SUCCESS;
goto end;
} else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_close(DeviceObject, Irp);
goto end;
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
IrpSp = IoGetCurrentIrpStackLocation(Irp);
// FIXME - unmount if called for volume
// FIXME - call FsRtlNotifyUninitializeSync(&Vcb->NotifySync) if unmounting
Status = close_file(IrpSp->FileObject, Irp);
end:
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
IoCompleteRequest( Irp, IO_DISK_INCREMENT );
if (top_level)
IoSetTopLevelIrp(NULL);
TRACE("returning %08x\n", Status);
FsRtlExitFileSystem();
return Status;
}
_Dispatch_type_(IRP_MJ_FLUSH_BUFFERS)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_flush_buffers(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
PFILE_OBJECT FileObject = IrpSp->FileObject;
fcb* fcb = FileObject->FsContext;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
TRACE("flush buffers\n");
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_flush_buffers(DeviceObject, Irp);
goto end;
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
if (!fcb) {
ERR("fcb was NULL\n");
Status = STATUS_INVALID_PARAMETER;
goto end;
}
if (fcb == Vcb->volume_fcb) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
Irp->IoStatus.Information = 0;
fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
Status = STATUS_SUCCESS;
Irp->IoStatus.Status = Status;
if (fcb->type != BTRFS_TYPE_DIRECTORY) {
CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &Irp->IoStatus);
if (fcb->Header.PagingIoResource) {
ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, TRUE);
ExReleaseResourceLite(fcb->Header.PagingIoResource);
}
Status = Irp->IoStatus.Status;
}
end:
IoCompleteRequest(Irp, IO_NO_INCREMENT);
TRACE("returning %08x\n", Status);
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
static void calculate_total_space(_In_ device_extension* Vcb, _Out_ UINT64* totalsize, _Out_ UINT64* freespace) {
UINT64 nfactor, dfactor, sectors_used;
if (Vcb->data_flags & BLOCK_FLAG_DUPLICATE || Vcb->data_flags & BLOCK_FLAG_RAID1 || Vcb->data_flags & BLOCK_FLAG_RAID10) {
nfactor = 1;
dfactor = 2;
} else if (Vcb->data_flags & BLOCK_FLAG_RAID5) {
nfactor = Vcb->superblock.num_devices - 1;
dfactor = Vcb->superblock.num_devices;
} else if (Vcb->data_flags & BLOCK_FLAG_RAID6) {
nfactor = Vcb->superblock.num_devices - 2;
dfactor = Vcb->superblock.num_devices;
} else {
nfactor = 1;
dfactor = 1;
}
sectors_used = Vcb->superblock.bytes_used / Vcb->superblock.sector_size;
*totalsize = (Vcb->superblock.total_bytes / Vcb->superblock.sector_size) * nfactor / dfactor;
*freespace = sectors_used > *totalsize ? 0 : (*totalsize - sectors_used);
}
#ifndef __REACTOS__
// This function exists because we have to lie about our FS type in certain situations.
// MPR!MprGetConnection queries the FS type, and compares it to a whitelist. If it doesn't match,
// it will return ERROR_NO_NET_OR_BAD_PATH, which prevents UAC from working.
// The command mklink refuses to create hard links on anything other than NTFS, so we have to
// blacklist cmd.exe too.
static BOOL lie_about_fs_type() {
NTSTATUS Status;
PROCESS_BASIC_INFORMATION pbi;
PPEB peb;
LIST_ENTRY* le;
ULONG retlen;
static WCHAR mpr[] = L"MPR.DLL";
static WCHAR cmd[] = L"CMD.EXE";
static WCHAR fsutil[] = L"FSUTIL.EXE";
UNICODE_STRING mprus, cmdus, fsutilus;
mprus.Buffer = mpr;
mprus.Length = mprus.MaximumLength = (USHORT)(wcslen(mpr) * sizeof(WCHAR));
cmdus.Buffer = cmd;
cmdus.Length = cmdus.MaximumLength = (USHORT)(wcslen(cmd) * sizeof(WCHAR));
fsutilus.Buffer = fsutil;
fsutilus.Length = fsutilus.MaximumLength = (USHORT)(wcslen(fsutil) * sizeof(WCHAR));
if (!PsGetCurrentProcess())
return FALSE;
Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &retlen);
if (!NT_SUCCESS(Status)) {
ERR("ZwQueryInformationProcess returned %08x\n", Status);
return FALSE;
}
if (!pbi.PebBaseAddress)
return FALSE;
peb = pbi.PebBaseAddress;
if (!peb->Ldr)
return FALSE;
le = peb->Ldr->InMemoryOrderModuleList.Flink;
while (le != &peb->Ldr->InMemoryOrderModuleList) {
LDR_DATA_TABLE_ENTRY* entry = CONTAINING_RECORD(le, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
BOOL blacklist = FALSE;
if (entry->FullDllName.Length >= mprus.Length) {
UNICODE_STRING name;
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - mprus.Length) / sizeof(WCHAR)];
name.Length = name.MaximumLength = mprus.Length;
blacklist = FsRtlAreNamesEqual(&name, &mprus, TRUE, NULL);
}
if (!blacklist && entry->FullDllName.Length >= cmdus.Length) {
UNICODE_STRING name;
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - cmdus.Length) / sizeof(WCHAR)];
name.Length = name.MaximumLength = cmdus.Length;
blacklist = FsRtlAreNamesEqual(&name, &cmdus, TRUE, NULL);
}
if (!blacklist && entry->FullDllName.Length >= fsutilus.Length) {
UNICODE_STRING name;
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - fsutilus.Length) / sizeof(WCHAR)];
name.Length = name.MaximumLength = fsutilus.Length;
blacklist = FsRtlAreNamesEqual(&name, &fsutilus, TRUE, NULL);
}
if (blacklist) {
void** frames;
ULONG i, num_frames;
frames = ExAllocatePoolWithTag(PagedPool, 256 * sizeof(void*), ALLOC_TAG);
if (!frames) {
ERR("out of memory\n");
return FALSE;
}
num_frames = RtlWalkFrameChain(frames, 256, 1);
for (i = 0; i < num_frames; i++) {
// entry->Reserved3[1] appears to be the image size
if (frames[i] >= entry->DllBase && (ULONG_PTR)frames[i] <= (ULONG_PTR)entry->DllBase + (ULONG_PTR)entry->Reserved3[1]) {
ExFreePool(frames);
return TRUE;
}
}
ExFreePool(frames);
}
le = le->Flink;
}
return FALSE;
}
#endif
_Dispatch_type_(IRP_MJ_QUERY_VOLUME_INFORMATION)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_query_volume_information(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
PIO_STACK_LOCATION IrpSp;
NTSTATUS Status;
ULONG BytesCopied = 0;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
TRACE("query volume information\n");
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_query_volume_information(DeviceObject, Irp);
goto end;
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
IrpSp = IoGetCurrentIrpStackLocation(Irp);
Status = STATUS_NOT_IMPLEMENTED;
switch (IrpSp->Parameters.QueryVolume.FsInformationClass) {
case FileFsAttributeInformation:
{
FILE_FS_ATTRIBUTE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;
BOOL overflow = FALSE;
#ifndef __REACTOS__
WCHAR* fs_name = (Irp->RequestorMode == UserMode && lie_about_fs_type()) ? L"NTFS" : L"Btrfs";
ULONG fs_name_len = (ULONG)wcslen(fs_name) * sizeof(WCHAR);
#else
WCHAR* fs_name = L"Btrfs";
ULONG fs_name_len = 5 * sizeof(WCHAR);
#endif
ULONG orig_fs_name_len = fs_name_len;
TRACE("FileFsAttributeInformation\n");
if (IrpSp->Parameters.QueryVolume.Length < sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len) {
if (IrpSp->Parameters.QueryVolume.Length > sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR))
fs_name_len = IrpSp->Parameters.QueryVolume.Length - sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + sizeof(WCHAR);
else
fs_name_len = 0;
overflow = TRUE;
}
data->FileSystemAttributes = FILE_CASE_PRESERVED_NAMES | FILE_CASE_SENSITIVE_SEARCH |
FILE_UNICODE_ON_DISK | FILE_NAMED_STREAMS | FILE_SUPPORTS_HARD_LINKS | FILE_PERSISTENT_ACLS |
FILE_SUPPORTS_REPARSE_POINTS | FILE_SUPPORTS_SPARSE_FILES | FILE_SUPPORTS_OBJECT_IDS |
FILE_SUPPORTS_OPEN_BY_FILE_ID | FILE_SUPPORTS_EXTENDED_ATTRIBUTES | FILE_SUPPORTS_BLOCK_REFCOUNTING;
if (Vcb->readonly)
data->FileSystemAttributes |= FILE_READ_ONLY_VOLUME;
// should also be FILE_FILE_COMPRESSION when supported
data->MaximumComponentNameLength = 255; // FIXME - check
data->FileSystemNameLength = orig_fs_name_len;
RtlCopyMemory(data->FileSystemName, fs_name, fs_name_len);
BytesCopied = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len;
Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
break;
}
case FileFsDeviceInformation:
{
FILE_FS_DEVICE_INFORMATION* ffdi = Irp->AssociatedIrp.SystemBuffer;
TRACE("FileFsDeviceInformation\n");
ffdi->DeviceType = FILE_DEVICE_DISK;
ExAcquireResourceSharedLite(&Vcb->tree_lock, TRUE);
ffdi->Characteristics = Vcb->Vpb->RealDevice->Characteristics;
ExReleaseResourceLite(&Vcb->tree_lock);
if (Vcb->readonly)
ffdi->Characteristics |= FILE_READ_ONLY_DEVICE;
else
ffdi->Characteristics &= ~FILE_READ_ONLY_DEVICE;
BytesCopied = sizeof(FILE_FS_DEVICE_INFORMATION);
Status = STATUS_SUCCESS;
break;
}
case FileFsFullSizeInformation:
{
FILE_FS_FULL_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer;
TRACE("FileFsFullSizeInformation\n");
calculate_total_space(Vcb, (UINT64*)&ffsi->TotalAllocationUnits.QuadPart, (UINT64*)&ffsi->ActualAvailableAllocationUnits.QuadPart);
ffsi->CallerAvailableAllocationUnits.QuadPart = ffsi->ActualAvailableAllocationUnits.QuadPart;
ffsi->SectorsPerAllocationUnit = 1;
ffsi->BytesPerSector = Vcb->superblock.sector_size;
BytesCopied = sizeof(FILE_FS_FULL_SIZE_INFORMATION);
Status = STATUS_SUCCESS;
break;
}
case FileFsObjectIdInformation:
{
FILE_FS_OBJECTID_INFORMATION* ffoi = Irp->AssociatedIrp.SystemBuffer;
TRACE("FileFsObjectIdInformation\n");
RtlCopyMemory(ffoi->ObjectId, &Vcb->superblock.uuid.uuid[0], sizeof(UCHAR) * 16);
RtlZeroMemory(ffoi->ExtendedInfo, sizeof(ffoi->ExtendedInfo));
BytesCopied = sizeof(FILE_FS_OBJECTID_INFORMATION);
Status = STATUS_SUCCESS;
break;
}
case FileFsSizeInformation:
{
FILE_FS_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer;
TRACE("FileFsSizeInformation\n");
calculate_total_space(Vcb, (UINT64*)&ffsi->TotalAllocationUnits.QuadPart, (UINT64*)&ffsi->AvailableAllocationUnits.QuadPart);
ffsi->SectorsPerAllocationUnit = 1;
ffsi->BytesPerSector = Vcb->superblock.sector_size;
BytesCopied = sizeof(FILE_FS_SIZE_INFORMATION);
Status = STATUS_SUCCESS;
break;
}
case FileFsVolumeInformation:
{
FILE_FS_VOLUME_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;
FILE_FS_VOLUME_INFORMATION ffvi;
BOOL overflow = FALSE;
ULONG label_len, orig_label_len;
TRACE("FileFsVolumeInformation\n");
TRACE("max length = %u\n", IrpSp->Parameters.QueryVolume.Length);
ExAcquireResourceSharedLite(&Vcb->tree_lock, TRUE);
Status = RtlUTF8ToUnicodeN(NULL, 0, &label_len, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));
if (!NT_SUCCESS(Status)) {
ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
ExReleaseResourceLite(&Vcb->tree_lock);
break;
}
orig_label_len = label_len;
if (IrpSp->Parameters.QueryVolume.Length < sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR) + label_len) {
if (IrpSp->Parameters.QueryVolume.Length > sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR))
label_len = IrpSp->Parameters.QueryVolume.Length - sizeof(FILE_FS_VOLUME_INFORMATION) + sizeof(WCHAR);
else
label_len = 0;
overflow = TRUE;
}
TRACE("label_len = %u\n", label_len);
ffvi.VolumeCreationTime.QuadPart = 0; // FIXME
ffvi.VolumeSerialNumber = Vcb->superblock.uuid.uuid[12] << 24 | Vcb->superblock.uuid.uuid[13] << 16 | Vcb->superblock.uuid.uuid[14] << 8 | Vcb->superblock.uuid.uuid[15];
ffvi.VolumeLabelLength = orig_label_len;
ffvi.SupportsObjects = FALSE;
RtlCopyMemory(data, &ffvi, min(sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR), IrpSp->Parameters.QueryVolume.Length));
if (label_len > 0) {
ULONG bytecount;
Status = RtlUTF8ToUnicodeN(&data->VolumeLabel[0], label_len, &bytecount, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));
if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {
ERR("RtlUTF8ToUnicodeN returned %08x\n", Status);
ExReleaseResourceLite(&Vcb->tree_lock);
break;
}
TRACE("label = %.*S\n", label_len / sizeof(WCHAR), data->VolumeLabel);
}
ExReleaseResourceLite(&Vcb->tree_lock);
BytesCopied = sizeof(FILE_FS_VOLUME_INFORMATION) - sizeof(WCHAR) + label_len;
Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
break;
}
#ifndef __REACTOS__
#ifdef _MSC_VER // not in mingw yet
case FileFsSectorSizeInformation:
{
FILE_FS_SECTOR_SIZE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;
data->LogicalBytesPerSector = Vcb->superblock.sector_size;
data->PhysicalBytesPerSectorForAtomicity = Vcb->superblock.sector_size;
data->PhysicalBytesPerSectorForPerformance = Vcb->superblock.sector_size;
data->FileSystemEffectivePhysicalBytesPerSectorForAtomicity = Vcb->superblock.sector_size;
data->ByteOffsetForSectorAlignment = 0;
data->ByteOffsetForPartitionAlignment = 0;
data->Flags = SSINFO_FLAGS_ALIGNED_DEVICE | SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE;
if (Vcb->trim && !Vcb->options.no_trim)
data->Flags |= SSINFO_FLAGS_TRIM_ENABLED;
BytesCopied = sizeof(FILE_FS_SECTOR_SIZE_INFORMATION);
break;
}
#endif
#endif /* __REACTOS__ */
default:
Status = STATUS_INVALID_PARAMETER;
WARN("unknown FsInformationClass %u\n", IrpSp->Parameters.QueryVolume.FsInformationClass);
break;
}
if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
Irp->IoStatus.Information = 0;
else
Irp->IoStatus.Information = BytesCopied;
end:
Irp->IoStatus.Status = Status;
IoCompleteRequest( Irp, IO_DISK_INCREMENT );
if (top_level)
IoSetTopLevelIrp(NULL);
TRACE("query volume information returning %08x\n", Status);
FsRtlExitFileSystem();
return Status;
}
_Function_class_(IO_COMPLETION_ROUTINE)
#ifdef __REACTOS__
static NTSTATUS NTAPI read_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) {
#else
static NTSTATUS read_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) {
#endif
read_context* context = conptr;
UNUSED(DeviceObject);
context->iosb = Irp->IoStatus;
KeSetEvent(&context->Event, 0, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS create_root(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ UINT64 id,
_Out_ root** rootptr, _In_ BOOL no_tree, _In_ UINT64 offset, _In_opt_ PIRP Irp) {
NTSTATUS Status;
root* r;
tree* t = NULL;
ROOT_ITEM* ri;
traverse_ptr tp;
r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG);
if (!r) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);
if (!r->nonpaged) {
ERR("out of memory\n");
ExFreePool(r);
return STATUS_INSUFFICIENT_RESOURCES;
}
if (!no_tree) {
t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);
if (!t) {
ERR("out of memory\n");
ExFreePool(r->nonpaged);
ExFreePool(r);
return STATUS_INSUFFICIENT_RESOURCES;
}
t->is_unique = TRUE;
t->uniqueness_determined = TRUE;
t->buf = NULL;
}
ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);
if (!ri) {
ERR("out of memory\n");
if (t)
ExFreePool(t);
ExFreePool(r->nonpaged);
ExFreePool(r);
return STATUS_INSUFFICIENT_RESOURCES;
}
r->id = id;
r->treeholder.address = 0;
r->treeholder.generation = Vcb->superblock.generation;
r->treeholder.tree = t;
r->lastinode = 0;
r->dirty = FALSE;
r->received = FALSE;
r->reserved = NULL;
r->parent = 0;
r->send_ops = 0;
RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM));
r->root_item.num_references = 1;
InitializeListHead(&r->fcbs);
RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM));
// We ask here for a traverse_ptr to the item we're inserting, so we can
// copy some of the tree's variables
Status = insert_tree_item(Vcb, Vcb->root_root, id, TYPE_ROOT_ITEM, offset, ri, sizeof(ROOT_ITEM), &tp, Irp);
if (!NT_SUCCESS(Status)) {
ERR("insert_tree_item returned %08x\n", Status);
ExFreePool(ri);
if (t)
ExFreePool(t);
ExFreePool(r->nonpaged);
ExFreePool(r);
return Status;
}
ExInitializeResourceLite(&r->nonpaged->load_tree_lock);
InsertTailList(&Vcb->roots, &r->list_entry);
if (!no_tree) {
RtlZeroMemory(&t->header, sizeof(tree_header));
t->header.fs_uuid = tp.tree->header.fs_uuid;
t->header.address = 0;
t->header.flags = HEADER_FLAG_MIXED_BACKREF | 1; // 1 == "written"? Why does the Linux driver record this?
t->header.chunk_tree_uuid = tp.tree->header.chunk_tree_uuid;
t->header.generation = Vcb->superblock.generation;
t->header.tree_id = id;
t->header.num_items = 0;
t->header.level = 0;
t->has_address = FALSE;
t->size = 0;
t->Vcb = Vcb;
t->parent = NULL;
t->paritem = NULL;
t->root = r;
InitializeListHead(&t->itemlist);
t->new_address = 0;
t->has_new_address = FALSE;
t->updated_extents = FALSE;
InsertTailList(&Vcb->trees, &t->list_entry);
t->list_entry_hash.Flink = NULL;
t->write = TRUE;
Vcb->need_write = TRUE;
}
*rootptr = r;
return STATUS_SUCCESS;
}
static NTSTATUS set_label(_In_ device_extension* Vcb, _In_ FILE_FS_LABEL_INFORMATION* ffli) {
ULONG utf8len;
NTSTATUS Status;
ULONG vollen, i;
TRACE("label = %.*S\n", ffli->VolumeLabelLength / sizeof(WCHAR), ffli->VolumeLabel);
vollen = ffli->VolumeLabelLength;
for (i = 0; i < ffli->VolumeLabelLength / sizeof(WCHAR); i++) {
if (ffli->VolumeLabel[i] == 0) {
vollen = i * sizeof(WCHAR);
break;
} else if (ffli->VolumeLabel[i] == '/' || ffli->VolumeLabel[i] == '\\') {
Status = STATUS_INVALID_VOLUME_LABEL;
goto end;
}
}
if (vollen == 0) {
utf8len = 0;
} else {
Status = RtlUnicodeToUTF8N(NULL, 0, &utf8len, ffli->VolumeLabel, vollen);
if (!NT_SUCCESS(Status))
goto end;
if (utf8len > MAX_LABEL_SIZE) {
Status = STATUS_INVALID_VOLUME_LABEL;
goto end;
}
}
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
if (utf8len > 0) {
Status = RtlUnicodeToUTF8N((PCHAR)&Vcb->superblock.label, MAX_LABEL_SIZE, &utf8len, ffli->VolumeLabel, vollen);
if (!NT_SUCCESS(Status))
goto release;
} else
Status = STATUS_SUCCESS;
if (utf8len < MAX_LABEL_SIZE)
RtlZeroMemory(Vcb->superblock.label + utf8len, MAX_LABEL_SIZE - utf8len);
Vcb->need_write = TRUE;
release:
ExReleaseResourceLite(&Vcb->tree_lock);
end:
TRACE("returning %08x\n", Status);
return Status;
}
_Dispatch_type_(IRP_MJ_SET_VOLUME_INFORMATION)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_set_volume_information(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
device_extension* Vcb = DeviceObject->DeviceExtension;
NTSTATUS Status;
BOOL top_level;
FsRtlEnterFileSystem();
TRACE("set volume information\n");
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_set_volume_information(DeviceObject, Irp);
goto end;
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
Status = STATUS_NOT_IMPLEMENTED;
if (Vcb->readonly) {
Status = STATUS_MEDIA_WRITE_PROTECTED;
goto end;
}
if (Vcb->removing || Vcb->locked) {
Status = STATUS_ACCESS_DENIED;
goto end;
}
switch (IrpSp->Parameters.SetVolume.FsInformationClass) {
case FileFsControlInformation:
FIXME("STUB: FileFsControlInformation\n");
break;
case FileFsLabelInformation:
TRACE("FileFsLabelInformation\n");
Status = set_label(Vcb, Irp->AssociatedIrp.SystemBuffer);
break;
case FileFsObjectIdInformation:
FIXME("STUB: FileFsObjectIdInformation\n");
break;
default:
WARN("Unrecognized FsInformationClass 0x%x\n", IrpSp->Parameters.SetVolume.FsInformationClass);
break;
}
end:
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
TRACE("returning %08x\n", Status);
IoCompleteRequest( Irp, IO_NO_INCREMENT );
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
static WCHAR* file_desc_fcb(_In_ fcb* fcb) {
char s[60];
NTSTATUS Status;
UNICODE_STRING us;
ANSI_STRING as;
if (fcb->debug_desc)
return fcb->debug_desc;
if (fcb == fcb->Vcb->volume_fcb)
return L"volume FCB";
fcb->debug_desc = ExAllocatePoolWithTag(PagedPool, 60 * sizeof(WCHAR), ALLOC_TAG);
if (!fcb->debug_desc)
return L"(memory error)";
// I know this is pretty hackish...
// GCC doesn't like %llx in sprintf, and MSVC won't let us use swprintf
// without the CRT, which breaks drivers.
sprintf(s, "subvol %x, inode %x", (UINT32)fcb->subvol->id, (UINT32)fcb->inode);
as.Buffer = s;
as.Length = as.MaximumLength = (USHORT)strlen(s);
us.Buffer = fcb->debug_desc;
us.MaximumLength = 60 * sizeof(WCHAR);
us.Length = 0;
Status = RtlAnsiStringToUnicodeString(&us, &as, FALSE);
if (!NT_SUCCESS(Status))
return L"(RtlAnsiStringToUnicodeString error)";
us.Buffer[us.Length / sizeof(WCHAR)] = 0;
return fcb->debug_desc;
}
WCHAR* file_desc_fileref(_In_ file_ref* fileref) {
NTSTATUS Status;
UNICODE_STRING fn;
ULONG reqlen;
if (fileref->debug_desc)
return fileref->debug_desc;
fn.Length = fn.MaximumLength = 0;
Status = fileref_get_filename(fileref, &fn, NULL, &reqlen);
if (Status != STATUS_BUFFER_OVERFLOW)
return L"ERROR";
if (reqlen > 0xffff - sizeof(WCHAR))
return L"(too long)";
fileref->debug_desc = ExAllocatePoolWithTag(PagedPool, reqlen + sizeof(WCHAR), ALLOC_TAG);
if (!fileref->debug_desc)
return L"(memory error)";
fn.Buffer = fileref->debug_desc;
fn.Length = 0;
fn.MaximumLength = (USHORT)(reqlen + sizeof(WCHAR));
Status = fileref_get_filename(fileref, &fn, NULL, &reqlen);
if (!NT_SUCCESS(Status)) {
ExFreePool(fileref->debug_desc);
fileref->debug_desc = NULL;
return L"ERROR";
}
fileref->debug_desc[fn.Length / sizeof(WCHAR)] = 0;
return fileref->debug_desc;
}
_Ret_z_
WCHAR* file_desc(_In_ PFILE_OBJECT FileObject) {
fcb* fcb = FileObject->FsContext;
ccb* ccb = FileObject->FsContext2;
file_ref* fileref = ccb ? ccb->fileref : NULL;
if (fileref)
return file_desc_fileref(fileref);
else
return file_desc_fcb(fcb);
}
void send_notification_fileref(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) {
UNICODE_STRING fn;
NTSTATUS Status;
ULONG reqlen;
USHORT name_offset;
fcb* fcb = fileref->fcb;
fn.Length = fn.MaximumLength = 0;
Status = fileref_get_filename(fileref, &fn, NULL, &reqlen);
if (Status != STATUS_BUFFER_OVERFLOW) {
ERR("fileref_get_filename returned %08x\n", Status);
return;
}
if (reqlen > 0xffff) {
WARN("reqlen was too long for FsRtlNotifyFilterReportChange\n");
return;
}
fn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);
if (!fn.Buffer) {
ERR("out of memory\n");
return;
}
fn.MaximumLength = (USHORT)reqlen;
fn.Length = 0;
Status = fileref_get_filename(fileref, &fn, &name_offset, &reqlen);
if (!NT_SUCCESS(Status)) {
ERR("fileref_get_filename returned %08x\n", Status);
ExFreePool(fn.Buffer);
return;
}
FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fn, name_offset,
(PSTRING)stream, NULL, filter_match, action, NULL, NULL);
ExFreePool(fn.Buffer);
}
void send_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) {
fcb* fcb = fileref->fcb;
LIST_ENTRY* le;
NTSTATUS Status;
// no point looking for hardlinks if st_nlink == 1
if (fileref->fcb->inode_item.st_nlink == 1) {
send_notification_fileref(fileref, filter_match, action, stream);
return;
}
acquire_fcb_lock_exclusive(fcb->Vcb);
le = fcb->hardlinks.Flink;
while (le != &fcb->hardlinks) {
hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);
file_ref* parfr;
Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, NULL);
if (!NT_SUCCESS(Status))
ERR("open_fileref_by_inode returned %08x\n", Status);
else if (!parfr->deleted) {
UNICODE_STRING fn;
ULONG pathlen;
fn.Length = fn.MaximumLength = 0;
Status = fileref_get_filename(parfr, &fn, NULL, &pathlen);
if (Status != STATUS_BUFFER_OVERFLOW) {
ERR("fileref_get_filename returned %08x\n", Status);
free_fileref(fcb->Vcb, parfr);
break;
}
if (parfr != fcb->Vcb->root_fileref)
pathlen += sizeof(WCHAR);
if (pathlen + hl->name.Length > 0xffff) {
WARN("pathlen + hl->name.Length was too long for FsRtlNotifyFilterReportChange\n");
free_fileref(fcb->Vcb, parfr);
break;
}
fn.MaximumLength = (USHORT)(pathlen + hl->name.Length);
fn.Buffer = ExAllocatePoolWithTag(PagedPool, fn.MaximumLength, ALLOC_TAG);
if (!fn.Buffer) {
ERR("out of memory\n");
free_fileref(fcb->Vcb, parfr);
break;
}
Status = fileref_get_filename(parfr, &fn, NULL, NULL);
if (!NT_SUCCESS(Status)) {
ERR("fileref_get_filename returned %08x\n", Status);
free_fileref(fcb->Vcb, parfr);
ExFreePool(fn.Buffer);
break;
}
if (parfr != fcb->Vcb->root_fileref) {
fn.Buffer[(pathlen / sizeof(WCHAR)) - 1] = '\\';
fn.Length += sizeof(WCHAR);
}
RtlCopyMemory(&fn.Buffer[pathlen / sizeof(WCHAR)], hl->name.Buffer, hl->name.Length);
fn.Length += hl->name.Length;
FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fn, (USHORT)pathlen,
(PSTRING)stream, NULL, filter_match, action, NULL, NULL);
ExFreePool(fn.Buffer);
free_fileref(fcb->Vcb, parfr);
}
le = le->Flink;
}
release_fcb_lock(fcb->Vcb);
}
void mark_fcb_dirty(_In_ fcb* fcb) {
if (!fcb->dirty) {
#ifdef DEBUG_FCB_REFCOUNTS
LONG rc;
#endif
fcb->dirty = TRUE;
#ifdef DEBUG_FCB_REFCOUNTS
rc = InterlockedIncrement(&fcb->refcount);
WARN("fcb %p: refcount now %i\n", fcb, rc);
#else
InterlockedIncrement(&fcb->refcount);
#endif
ExAcquireResourceExclusiveLite(&fcb->Vcb->dirty_fcbs_lock, TRUE);
InsertTailList(&fcb->Vcb->dirty_fcbs, &fcb->list_entry_dirty);
ExReleaseResourceLite(&fcb->Vcb->dirty_fcbs_lock);
}
fcb->Vcb->need_write = TRUE;
}
void mark_fileref_dirty(_In_ file_ref* fileref) {
if (!fileref->dirty) {
fileref->dirty = TRUE;
increase_fileref_refcount(fileref);
ExAcquireResourceExclusiveLite(&fileref->fcb->Vcb->dirty_filerefs_lock, TRUE);
InsertTailList(&fileref->fcb->Vcb->dirty_filerefs, &fileref->list_entry_dirty);
ExReleaseResourceLite(&fileref->fcb->Vcb->dirty_filerefs_lock);
}
fileref->fcb->Vcb->need_write = TRUE;
}
#ifdef DEBUG_FCB_REFCOUNTS
void _free_fcb(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Inout_ fcb* fcb, _In_ const char* func) {
#else
void free_fcb(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Inout_ fcb* fcb) {
#endif
LONG rc;
rc = InterlockedDecrement(&fcb->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
#ifdef DEBUG_LONG_MESSAGES
ERR("fcb %p: refcount now %i (subvol %llx, inode %llx)\n", fcb, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode);
#else
ERR("fcb %p: refcount now %i (subvol %llx, inode %llx)\n", fcb, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode);
#endif
#endif
if (rc > 0)
return;
if (fcb->list_entry.Flink)
RemoveEntryList(&fcb->list_entry);
if (fcb->list_entry_all.Flink)
RemoveEntryList(&fcb->list_entry_all);
ExDeleteResourceLite(&fcb->nonpaged->resource);
ExDeleteResourceLite(&fcb->nonpaged->paging_resource);
ExDeleteResourceLite(&fcb->nonpaged->dir_children_lock);
ExFreeToNPagedLookasideList(&Vcb->fcb_np_lookaside, fcb->nonpaged);
if (fcb->sd)
ExFreePool(fcb->sd);
if (fcb->adsxattr.Buffer)
ExFreePool(fcb->adsxattr.Buffer);
if (fcb->reparse_xattr.Buffer)
ExFreePool(fcb->reparse_xattr.Buffer);
if (fcb->ea_xattr.Buffer)
ExFreePool(fcb->ea_xattr.Buffer);
if (fcb->adsdata.Buffer)
ExFreePool(fcb->adsdata.Buffer);
if (fcb->debug_desc)
ExFreePool(fcb->debug_desc);
while (!IsListEmpty(&fcb->extents)) {
LIST_ENTRY* le = RemoveHeadList(&fcb->extents);
extent* ext = CONTAINING_RECORD(le, extent, list_entry);
if (ext->csum)
ExFreePool(ext->csum);
ExFreePool(ext);
}
while (!IsListEmpty(&fcb->hardlinks)) {
LIST_ENTRY* le = RemoveHeadList(&fcb->hardlinks);
hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);
if (hl->name.Buffer)
ExFreePool(hl->name.Buffer);
if (hl->utf8.Buffer)
ExFreePool(hl->utf8.Buffer);
ExFreePool(hl);
}
while (!IsListEmpty(&fcb->xattrs)) {
xattr* xa = CONTAINING_RECORD(RemoveHeadList(&fcb->xattrs), xattr, list_entry);
ExFreePool(xa);
}
while (!IsListEmpty(&fcb->dir_children_index)) {
LIST_ENTRY* le = RemoveHeadList(&fcb->dir_children_index);
dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);
ExFreePool(dc->utf8.Buffer);
ExFreePool(dc->name.Buffer);
ExFreePool(dc->name_uc.Buffer);
ExFreePool(dc);
}
if (fcb->hash_ptrs)
ExFreePool(fcb->hash_ptrs);
if (fcb->hash_ptrs_uc)
ExFreePool(fcb->hash_ptrs_uc);
FsRtlUninitializeFileLock(&fcb->lock);
if (fcb->pool_type == NonPagedPool)
ExFreePool(fcb);
else
ExFreeToPagedLookasideList(&Vcb->fcb_lookaside, fcb);
#ifdef DEBUG_FCB_REFCOUNTS
#ifdef DEBUG_LONG_MESSAGES
_debug_message(func, file, line, "freeing fcb %p\n", fcb);
#else
_debug_message(func, "freeing fcb %p\n", fcb);
#endif
#endif
}
void free_fileref(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Inout_ file_ref* fr) {
LONG rc;
rc = InterlockedDecrement(&fr->refcount);
#ifdef DEBUG_FCB_REFCOUNTS
ERR("fileref %p: refcount now %i\n", fr, rc);
#endif
#ifdef _DEBUG
if (rc < 0) {
ERR("fileref %p: refcount now %i\n", fr, rc);
int3;
}
#endif
if (rc > 0)
return;
if (fr->parent)
ExAcquireResourceExclusiveLite(&fr->parent->nonpaged->children_lock, TRUE);
// FIXME - do we need a file_ref lock?
// FIXME - do delete if needed
if (fr->debug_desc)
ExFreePool(fr->debug_desc);
ExDeleteResourceLite(&fr->nonpaged->children_lock);
ExDeleteResourceLite(&fr->nonpaged->fileref_lock);
ExFreeToNPagedLookasideList(&Vcb->fileref_np_lookaside, fr->nonpaged);
// FIXME - throw error if children not empty
if (fr->fcb->fileref == fr)
fr->fcb->fileref = NULL;
if (fr->dc) {
if (fr->fcb->ads)
fr->dc->size = fr->fcb->adsdata.Length;
fr->dc->fileref = NULL;
}
if (fr->list_entry.Flink)
RemoveEntryList(&fr->list_entry);
if (fr->parent) {
ExReleaseResourceLite(&fr->parent->nonpaged->children_lock);
free_fileref(Vcb, fr->parent);
}
free_fcb(Vcb, fr->fcb);
ExFreeToPagedLookasideList(&Vcb->fileref_lookaside, fr);
}
static NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp) {
fcb* fcb;
ccb* ccb;
file_ref* fileref = NULL;
LONG open_files;
device_extension* Vcb;
UNUSED(Irp);
TRACE("FileObject = %p\n", FileObject);
fcb = FileObject->FsContext;
if (!fcb) {
TRACE("FCB was NULL, returning success\n");
return STATUS_SUCCESS;
}
open_files = InterlockedDecrement(&fcb->Vcb->open_files);
ccb = FileObject->FsContext2;
TRACE("close called for %S (fcb == %p)\n", file_desc(FileObject), fcb);
// FIXME - make sure notification gets sent if file is being deleted
if (ccb) {
if (ccb->query_string.Buffer)
RtlFreeUnicodeString(&ccb->query_string);
if (ccb->filename.Buffer)
ExFreePool(ccb->filename.Buffer);
// FIXME - use refcounts for fileref
fileref = ccb->fileref;
if (fcb->Vcb->running_sends > 0) {
BOOL send_cancelled = FALSE;
ExAcquireResourceExclusiveLite(&fcb->Vcb->send_load_lock, TRUE);
if (ccb->send) {
ccb->send->cancelling = TRUE;
send_cancelled = TRUE;
KeSetEvent(&ccb->send->cleared_event, 0, FALSE);
}
ExReleaseResourceLite(&fcb->Vcb->send_load_lock);
if (send_cancelled) {
while (ccb->send) {
ExAcquireResourceExclusiveLite(&fcb->Vcb->send_load_lock, TRUE);
ExReleaseResourceLite(&fcb->Vcb->send_load_lock);
}
}
}
ExFreePool(ccb);
}
CcUninitializeCacheMap(FileObject, NULL, NULL);
if (open_files == 0 && fcb->Vcb->removing) {
uninit(fcb->Vcb, FALSE);
return STATUS_SUCCESS;
}
if (!(fcb->Vcb->Vpb->Flags & VPB_MOUNTED))
return STATUS_SUCCESS;
Vcb = fcb->Vcb;
acquire_fcb_lock_exclusive(Vcb);
if (fileref)
free_fileref(fcb->Vcb, fileref);
else
free_fcb(Vcb, fcb);
release_fcb_lock(Vcb);
return STATUS_SUCCESS;
}
void uninit(_In_ device_extension* Vcb, _In_ BOOL flush) {
UINT64 i;
NTSTATUS Status;
LIST_ENTRY* le;
LARGE_INTEGER time;
if (!Vcb->removing) {
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
Vcb->removing = TRUE;
ExReleaseResourceLite(&Vcb->tree_lock);
}
RemoveEntryList(&Vcb->list_entry);
if (Vcb->balance.thread) {
Vcb->balance.paused = FALSE;
Vcb->balance.stopping = TRUE;
KeSetEvent(&Vcb->balance.event, 0, FALSE);
KeWaitForSingleObject(&Vcb->balance.finished, Executive, KernelMode, FALSE, NULL);
}
if (Vcb->scrub.thread) {
Vcb->scrub.paused = FALSE;
Vcb->scrub.stopping = TRUE;
KeSetEvent(&Vcb->scrub.event, 0, FALSE);
KeWaitForSingleObject(&Vcb->scrub.finished, Executive, KernelMode, FALSE, NULL);
}
if (Vcb->running_sends != 0) {
BOOL send_cancelled = FALSE;
ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, TRUE);
le = Vcb->send_ops.Flink;
while (le != &Vcb->send_ops) {
send_info* send = CONTAINING_RECORD(le, send_info, list_entry);
if (!send->cancelling) {
send->cancelling = TRUE;
send_cancelled = TRUE;
send->ccb = NULL;
KeSetEvent(&send->cleared_event, 0, FALSE);
}
le = le->Flink;
}
ExReleaseResourceLite(&Vcb->send_load_lock);
if (send_cancelled) {
while (Vcb->running_sends != 0) {
ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, TRUE);
ExReleaseResourceLite(&Vcb->send_load_lock);
}
}
}
Status = registry_mark_volume_unmounted(&Vcb->superblock.uuid);
if (!NT_SUCCESS(Status) && Status != STATUS_TOO_LATE)
WARN("registry_mark_volume_unmounted returned %08x\n", Status);
if (flush) {
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
if (Vcb->need_write && !Vcb->readonly) {
Status = do_write(Vcb, NULL);
if (!NT_SUCCESS(Status))
ERR("do_write returned %08x\n", Status);
}
free_trees(Vcb);
ExReleaseResourceLite(&Vcb->tree_lock);
}
for (i = 0; i < Vcb->calcthreads.num_threads; i++) {
Vcb->calcthreads.threads[i].quit = TRUE;
}
KeSetEvent(&Vcb->calcthreads.event, 0, FALSE);
for (i = 0; i < Vcb->calcthreads.num_threads; i++) {
KeWaitForSingleObject(&Vcb->calcthreads.threads[i].finished, Executive, KernelMode, FALSE, NULL);
ZwClose(Vcb->calcthreads.threads[i].handle);
}
ExDeleteResourceLite(&Vcb->calcthreads.lock);
ExFreePool(Vcb->calcthreads.threads);
time.QuadPart = 0;
KeSetTimer(&Vcb->flush_thread_timer, time, NULL); // trigger the timer early
KeWaitForSingleObject(&Vcb->flush_thread_finished, Executive, KernelMode, FALSE, NULL);
acquire_fcb_lock_exclusive(Vcb);
free_fcb(Vcb, Vcb->volume_fcb);
free_fcb(Vcb, Vcb->dummy_fcb);
release_fcb_lock(Vcb);
if (Vcb->root_file)
ObDereferenceObject(Vcb->root_file);
le = Vcb->chunks.Flink;
while (le != &Vcb->chunks) {
chunk* c = CONTAINING_RECORD(le, chunk, list_entry);
if (c->cache) {
acquire_fcb_lock_exclusive(Vcb);
free_fcb(Vcb, c->cache);
release_fcb_lock(Vcb);
c->cache = NULL;
}
le = le->Flink;
}
while (!IsListEmpty(&Vcb->roots)) {
root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->roots), root, list_entry);
ExDeleteResourceLite(&r->nonpaged->load_tree_lock);
ExFreePool(r->nonpaged);
ExFreePool(r);
}
while (!IsListEmpty(&Vcb->chunks)) {
chunk* c = CONTAINING_RECORD(RemoveHeadList(&Vcb->chunks), chunk, list_entry);
while (!IsListEmpty(&c->space)) {
LIST_ENTRY* le2 = RemoveHeadList(&c->space);
space* s = CONTAINING_RECORD(le2, space, list_entry);
ExFreePool(s);
}
while (!IsListEmpty(&c->deleting)) {
LIST_ENTRY* le2 = RemoveHeadList(&c->deleting);
space* s = CONTAINING_RECORD(le2, space, list_entry);
ExFreePool(s);
}
if (c->devices)
ExFreePool(c->devices);
if (c->cache) {
acquire_fcb_lock_exclusive(Vcb);
free_fcb(Vcb, c->cache);
release_fcb_lock(Vcb);
}
ExDeleteResourceLite(&c->range_locks_lock);
ExDeleteResourceLite(&c->partial_stripes_lock);
ExDeleteResourceLite(&c->lock);
ExDeleteResourceLite(&c->changed_extents_lock);
ExFreePool(c->chunk_item);
ExFreePool(c);
}
// FIXME - free any open fcbs?
while (!IsListEmpty(&Vcb->devices)) {
device* dev = CONTAINING_RECORD(RemoveHeadList(&Vcb->devices), device, list_entry);
while (!IsListEmpty(&dev->space)) {
LIST_ENTRY* le2 = RemoveHeadList(&dev->space);
space* s = CONTAINING_RECORD(le2, space, list_entry);
ExFreePool(s);
}
ExFreePool(dev);
}
ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, TRUE);
while (!IsListEmpty(&Vcb->scrub.errors)) {
scrub_error* err = CONTAINING_RECORD(RemoveHeadList(&Vcb->scrub.errors), scrub_error, list_entry);
ExFreePool(err);
}
ExReleaseResourceLite(&Vcb->scrub.stats_lock);
ExDeleteResourceLite(&Vcb->fcb_lock);
ExDeleteResourceLite(&Vcb->load_lock);
ExDeleteResourceLite(&Vcb->tree_lock);
ExDeleteResourceLite(&Vcb->chunk_lock);
ExDeleteResourceLite(&Vcb->dirty_fcbs_lock);
ExDeleteResourceLite(&Vcb->dirty_filerefs_lock);
ExDeleteResourceLite(&Vcb->dirty_subvols_lock);
ExDeleteResourceLite(&Vcb->scrub.stats_lock);
ExDeleteResourceLite(&Vcb->send_load_lock);
ExDeletePagedLookasideList(&Vcb->tree_data_lookaside);
ExDeletePagedLookasideList(&Vcb->traverse_ptr_lookaside);
ExDeletePagedLookasideList(&Vcb->batch_item_lookaside);
ExDeletePagedLookasideList(&Vcb->fileref_lookaside);
ExDeletePagedLookasideList(&Vcb->fcb_lookaside);
ExDeletePagedLookasideList(&Vcb->name_bit_lookaside);
ExDeleteNPagedLookasideList(&Vcb->range_lock_lookaside);
ExDeleteNPagedLookasideList(&Vcb->fileref_np_lookaside);
ExDeleteNPagedLookasideList(&Vcb->fcb_np_lookaside);
ZwClose(Vcb->flush_thread_handle);
}
NTSTATUS delete_fileref(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback) {
LARGE_INTEGER newlength, time;
BTRFS_TIME now;
NTSTATUS Status;
ULONG utf8len = 0;
KeQuerySystemTime(&time);
win_time_to_unix(time, &now);
ExAcquireResourceExclusiveLite(fileref->fcb->Header.Resource, TRUE);
if (fileref->deleted) {
ExReleaseResourceLite(fileref->fcb->Header.Resource);
return STATUS_SUCCESS;
}
if (fileref->fcb->subvol->send_ops > 0) {
ExReleaseResourceLite(fileref->fcb->Header.Resource);
return STATUS_ACCESS_DENIED;
}
fileref->deleted = TRUE;
mark_fileref_dirty(fileref);
// delete INODE_ITEM (0x1)
TRACE("nlink = %u\n", fileref->fcb->inode_item.st_nlink);
if (!fileref->fcb->ads) {
if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {
LIST_ENTRY* le;
mark_fcb_dirty(fileref->fcb);
fileref->fcb->inode_item_changed = TRUE;
if (fileref->fcb->inode_item.st_nlink > 1) {
fileref->fcb->inode_item.st_nlink--;
fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
fileref->fcb->inode_item.sequence++;
fileref->fcb->inode_item.st_ctime = now;
} else {
// excise extents
if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY && fileref->fcb->inode_item.st_size > 0) {
Status = excise_extents(fileref->fcb->Vcb, fileref->fcb, 0, sector_align(fileref->fcb->inode_item.st_size, fileref->fcb->Vcb->superblock.sector_size), Irp, rollback);
if (!NT_SUCCESS(Status)) {
ERR("excise_extents returned %08x\n", Status);
ExReleaseResourceLite(fileref->fcb->Header.Resource);
return Status;
}
}
fileref->fcb->Header.AllocationSize.QuadPart = 0;
fileref->fcb->Header.FileSize.QuadPart = 0;
fileref->fcb->Header.ValidDataLength.QuadPart = 0;
if (FileObject) {
CC_FILE_SIZES ccfs;
ccfs.AllocationSize = fileref->fcb->Header.AllocationSize;
ccfs.FileSize = fileref->fcb->Header.FileSize;
ccfs.ValidDataLength = fileref->fcb->Header.ValidDataLength;
Status = STATUS_SUCCESS;
_SEH2_TRY {
CcSetFileSizes(FileObject, &ccfs);
} _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
Status = _SEH2_GetExceptionCode();
} _SEH2_END;
if (!NT_SUCCESS(Status)) {
ERR("CcSetFileSizes threw exception %08x\n", Status);
ExReleaseResourceLite(fileref->fcb->Header.Resource);
return Status;
}
}
fileref->fcb->deleted = TRUE;
le = fileref->children.Flink;
while (le != &fileref->children) {
file_ref* fr2 = CONTAINING_RECORD(le, file_ref, list_entry);
if (fr2->fcb->ads) {
fr2->fcb->deleted = TRUE;
mark_fcb_dirty(fr2->fcb);
}
le = le->Flink;
}
}
if (fileref->dc) {
le = fileref->fcb->hardlinks.Flink;
while (le != &fileref->fcb->hardlinks) {
hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);
if (hl->parent == fileref->parent->fcb->inode && hl->index == fileref->dc->index) {
RemoveEntryList(&hl->list_entry);
if (hl->name.Buffer)
ExFreePool(hl->name.Buffer);
if (hl->utf8.Buffer)
ExFreePool(hl->utf8.Buffer);
ExFreePool(hl);
break;
}
le = le->Flink;
}
}
} else if (fileref->fcb->subvol->parent == fileref->parent->fcb->subvol->id) { // valid subvolume
if (fileref->fcb->subvol->root_item.num_references > 1) {
fileref->fcb->subvol->root_item.num_references--;
mark_fcb_dirty(fileref->fcb); // so ROOT_ITEM gets updated
} else {
LIST_ENTRY* le;
// FIXME - we need a lock here
RemoveEntryList(&fileref->fcb->subvol->list_entry);
InsertTailList(&fileref->fcb->Vcb->drop_roots, &fileref->fcb->subvol->list_entry);
le = fileref->children.Flink;
while (le != &fileref->children) {
file_ref* fr2 = CONTAINING_RECORD(le, file_ref, list_entry);
if (fr2->fcb->ads) {
fr2->fcb->deleted = TRUE;
mark_fcb_dirty(fr2->fcb);
}
le = le->Flink;
}
}
}
} else {
fileref->fcb->deleted = TRUE;
mark_fcb_dirty(fileref->fcb);
}
// remove dir_child from parent
if (fileref->dc) {
TRACE("delete file %.*S\n", fileref->dc->name.Length / sizeof(WCHAR), fileref->dc->name.Buffer);
ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, TRUE);
RemoveEntryList(&fileref->dc->list_entry_index);
if (!fileref->fcb->ads)
remove_dir_child_from_hash_lists(fileref->parent->fcb, fileref->dc);
ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);
if (!fileref->oldutf8.Buffer)
fileref->oldutf8 = fileref->dc->utf8;
else
ExFreePool(fileref->dc->utf8.Buffer);
utf8len = fileref->dc->utf8.Length;
fileref->oldindex = fileref->dc->index;
ExFreePool(fileref->dc->name.Buffer);
ExFreePool(fileref->dc->name_uc.Buffer);
ExFreePool(fileref->dc);
fileref->dc = NULL;
}
// update INODE_ITEM of parent
ExAcquireResourceExclusiveLite(fileref->parent->fcb->Header.Resource, TRUE);
fileref->parent->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
fileref->parent->fcb->inode_item.sequence++;
fileref->parent->fcb->inode_item.st_ctime = now;
if (!fileref->fcb->ads) {
TRACE("fileref->parent->fcb->inode_item.st_size (inode %llx) was %llx\n", fileref->parent->fcb->inode, fileref->parent->fcb->inode_item.st_size);
fileref->parent->fcb->inode_item.st_size -= utf8len * 2;
TRACE("fileref->parent->fcb->inode_item.st_size (inode %llx) now %llx\n", fileref->parent->fcb->inode, fileref->parent->fcb->inode_item.st_size);
fileref->parent->fcb->inode_item.st_mtime = now;
}
fileref->parent->fcb->inode_item_changed = TRUE;
ExReleaseResourceLite(fileref->parent->fcb->Header.Resource);
if (!fileref->fcb->ads && fileref->parent->dc)
send_notification_fcb(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);
mark_fcb_dirty(fileref->parent->fcb);
fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation;
fileref->fcb->subvol->root_item.ctime = now;
newlength.QuadPart = 0;
if (FileObject && !CcUninitializeCacheMap(FileObject, &newlength, NULL))
TRACE("CcUninitializeCacheMap failed\n");
ExReleaseResourceLite(fileref->fcb->Header.Resource);
return STATUS_SUCCESS;
}
_Dispatch_type_(IRP_MJ_CLEANUP)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_cleanup(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PFILE_OBJECT FileObject = IrpSp->FileObject;
device_extension* Vcb = DeviceObject->DeviceExtension;
fcb* fcb = FileObject->FsContext;
BOOL top_level;
FsRtlEnterFileSystem();
TRACE("cleanup\n");
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_cleanup(DeviceObject, Irp);
goto exit;
} else if (DeviceObject == master_devobj) {
TRACE("closing file system\n");
Status = STATUS_SUCCESS;
goto exit;
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
Status = STATUS_INVALID_PARAMETER;
goto exit;
}
if (FileObject->Flags & FO_CLEANUP_COMPLETE) {
TRACE("FileObject %p already cleaned up\n", FileObject);
Status = STATUS_SUCCESS;
goto exit;
}
if (!fcb) {
ERR("fcb was NULL\n");
Status = STATUS_INVALID_PARAMETER;
goto exit;
}
// We have to use the pointer to Vcb stored in the fcb, as we can receive cleanup
// messages belonging to other devices.
if (FileObject && FileObject->FsContext) {
LONG oc;
ccb* ccb;
file_ref* fileref;
BOOL locked = TRUE;
ccb = FileObject->FsContext2;
fileref = ccb ? ccb->fileref : NULL;
TRACE("cleanup called for FileObject %p\n", FileObject);
TRACE("fileref %p (%S), refcount = %u, open_count = %u\n", fileref, file_desc(FileObject), fileref ? fileref->refcount : 0, fileref ? fileref->open_count : 0);
ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, TRUE);
ExAcquireResourceExclusiveLite(fcb->Header.Resource, TRUE);
IoRemoveShareAccess(FileObject, &fcb->share_access);
if (ccb)
FsRtlNotifyCleanup(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, ccb);
if (fileref) {
oc = InterlockedDecrement(&fileref->open_count);
#ifdef DEBUG_FCB_REFCOUNTS
ERR("fileref %p: open_count now %i\n", fileref, oc);
#endif
}
if (ccb && ccb->options & FILE_DELETE_ON_CLOSE && fileref)
fileref->delete_on_close = TRUE;
if (fileref && fileref->delete_on_close && fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && fcb != fcb->Vcb->dummy_fcb)
fileref->delete_on_close = FALSE;
if (fcb->Vcb->locked && fcb->Vcb->locked_fileobj == FileObject) {
TRACE("unlocking volume\n");
do_unlock_volume(fcb->Vcb);
FsRtlNotifyVolumeEvent(FileObject, FSRTL_VOLUME_UNLOCK);
}
if (ccb && ccb->reserving) {
fcb->subvol->reserved = NULL;
ccb->reserving = FALSE;
// FIXME - flush all of subvol's fcbs
}
if (fileref && oc == 0) {
if (!fcb->Vcb->removing) {
if (fileref && fileref->delete_on_close && fileref != fcb->Vcb->root_fileref && fcb != fcb->Vcb->volume_fcb) {
LIST_ENTRY rollback;
InitializeListHead(&rollback);
if (!fileref->fcb->ads || fileref->dc) {
if (fileref->fcb->ads) {
send_notification_fileref(fileref->parent, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
FILE_ACTION_REMOVED, &fileref->dc->name);
} else
send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL);
}
ExReleaseResourceLite(fcb->Header.Resource);
locked = FALSE;
// fcb_lock needs to be acquired before fcb->Header.Resource
acquire_fcb_lock_exclusive(fcb->Vcb);
Status = delete_fileref(fileref, FileObject, Irp, &rollback);
if (!NT_SUCCESS(Status)) {
ERR("delete_fileref returned %08x\n", Status);
do_rollback(fcb->Vcb, &rollback);
release_fcb_lock(fcb->Vcb);
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
goto exit;
}
release_fcb_lock(fcb->Vcb);
locked = FALSE;
clear_rollback(&rollback);
} else if (FileObject->Flags & FO_CACHE_SUPPORTED && fcb->nonpaged->segment_object.DataSectionObject) {
IO_STATUS_BLOCK iosb;
CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);
if (!NT_SUCCESS(iosb.Status)) {
ERR("CcFlushCache returned %08x\n", iosb.Status);
}
if (!ExIsResourceAcquiredSharedLite(fcb->Header.PagingIoResource)) {
ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, TRUE);
ExReleaseResourceLite(fcb->Header.PagingIoResource);
}
CcPurgeCacheSection(&fcb->nonpaged->segment_object, NULL, 0, FALSE);
TRACE("flushed cache on close (FileObject = %p, fcb = %p, AllocationSize = %llx, FileSize = %llx, ValidDataLength = %llx)\n",
FileObject, fcb, fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
}
}
if (fcb->Vcb && fcb != fcb->Vcb->volume_fcb)
CcUninitializeCacheMap(FileObject, NULL, NULL);
}
if (locked)
ExReleaseResourceLite(fcb->Header.Resource);
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
FileObject->Flags |= FO_CLEANUP_COMPLETE;
}
Status = STATUS_SUCCESS;
exit:
TRACE("returning %08x\n", Status);
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
_Success_(return)
BOOL get_file_attributes_from_xattr(_In_reads_bytes_(len) char* val, _In_ UINT16 len, _Out_ ULONG* atts) {
if (len > 2 && val[0] == '0' && val[1] == 'x') {
int i;
ULONG dosnum = 0;
for (i = 2; i < len; i++) {
dosnum *= 0x10;
if (val[i] >= '0' && val[i] <= '9')
dosnum |= val[i] - '0';
else if (val[i] >= 'a' && val[i] <= 'f')
dosnum |= val[i] + 10 - 'a';
else if (val[i] >= 'A' && val[i] <= 'F')
dosnum |= val[i] + 10 - 'a';
}
TRACE("DOSATTRIB: %08x\n", dosnum);
*atts = dosnum;
return TRUE;
}
return FALSE;
}
ULONG get_file_attributes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ UINT64 inode,
_In_ UINT8 type, _In_ BOOL dotfile, _In_ BOOL ignore_xa, _In_opt_ PIRP Irp) {
ULONG att;
char* eaval;
UINT16 ealen;
if (!ignore_xa && get_xattr(Vcb, r, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (UINT8**)&eaval, &ealen, Irp)) {
ULONG dosnum = 0;
if (get_file_attributes_from_xattr(eaval, ealen, &dosnum)) {
ExFreePool(eaval);
if (type == BTRFS_TYPE_DIRECTORY)
dosnum |= FILE_ATTRIBUTE_DIRECTORY;
else if (type == BTRFS_TYPE_SYMLINK)
dosnum |= FILE_ATTRIBUTE_REPARSE_POINT;
if (type != BTRFS_TYPE_DIRECTORY)
dosnum &= ~FILE_ATTRIBUTE_DIRECTORY;
if (inode == SUBVOL_ROOT_INODE) {
if (r->root_item.flags & BTRFS_SUBVOL_READONLY)
dosnum |= FILE_ATTRIBUTE_READONLY;
else
dosnum &= ~FILE_ATTRIBUTE_READONLY;
}
return dosnum;
}
ExFreePool(eaval);
}
switch (type) {
case BTRFS_TYPE_DIRECTORY:
att = FILE_ATTRIBUTE_DIRECTORY;
break;
case BTRFS_TYPE_SYMLINK:
att = FILE_ATTRIBUTE_REPARSE_POINT;
break;
default:
att = 0;
break;
}
if (dotfile) {
att |= FILE_ATTRIBUTE_HIDDEN;
}
att |= FILE_ATTRIBUTE_ARCHIVE;
if (inode == SUBVOL_ROOT_INODE) {
if (r->root_item.flags & BTRFS_SUBVOL_READONLY)
att |= FILE_ATTRIBUTE_READONLY;
else
att &= ~FILE_ATTRIBUTE_READONLY;
}
// FIXME - get READONLY from ii->st_mode
// FIXME - return SYSTEM for block/char devices?
if (att == 0)
att = FILE_ATTRIBUTE_NORMAL;
return att;
}
NTSTATUS sync_read_phys(_In_ PDEVICE_OBJECT DeviceObject, _In_ UINT64 StartingOffset, _In_ ULONG Length,
_Out_writes_bytes_(Length) PUCHAR Buffer, _In_ BOOL override) {
IO_STATUS_BLOCK IoStatus;
LARGE_INTEGER Offset;
PIRP Irp;
PIO_STACK_LOCATION IrpSp;
NTSTATUS Status;
read_context context;
num_reads++;
RtlZeroMemory(&context, sizeof(read_context));
KeInitializeEvent(&context.Event, NotificationEvent, FALSE);
Offset.QuadPart = (LONGLONG)StartingOffset;
Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
if (!Irp) {
ERR("IoAllocateIrp failed\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
Irp->Flags |= IRP_NOCACHE;
IrpSp = IoGetNextIrpStackLocation(Irp);
IrpSp->MajorFunction = IRP_MJ_READ;
if (override)
IrpSp->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
if (DeviceObject->Flags & DO_BUFFERED_IO) {
Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, Length, ALLOC_TAG);
if (!Irp->AssociatedIrp.SystemBuffer) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;
Irp->UserBuffer = Buffer;
} else if (DeviceObject->Flags & DO_DIRECT_IO) {
Irp->MdlAddress = IoAllocateMdl(Buffer, Length, FALSE, FALSE, NULL);
if (!Irp->MdlAddress) {
ERR("IoAllocateMdl failed\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
Status = STATUS_SUCCESS;
_SEH2_TRY {
MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoWriteAccess);
} _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
Status = _SEH2_GetExceptionCode();
} _SEH2_END;
if (!NT_SUCCESS(Status)) {
ERR("MmProbeAndLockPages threw exception %08x\n", Status);
IoFreeMdl(Irp->MdlAddress);
goto exit;
}
} else
Irp->UserBuffer = Buffer;
IrpSp->Parameters.Read.Length = Length;
IrpSp->Parameters.Read.ByteOffset = Offset;
Irp->UserIosb = &IoStatus;
Irp->UserEvent = &context.Event;
IoSetCompletionRoutine(Irp, read_completion, &context, TRUE, TRUE, TRUE);
Status = IoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(&context.Event, Executive, KernelMode, FALSE, NULL);
Status = context.iosb.Status;
}
if (DeviceObject->Flags & DO_DIRECT_IO) {
MmUnlockPages(Irp->MdlAddress);
IoFreeMdl(Irp->MdlAddress);
}
exit:
IoFreeIrp(Irp);
return Status;
}
static NTSTATUS read_superblock(_In_ device_extension* Vcb, _In_ PDEVICE_OBJECT device, _In_ UINT64 length) {
NTSTATUS Status;
superblock* sb;
ULONG i, to_read;
UINT8 valid_superblocks;
to_read = device->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), device->SectorSize);
sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);
if (!sb) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
if (superblock_addrs[0] + to_read > length) {
WARN("device was too short to have any superblock\n");
ExFreePool(sb);
return STATUS_UNRECOGNIZED_VOLUME;
}
i = 0;
valid_superblocks = 0;
while (superblock_addrs[i] > 0) {
UINT32 crc32;
if (i > 0 && superblock_addrs[i] + to_read > length)
break;
Status = sync_read_phys(device, superblock_addrs[i], to_read, (PUCHAR)sb, FALSE);
if (!NT_SUCCESS(Status)) {
ERR("Failed to read superblock %u: %08x\n", i, Status);
ExFreePool(sb);
return Status;
}
if (sb->magic != BTRFS_MAGIC) {
if (i == 0) {
TRACE("not a BTRFS volume\n");
ExFreePool(sb);
return STATUS_UNRECOGNIZED_VOLUME;
}
} else {
TRACE("got superblock %u!\n", i);
crc32 = ~calc_crc32c(0xffffffff, (UINT8*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
if (crc32 != *((UINT32*)sb->checksum))
WARN("crc32 was %08x, expected %08x\n", crc32, *((UINT32*)sb->checksum));
else if (sb->sector_size == 0)
WARN("superblock sector size was 0\n");
else if (sb->node_size < sizeof(tree_header) + sizeof(internal_node) || sb->node_size > 0x10000)
WARN("invalid node size %x\n", sb->node_size);
else if ((sb->node_size % sb->sector_size) != 0)
WARN("node size %x was not a multiple of sector_size %x\n", sb->node_size, sb->sector_size);
else if (valid_superblocks == 0 || sb->generation > Vcb->superblock.generation) {
RtlCopyMemory(&Vcb->superblock, sb, sizeof(superblock));
valid_superblocks++;
}
}
i++;
}
ExFreePool(sb);
if (valid_superblocks == 0) {
ERR("could not find any valid superblocks\n");
return STATUS_INTERNAL_ERROR;
}
TRACE("label is %s\n", Vcb->superblock.label);
return STATUS_SUCCESS;
}
NTSTATUS dev_ioctl(_In_ PDEVICE_OBJECT DeviceObject, _In_ ULONG ControlCode, _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, _In_ ULONG InputBufferSize,
_Out_writes_bytes_opt_(OutputBufferSize) PVOID OutputBuffer, _In_ ULONG OutputBufferSize, _In_ BOOLEAN Override, _Out_opt_ IO_STATUS_BLOCK* iosb) {
PIRP Irp;
KEVENT Event;
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp;
IO_STATUS_BLOCK IoStatus;
KeInitializeEvent(&Event, NotificationEvent, FALSE);
Irp = IoBuildDeviceIoControlRequest(ControlCode,
DeviceObject,
InputBuffer,
InputBufferSize,
OutputBuffer,
OutputBufferSize,
FALSE,
&Event,
&IoStatus);
if (!Irp) return STATUS_INSUFFICIENT_RESOURCES;
if (Override) {
IrpSp = IoGetNextIrpStackLocation(Irp);
IrpSp->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
}
Status = IoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
Status = IoStatus.Status;
}
if (iosb)
*iosb = IoStatus;
return Status;
}
_Requires_exclusive_lock_held_(Vcb->tree_lock)
static NTSTATUS add_root(_Inout_ device_extension* Vcb, _In_ UINT64 id, _In_ UINT64 addr,
_In_ UINT64 generation, _In_opt_ traverse_ptr* tp) {
root* r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG);
if (!r) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
r->id = id;
r->dirty = FALSE;
r->received = FALSE;
r->reserved = NULL;
r->treeholder.address = addr;
r->treeholder.tree = NULL;
r->treeholder.generation = generation;
r->parent = 0;
r->send_ops = 0;
InitializeListHead(&r->fcbs);
r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);
if (!r->nonpaged) {
ERR("out of memory\n");
ExFreePool(r);
return STATUS_INSUFFICIENT_RESOURCES;
}
ExInitializeResourceLite(&r->nonpaged->load_tree_lock);
r->lastinode = 0;
if (tp) {
RtlCopyMemory(&r->root_item, tp->item->data, min(sizeof(ROOT_ITEM), tp->item->size));
if (tp->item->size < sizeof(ROOT_ITEM))
RtlZeroMemory(((UINT8*)&r->root_item) + tp->item->size, sizeof(ROOT_ITEM) - tp->item->size);
} else
RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM));
if (!Vcb->readonly && (r->id == BTRFS_ROOT_ROOT || r->id == BTRFS_ROOT_FSTREE || (r->id >= 0x100 && !(r->id & 0xf000000000000000)))) { // FS tree root
// FIXME - don't call this if subvol is readonly (though we will have to if we ever toggle this flag)
get_last_inode(Vcb, r, NULL);
if (r->id == BTRFS_ROOT_ROOT && r->lastinode < 0x100)
r->lastinode = 0x100;
}
InsertTailList(&Vcb->roots, &r->list_entry);
switch (r->id) {
case BTRFS_ROOT_ROOT:
Vcb->root_root = r;
break;
case BTRFS_ROOT_EXTENT:
Vcb->extent_root = r;
break;
case BTRFS_ROOT_CHUNK:
Vcb->chunk_root = r;
break;
case BTRFS_ROOT_DEVTREE:
Vcb->dev_root = r;
break;
case BTRFS_ROOT_CHECKSUM:
Vcb->checksum_root = r;
break;
case BTRFS_ROOT_UUID:
Vcb->uuid_root = r;
break;
case BTRFS_ROOT_FREE_SPACE:
Vcb->space_root = r;
break;
case BTRFS_ROOT_DATA_RELOC:
Vcb->data_reloc_root = r;
break;
}
return STATUS_SUCCESS;
}
static NTSTATUS look_for_roots(_Requires_exclusive_lock_held_(_Curr_->tree_lock) _In_ device_extension* Vcb, _In_opt_ PIRP Irp) {
traverse_ptr tp, next_tp;
KEY searchkey;
BOOL b;
NTSTATUS Status;
searchkey.obj_id = 0;
searchkey.obj_type = 0;
searchkey.offset = 0;
Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
return Status;
}
do {
TRACE("(%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
if (tp.item->key.obj_type == TYPE_ROOT_ITEM) {
ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;
if (tp.item->size < offsetof(ROOT_ITEM, byte_limit)) {
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(ROOT_ITEM, byte_limit));
} else {
TRACE("root %llx - address %llx\n", tp.item->key.obj_id, ri->block_number);
Status = add_root(Vcb, tp.item->key.obj_id, ri->block_number, ri->generation, &tp);
if (!NT_SUCCESS(Status)) {
ERR("add_root returned %08x\n", Status);
return Status;
}
}
} else if (tp.item->key.obj_type == TYPE_ROOT_BACKREF && !IsListEmpty(&Vcb->roots)) {
root* lastroot = CONTAINING_RECORD(Vcb->roots.Blink, root, list_entry);
if (lastroot->id == tp.item->key.obj_id)
lastroot->parent = tp.item->key.offset;
}
b = find_next_item(Vcb, &tp, &next_tp, FALSE, Irp);
if (b)
tp = next_tp;
} while (b);
if (!Vcb->readonly && !Vcb->data_reloc_root) {
root* reloc_root;
INODE_ITEM* ii;
UINT16 irlen;
INODE_REF* ir;
LARGE_INTEGER time;
BTRFS_TIME now;
WARN("data reloc root doesn't exist, creating it\n");
Status = create_root(Vcb, BTRFS_ROOT_DATA_RELOC, &reloc_root, FALSE, 0, Irp);
if (!NT_SUCCESS(Status)) {
ERR("create_root returned %08x\n", Status);
return Status;
}
reloc_root->root_item.inode.generation = 1;
reloc_root->root_item.inode.st_size = 3;
reloc_root->root_item.inode.st_blocks = Vcb->superblock.node_size;
reloc_root->root_item.inode.st_nlink = 1;
reloc_root->root_item.inode.st_mode = 040755;
reloc_root->root_item.inode.flags = 0xffffffff80000000;
reloc_root->root_item.objid = SUBVOL_ROOT_INODE;
reloc_root->root_item.bytes_used = Vcb->superblock.node_size;
ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);
if (!ii) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
KeQuerySystemTime(&time);
win_time_to_unix(time, &now);
RtlZeroMemory(ii, sizeof(INODE_ITEM));
ii->generation = Vcb->superblock.generation;
ii->st_blocks = Vcb->superblock.node_size;
ii->st_nlink = 1;
ii->st_mode = 040755;
ii->st_atime = now;
ii->st_ctime = now;
ii->st_mtime = now;
Status = insert_tree_item(Vcb, reloc_root, SUBVOL_ROOT_INODE, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp);
if (!NT_SUCCESS(Status)) {
ERR("insert_tree_item returned %08x\n", Status);
ExFreePool(ii);
return Status;
}
irlen = (UINT16)offsetof(INODE_REF, name[0]) + 2;
ir = ExAllocatePoolWithTag(PagedPool, irlen, ALLOC_TAG);
if (!ir) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
ir->index = 0;
ir->n = 2;
ir->name[0] = '.';
ir->name[1] = '.';
Status = insert_tree_item(Vcb, reloc_root, SUBVOL_ROOT_INODE, TYPE_INODE_REF, SUBVOL_ROOT_INODE, ir, irlen, NULL, Irp);
if (!NT_SUCCESS(Status)) {
ERR("insert_tree_item returned %08x\n", Status);
ExFreePool(ir);
return Status;
}
Vcb->data_reloc_root = reloc_root;
Vcb->need_write = TRUE;
}
return STATUS_SUCCESS;
}
static NTSTATUS find_disk_holes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ device* dev, _In_opt_ PIRP Irp) {
KEY searchkey;
traverse_ptr tp, next_tp;
BOOL b;
UINT64 lastaddr;
NTSTATUS Status;
InitializeListHead(&dev->space);
searchkey.obj_id = 0;
searchkey.obj_type = TYPE_DEV_STATS;
searchkey.offset = dev->devitem.dev_id;
Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, FALSE, Irp);
if (NT_SUCCESS(Status) && !keycmp(tp.item->key, searchkey))
RtlCopyMemory(dev->stats, tp.item->data, min(sizeof(UINT64) * 5, tp.item->size));
searchkey.obj_id = dev->devitem.dev_id;
searchkey.obj_type = TYPE_DEV_EXTENT;
searchkey.offset = 0;
Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
return Status;
}
lastaddr = 0;
do {
if (tp.item->key.obj_id == dev->devitem.dev_id && tp.item->key.obj_type == TYPE_DEV_EXTENT) {
if (tp.item->size >= sizeof(DEV_EXTENT)) {
DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data;
if (tp.item->key.offset > lastaddr) {
Status = add_space_entry(&dev->space, NULL, lastaddr, tp.item->key.offset - lastaddr);
if (!NT_SUCCESS(Status)) {
ERR("add_space_entry returned %08x\n", Status);
return Status;
}
}
lastaddr = tp.item->key.offset + de->length;
} else {
ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DEV_EXTENT));
}
}
b = find_next_item(Vcb, &tp, &next_tp, FALSE, Irp);
if (b) {
tp = next_tp;
if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)
break;
}
} while (b);
if (lastaddr < dev->devitem.num_bytes) {
Status = add_space_entry(&dev->space, NULL, lastaddr, dev->devitem.num_bytes - lastaddr);
if (!NT_SUCCESS(Status)) {
ERR("add_space_entry returned %08x\n", Status);
return Status;
}
}
// The Linux driver doesn't like to allocate chunks within the first megabyte of a device.
space_list_subtract2(&dev->space, NULL, 0, 0x100000, NULL, NULL);
return STATUS_SUCCESS;
}
static void add_device_to_list(_In_ device_extension* Vcb, _In_ device* dev) {
LIST_ENTRY* le;
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
device* dev2 = CONTAINING_RECORD(le, device, list_entry);
if (dev2->devitem.dev_id > dev->devitem.dev_id) {
InsertHeadList(le->Blink, &dev->list_entry);
return;
}
le = le->Flink;
}
InsertTailList(&Vcb->devices, &dev->list_entry);
}
_Ret_maybenull_
device* find_device_from_uuid(_In_ device_extension* Vcb, _In_ BTRFS_UUID* uuid) {
volume_device_extension* vde;
pdo_device_extension* pdode;
LIST_ENTRY* le;
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
device* dev = CONTAINING_RECORD(le, device, list_entry);
TRACE("device %llx, uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", dev->devitem.dev_id,
dev->devitem.device_uuid.uuid[0], dev->devitem.device_uuid.uuid[1], dev->devitem.device_uuid.uuid[2], dev->devitem.device_uuid.uuid[3], dev->devitem.device_uuid.uuid[4], dev->devitem.device_uuid.uuid[5], dev->devitem.device_uuid.uuid[6], dev->devitem.device_uuid.uuid[7],
dev->devitem.device_uuid.uuid[8], dev->devitem.device_uuid.uuid[9], dev->devitem.device_uuid.uuid[10], dev->devitem.device_uuid.uuid[11], dev->devitem.device_uuid.uuid[12], dev->devitem.device_uuid.uuid[13], dev->devitem.device_uuid.uuid[14], dev->devitem.device_uuid.uuid[15]);
if (RtlCompareMemory(&dev->devitem.device_uuid, uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
TRACE("returning device %llx\n", dev->devitem.dev_id);
return dev;
}
le = le->Flink;
}
vde = Vcb->vde;
if (!vde)
goto end;
pdode = vde->pdode;
ExAcquireResourceSharedLite(&pdode->child_lock, TRUE);
if (Vcb->devices_loaded < Vcb->superblock.num_devices) {
le = pdode->children.Flink;
while (le != &pdode->children) {
volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
if (RtlCompareMemory(uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
device* dev;
dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);
if (!dev) {
ExReleaseResourceLite(&pdode->child_lock);
ERR("out of memory\n");
return NULL;
}
RtlZeroMemory(dev, sizeof(device));
dev->devobj = vc->devobj;
dev->devitem.device_uuid = *uuid;
dev->devitem.dev_id = vc->devid;
dev->devitem.num_bytes = vc->size;
dev->seeding = vc->seeding;
dev->readonly = dev->seeding;
dev->reloc = FALSE;
dev->removable = FALSE;
dev->disk_num = vc->disk_num;
dev->part_num = vc->part_num;
dev->num_trim_entries = 0;
InitializeListHead(&dev->trim_list);
add_device_to_list(Vcb, dev);
Vcb->devices_loaded++;
ExReleaseResourceLite(&pdode->child_lock);
return dev;
}
le = le->Flink;
}
}
ExReleaseResourceLite(&pdode->child_lock);
end:
WARN("could not find device with uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
uuid->uuid[0], uuid->uuid[1], uuid->uuid[2], uuid->uuid[3], uuid->uuid[4], uuid->uuid[5], uuid->uuid[6], uuid->uuid[7],
uuid->uuid[8], uuid->uuid[9], uuid->uuid[10], uuid->uuid[11], uuid->uuid[12], uuid->uuid[13], uuid->uuid[14], uuid->uuid[15]);
return NULL;
}
static BOOL is_device_removable(_In_ PDEVICE_OBJECT devobj) {
NTSTATUS Status;
STORAGE_HOTPLUG_INFO shi;
Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_HOTPLUG_INFO, NULL, 0, &shi, sizeof(STORAGE_HOTPLUG_INFO), TRUE, NULL);
if (!NT_SUCCESS(Status)) {
ERR("dev_ioctl returned %08x\n", Status);
return FALSE;
}
return shi.MediaRemovable != 0 ? TRUE : FALSE;
}
static ULONG get_device_change_count(_In_ PDEVICE_OBJECT devobj) {
NTSTATUS Status;
ULONG cc;
IO_STATUS_BLOCK iosb;
Status = dev_ioctl(devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), TRUE, &iosb);
if (!NT_SUCCESS(Status)) {
ERR("dev_ioctl returned %08x\n", Status);
return 0;
}
if (iosb.Information < sizeof(ULONG)) {
ERR("iosb.Information was too short\n");
return 0;
}
return cc;
}
void init_device(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ BOOL get_nums) {
NTSTATUS Status;
ULONG aptelen;
ATA_PASS_THROUGH_EX* apte;
STORAGE_PROPERTY_QUERY spq;
DEVICE_TRIM_DESCRIPTOR dtd;
dev->removable = is_device_removable(dev->devobj);
dev->change_count = dev->removable ? get_device_change_count(dev->devobj) : 0;
if (get_nums) {
STORAGE_DEVICE_NUMBER sdn;
Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
&sdn, sizeof(STORAGE_DEVICE_NUMBER), TRUE, NULL);
if (!NT_SUCCESS(Status)) {
WARN("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08x\n", Status);
dev->disk_num = 0xffffffff;
dev->part_num = 0xffffffff;
} else {
dev->disk_num = sdn.DeviceNumber;
dev->part_num = sdn.PartitionNumber;
}
}
dev->trim = FALSE;
dev->readonly = dev->seeding;
dev->reloc = FALSE;
dev->num_trim_entries = 0;
dev->stats_changed = FALSE;
InitializeListHead(&dev->trim_list);
if (!dev->readonly) {
Status = dev_ioctl(dev->devobj, IOCTL_DISK_IS_WRITABLE, NULL, 0,
NULL, 0, TRUE, NULL);
if (Status == STATUS_MEDIA_WRITE_PROTECTED)
dev->readonly = TRUE;
}
aptelen = sizeof(ATA_PASS_THROUGH_EX) + 512;
apte = ExAllocatePoolWithTag(NonPagedPool, aptelen, ALLOC_TAG);
if (!apte) {
ERR("out of memory\n");
return;
}
RtlZeroMemory(apte, aptelen);
apte->Length = sizeof(ATA_PASS_THROUGH_EX);
apte->AtaFlags = ATA_FLAGS_DATA_IN;
apte->DataTransferLength = aptelen - sizeof(ATA_PASS_THROUGH_EX);
apte->TimeOutValue = 3;
apte->DataBufferOffset = apte->Length;
apte->CurrentTaskFile[6] = IDE_COMMAND_IDENTIFY;
Status = dev_ioctl(dev->devobj, IOCTL_ATA_PASS_THROUGH, apte, aptelen,
apte, aptelen, TRUE, NULL);
if (!NT_SUCCESS(Status))
TRACE("IOCTL_ATA_PASS_THROUGH returned %08x for IDENTIFY DEVICE\n", Status);
else {
IDENTIFY_DEVICE_DATA* idd = (IDENTIFY_DEVICE_DATA*)((UINT8*)apte + sizeof(ATA_PASS_THROUGH_EX));
if (idd->CommandSetSupport.FlushCache) {
dev->can_flush = TRUE;
TRACE("FLUSH CACHE supported\n");
} else
TRACE("FLUSH CACHE not supported\n");
}
ExFreePool(apte);
spq.PropertyId = StorageDeviceTrimProperty;
spq.QueryType = PropertyStandardQuery;
spq.AdditionalParameters[0] = 0;
Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(STORAGE_PROPERTY_QUERY),
&dtd, sizeof(DEVICE_TRIM_DESCRIPTOR), TRUE, NULL);
if (NT_SUCCESS(Status)) {
if (dtd.TrimEnabled) {
dev->trim = TRUE;
Vcb->trim = TRUE;
TRACE("TRIM supported\n");
} else
TRACE("TRIM not supported\n");
}
RtlZeroMemory(dev->stats, sizeof(UINT64) * 5);
}
static NTSTATUS load_chunk_root(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {
traverse_ptr tp, next_tp;
KEY searchkey;
BOOL b;
chunk* c;
NTSTATUS Status;
searchkey.obj_id = 0;
searchkey.obj_type = 0;
searchkey.offset = 0;
Vcb->data_flags = 0;
Vcb->metadata_flags = 0;
Vcb->system_flags = 0;
Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
return Status;
}
do {
TRACE("(%llx,%x,%llx)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
if (tp.item->key.obj_id == 1 && tp.item->key.obj_type == TYPE_DEV_ITEM) {
if (tp.item->size < sizeof(DEV_ITEM)) {
ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DEV_ITEM));
} else {
DEV_ITEM* di = (DEV_ITEM*)tp.item->data;
LIST_ENTRY* le;
BOOL done = FALSE;
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
device* dev = CONTAINING_RECORD(le, device, list_entry);
if (dev->devobj && RtlCompareMemory(&dev->devitem.device_uuid, &di->device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
RtlCopyMemory(&dev->devitem, tp.item->data, min(tp.item->size, sizeof(DEV_ITEM)));
if (le != Vcb->devices.Flink)
init_device(Vcb, dev, TRUE);
done = TRUE;
break;
}
le = le->Flink;
}
if (!done && Vcb->vde) {
volume_device_extension* vde = Vcb->vde;
pdo_device_extension* pdode = vde->pdode;
ExAcquireResourceSharedLite(&pdode->child_lock, TRUE);
if (Vcb->devices_loaded < Vcb->superblock.num_devices) {
le = pdode->children.Flink;
while (le != &pdode->children) {
volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);
if (RtlCompareMemory(&di->device_uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
device* dev;
dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);
if (!dev) {
ExReleaseResourceLite(&pdode->child_lock);
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(dev, sizeof(device));
dev->devobj = vc->devobj;
RtlCopyMemory(&dev->devitem, di, min(tp.item->size, sizeof(DEV_ITEM)));
dev->seeding = vc->seeding;
init_device(Vcb, dev, FALSE);
if (dev->devitem.num_bytes > vc->size) {
WARN("device %llx: DEV_ITEM says %llx bytes, but Windows only reports %llx\n", tp.item->key.offset,
dev->devitem.num_bytes, vc->size);
dev->devitem.num_bytes = vc->size;
}
dev->disk_num = vc->disk_num;
dev->part_num = vc->part_num;
add_device_to_list(Vcb, dev);
Vcb->devices_loaded++;
done = TRUE;
break;
}
le = le->Flink;
}
if (!done) {
if (!Vcb->options.allow_degraded) {
ERR("volume not found: device %llx, uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", tp.item->key.offset,
di->device_uuid.uuid[0], di->device_uuid.uuid[1], di->device_uuid.uuid[2], di->device_uuid.uuid[3], di->device_uuid.uuid[4], di->device_uuid.uuid[5], di->device_uuid.uuid[6], di->device_uuid.uuid[7],
di->device_uuid.uuid[8], di->device_uuid.uuid[9], di->device_uuid.uuid[10], di->device_uuid.uuid[11], di->device_uuid.uuid[12], di->device_uuid.uuid[13], di->device_uuid.uuid[14], di->device_uuid.uuid[15]);
} else {
device* dev;
dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);
if (!dev) {
ExReleaseResourceLite(&pdode->child_lock);
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(dev, sizeof(device));
// Missing device, so we keep dev->devobj as NULL
RtlCopyMemory(&dev->devitem, di, min(tp.item->size, sizeof(DEV_ITEM)));
InitializeListHead(&dev->trim_list);
add_device_to_list(Vcb, dev);
Vcb->devices_loaded++;
}
}
} else
ERR("unexpected device %llx found\n", tp.item->key.offset);
ExReleaseResourceLite(&pdode->child_lock);
}
}
} else if (tp.item->key.obj_type == TYPE_CHUNK_ITEM) {
if (tp.item->size < sizeof(CHUNK_ITEM)) {
ERR("(%llx,%x,%llx) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(CHUNK_ITEM));
} else {
c = ExAllocatePoolWithTag(NonPagedPool, sizeof(chunk), ALLOC_TAG);
if (!c) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
c->size = tp.item->size;
c->offset = tp.item->key.offset;
c->used = c->oldused = 0;
c->cache = c->old_cache = NULL;
c->created = FALSE;
c->readonly = FALSE;
c->reloc = FALSE;
c->cache_loaded = FALSE;
c->changed = FALSE;
c->space_changed = FALSE;
c->balance_num = 0;
c->chunk_item = ExAllocatePoolWithTag(NonPagedPool, tp.item->size, ALLOC_TAG);
if (!c->chunk_item) {
ERR("out of memory\n");
ExFreePool(c);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(c->chunk_item, tp.item->data, tp.item->size);
if (c->chunk_item->type & BLOCK_FLAG_DATA && c->chunk_item->type > Vcb->data_flags)
Vcb->data_flags = c->chunk_item->type;
if (c->chunk_item->type & BLOCK_FLAG_METADATA && c->chunk_item->type > Vcb->metadata_flags)
Vcb->metadata_flags = c->chunk_item->type;
if (c->chunk_item->type & BLOCK_FLAG_SYSTEM && c->chunk_item->type > Vcb->system_flags)
Vcb->system_flags = c->chunk_item->type;
if (c->chunk_item->type & BLOCK_FLAG_RAID10) {
if (c->chunk_item->sub_stripes == 0 || c->chunk_item->sub_stripes > c->chunk_item->num_stripes) {
ERR("chunk %llx: invalid stripes (num_stripes %u, sub_stripes %u)\n", c->offset, c->chunk_item->num_stripes, c->chunk_item->sub_stripes);
ExFreePool(c->chunk_item);
ExFreePool(c);
return STATUS_INTERNAL_ERROR;
}
}
if (c->chunk_item->num_stripes > 0) {
CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];
UINT16 i;
c->devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * c->chunk_item->num_stripes, ALLOC_TAG);
if (!c->devices) {
ERR("out of memory\n");
ExFreePool(c->chunk_item);
ExFreePool(c);
return STATUS_INSUFFICIENT_RESOURCES;
}
for (i = 0; i < c->chunk_item->num_stripes; i++) {
c->devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid);
TRACE("device %llu = %p\n", i, c->devices[i]);
if (!c->devices[i]) {
ERR("missing device\n");
ExFreePool(c->chunk_item);
ExFreePool(c);
return STATUS_INTERNAL_ERROR;
}
if (c->devices[i]->readonly)
c->readonly = TRUE;
}
} else {
ERR("chunk %llx: number of stripes is 0\n", c->offset);
ExFreePool(c->chunk_item);
ExFreePool(c);
return STATUS_INTERNAL_ERROR;
}
ExInitializeResourceLite(&c->lock);
ExInitializeResourceLite(&c->changed_extents_lock);
InitializeListHead(&c->space);
InitializeListHead(&c->space_size);
InitializeListHead(&c->deleting);
InitializeListHead(&c->changed_extents);
InitializeListHead(&c->range_locks);
ExInitializeResourceLite(&c->range_locks_lock);
KeInitializeEvent(&c->range_locks_event, NotificationEvent, FALSE);
InitializeListHead(&c->partial_stripes);
ExInitializeResourceLite(&c->partial_stripes_lock);
c->last_alloc_set = FALSE;
c->last_stripe = 0;
InsertTailList(&Vcb->chunks, &c->list_entry);
c->list_entry_balance.Flink = NULL;
}
}
b = find_next_item(Vcb, &tp, &next_tp, FALSE, Irp);
if (b)
tp = next_tp;
} while (b);
Vcb->log_to_phys_loaded = TRUE;
if (Vcb->data_flags == 0)
Vcb->data_flags = BLOCK_FLAG_DATA | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID0 : 0);
if (Vcb->metadata_flags == 0)
Vcb->metadata_flags = BLOCK_FLAG_METADATA | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID1 : BLOCK_FLAG_DUPLICATE);
if (Vcb->system_flags == 0)
Vcb->system_flags = BLOCK_FLAG_SYSTEM | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID1 : BLOCK_FLAG_DUPLICATE);
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) {
Vcb->metadata_flags |= BLOCK_FLAG_DATA;
Vcb->data_flags = Vcb->metadata_flags;
}
return STATUS_SUCCESS;
}
void protect_superblocks(_Inout_ chunk* c) {
UINT16 i = 0, j;
UINT64 off_start, off_end;
// The Linux driver also protects all the space before the first superblock.
// I realize this confuses physical and logical addresses, but this is what btrfs-progs does -
// evidently Linux assumes the chunk at 0 is always SINGLE.
if (c->offset < superblock_addrs[0])
space_list_subtract(c, FALSE, c->offset, superblock_addrs[0] - c->offset, NULL);
while (superblock_addrs[i] != 0) {
CHUNK_ITEM* ci = c->chunk_item;
CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];
if (ci->type & BLOCK_FLAG_RAID0 || ci->type & BLOCK_FLAG_RAID10) {
for (j = 0; j < ci->num_stripes; j++) {
UINT16 sub_stripes = max(ci->sub_stripes, 1);
if (cis[j].offset + (ci->size * ci->num_stripes / sub_stripes) > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {
#ifdef _DEBUG
UINT64 startoff;
UINT16 startoffstripe;
#endif
TRACE("cut out superblock in chunk %llx\n", c->offset);
off_start = superblock_addrs[i] - cis[j].offset;
off_start -= off_start % ci->stripe_length;
off_start *= ci->num_stripes / sub_stripes;
off_start += (j / sub_stripes) * ci->stripe_length;
off_end = off_start + ci->stripe_length;
#ifdef _DEBUG
get_raid0_offset(off_start, ci->stripe_length, ci->num_stripes / sub_stripes, &startoff, &startoffstripe);
TRACE("j = %u, startoffstripe = %u\n", j, startoffstripe);
TRACE("startoff = %llx, superblock = %llx\n", startoff + cis[j].offset, superblock_addrs[i]);
#endif
space_list_subtract(c, FALSE, c->offset + off_start, off_end - off_start, NULL);
}
}
} else if (ci->type & BLOCK_FLAG_RAID5) {
UINT64 stripe_size = ci->size / (ci->num_stripes - 1);
for (j = 0; j < ci->num_stripes; j++) {
if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {
TRACE("cut out superblock in chunk %llx\n", c->offset);
off_start = superblock_addrs[i] - cis[j].offset;
off_start -= off_start % ci->stripe_length;
off_start *= ci->num_stripes - 1;
off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), ci->stripe_length);
off_end *= ci->num_stripes - 1;
TRACE("cutting out %llx, size %llx\n", c->offset + off_start, off_end - off_start);
space_list_subtract(c, FALSE, c->offset + off_start, off_end - off_start, NULL);
}
}
} else if (ci->type & BLOCK_FLAG_RAID6) {
UINT64 stripe_size = ci->size / (ci->num_stripes - 2);
for (j = 0; j < ci->num_stripes; j++) {
if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {
TRACE("cut out superblock in chunk %llx\n", c->offset);
off_start = superblock_addrs[i] - cis[j].offset;
off_start -= off_start % ci->stripe_length;
off_start *= ci->num_stripes - 2;
off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), ci->stripe_length);
off_end *= ci->num_stripes - 2;
TRACE("cutting out %llx, size %llx\n", c->offset + off_start, off_end - off_start);
space_list_subtract(c, FALSE, c->offset + off_start, off_end - off_start, NULL);
}
}
} else { // SINGLE, DUPLICATE, RAID1
for (j = 0; j < ci->num_stripes; j++) {
if (cis[j].offset + ci->size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {
TRACE("cut out superblock in chunk %llx\n", c->offset);
// The Linux driver protects the whole stripe in which the superblock lives
off_start = ((superblock_addrs[i] - cis[j].offset) / c->chunk_item->stripe_length) * c->chunk_item->stripe_length;
off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), c->chunk_item->stripe_length);
space_list_subtract(c, FALSE, c->offset + off_start, off_end - off_start, NULL);
}
}
}
i++;
}
}
NTSTATUS find_chunk_usage(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {
LIST_ENTRY* le = Vcb->chunks.Flink;
chunk* c;
KEY searchkey;
traverse_ptr tp;
BLOCK_GROUP_ITEM* bgi;
NTSTATUS Status;
searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;
while (le != &Vcb->chunks) {
c = CONTAINING_RECORD(le, chunk, list_entry);
searchkey.obj_id = c->offset;
searchkey.offset = c->chunk_item->size;
Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
return Status;
}
if (!keycmp(searchkey, tp.item->key)) {
if (tp.item->size >= sizeof(BLOCK_GROUP_ITEM)) {
bgi = (BLOCK_GROUP_ITEM*)tp.item->data;
c->used = c->oldused = bgi->used;
TRACE("chunk %llx has %llx bytes used\n", c->offset, c->used);
} else {
ERR("(%llx;%llx,%x,%llx) is %u bytes, expected %u\n",
Vcb->extent_root->id, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(BLOCK_GROUP_ITEM));
}
}
le = le->Flink;
}
Vcb->chunk_usage_found = TRUE;
return STATUS_SUCCESS;
}
static NTSTATUS load_sys_chunks(_In_ device_extension* Vcb) {
KEY key;
ULONG n = Vcb->superblock.n;
while (n > 0) {
if (n > sizeof(KEY)) {
RtlCopyMemory(&key, &Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n], sizeof(KEY));
n -= sizeof(KEY);
} else
return STATUS_SUCCESS;
TRACE("bootstrap: %llx,%x,%llx\n", key.obj_id, key.obj_type, key.offset);
if (key.obj_type == TYPE_CHUNK_ITEM) {
CHUNK_ITEM* ci;
USHORT cisize;
sys_chunk* sc;
if (n < sizeof(CHUNK_ITEM))
return STATUS_SUCCESS;
ci = (CHUNK_ITEM*)&Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n];
cisize = sizeof(CHUNK_ITEM) + (ci->num_stripes * sizeof(CHUNK_ITEM_STRIPE));
if (n < cisize)
return STATUS_SUCCESS;
sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG);
if (!sc) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
sc->key = key;
sc->size = cisize;
sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG);
if (!sc->data) {
ERR("out of memory\n");
ExFreePool(sc);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(sc->data, ci, sc->size);
InsertTailList(&Vcb->sys_chunks, &sc->list_entry);
n -= cisize;
} else {
ERR("unexpected item %llx,%x,%llx in bootstrap\n", key.obj_id, key.obj_type, key.offset);
return STATUS_INTERNAL_ERROR;
}
}
return STATUS_SUCCESS;
}
_Ret_maybenull_
static root* find_default_subvol(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {
LIST_ENTRY* le;
static char fn[] = "default";
static UINT32 crc32 = 0x8dbfc2d2;
if (Vcb->options.subvol_id != 0) {
le = Vcb->roots.Flink;
while (le != &Vcb->roots) {
root* r = CONTAINING_RECORD(le, root, list_entry);
if (r->id == Vcb->options.subvol_id)
return r;
le = le->Flink;
}
}
if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL) {
NTSTATUS Status;
KEY searchkey;
traverse_ptr tp;
DIR_ITEM* di;
searchkey.obj_id = Vcb->superblock.root_dir_objectid;
searchkey.obj_type = TYPE_DIR_ITEM;
searchkey.offset = crc32;
Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
goto end;
}
if (keycmp(tp.item->key, searchkey)) {
ERR("could not find (%llx,%x,%llx) in root tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
goto end;
}
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));
goto end;
}
di = (DIR_ITEM*)tp.item->data;
if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->n) {
ERR("(%llx,%x,%llx) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM) - 1 + di->n);
goto end;
}
if (di->n != strlen(fn) || RtlCompareMemory(di->name, fn, di->n) != di->n) {
ERR("root DIR_ITEM had same CRC32, but was not \"default\"\n");
goto end;
}
if (di->key.obj_type != TYPE_ROOT_ITEM) {
ERR("default root has key (%llx,%x,%llx), expected subvolume\n", di->key.obj_id, di->key.obj_type, di->key.offset);
goto end;
}
le = Vcb->roots.Flink;
while (le != &Vcb->roots) {
root* r = CONTAINING_RECORD(le, root, list_entry);
if (r->id == di->key.obj_id)
return r;
le = le->Flink;
}
ERR("could not find root %llx, using default instead\n", di->key.obj_id);
}
end:
le = Vcb->roots.Flink;
while (le != &Vcb->roots) {
root* r = CONTAINING_RECORD(le, root, list_entry);
if (r->id == BTRFS_ROOT_FSTREE)
return r;
le = le->Flink;
}
return NULL;
}
void init_file_cache(_In_ PFILE_OBJECT FileObject, _In_ CC_FILE_SIZES* ccfs) {
TRACE("(%p, %p)\n", FileObject, ccfs);
CcInitializeCacheMap(FileObject, ccfs, FALSE, cache_callbacks, FileObject);
if (diskacc)
fCcSetAdditionalCacheAttributesEx(FileObject, CC_ENABLE_DISK_IO_ACCOUNTING);
CcSetReadAheadGranularity(FileObject, READ_AHEAD_GRANULARITY);
}
static NTSTATUS create_calc_threads(_In_ PDEVICE_OBJECT DeviceObject) {
device_extension* Vcb = DeviceObject->DeviceExtension;
ULONG i;
Vcb->calcthreads.num_threads = KeQueryActiveProcessorCount(NULL);
Vcb->calcthreads.threads = ExAllocatePoolWithTag(NonPagedPool, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads, ALLOC_TAG);
if (!Vcb->calcthreads.threads) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
InitializeListHead(&Vcb->calcthreads.job_list);
ExInitializeResourceLite(&Vcb->calcthreads.lock);
KeInitializeEvent(&Vcb->calcthreads.event, NotificationEvent, FALSE);
RtlZeroMemory(Vcb->calcthreads.threads, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads);
for (i = 0; i < Vcb->calcthreads.num_threads; i++) {
NTSTATUS Status;
Vcb->calcthreads.threads[i].DeviceObject = DeviceObject;
KeInitializeEvent(&Vcb->calcthreads.threads[i].finished, NotificationEvent, FALSE);
Status = PsCreateSystemThread(&Vcb->calcthreads.threads[i].handle, 0, NULL, NULL, NULL, calc_thread, &Vcb->calcthreads.threads[i]);
if (!NT_SUCCESS(Status)) {
ULONG j;
ERR("PsCreateSystemThread returned %08x\n", Status);
for (j = 0; j < i; j++) {
Vcb->calcthreads.threads[i].quit = TRUE;
}
KeSetEvent(&Vcb->calcthreads.event, 0, FALSE);
return Status;
}
}
return STATUS_SUCCESS;
}
static BOOL is_btrfs_volume(_In_ PDEVICE_OBJECT DeviceObject) {
NTSTATUS Status;
MOUNTDEV_NAME mdn, *mdn2;
ULONG mdnsize;
Status = dev_ioctl(DeviceObject, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), TRUE, NULL);
if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {
ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08x\n", Status);
return FALSE;
}
mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;
mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG);
if (!mdn2) {
ERR("out of memory\n");
return FALSE;
}
Status = dev_ioctl(DeviceObject, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, TRUE, NULL);
if (!NT_SUCCESS(Status)) {
ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08x\n", Status);
ExFreePool(mdn2);
return FALSE;
}
if (mdn2->NameLength > wcslen(BTRFS_VOLUME_PREFIX) * sizeof(WCHAR) &&
RtlCompareMemory(mdn2->Name, BTRFS_VOLUME_PREFIX, wcslen(BTRFS_VOLUME_PREFIX) * sizeof(WCHAR)) == wcslen(BTRFS_VOLUME_PREFIX) * sizeof(WCHAR)) {
ExFreePool(mdn2);
return TRUE;
}
ExFreePool(mdn2);
return FALSE;
}
static NTSTATUS get_device_pnp_name_guid(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _In_ const GUID* guid) {
NTSTATUS Status;
WCHAR *list = NULL, *s;
Status = IoGetDeviceInterfaces((PVOID)guid, NULL, 0, &list);
if (!NT_SUCCESS(Status)) {
ERR("IoGetDeviceInterfaces returned %08x\n", Status);
return Status;
}
s = list;
while (s[0] != 0) {
PFILE_OBJECT FileObject;
PDEVICE_OBJECT devobj;
UNICODE_STRING name;
name.Length = name.MaximumLength = (USHORT)wcslen(s) * sizeof(WCHAR);
name.Buffer = s;
if (NT_SUCCESS(IoGetDeviceObjectPointer(&name, FILE_READ_ATTRIBUTES, &FileObject, &devobj))) {
if (DeviceObject == devobj || DeviceObject == FileObject->DeviceObject) {
ObDereferenceObject(FileObject);
pnp_name->Buffer = ExAllocatePoolWithTag(PagedPool, name.Length, ALLOC_TAG);
if (!pnp_name->Buffer) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
RtlCopyMemory(pnp_name->Buffer, name.Buffer, name.Length);
pnp_name->Length = pnp_name->MaximumLength = name.Length;
Status = STATUS_SUCCESS;
goto end;
}
ObDereferenceObject(FileObject);
}
s = &s[wcslen(s) + 1];
}
pnp_name->Length = pnp_name->MaximumLength = 0;
pnp_name->Buffer = 0;
Status = STATUS_NOT_FOUND;
end:
if (list)
ExFreePool(list);
return Status;
}
NTSTATUS get_device_pnp_name(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _Out_ const GUID** guid) {
NTSTATUS Status;
Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_VOLUME);
if (NT_SUCCESS(Status)) {
*guid = &GUID_DEVINTERFACE_VOLUME;
return Status;
}
Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_HIDDEN_VOLUME);
if (NT_SUCCESS(Status)) {
*guid = &GUID_DEVINTERFACE_HIDDEN_VOLUME;
return Status;
}
Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_DISK);
if (NT_SUCCESS(Status)) {
*guid = &GUID_DEVINTERFACE_DISK;
return Status;
}
return STATUS_NOT_FOUND;
}
_Success_(return>=0)
static NTSTATUS check_mount_device(_In_ PDEVICE_OBJECT DeviceObject, _Out_ BOOL* no_pnp) {
NTSTATUS Status;
ULONG to_read;
superblock* sb;
UINT32 crc32;
UNICODE_STRING pnp_name;
const GUID* guid;
to_read = DeviceObject->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), DeviceObject->SectorSize);
sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);
if (!sb) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = sync_read_phys(DeviceObject, superblock_addrs[0], to_read, (PUCHAR)sb, TRUE);
if (!NT_SUCCESS(Status)) {
ERR("sync_read_phys returned %08x\n", Status);
goto end;
}
if (sb->magic != BTRFS_MAGIC) {
Status = STATUS_SUCCESS;
goto end;
}
crc32 = ~calc_crc32c(0xffffffff, (UINT8*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
if (crc32 != *((UINT32*)sb->checksum)) {
WARN("crc32 was %08x, expected %08x\n", crc32, *((UINT32*)sb->checksum));
Status = STATUS_SUCCESS;
goto end;
}
DeviceObject->Flags &= ~DO_VERIFY_VOLUME;
pnp_name.Buffer = NULL;
Status = get_device_pnp_name(DeviceObject, &pnp_name, &guid);
if (!NT_SUCCESS(Status)) {
WARN("get_device_pnp_name returned %08x\n", Status);
pnp_name.Length = 0;
}
if (pnp_name.Length == 0)
*no_pnp = TRUE;
else {
*no_pnp = FALSE;
volume_arrival(drvobj, &pnp_name);
}
if (pnp_name.Buffer)
ExFreePool(pnp_name.Buffer);
Status = STATUS_SUCCESS;
end:
ExFreePool(sb);
return Status;
}
static BOOL still_has_superblock(_In_ PDEVICE_OBJECT device) {
NTSTATUS Status;
ULONG to_read;
superblock* sb;
PDEVICE_OBJECT device2;
if (!device)
return FALSE;
to_read = device->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), device->SectorSize);
sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);
if (!sb) {
ERR("out of memory\n");
return FALSE;
}
Status = sync_read_phys(device, superblock_addrs[0], to_read, (PUCHAR)sb, TRUE);
if (!NT_SUCCESS(Status)) {
ERR("Failed to read superblock: %08x\n", Status);
ExFreePool(sb);
return FALSE;
}
if (sb->magic != BTRFS_MAGIC) {
TRACE("not a BTRFS volume\n");
ExFreePool(sb);
return FALSE;
} else {
UINT32 crc32 = ~calc_crc32c(0xffffffff, (UINT8*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
if (crc32 != *((UINT32*)sb->checksum)) {
WARN("crc32 was %08x, expected %08x\n", crc32, *((UINT32*)sb->checksum));
ExFreePool(sb);
return FALSE;
}
}
device2 = device;
do {
device2->Flags &= ~DO_VERIFY_VOLUME;
device2 = IoGetLowerDeviceObject(device2);
} while (device2);
ExFreePool(sb);
return TRUE;
}
static NTSTATUS mount_vol(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
PIO_STACK_LOCATION IrpSp;
PDEVICE_OBJECT NewDeviceObject = NULL;
PDEVICE_OBJECT DeviceToMount, readobj;
NTSTATUS Status;
device_extension* Vcb = NULL;
LIST_ENTRY *le, batchlist;
KEY searchkey;
traverse_ptr tp;
fcb* root_fcb = NULL;
ccb* root_ccb = NULL;
BOOL init_lookaside = FALSE;
device* dev;
volume_device_extension* vde = NULL;
pdo_device_extension* pdode = NULL;
volume_child* vc;
BOOL no_pnp = FALSE;
UINT64 readobjsize;
TRACE("(%p, %p)\n", DeviceObject, Irp);
if (DeviceObject != master_devobj) {
Status = STATUS_INVALID_DEVICE_REQUEST;
goto exit;
}
IrpSp = IoGetCurrentIrpStackLocation(Irp);
DeviceToMount = IrpSp->Parameters.MountVolume.DeviceObject;
if (!is_btrfs_volume(DeviceToMount)) {
Status = check_mount_device(DeviceToMount, &no_pnp);
if (!NT_SUCCESS(Status))
WARN("check_mount_device returned %08x\n", Status);
if (!no_pnp) {
Status = STATUS_UNRECOGNIZED_VOLUME;
goto exit2;
}
} else {
PDEVICE_OBJECT pdo;
pdo = DeviceToMount;
while (IoGetLowerDeviceObject(pdo)) {
pdo = IoGetLowerDeviceObject(pdo);
}
ExAcquireResourceSharedLite(&pdo_list_lock, TRUE);
le = pdo_list.Flink;
while (le != &pdo_list) {
pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
if (pdode->pdo == pdo) {
vde = pdode->vde;
break;
}
le = le->Flink;
}
ExReleaseResourceLite(&pdo_list_lock);
if (!vde || vde->type != VCB_TYPE_VOLUME) {
vde = NULL;
Status = STATUS_UNRECOGNIZED_VOLUME;
goto exit2;
}
}
if (vde) {
pdode = vde->pdode;
ExAcquireResourceExclusiveLite(&pdode->child_lock, TRUE);
le = pdode->children.Flink;
while (le != &pdode->children) {
LIST_ENTRY* le2 = le->Flink;
vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);
if (!still_has_superblock(vc->devobj)) {
remove_volume_child(vde, vc, FALSE);
if (pdode->num_children == 0) {
ERR("error - number of devices is zero\n");
Status = STATUS_INTERNAL_ERROR;
goto exit2;
}
Status = STATUS_DEVICE_NOT_READY;
goto exit2;
}
le = le2;
}
if (pdode->num_children == 0 || pdode->children_loaded == 0) {
ERR("error - number of devices is zero\n");
Status = STATUS_INTERNAL_ERROR;
goto exit;
}
ExConvertExclusiveToSharedLite(&pdode->child_lock);
vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);
readobj = vc->devobj;
readobjsize = vc->size;
vde->device->Characteristics &= ~FILE_DEVICE_SECURE_OPEN;
} else {
GET_LENGTH_INFORMATION gli;
vc = NULL;
readobj = DeviceToMount;
Status = dev_ioctl(readobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,
&gli, sizeof(gli), TRUE, NULL);
if (!NT_SUCCESS(Status)) {
ERR("error reading length information: %08x\n", Status);
goto exit;
}
readobjsize = gli.Length.QuadPart;
}
Status = IoCreateDevice(drvobj, sizeof(device_extension), NULL, FILE_DEVICE_DISK_FILE_SYSTEM, 0, FALSE, &NewDeviceObject);
if (!NT_SUCCESS(Status)) {
ERR("IoCreateDevice returned %08x\n", Status);
Status = STATUS_UNRECOGNIZED_VOLUME;
goto exit;
}
NewDeviceObject->Flags |= DO_DIRECT_IO;
// Some programs seem to expect that the sector size will be 512, for
// FILE_NO_INTERMEDIATE_BUFFERING and the like.
NewDeviceObject->SectorSize = min(DeviceToMount->SectorSize, 512);
Vcb = (PVOID)NewDeviceObject->DeviceExtension;
RtlZeroMemory(Vcb, sizeof(device_extension));
Vcb->type = VCB_TYPE_FS;
Vcb->vde = vde;
ExInitializeResourceLite(&Vcb->tree_lock);
Vcb->need_write = FALSE;
ExInitializeResourceLite(&Vcb->fcb_lock);
ExInitializeResourceLite(&Vcb->chunk_lock);
ExInitializeResourceLite(&Vcb->dirty_fcbs_lock);
ExInitializeResourceLite(&Vcb->dirty_filerefs_lock);
ExInitializeResourceLite(&Vcb->dirty_subvols_lock);
ExInitializeResourceLite(&Vcb->scrub.stats_lock);
ExInitializeResourceLite(&Vcb->load_lock);
ExAcquireResourceExclusiveLite(&Vcb->load_lock, TRUE);
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
DeviceToMount->Flags |= DO_DIRECT_IO;
Status = read_superblock(Vcb, readobj, readobjsize);
if (!NT_SUCCESS(Status)) {
if (!IoIsErrorUserInduced(Status))
Status = STATUS_UNRECOGNIZED_VOLUME;
else if (Irp->Tail.Overlay.Thread)
IoSetHardErrorOrVerifyDevice(Irp, readobj);
goto exit;
}
if (!vde && Vcb->superblock.num_devices > 1) {
ERR("cannot mount multi-device FS with non-PNP device\n");
Status = STATUS_UNRECOGNIZED_VOLUME;
goto exit;
}
Status = registry_load_volume_options(Vcb);
if (!NT_SUCCESS(Status)) {
ERR("registry_load_volume_options returned %08x\n", Status);
goto exit;
}
if (pdode && pdode->children_loaded < pdode->num_children && (!Vcb->options.allow_degraded || !finished_probing || degraded_wait)) {
ERR("could not mount as %u device(s) missing\n", pdode->num_children - pdode->children_loaded);
Status = STATUS_DEVICE_NOT_READY;
goto exit;
}
if (Vcb->options.ignore) {
TRACE("ignoring volume\n");
Status = STATUS_UNRECOGNIZED_VOLUME;
goto exit;
}
if (Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED) {
WARN("cannot mount because of unsupported incompat flags (%llx)\n", Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED);
Status = STATUS_UNRECOGNIZED_VOLUME;
goto exit;
}
Vcb->readonly = FALSE;
if (Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED) {
WARN("mounting read-only because of unsupported flags (%llx)\n", Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED);
Vcb->readonly = TRUE;
}
if (Vcb->options.readonly)
Vcb->readonly = TRUE;
Vcb->superblock.generation++;
Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF;
InitializeListHead(&Vcb->devices);
dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);
if (!dev) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
dev->devobj = readobj;
RtlCopyMemory(&dev->devitem, &Vcb->superblock.dev_item, sizeof(DEV_ITEM));
if (dev->devitem.num_bytes > readobjsize) {
WARN("device %llx: DEV_ITEM says %llx bytes, but Windows only reports %llx\n", dev->devitem.dev_id,
dev->devitem.num_bytes, readobjsize);
dev->devitem.num_bytes = readobjsize;
}
dev->seeding = Vcb->superblock.flags & BTRFS_SUPERBLOCK_FLAGS_SEEDING ? TRUE : FALSE;
init_device(Vcb, dev, TRUE);
InsertTailList(&Vcb->devices, &dev->list_entry);
Vcb->devices_loaded = 1;
if (DeviceToMount->Flags & DO_SYSTEM_BOOT_PARTITION)
Vcb->disallow_dismount = TRUE;
TRACE("DeviceToMount = %p\n", DeviceToMount);
TRACE("IrpSp->Parameters.MountVolume.Vpb = %p\n", IrpSp->Parameters.MountVolume.Vpb);
NewDeviceObject->StackSize = DeviceToMount->StackSize + 1;
NewDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
InitializeListHead(&Vcb->roots);
InitializeListHead(&Vcb->drop_roots);
Vcb->log_to_phys_loaded = FALSE;
add_root(Vcb, BTRFS_ROOT_CHUNK, Vcb->superblock.chunk_tree_addr, Vcb->superblock.chunk_root_generation, NULL);
if (!Vcb->chunk_root) {
ERR("Could not load chunk root.\n");
Status = STATUS_INTERNAL_ERROR;
goto exit;
}
InitializeListHead(&Vcb->sys_chunks);
Status = load_sys_chunks(Vcb);
if (!NT_SUCCESS(Status)) {
ERR("load_sys_chunks returned %08x\n", Status);
goto exit;
}
InitializeListHead(&Vcb->chunks);
InitializeListHead(&Vcb->trees);
InitializeListHead(&Vcb->trees_hash);
InitializeListHead(&Vcb->all_fcbs);
InitializeListHead(&Vcb->dirty_fcbs);
InitializeListHead(&Vcb->dirty_filerefs);
InitializeListHead(&Vcb->dirty_subvols);
InitializeListHead(&Vcb->send_ops);
InitializeListHead(&Vcb->DirNotifyList);
InitializeListHead(&Vcb->scrub.errors);
FsRtlNotifyInitializeSync(&Vcb->NotifySync);
ExInitializePagedLookasideList(&Vcb->tree_data_lookaside, NULL, NULL, 0, sizeof(tree_data), ALLOC_TAG, 0);
ExInitializePagedLookasideList(&Vcb->traverse_ptr_lookaside, NULL, NULL, 0, sizeof(traverse_ptr), ALLOC_TAG, 0);
ExInitializePagedLookasideList(&Vcb->batch_item_lookaside, NULL, NULL, 0, sizeof(batch_item), ALLOC_TAG, 0);
ExInitializePagedLookasideList(&Vcb->fileref_lookaside, NULL, NULL, 0, sizeof(file_ref), ALLOC_TAG, 0);
ExInitializePagedLookasideList(&Vcb->fcb_lookaside, NULL, NULL, 0, sizeof(fcb), ALLOC_TAG, 0);
ExInitializePagedLookasideList(&Vcb->name_bit_lookaside, NULL, NULL, 0, sizeof(name_bit), ALLOC_TAG, 0);
ExInitializeNPagedLookasideList(&Vcb->range_lock_lookaside, NULL, NULL, 0, sizeof(range_lock), ALLOC_TAG, 0);
ExInitializeNPagedLookasideList(&Vcb->fileref_np_lookaside, NULL, NULL, 0, sizeof(file_ref_nonpaged), ALLOC_TAG, 0);
ExInitializeNPagedLookasideList(&Vcb->fcb_np_lookaside, NULL, NULL, 0, sizeof(fcb_nonpaged), ALLOC_TAG, 0);
init_lookaside = TRUE;
Vcb->Vpb = IrpSp->Parameters.MountVolume.Vpb;
Status = load_chunk_root(Vcb, Irp);
if (!NT_SUCCESS(Status)) {
ERR("load_chunk_root returned %08x\n", Status);
goto exit;
}
if (Vcb->superblock.num_devices > 1) {
if (Vcb->devices_loaded < Vcb->superblock.num_devices && (!Vcb->options.allow_degraded || !finished_probing)) {
ERR("could not mount as %u device(s) missing\n", Vcb->superblock.num_devices - Vcb->devices_loaded);
IoRaiseInformationalHardError(IO_ERR_INTERNAL_ERROR, NULL, NULL);
Status = STATUS_INTERNAL_ERROR;
goto exit;
}
if (dev->readonly && !Vcb->readonly) {
Vcb->readonly = TRUE;
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
device* dev2 = CONTAINING_RECORD(le, device, list_entry);
if (dev2->readonly && !dev2->seeding)
break;
if (!dev2->readonly) {
Vcb->readonly = FALSE;
break;
}
le = le->Flink;
}
if (Vcb->readonly)
WARN("setting volume to readonly\n");
}
} else {
if (dev->readonly) {
WARN("setting volume to readonly as device is readonly\n");
Vcb->readonly = TRUE;
}
}
add_root(Vcb, BTRFS_ROOT_ROOT, Vcb->superblock.root_tree_addr, Vcb->superblock.generation - 1, NULL);
if (!Vcb->root_root) {
ERR("Could not load root of roots.\n");
Status = STATUS_INTERNAL_ERROR;
goto exit;
}
Status = look_for_roots(Vcb, Irp);
if (!NT_SUCCESS(Status)) {
ERR("look_for_roots returned %08x\n", Status);
goto exit;
}
if (!Vcb->readonly) {
Status = find_chunk_usage(Vcb, Irp);
if (!NT_SUCCESS(Status)) {
ERR("find_chunk_usage returned %08x\n", Status);
goto exit;
}
}
InitializeListHead(&batchlist);
// We've already increased the generation by one
if (!Vcb->readonly && (
Vcb->options.clear_cache ||
(!(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) && Vcb->superblock.generation - 1 != Vcb->superblock.cache_generation) ||
(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID)))) {
if (Vcb->options.clear_cache)
WARN("ClearCache option was set, clearing cache...\n");
else if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID))
WARN("clearing free-space tree created by buggy Linux driver\n");
else
WARN("generation was %llx, free-space cache generation was %llx; clearing cache...\n", Vcb->superblock.generation - 1, Vcb->superblock.cache_generation);
Status = clear_free_space_cache(Vcb, &batchlist, Irp);
if (!NT_SUCCESS(Status)) {
ERR("clear_free_space_cache returned %08x\n", Status);
clear_batch_list(Vcb, &batchlist);
goto exit;
}
}
Status = commit_batch_list(Vcb, &batchlist, Irp);
if (!NT_SUCCESS(Status)) {
ERR("commit_batch_list returned %08x\n", Status);
goto exit;
}
Vcb->volume_fcb = create_fcb(Vcb, NonPagedPool);
if (!Vcb->volume_fcb) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
Vcb->volume_fcb->Vcb = Vcb;
Vcb->volume_fcb->sd = NULL;
Vcb->dummy_fcb = create_fcb(Vcb, NonPagedPool);
if (!Vcb->dummy_fcb) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
Vcb->dummy_fcb->Vcb = Vcb;
Vcb->dummy_fcb->type = BTRFS_TYPE_DIRECTORY;
Vcb->dummy_fcb->inode = 2;
Vcb->dummy_fcb->subvol = Vcb->root_root;
Vcb->dummy_fcb->atts = FILE_ATTRIBUTE_DIRECTORY;
Vcb->dummy_fcb->inode_item.st_nlink = 1;
Vcb->dummy_fcb->inode_item.st_mode = __S_IFDIR;
Vcb->dummy_fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);
if (!Vcb->dummy_fcb->hash_ptrs) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
RtlZeroMemory(Vcb->dummy_fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);
Vcb->dummy_fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);
if (!Vcb->dummy_fcb->hash_ptrs_uc) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
RtlZeroMemory(Vcb->dummy_fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);
root_fcb = create_fcb(Vcb, NonPagedPool);
if (!root_fcb) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
root_fcb->Vcb = Vcb;
root_fcb->inode = SUBVOL_ROOT_INODE;
root_fcb->type = BTRFS_TYPE_DIRECTORY;
#ifdef DEBUG_FCB_REFCOUNTS
WARN("volume FCB = %p\n", Vcb->volume_fcb);
WARN("root FCB = %p\n", root_fcb);
#endif
root_fcb->subvol = find_default_subvol(Vcb, Irp);
if (!root_fcb->subvol) {
ERR("could not find top subvol\n");
Status = STATUS_INTERNAL_ERROR;
goto exit;
}
Status = load_dir_children(Vcb, root_fcb, TRUE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("load_dir_children returned %08x\n", Status);
goto exit;
}
searchkey.obj_id = root_fcb->inode;
searchkey.obj_type = TYPE_INODE_ITEM;
searchkey.offset = 0xffffffffffffffff;
Status = find_item(Vcb, root_fcb->subvol, &tp, &searchkey, FALSE, Irp);
if (!NT_SUCCESS(Status)) {
ERR("error - find_item returned %08x\n", Status);
goto exit;
}
if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {
ERR("couldn't find INODE_ITEM for root directory\n");
Status = STATUS_INTERNAL_ERROR;
goto exit;
}
if (tp.item->size > 0)
RtlCopyMemory(&root_fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));
fcb_get_sd(root_fcb, NULL, TRUE, Irp);
root_fcb->atts = get_file_attributes(Vcb, root_fcb->subvol, root_fcb->inode, root_fcb->type, FALSE, FALSE, Irp);
Vcb->root_fileref = create_fileref(Vcb);
if (!Vcb->root_fileref) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
Vcb->root_fileref->fcb = root_fcb;
InsertTailList(&root_fcb->subvol->fcbs, &root_fcb->list_entry);
InsertTailList(&Vcb->all_fcbs, &root_fcb->list_entry_all);
root_fcb->fileref = Vcb->root_fileref;
root_ccb = ExAllocatePoolWithTag(PagedPool, sizeof(ccb), ALLOC_TAG);
if (!root_ccb) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
/* HACK: stream file object seems to get deleted at some point
* leading to use after free when installing ReactOS on
* BtrFS.
* Workaround: leak a handle to the fileobject
* XXX: Could be improved by storing it somewhere and releasing it
* on dismount. Or even by referencing again the file object.
*/
#ifndef __REACTOS__
Vcb->root_file = IoCreateStreamFileObject(NULL, DeviceToMount);
#else
{
HANDLE Dummy;
Vcb->root_file = IoCreateStreamFileObjectEx(NULL, DeviceToMount, &Dummy);
}
#endif
Vcb->root_file->FsContext = root_fcb;
Vcb->root_file->SectionObjectPointer = &root_fcb->nonpaged->segment_object;
Vcb->root_file->Vpb = DeviceObject->Vpb;
RtlZeroMemory(root_ccb, sizeof(ccb));
root_ccb->NodeType = BTRFS_NODE_TYPE_CCB;
root_ccb->NodeSize = sizeof(ccb);
Vcb->root_file->FsContext2 = root_ccb;
_SEH2_TRY {
CcInitializeCacheMap(Vcb->root_file, (PCC_FILE_SIZES)(&root_fcb->Header.AllocationSize), FALSE, cache_callbacks, Vcb->root_file);
} _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
Status = _SEH2_GetExceptionCode();
goto exit;
} _SEH2_END;
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
device* dev2 = CONTAINING_RECORD(le, device, list_entry);
Status = find_disk_holes(Vcb, dev2, Irp);
if (!NT_SUCCESS(Status)) {
ERR("find_disk_holes returned %08x\n", Status);
goto exit;
}
le = le->Flink;
}
NewDeviceObject->Vpb = IrpSp->Parameters.MountVolume.Vpb;
IrpSp->Parameters.MountVolume.Vpb->DeviceObject = NewDeviceObject;
IrpSp->Parameters.MountVolume.Vpb->Flags |= VPB_MOUNTED;
NewDeviceObject->Vpb->VolumeLabelLength = 4; // FIXME
NewDeviceObject->Vpb->VolumeLabel[0] = '?';
NewDeviceObject->Vpb->VolumeLabel[1] = 0;
NewDeviceObject->Vpb->ReferenceCount++; // FIXME - should we deref this at any point?
KeInitializeEvent(&Vcb->flush_thread_finished, NotificationEvent, FALSE);
Status = PsCreateSystemThread(&Vcb->flush_thread_handle, 0, NULL, NULL, NULL, flush_thread, NewDeviceObject);
if (!NT_SUCCESS(Status)) {
ERR("PsCreateSystemThread returned %08x\n", Status);
goto exit;
}
Status = create_calc_threads(NewDeviceObject);
if (!NT_SUCCESS(Status)) {
ERR("create_calc_threads returned %08x\n", Status);
goto exit;
}
Status = registry_mark_volume_mounted(&Vcb->superblock.uuid);
if (!NT_SUCCESS(Status))
WARN("registry_mark_volume_mounted returned %08x\n", Status);
Status = look_for_balance_item(Vcb);
if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND)
WARN("look_for_balance_item returned %08x\n", Status);
Status = STATUS_SUCCESS;
if (vde)
vde->mounted_device = NewDeviceObject;
ExInitializeResourceLite(&Vcb->send_load_lock);
exit:
if (pdode)
ExReleaseResourceLite(&pdode->child_lock);
exit2:
if (Vcb) {
ExReleaseResourceLite(&Vcb->tree_lock);
ExReleaseResourceLite(&Vcb->load_lock);
}
if (!NT_SUCCESS(Status)) {
if (Vcb) {
if (init_lookaside) {
ExDeletePagedLookasideList(&Vcb->tree_data_lookaside);
ExDeletePagedLookasideList(&Vcb->traverse_ptr_lookaside);
ExDeletePagedLookasideList(&Vcb->batch_item_lookaside);
ExDeletePagedLookasideList(&Vcb->fileref_lookaside);
ExDeletePagedLookasideList(&Vcb->fcb_lookaside);
ExDeletePagedLookasideList(&Vcb->name_bit_lookaside);
ExDeleteNPagedLookasideList(&Vcb->range_lock_lookaside);
ExDeleteNPagedLookasideList(&Vcb->fileref_np_lookaside);
ExDeleteNPagedLookasideList(&Vcb->fcb_np_lookaside);
}
if (Vcb->root_file)
ObDereferenceObject(Vcb->root_file);
else if (Vcb->root_fileref) {
acquire_fcb_lock_exclusive(Vcb);
free_fileref(Vcb, Vcb->root_fileref);
release_fcb_lock(Vcb);
} else if (root_fcb) {
acquire_fcb_lock_exclusive(Vcb);
free_fcb(Vcb, root_fcb);
release_fcb_lock(Vcb);
}
if (Vcb->volume_fcb) {
acquire_fcb_lock_exclusive(Vcb);
free_fcb(Vcb, Vcb->volume_fcb);
release_fcb_lock(Vcb);
}
ExDeleteResourceLite(&Vcb->tree_lock);
ExDeleteResourceLite(&Vcb->load_lock);
ExDeleteResourceLite(&Vcb->fcb_lock);
ExDeleteResourceLite(&Vcb->chunk_lock);
ExDeleteResourceLite(&Vcb->dirty_fcbs_lock);
ExDeleteResourceLite(&Vcb->dirty_filerefs_lock);
ExDeleteResourceLite(&Vcb->dirty_subvols_lock);
ExDeleteResourceLite(&Vcb->scrub.stats_lock);
if (Vcb->devices.Flink) {
while (!IsListEmpty(&Vcb->devices)) {
device* dev2 = CONTAINING_RECORD(RemoveHeadList(&Vcb->devices), device, list_entry);
ExFreePool(dev2);
}
}
}
if (NewDeviceObject)
IoDeleteDevice(NewDeviceObject);
} else {
ExAcquireResourceExclusiveLite(&global_loading_lock, TRUE);
InsertTailList(&VcbList, &Vcb->list_entry);
ExReleaseResourceLite(&global_loading_lock);
FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_MOUNT);
}
TRACE("mount_vol done (status: %lx)\n", Status);
return Status;
}
static NTSTATUS verify_device(_In_ device_extension* Vcb, _Inout_ device* dev) {
NTSTATUS Status;
superblock* sb;
UINT32 crc32;
ULONG to_read, cc;
if (!dev->devobj)
return STATUS_WRONG_VOLUME;
if (dev->removable) {
IO_STATUS_BLOCK iosb;
Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), TRUE, &iosb);
if (IoIsErrorUserInduced(Status)) {
ERR("IOCTL_STORAGE_CHECK_VERIFY returned %08x (user-induced)\n", Status);
if (Vcb->vde) {
pdo_device_extension* pdode = Vcb->vde->pdode;
LIST_ENTRY* le2;
BOOL changed = FALSE;
ExAcquireResourceExclusiveLite(&pdode->child_lock, TRUE);
le2 = pdode->children.Flink;
while (le2 != &pdode->children) {
volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);
if (vc->devobj == dev->devobj) {
TRACE("removing device\n");
remove_volume_child(Vcb->vde, vc, TRUE);
changed = TRUE;
break;
}
le2 = le2->Flink;
}
if (!changed)
ExReleaseResourceLite(&pdode->child_lock);
}
} else if (!NT_SUCCESS(Status)) {
ERR("IOCTL_STORAGE_CHECK_VERIFY returned %08x\n", Status);
return Status;
} else if (iosb.Information < sizeof(ULONG)) {
ERR("iosb.Information was too short\n");
return STATUS_INTERNAL_ERROR;
}
dev->change_count = cc;
}
to_read = dev->devobj->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), dev->devobj->SectorSize);
sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);
if (!sb) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = sync_read_phys(dev->devobj, superblock_addrs[0], to_read, (PUCHAR)sb, TRUE);
if (!NT_SUCCESS(Status)) {
ERR("Failed to read superblock: %08x\n", Status);
ExFreePool(sb);
return Status;
}
if (sb->magic != BTRFS_MAGIC) {
ERR("not a BTRFS volume\n");
ExFreePool(sb);
return STATUS_WRONG_VOLUME;
}
crc32 = ~calc_crc32c(0xffffffff, (UINT8*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
TRACE("crc32 was %08x, expected %08x\n", crc32, *((UINT32*)sb->checksum));
if (crc32 != *((UINT32*)sb->checksum)) {
ERR("checksum error\n");
ExFreePool(sb);
return STATUS_WRONG_VOLUME;
}
if (RtlCompareMemory(&sb->uuid, &Vcb->superblock.uuid, sizeof(BTRFS_UUID)) != sizeof(BTRFS_UUID)) {
ERR("different UUIDs\n");
ExFreePool(sb);
return STATUS_WRONG_VOLUME;
}
ExFreePool(sb);
dev->devobj->Flags &= ~DO_VERIFY_VOLUME;
return STATUS_SUCCESS;
}
static NTSTATUS verify_volume(_In_ PDEVICE_OBJECT devobj) {
device_extension* Vcb = devobj->DeviceExtension;
NTSTATUS Status;
LIST_ENTRY* le;
UINT64 failed_devices = 0;
BOOL locked = FALSE, remove = FALSE;
if (!(Vcb->Vpb->Flags & VPB_MOUNTED))
return STATUS_WRONG_VOLUME;
if (!ExIsResourceAcquiredExclusive(&Vcb->tree_lock)) {
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
locked = TRUE;
}
if (Vcb->removing) {
if (locked) ExReleaseResourceLite(&Vcb->tree_lock);
return STATUS_WRONG_VOLUME;
}
InterlockedIncrement(&Vcb->open_files); // so pnp_surprise_removal doesn't uninit the device while we're still using it
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
device* dev = CONTAINING_RECORD(le, device, list_entry);
Status = verify_device(Vcb, dev);
if (!NT_SUCCESS(Status)) {
failed_devices++;
if (dev->devobj && Vcb->options.allow_degraded)
dev->devobj = NULL;
}
le = le->Flink;
}
InterlockedDecrement(&Vcb->open_files);
if (Vcb->removing && Vcb->open_files == 0)
remove = TRUE;
if (locked)
ExReleaseResourceLite(&Vcb->tree_lock);
if (remove) {
uninit(Vcb, FALSE);
return Status;
}
if (failed_devices == 0 || (Vcb->options.allow_degraded && failed_devices < Vcb->superblock.num_devices)) {
Vcb->Vpb->RealDevice->Flags &= ~DO_VERIFY_VOLUME;
return STATUS_SUCCESS;
}
return Status;
}
_Dispatch_type_(IRP_MJ_FILE_SYSTEM_CONTROL)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_file_system_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
PIO_STACK_LOCATION IrpSp;
NTSTATUS Status;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
TRACE("file system control\n");
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_file_system_control(DeviceObject, Irp);
goto end;
} else if (!Vcb || (Vcb->type != VCB_TYPE_FS && Vcb->type != VCB_TYPE_CONTROL)) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
Status = STATUS_NOT_IMPLEMENTED;
IrpSp = IoGetCurrentIrpStackLocation( Irp );
Irp->IoStatus.Information = 0;
switch (IrpSp->MinorFunction) {
case IRP_MN_MOUNT_VOLUME:
TRACE("IRP_MN_MOUNT_VOLUME\n");
Status = mount_vol(DeviceObject, Irp);
break;
case IRP_MN_KERNEL_CALL:
TRACE("IRP_MN_KERNEL_CALL\n");
Status = fsctl_request(DeviceObject, &Irp, IrpSp->Parameters.FileSystemControl.FsControlCode);
break;
case IRP_MN_USER_FS_REQUEST:
TRACE("IRP_MN_USER_FS_REQUEST\n");
Status = fsctl_request(DeviceObject, &Irp, IrpSp->Parameters.FileSystemControl.FsControlCode);
break;
case IRP_MN_VERIFY_VOLUME:
TRACE("IRP_MN_VERIFY_VOLUME\n");
Status = verify_volume(DeviceObject);
if (!NT_SUCCESS(Status) && Vcb->Vpb->Flags & VPB_MOUNTED) {
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
Vcb->removing = TRUE;
ExReleaseResourceLite(&Vcb->tree_lock);
}
break;
default:
break;
}
end:
TRACE("returning %08x\n", Status);
if (Irp) {
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
_Dispatch_type_(IRP_MJ_LOCK_CONTROL)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_lock_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
fcb* fcb = IrpSp->FileObject->FsContext;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_lock_control(DeviceObject, Irp);
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
goto exit;
}
TRACE("lock control\n");
Status = FsRtlProcessFileLock(&fcb->lock, Irp, NULL);
fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
exit:
TRACE("returning %08x\n", Status);
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
_Dispatch_type_(IRP_MJ_SHUTDOWN)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_shutdown(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
BOOL top_level;
device_extension* Vcb = DeviceObject->DeviceExtension;
FsRtlEnterFileSystem();
TRACE("shutdown\n");
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_shutdown(DeviceObject, Irp);
goto end;
}
Status = STATUS_SUCCESS;
shutting_down = TRUE;
KeSetEvent(&mountmgr_thread_event, 0, FALSE);
while (!IsListEmpty(&VcbList)) {
Vcb = CONTAINING_RECORD(VcbList.Flink, device_extension, list_entry);
TRACE("shutting down Vcb %p\n", Vcb);
uninit(Vcb, TRUE);
}
#ifdef _DEBUG
if (comfo) {
ObDereferenceObject(comfo);
comdo = NULL;
comfo = NULL;
}
#endif
end:
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
_Dispatch_type_(IRP_MJ_POWER)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_power(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
top_level = is_top_level(Irp);
Irp->IoStatus.Information = 0;
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
Status = vol_power(DeviceObject, Irp);
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
goto exit;
} else if (Vcb && Vcb->type == VCB_TYPE_FS) {
IoSkipCurrentIrpStackLocation(Irp);
Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);
goto exit;
}
Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
exit:
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)
_Function_class_(DRIVER_DISPATCH)
static NTSTATUS drv_system_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
NTSTATUS Status;
device_extension* Vcb = DeviceObject->DeviceExtension;
BOOL top_level;
FsRtlEnterFileSystem();
top_level = is_top_level(Irp);
Irp->IoStatus.Information = 0;
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
volume_device_extension* vde = DeviceObject->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
Status = IoCallDriver(vde->pdo, Irp);
goto exit;
} else if (Vcb && Vcb->type == VCB_TYPE_FS) {
IoSkipCurrentIrpStackLocation(Irp);
Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);
goto exit;
}
Status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
exit:
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}
BOOL is_file_name_valid(_In_ PUNICODE_STRING us, _In_ BOOL posix) {
ULONG i;
if (us->Length < sizeof(WCHAR))
return FALSE;
if (us->Length > 255 * sizeof(WCHAR))
return FALSE;
for (i = 0; i < us->Length / sizeof(WCHAR); i++) {
if (us->Buffer[i] == '/' || us->Buffer[i] == 0 ||
(!posix && (us->Buffer[i] == '<' || us->Buffer[i] == '>' || us->Buffer[i] == ':' || us->Buffer[i] == '"' ||
us->Buffer[i] == '|' || us->Buffer[i] == '?' || us->Buffer[i] == '*' || (us->Buffer[i] >= 1 && us->Buffer[i] <= 31))))
return FALSE;
}
if (us->Buffer[0] == '.' && (us->Length == sizeof(WCHAR) || (us->Length == 2 * sizeof(WCHAR) && us->Buffer[1] == '.')))
return FALSE;
return TRUE;
}
void chunk_lock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ UINT64 start, _In_ UINT64 length) {
LIST_ENTRY* le;
BOOL locked;
range_lock* rl;
rl = ExAllocateFromNPagedLookasideList(&Vcb->range_lock_lookaside);
if (!rl) {
ERR("out of memory\n");
return;
}
rl->start = start;
rl->length = length;
rl->thread = PsGetCurrentThread();
while (TRUE) {
locked = FALSE;
ExAcquireResourceExclusiveLite(&c->range_locks_lock, TRUE);
le = c->range_locks.Flink;
while (le != &c->range_locks) {
range_lock* rl2 = CONTAINING_RECORD(le, range_lock, list_entry);
if (rl2->start < start + length && rl2->start + rl2->length > start && rl2->thread != PsGetCurrentThread()) {
locked = TRUE;
break;
}
le = le->Flink;
}
if (!locked) {
InsertTailList(&c->range_locks, &rl->list_entry);
ExReleaseResourceLite(&c->range_locks_lock);
return;
}
KeClearEvent(&c->range_locks_event);
ExReleaseResourceLite(&c->range_locks_lock);
KeWaitForSingleObject(&c->range_locks_event, UserRequest, KernelMode, FALSE, NULL);
}
}
void chunk_unlock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ UINT64 start, _In_ UINT64 length) {
LIST_ENTRY* le;
ExAcquireResourceExclusiveLite(&c->range_locks_lock, TRUE);
le = c->range_locks.Flink;
while (le != &c->range_locks) {
range_lock* rl = CONTAINING_RECORD(le, range_lock, list_entry);
if (rl->start == start && rl->length == length) {
RemoveEntryList(&rl->list_entry);
ExFreeToNPagedLookasideList(&Vcb->range_lock_lookaside, rl);
break;
}
le = le->Flink;
}
KeSetEvent(&c->range_locks_event, 0, FALSE);
ExReleaseResourceLite(&c->range_locks_lock);
}
void log_device_error(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ int error) {
dev->stats[error]++;
dev->stats_changed = TRUE;
Vcb->stats_changed = TRUE;
}
#ifdef _DEBUG
_Function_class_(KSTART_ROUTINE)
static void serial_thread(void* context) {
LARGE_INTEGER due_time;
KTIMER timer;
UNUSED(context);
KeInitializeTimer(&timer);
due_time.QuadPart = (UINT64)-10000000;
KeSetTimer(&timer, due_time, NULL);
while (TRUE) {
KeWaitForSingleObject(&timer, Executive, KernelMode, FALSE, NULL);
init_serial(FALSE);
if (comdo)
break;
KeSetTimer(&timer, due_time, NULL);
}
KeCancelTimer(&timer);
PsTerminateSystemThread(STATUS_SUCCESS);
serial_thread_handle = NULL;
}
static void init_serial(BOOL first_time) {
NTSTATUS Status;
Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo);
if (!NT_SUCCESS(Status)) {
ERR("IoGetDeviceObjectPointer returned %08x\n", Status);
if (first_time) {
NTSTATUS Status;
Status = PsCreateSystemThread(&serial_thread_handle, 0, NULL, NULL, NULL, serial_thread, NULL);
if (!NT_SUCCESS(Status)) {
ERR("PsCreateSystemThread returned %08x\n", Status);
return;
}
}
}
}
#endif
#ifndef __REACTOS__
static void check_cpu() {
unsigned int cpuInfo[4];
#ifndef _MSC_VER
__get_cpuid(1, &cpuInfo[0], &cpuInfo[1], &cpuInfo[2], &cpuInfo[3]);
have_sse42 = cpuInfo[2] & bit_SSE4_2;
have_sse2 = cpuInfo[3] & bit_SSE2;
#else
__cpuid(cpuInfo, 1);
have_sse42 = cpuInfo[2] & (1 << 20);
have_sse2 = cpuInfo[3] & (1 << 26);
#endif
if (have_sse42)
TRACE("SSE4.2 is supported\n");
else
TRACE("SSE4.2 not supported\n");
if (have_sse2)
TRACE("SSE2 is supported\n");
else
TRACE("SSE2 is not supported\n");
}
#endif
#ifdef _DEBUG
static void init_logging() {
ExAcquireResourceExclusiveLite(&log_lock, TRUE);
if (log_device.Length > 0)
init_serial(TRUE);
else if (log_file.Length > 0) {
NTSTATUS Status;
OBJECT_ATTRIBUTES oa;
IO_STATUS_BLOCK iosb;
char* dateline;
LARGE_INTEGER time;
TIME_FIELDS tf;
InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0);
if (!NT_SUCCESS(Status)) {
ERR("ZwCreateFile returned %08x\n", Status);
goto end;
}
if (iosb.Information == FILE_OPENED) { // already exists
FILE_STANDARD_INFORMATION fsi;
FILE_POSITION_INFORMATION fpi;
static char delim[] = "\n---\n";
// move to end of file
Status = ZwQueryInformationFile(log_handle, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
if (!NT_SUCCESS(Status)) {
ERR("ZwQueryInformationFile returned %08x\n", Status);
goto end;
}
fpi.CurrentByteOffset = fsi.EndOfFile;
Status = ZwSetInformationFile(log_handle, &iosb, &fpi, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation);
if (!NT_SUCCESS(Status)) {
ERR("ZwSetInformationFile returned %08x\n", Status);
goto end;
}
Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, delim, (ULONG)strlen(delim), NULL, NULL);
if (!NT_SUCCESS(Status)) {
ERR("ZwWriteFile returned %08x\n", Status);
goto end;
}
}
dateline = ExAllocatePoolWithTag(PagedPool, 256, ALLOC_TAG);
if (!dateline) {
ERR("out of memory\n");
goto end;
}
KeQuerySystemTime(&time);
RtlTimeToTimeFields(&time, &tf);
sprintf(dateline, "Starting logging at %04i-%02i-%02i %02i:%02i:%02i\n", tf.Year, tf.Month, tf.Day, tf.Hour, tf.Minute, tf.Second);
Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, dateline, (ULONG)strlen(dateline), NULL, NULL);
ExFreePool(dateline);
if (!NT_SUCCESS(Status)) {
ERR("ZwWriteFile returned %08x\n", Status);
goto end;
}
}
end:
ExReleaseResourceLite(&log_lock);
}
#endif
_Function_class_(KSTART_ROUTINE)
#ifdef __REACTOS__
static void NTAPI degraded_wait_thread(_In_ void* context) {
#else
static void degraded_wait_thread(_In_ void* context) {
#endif
KTIMER timer;
LARGE_INTEGER delay;
UNUSED(context);
KeInitializeTimer(&timer);
delay.QuadPart = -30000000; // wait three seconds
KeSetTimer(&timer, delay, NULL);
KeWaitForSingleObject(&timer, Executive, KernelMode, FALSE, NULL);
TRACE("timer expired\n");
degraded_wait = FALSE;
ZwClose(degraded_wait_handle);
degraded_wait_handle = NULL;
PsTerminateSystemThread(STATUS_SUCCESS);
}
#ifdef __REACTOS__
NTSTATUS NTAPI AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject) {
#else
NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject) {
#endif
LIST_ENTRY* le;
NTSTATUS Status;
UNICODE_STRING volname;
ULONG i, j;
pdo_device_extension* pdode = NULL;
PDEVICE_OBJECT voldev;
volume_device_extension* vde;
TRACE("(%p, %p)\n", DriverObject, PhysicalDeviceObject);
ExAcquireResourceSharedLite(&pdo_list_lock, TRUE);
le = pdo_list.Flink;
while (le != &pdo_list) {
pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
if (pdode2->pdo == PhysicalDeviceObject) {
pdode = pdode2;
break;
}
le = le->Flink;
}
if (!pdode) {
WARN("unrecognized PDO %p\n", PhysicalDeviceObject);
Status = STATUS_NOT_SUPPORTED;
goto end;
}
ExAcquireResourceSharedLite(&pdode->child_lock, TRUE);
volname.Length = volname.MaximumLength = (USHORT)((wcslen(BTRFS_VOLUME_PREFIX) + 36 + 1) * sizeof(WCHAR));
volname.Buffer = ExAllocatePoolWithTag(PagedPool, volname.MaximumLength, ALLOC_TAG); // FIXME - when do we free this?
if (!volname.Buffer) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end2;
}
RtlCopyMemory(volname.Buffer, BTRFS_VOLUME_PREFIX, wcslen(BTRFS_VOLUME_PREFIX) * sizeof(WCHAR));
j = (ULONG)wcslen(BTRFS_VOLUME_PREFIX);
for (i = 0; i < 16; i++) {
volname.Buffer[j] = hex_digit(pdode->uuid.uuid[i] >> 4); j++;
volname.Buffer[j] = hex_digit(pdode->uuid.uuid[i] & 0xf); j++;
if (i == 3 || i == 5 || i == 7 || i == 9) {
volname.Buffer[j] = '-';
j++;
}
}
volname.Buffer[j] = '}';
Status = IoCreateDevice(drvobj, sizeof(volume_device_extension), &volname, FILE_DEVICE_DISK,
RtlIsNtDdiVersionAvailable(NTDDI_WIN8) ? FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL : 0, FALSE, &voldev);
if (!NT_SUCCESS(Status)) {
ERR("IoCreateDevice returned %08x\n", Status);
goto end2;
}
voldev->SectorSize = PhysicalDeviceObject->SectorSize;
voldev->Flags |= DO_DIRECT_IO;
vde = voldev->DeviceExtension;
vde->type = VCB_TYPE_VOLUME;
vde->name = volname;
vde->device = voldev;
vde->mounted_device = NULL;
vde->pdo = PhysicalDeviceObject;
vde->pdode = pdode;
vde->removing = FALSE;
vde->open_count = 0;
Status = IoRegisterDeviceInterface(PhysicalDeviceObject, &GUID_DEVINTERFACE_VOLUME, NULL, &vde->bus_name);
if (!NT_SUCCESS(Status))
WARN("IoRegisterDeviceInterface returned %08x\n", Status);
vde->attached_device = IoAttachDeviceToDeviceStack(voldev, PhysicalDeviceObject);
pdode->vde = vde;
if (pdode->removable)
voldev->Characteristics |= FILE_REMOVABLE_MEDIA;
voldev->Flags &= ~DO_DEVICE_INITIALIZING;
Status = IoSetDeviceInterfaceState(&vde->bus_name, TRUE);
if (!NT_SUCCESS(Status))
WARN("IoSetDeviceInterfaceState returned %08x\n", Status);
Status = STATUS_SUCCESS;
end2:
ExReleaseResourceLite(&pdode->child_lock);
end:
ExReleaseResourceLite(&pdo_list_lock);
return Status;
}
_Function_class_(DRIVER_INITIALIZE)
#ifdef __REACTOS__
NTSTATUS NTAPI DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
#else
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
#endif
NTSTATUS Status;
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING device_nameW;
UNICODE_STRING dosdevice_nameW;
control_device_extension* cde;
HANDLE regh;
OBJECT_ATTRIBUTES oa;
ULONG dispos;
InitializeListHead(&uid_map_list);
InitializeListHead(&gid_map_list);
#ifdef _DEBUG
ExInitializeResourceLite(&log_lock);
#endif
ExInitializeResourceLite(&mapping_lock);
log_device.Buffer = NULL;
log_device.Length = log_device.MaximumLength = 0;
log_file.Buffer = NULL;
log_file.Length = log_file.MaximumLength = 0;
registry_path.Length = registry_path.MaximumLength = RegistryPath->Length;
registry_path.Buffer = ExAllocatePoolWithTag(PagedPool, registry_path.Length, ALLOC_TAG);
if (!registry_path.Buffer) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(registry_path.Buffer, RegistryPath->Buffer, registry_path.Length);
read_registry(&registry_path, FALSE);
#ifdef _DEBUG
if (debug_log_level > 0)
init_logging();
log_started = TRUE;
#endif
TRACE("DriverEntry\n");
#ifndef __REACTOS__
check_cpu();
#endif
if (RtlIsNtDdiVersionAvailable(NTDDI_WIN8)) {
UNICODE_STRING name;
tPsIsDiskCountersEnabled fPsIsDiskCountersEnabled;
RtlInitUnicodeString(&name, L"PsIsDiskCountersEnabled");
fPsIsDiskCountersEnabled = (tPsIsDiskCountersEnabled)MmGetSystemRoutineAddress(&name);
if (fPsIsDiskCountersEnabled) {
diskacc = fPsIsDiskCountersEnabled();
RtlInitUnicodeString(&name, L"PsUpdateDiskCounters");
fPsUpdateDiskCounters = (tPsUpdateDiskCounters)MmGetSystemRoutineAddress(&name);
if (!fPsUpdateDiskCounters)
diskacc = FALSE;
RtlInitUnicodeString(&name, L"FsRtlUpdateDiskCounters");
fFsRtlUpdateDiskCounters = (tFsRtlUpdateDiskCounters)MmGetSystemRoutineAddress(&name);
}
RtlInitUnicodeString(&name, L"CcCopyReadEx");
fCcCopyReadEx = (tCcCopyReadEx)MmGetSystemRoutineAddress(&name);
RtlInitUnicodeString(&name, L"CcCopyWriteEx");
fCcCopyWriteEx = (tCcCopyWriteEx)MmGetSystemRoutineAddress(&name);
RtlInitUnicodeString(&name, L"CcSetAdditionalCacheAttributesEx");
fCcSetAdditionalCacheAttributesEx = (tCcSetAdditionalCacheAttributesEx)MmGetSystemRoutineAddress(&name);
} else {
fPsUpdateDiskCounters = NULL;
fCcCopyReadEx = NULL;
fCcCopyWriteEx = NULL;
fCcSetAdditionalCacheAttributesEx = NULL;
fFsRtlUpdateDiskCounters = NULL;
}
drvobj = DriverObject;
DriverObject->DriverUnload = DriverUnload;
DriverObject->DriverExtension->AddDevice = AddDevice;
DriverObject->MajorFunction[IRP_MJ_CREATE] = (PDRIVER_DISPATCH)drv_create;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = (PDRIVER_DISPATCH)drv_close;
DriverObject->MajorFunction[IRP_MJ_READ] = (PDRIVER_DISPATCH)drv_read;
DriverObject->MajorFunction[IRP_MJ_WRITE] = (PDRIVER_DISPATCH)drv_write;
DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = (PDRIVER_DISPATCH)drv_query_information;
DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = (PDRIVER_DISPATCH)drv_set_information;
DriverObject->MajorFunction[IRP_MJ_QUERY_EA] = (PDRIVER_DISPATCH)drv_query_ea;
DriverObject->MajorFunction[IRP_MJ_SET_EA] = (PDRIVER_DISPATCH)drv_set_ea;
DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = (PDRIVER_DISPATCH)drv_flush_buffers;
DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = (PDRIVER_DISPATCH)drv_query_volume_information;
DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = (PDRIVER_DISPATCH)drv_set_volume_information;
DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = (PDRIVER_DISPATCH)drv_directory_control;
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = (PDRIVER_DISPATCH)drv_file_system_control;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = (PDRIVER_DISPATCH)drv_device_control;
DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = (PDRIVER_DISPATCH)drv_shutdown;
DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL] = (PDRIVER_DISPATCH)drv_lock_control;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = (PDRIVER_DISPATCH)drv_cleanup;
DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY] = (PDRIVER_DISPATCH)drv_query_security;
DriverObject->MajorFunction[IRP_MJ_SET_SECURITY] = (PDRIVER_DISPATCH)drv_set_security;
DriverObject->MajorFunction[IRP_MJ_POWER] = (PDRIVER_DISPATCH)drv_power;
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = (PDRIVER_DISPATCH)drv_system_control;
DriverObject->MajorFunction[IRP_MJ_PNP] = (PDRIVER_DISPATCH)drv_pnp;
init_fast_io_dispatch(&DriverObject->FastIoDispatch);
device_nameW.Buffer = device_name;
device_nameW.Length = device_nameW.MaximumLength = (USHORT)wcslen(device_name) * sizeof(WCHAR);
dosdevice_nameW.Buffer = dosdevice_name;
dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = (USHORT)wcslen(dosdevice_name) * sizeof(WCHAR);
Status = IoCreateDevice(DriverObject, sizeof(control_device_extension), &device_nameW, FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN, FALSE, &DeviceObject);
if (!NT_SUCCESS(Status)) {
ERR("IoCreateDevice returned %08x\n", Status);
return Status;
}
master_devobj = DeviceObject;
cde = (control_device_extension*)master_devobj->DeviceExtension;
RtlZeroMemory(cde, sizeof(control_device_extension));
cde->type = VCB_TYPE_CONTROL;
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
Status = IoCreateSymbolicLink(&dosdevice_nameW, &device_nameW);
if (!NT_SUCCESS(Status)) {
ERR("IoCreateSymbolicLink returned %08x\n", Status);
return Status;
}
Status = init_cache();
if (!NT_SUCCESS(Status)) {
ERR("init_cache returned %08x\n", Status);
return Status;
}
InitializeListHead(&VcbList);
ExInitializeResourceLite(&global_loading_lock);
ExInitializeResourceLite(&pdo_list_lock);
InitializeListHead(&pdo_list);
InitializeObjectAttributes(&oa, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
Status = ZwCreateKey(&regh, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);
/* ReactOS specific hack: allow BtrFS driver to start in 1st stage with no hive */
#ifndef __REACTOS__
if (!NT_SUCCESS(Status)) {
ERR("ZwCreateKey returned %08x\n", Status);
return Status;
}
watch_registry(regh);
#else
if (NT_SUCCESS(Status)) {
watch_registry(regh);
}
#endif
Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF,
NULL, NULL, 0, &cde->buspdo);
if (!NT_SUCCESS(Status)) {
ERR("IoReportDetectedDevice returned %08x\n", Status);
return Status;
}
Status = IoRegisterDeviceInterface(cde->buspdo, &BtrfsBusInterface, NULL, &cde->bus_name);
if (!NT_SUCCESS(Status))
WARN("IoRegisterDeviceInterface returned %08x\n", Status);
cde->attached_device = IoAttachDeviceToDeviceStack(DeviceObject, cde->buspdo);
Status = IoSetDeviceInterfaceState(&cde->bus_name, TRUE);
if (!NT_SUCCESS(Status))
WARN("IoSetDeviceInterfaceState returned %08x\n", Status);
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
IoInvalidateDeviceRelations(cde->buspdo, BusRelations);
Status = PsCreateSystemThread(&degraded_wait_handle, 0, NULL, NULL, NULL, degraded_wait_thread, NULL);
if (!NT_SUCCESS(Status))
WARN("PsCreateSystemThread returned %08x\n", Status);
Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
(PVOID)&GUID_DEVINTERFACE_VOLUME, DriverObject, volume_notification, DriverObject, &notification_entry2);
if (!NT_SUCCESS(Status))
ERR("IoRegisterPlugPlayNotification returned %08x\n", Status);
Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
(PVOID)&GUID_DEVINTERFACE_HIDDEN_VOLUME, DriverObject, volume_notification, DriverObject, &notification_entry3);
if (!NT_SUCCESS(Status))
ERR("IoRegisterPlugPlayNotification returned %08x\n", Status);
Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
(PVOID)&GUID_DEVINTERFACE_DISK, DriverObject, pnp_notification, DriverObject, &notification_entry);
if (!NT_SUCCESS(Status))
ERR("IoRegisterPlugPlayNotification returned %08x\n", Status);
finished_probing = TRUE;
KeInitializeEvent(&mountmgr_thread_event, NotificationEvent, FALSE);
#ifndef __REACTOS__
Status = PsCreateSystemThread(&mountmgr_thread_handle, 0, NULL, NULL, NULL, mountmgr_thread, NULL);
if (!NT_SUCCESS(Status))
WARN("PsCreateSystemThread returned %08x\n", Status);
#endif
IoRegisterFileSystem(DeviceObject);
return STATUS_SUCCESS;
}