mirror of
https://github.com/reactos/reactos.git
synced 2024-08-26 13:18:51 +00:00
6419 lines
203 KiB
C
6419 lines
203 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"
|
|
#include "xxhash.h"
|
|
#include "crc32c.h"
|
|
#ifndef __REACTOS__
|
|
#ifndef _MSC_VER
|
|
#include <cpuid.h>
|
|
#else
|
|
#include <intrin.h>
|
|
#endif
|
|
#endif // __REACTOS__
|
|
#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
|
|
|
|
#ifdef _MSC_VER
|
|
#include <ntstrsafe.h>
|
|
#else
|
|
NTSTATUS RtlStringCbVPrintfA(char* pszDest, size_t cbDest, const char* pszFormat, va_list argList); // not in mingw
|
|
#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 | \
|
|
BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD | BTRFS_INCOMPAT_FLAGS_METADATA_UUID | BTRFS_INCOMPAT_FLAGS_RAID1C34)
|
|
#define COMPAT_RO_SUPPORTED (BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE | BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID)
|
|
|
|
static const WCHAR device_name[] = {'\\','B','t','r','f','s',0};
|
|
static const 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, busobj;
|
|
#ifndef __REACTOS__
|
|
bool have_sse2 = false;
|
|
#endif
|
|
uint64_t num_reads = 0;
|
|
LIST_ENTRY uid_map_list, gid_map_list;
|
|
LIST_ENTRY VcbList;
|
|
ERESOURCE global_loading_lock;
|
|
uint32_t debug_log_level = 0;
|
|
uint32_t mount_compress = 0;
|
|
uint32_t mount_compress_force = 0;
|
|
uint32_t mount_compress_type = 0;
|
|
uint32_t mount_zlib_level = 3;
|
|
uint32_t mount_zstd_level = 3;
|
|
uint32_t mount_flush_interval = 30;
|
|
uint32_t mount_max_inline = 2048;
|
|
uint32_t mount_skip_balance = 0;
|
|
uint32_t mount_no_barrier = 0;
|
|
uint32_t mount_no_trim = 0;
|
|
uint32_t mount_clear_cache = 0;
|
|
uint32_t mount_allow_degraded = 0;
|
|
uint32_t mount_readonly = 0;
|
|
uint32_t mount_no_root_dir = 0;
|
|
uint32_t 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;
|
|
tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;
|
|
tFsRtlGetEcpListFromIrp fFsRtlGetEcpListFromIrp;
|
|
tFsRtlGetNextExtraCreateParameter fFsRtlGetNextExtraCreateParameter;
|
|
tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer;
|
|
tFsRtlCheckLockForOplockRequest fFsRtlCheckLockForOplockRequest;
|
|
tFsRtlAreThereCurrentOrInProgressFileLocks fFsRtlAreThereCurrentOrInProgressFileLocks;
|
|
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;
|
|
ERESOURCE boot_lock;
|
|
extern uint64_t boot_subvol;
|
|
|
|
#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;
|
|
|
|
// no longer in Windows headers??
|
|
extern BOOLEAN WdmlibRtlIsNtDdiVersionAvailable(ULONG Version);
|
|
|
|
#ifdef _DEBUG
|
|
_Function_class_(IO_COMPLETION_ROUTINE)
|
|
static NTSTATUS __stdcall 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;
|
|
}
|
|
|
|
#define DEBUG_MESSAGE_LEN 1024
|
|
|
|
#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_t length;
|
|
|
|
buf2 = ExAllocatePoolWithTag(NonPagedPool, DEBUG_MESSAGE_LEN, ALLOC_TAG);
|
|
|
|
if (!buf2) {
|
|
DbgPrint("Couldn't allocate buffer in debug_message\n");
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_LONG_MESSAGES
|
|
sprintf(buf2, "%p:%s:%s:%u:", (void*)PsGetCurrentThread(), func, file, line);
|
|
#else
|
|
sprintf(buf2, "%p:%s:", (void*)PsGetCurrentThread(), func);
|
|
#endif
|
|
buf = &buf2[strlen(buf2)];
|
|
|
|
va_start(ap, s);
|
|
|
|
RtlStringCbVPrintfA(buf, DEBUG_MESSAGE_LEN - strlen(buf2), 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(buf2);
|
|
goto exit2;
|
|
}
|
|
|
|
length = (uint32_t)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;
|
|
IrpSp->FileObject = comfo;
|
|
|
|
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 %08lx\n", Status);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
IoFreeIrp(Irp);
|
|
} else if (log_handle != NULL) {
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
length = (uint32_t)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 %08lx\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)
|
|
static void __stdcall DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {
|
|
UNICODE_STRING dosdevice_nameW;
|
|
|
|
TRACE("(%p)\n", DriverObject);
|
|
|
|
dosdevice_nameW.Buffer = (WCHAR*)dosdevice_name;
|
|
dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = sizeof(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 %08lx\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 %I64x is %I64x\n", r->id, r->lastinode);
|
|
return true;
|
|
}
|
|
|
|
while (find_prev_item(Vcb, &tp, &prev_tp, Irp)) {
|
|
tp = prev_tp;
|
|
|
|
TRACE("moving on to %I64x,%x,%I64x\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 %I64x is %I64x\n", r->id, r->lastinode);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
r->lastinode = SUBVOL_ROOT_INODE;
|
|
|
|
WARN("no INODE_ITEMs in tree %I64x\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_t** data, _Out_ uint16_t* 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_t inode, _In_z_ char* name, _In_ uint32_t crc32,
|
|
_Out_ uint8_t** data, _Out_ uint16_t* datalen, _In_opt_ PIRP Irp) {
|
|
KEY searchkey;
|
|
traverse_ptr tp;
|
|
NTSTATUS Status;
|
|
|
|
TRACE("(%p, %I64x, %I64x, %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 %08lx\n", Status);
|
|
return false;
|
|
}
|
|
|
|
if (keycmp(tp.item->key, searchkey)) {
|
|
TRACE("could not find item (%I64x,%x,%I64x)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
|
|
return false;
|
|
}
|
|
|
|
if (tp.item->size < sizeof(DIR_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(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 __stdcall 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 - 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 %08lx\n", Status);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_FLUSH_BUFFERS)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
static NTSTATUS __stdcall 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_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
if (!fcb) {
|
|
ERR("fcb was NULL\n");
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb == Vcb->volume_fcb) {
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);
|
|
|
|
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(FileObject->SectionObjectPointer, 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 %08lx\n", Status);
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
static void calculate_total_space(_In_ device_extension* Vcb, _Out_ uint64_t* totalsize, _Out_ uint64_t* freespace) {
|
|
uint64_t 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 if (Vcb->data_flags & BLOCK_FLAG_RAID1C3) {
|
|
nfactor = 1;
|
|
dfactor = 3;
|
|
} else if (Vcb->data_flags & BLOCK_FLAG_RAID1C4) {
|
|
nfactor = 1;
|
|
dfactor = 4;
|
|
} else {
|
|
nfactor = 1;
|
|
dfactor = 1;
|
|
}
|
|
|
|
sectors_used = (Vcb->superblock.bytes_used / Vcb->superblock.sector_size) * nfactor / dfactor;
|
|
|
|
*totalsize = (Vcb->superblock.total_bytes / Vcb->superblock.sector_size) * nfactor / dfactor;
|
|
*freespace = sectors_used > *totalsize ? 0 : (*totalsize - sectors_used);
|
|
}
|
|
|
|
#ifndef __REACTOS__
|
|
#define INIT_UNICODE_STRING(var, val) UNICODE_STRING us##var; us##var.Buffer = (WCHAR*)val; us##var.Length = us##var.MaximumLength = sizeof(val) - sizeof(WCHAR);
|
|
|
|
// 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;
|
|
#ifdef _AMD64_
|
|
ULONG_PTR wow64info;
|
|
#endif
|
|
|
|
INIT_UNICODE_STRING(mpr, L"MPR.DLL");
|
|
INIT_UNICODE_STRING(cmd, L"CMD.EXE");
|
|
INIT_UNICODE_STRING(fsutil, L"FSUTIL.EXE");
|
|
INIT_UNICODE_STRING(storsvc, L"STORSVC.DLL");
|
|
|
|
if (!PsGetCurrentProcess())
|
|
return false;
|
|
|
|
#ifdef _AMD64_
|
|
Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information, &wow64info, sizeof(wow64info), NULL);
|
|
|
|
if (NT_SUCCESS(Status) && wow64info != 0)
|
|
return true;
|
|
#endif
|
|
|
|
Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &retlen);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("ZwQueryInformationProcess returned %08lx\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 >= usmpr.Length) {
|
|
UNICODE_STRING name;
|
|
|
|
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usmpr.Length) / sizeof(WCHAR)];
|
|
name.Length = name.MaximumLength = usmpr.Length;
|
|
|
|
blacklist = FsRtlAreNamesEqual(&name, &usmpr, true, NULL);
|
|
}
|
|
|
|
if (!blacklist && entry->FullDllName.Length >= uscmd.Length) {
|
|
UNICODE_STRING name;
|
|
|
|
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - uscmd.Length) / sizeof(WCHAR)];
|
|
name.Length = name.MaximumLength = uscmd.Length;
|
|
|
|
blacklist = FsRtlAreNamesEqual(&name, &uscmd, true, NULL);
|
|
}
|
|
|
|
if (!blacklist && entry->FullDllName.Length >= usfsutil.Length) {
|
|
UNICODE_STRING name;
|
|
|
|
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usfsutil.Length) / sizeof(WCHAR)];
|
|
name.Length = name.MaximumLength = usfsutil.Length;
|
|
|
|
blacklist = FsRtlAreNamesEqual(&name, &usfsutil, true, NULL);
|
|
}
|
|
|
|
if (!blacklist && entry->FullDllName.Length >= usstorsvc.Length) {
|
|
UNICODE_STRING name;
|
|
|
|
name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usstorsvc.Length) / sizeof(WCHAR)];
|
|
name.Length = name.MaximumLength = usstorsvc.Length;
|
|
|
|
blacklist = FsRtlAreNamesEqual(&name, &usstorsvc, 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 // __REACTOS__
|
|
|
|
// version of RtlUTF8ToUnicodeN for Vista and below
|
|
NTSTATUS utf8_to_utf16(WCHAR* dest, ULONG dest_max, ULONG* dest_len, char* src, ULONG src_len) {
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
uint8_t* in = (uint8_t*)src;
|
|
uint16_t* out = (uint16_t*)dest;
|
|
ULONG needed = 0, left = dest_max / sizeof(uint16_t);
|
|
#ifdef __REACTOS__
|
|
ULONG i;
|
|
|
|
for (i = 0; i < src_len; ++i) {
|
|
#else
|
|
|
|
for (ULONG i = 0; i < src_len; i++) {
|
|
#endif
|
|
uint32_t cp;
|
|
|
|
if (!(in[i] & 0x80))
|
|
cp = in[i];
|
|
else if ((in[i] & 0xe0) == 0xc0) {
|
|
if (i == src_len - 1 || (in[i+1] & 0xc0) != 0x80) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
} else {
|
|
cp = ((in[i] & 0x1f) << 6) | (in[i+1] & 0x3f);
|
|
i++;
|
|
}
|
|
} else if ((in[i] & 0xf0) == 0xe0) {
|
|
if (i >= src_len - 2 || (in[i+1] & 0xc0) != 0x80 || (in[i+2] & 0xc0) != 0x80) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
} else {
|
|
cp = ((in[i] & 0xf) << 12) | ((in[i+1] & 0x3f) << 6) | (in[i+2] & 0x3f);
|
|
i += 2;
|
|
}
|
|
} else if ((in[i] & 0xf8) == 0xf0) {
|
|
if (i >= src_len - 3 || (in[i+1] & 0xc0) != 0x80 || (in[i+2] & 0xc0) != 0x80 || (in[i+3] & 0xc0) != 0x80) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
} else {
|
|
cp = ((in[i] & 0x7) << 18) | ((in[i+1] & 0x3f) << 12) | ((in[i+2] & 0x3f) << 6) | (in[i+3] & 0x3f);
|
|
i += 3;
|
|
}
|
|
} else {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
}
|
|
|
|
if (cp > 0x10ffff) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
}
|
|
|
|
if (dest) {
|
|
if (cp <= 0xffff) {
|
|
if (left < 1)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
*out = (uint16_t)cp;
|
|
out++;
|
|
|
|
left--;
|
|
} else {
|
|
if (left < 2)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
cp -= 0x10000;
|
|
|
|
*out = 0xd800 | ((cp & 0xffc00) >> 10);
|
|
out++;
|
|
|
|
*out = 0xdc00 | (cp & 0x3ff);
|
|
out++;
|
|
|
|
left -= 2;
|
|
}
|
|
}
|
|
|
|
if (cp <= 0xffff)
|
|
needed += sizeof(uint16_t);
|
|
else
|
|
needed += 2 * sizeof(uint16_t);
|
|
}
|
|
|
|
if (dest_len)
|
|
*dest_len = needed;
|
|
|
|
return Status;
|
|
}
|
|
|
|
// version of RtlUnicodeToUTF8N for Vista and below
|
|
NTSTATUS utf16_to_utf8(char* dest, ULONG dest_max, ULONG* dest_len, WCHAR* src, ULONG src_len) {
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
uint16_t* in = (uint16_t*)src;
|
|
uint8_t* out = (uint8_t*)dest;
|
|
ULONG in_len = src_len / sizeof(uint16_t);
|
|
ULONG needed = 0, left = dest_max;
|
|
#ifdef __REACTOS__
|
|
ULONG i = 0;
|
|
|
|
for (i = 0; i < in_len; i++) {
|
|
#else
|
|
|
|
for (ULONG i = 0; i < in_len; i++) {
|
|
#endif
|
|
uint32_t cp = *in;
|
|
in++;
|
|
|
|
if ((cp & 0xfc00) == 0xd800) {
|
|
if (i == in_len - 1 || (*in & 0xfc00) != 0xdc00) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
} else {
|
|
cp = (cp & 0x3ff) << 10;
|
|
cp |= *in & 0x3ff;
|
|
cp += 0x10000;
|
|
|
|
in++;
|
|
i++;
|
|
}
|
|
} else if ((cp & 0xfc00) == 0xdc00) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
}
|
|
|
|
if (cp > 0x10ffff) {
|
|
cp = 0xfffd;
|
|
Status = STATUS_SOME_NOT_MAPPED;
|
|
}
|
|
|
|
if (dest) {
|
|
if (cp < 0x80) {
|
|
if (left < 1)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
*out = (uint8_t)cp;
|
|
out++;
|
|
|
|
left--;
|
|
} else if (cp < 0x800) {
|
|
if (left < 2)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
*out = 0xc0 | ((cp & 0x7c0) >> 6);
|
|
out++;
|
|
|
|
*out = 0x80 | (cp & 0x3f);
|
|
out++;
|
|
|
|
left -= 2;
|
|
} else if (cp < 0x10000) {
|
|
if (left < 3)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
*out = 0xe0 | ((cp & 0xf000) >> 12);
|
|
out++;
|
|
|
|
*out = 0x80 | ((cp & 0xfc0) >> 6);
|
|
out++;
|
|
|
|
*out = 0x80 | (cp & 0x3f);
|
|
out++;
|
|
|
|
left -= 3;
|
|
} else {
|
|
if (left < 4)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
*out = 0xf0 | ((cp & 0x1c0000) >> 18);
|
|
out++;
|
|
|
|
*out = 0x80 | ((cp & 0x3f000) >> 12);
|
|
out++;
|
|
|
|
*out = 0x80 | ((cp & 0xfc0) >> 6);
|
|
out++;
|
|
|
|
*out = 0x80 | (cp & 0x3f);
|
|
out++;
|
|
|
|
left -= 4;
|
|
}
|
|
}
|
|
|
|
if (cp < 0x80)
|
|
needed++;
|
|
else if (cp < 0x800)
|
|
needed += 2;
|
|
else if (cp < 0x10000)
|
|
needed += 3;
|
|
else
|
|
needed += 4;
|
|
}
|
|
|
|
if (dest_len)
|
|
*dest_len = needed;
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_QUERY_VOLUME_INFORMATION)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
static NTSTATUS __stdcall 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__
|
|
static const WCHAR ntfs[] = L"NTFS";
|
|
#endif
|
|
static const WCHAR btrfs[] = L"Btrfs";
|
|
const WCHAR* fs_name;
|
|
ULONG fs_name_len, orig_fs_name_len;
|
|
|
|
#ifndef __REACTOS__
|
|
if (Irp->RequestorMode == UserMode && lie_about_fs_type()) {
|
|
fs_name = ntfs;
|
|
orig_fs_name_len = fs_name_len = sizeof(ntfs) - sizeof(WCHAR);
|
|
} else {
|
|
fs_name = btrfs;
|
|
orig_fs_name_len = fs_name_len = sizeof(btrfs) - sizeof(WCHAR);
|
|
}
|
|
#else
|
|
fs_name = btrfs;
|
|
orig_fs_name_len = fs_name_len = sizeof(btrfs) - sizeof(WCHAR);
|
|
#endif
|
|
|
|
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 |
|
|
FILE_SUPPORTS_POSIX_UNLINK_RENAME;
|
|
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_t*)&ffsi->TotalAllocationUnits.QuadPart, (uint64_t*)&ffsi->ActualAvailableAllocationUnits.QuadPart);
|
|
ffsi->CallerAvailableAllocationUnits.QuadPart = ffsi->ActualAvailableAllocationUnits.QuadPart;
|
|
ffsi->SectorsPerAllocationUnit = Vcb->superblock.sector_size / 512;
|
|
ffsi->BytesPerSector = 512;
|
|
|
|
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_t*)&ffsi->TotalAllocationUnits.QuadPart, (uint64_t*)&ffsi->AvailableAllocationUnits.QuadPart);
|
|
ffsi->SectorsPerAllocationUnit = Vcb->superblock.sector_size / 512;
|
|
ffsi->BytesPerSector = 512;
|
|
|
|
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 = %lu\n", IrpSp->Parameters.QueryVolume.Length);
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
|
|
Status = utf8_to_utf16(NULL, 0, &label_len, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("utf8_to_utf16 returned %08lx\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 = %lu\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 = utf8_to_utf16(&data->VolumeLabel[0], label_len, &bytecount, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));
|
|
if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {
|
|
ERR("utf8_to_utf16 returned %08lx\n", Status);
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
break;
|
|
}
|
|
|
|
TRACE("label = %.*S\n", (int)(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);
|
|
Status = STATUS_SUCCESS;
|
|
|
|
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 %08lx\n", Status);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Function_class_(IO_COMPLETION_ROUTINE)
|
|
static NTSTATUS __stdcall read_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;
|
|
}
|
|
|
|
NTSTATUS create_root(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ uint64_t id,
|
|
_Out_ root** rootptr, _In_ bool no_tree, _In_ uint64_t 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->nonpaged = NULL;
|
|
|
|
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;
|
|
r->fcbs_version = 0;
|
|
r->checked_for_orphans = true;
|
|
r->dropped = false;
|
|
InitializeListHead(&r->fcbs);
|
|
RtlZeroMemory(r->fcbs_ptrs, sizeof(LIST_ENTRY*) * 256);
|
|
|
|
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 %08lx\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", (int)(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 = utf16_to_utf8(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 = utf16_to_utf8((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 %08lx\n", Status);
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_SET_VOLUME_INFORMATION)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
static NTSTATUS __stdcall 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 %08lx\n", Status);
|
|
|
|
IoCompleteRequest( Irp, IO_NO_INCREMENT );
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
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 %08lx\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 %08lx\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);
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);
|
|
|
|
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 %08lx\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 %08lx\n", Status);
|
|
free_fileref(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(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(parfr);
|
|
break;
|
|
}
|
|
|
|
Status = fileref_get_filename(parfr, &fn, NULL, NULL);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("fileref_get_filename returned %08lx\n", Status);
|
|
free_fileref(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(parfr);
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
ExReleaseResourceLite(&fcb->Vcb->fileref_lock);
|
|
}
|
|
|
|
typedef struct {
|
|
file_ref* fileref;
|
|
ULONG filter_match;
|
|
ULONG action;
|
|
PUNICODE_STRING stream;
|
|
PIO_WORKITEM work_item;
|
|
} notification_fcb;
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
static void __stdcall notification_work_item(PDEVICE_OBJECT DeviceObject, PVOID con) {
|
|
notification_fcb* nf = con;
|
|
|
|
UNUSED(DeviceObject);
|
|
|
|
ExAcquireResourceSharedLite(&nf->fileref->fcb->Vcb->tree_lock, TRUE); // protect us from fileref being reaped
|
|
|
|
send_notification_fcb(nf->fileref, nf->filter_match, nf->action, nf->stream);
|
|
|
|
free_fileref(nf->fileref);
|
|
|
|
ExReleaseResourceLite(&nf->fileref->fcb->Vcb->tree_lock);
|
|
|
|
IoFreeWorkItem(nf->work_item);
|
|
|
|
ExFreePool(nf);
|
|
}
|
|
|
|
void queue_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) {
|
|
notification_fcb* nf;
|
|
PIO_WORKITEM work_item;
|
|
|
|
nf = ExAllocatePoolWithTag(PagedPool, sizeof(notification_fcb), ALLOC_TAG);
|
|
if (!nf) {
|
|
ERR("out of memory\n");
|
|
return;
|
|
}
|
|
|
|
work_item = IoAllocateWorkItem(master_devobj);
|
|
if (!work_item) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(nf);
|
|
return;
|
|
}
|
|
|
|
InterlockedIncrement(&fileref->refcount);
|
|
|
|
nf->fileref = fileref;
|
|
nf->filter_match = filter_match;
|
|
nf->action = action;
|
|
nf->stream = stream;
|
|
nf->work_item = work_item;
|
|
|
|
IoQueueWorkItem(work_item, notification_work_item, DelayedWorkQueue, nf);
|
|
}
|
|
|
|
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(_Inout_ fcb* fcb, _In_ const char* func) {
|
|
LONG rc = InterlockedDecrement(&fcb->refcount);
|
|
#else
|
|
void free_fcb(_Inout_ fcb* fcb) {
|
|
InterlockedDecrement(&fcb->refcount);
|
|
#endif
|
|
|
|
#ifdef DEBUG_FCB_REFCOUNTS
|
|
ERR("fcb %p (%s): refcount now %i (subvol %I64x, inode %I64x)\n", fcb, func, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode);
|
|
#endif
|
|
}
|
|
|
|
void reap_fcb(fcb* fcb) {
|
|
uint8_t c = fcb->hash >> 24;
|
|
|
|
if (fcb->subvol && fcb->subvol->fcbs_ptrs[c] == &fcb->list_entry) {
|
|
if (fcb->list_entry.Flink != &fcb->subvol->fcbs && (CONTAINING_RECORD(fcb->list_entry.Flink, struct _fcb, list_entry)->hash >> 24) == c)
|
|
fcb->subvol->fcbs_ptrs[c] = fcb->list_entry.Flink;
|
|
else
|
|
fcb->subvol->fcbs_ptrs[c] = NULL;
|
|
}
|
|
|
|
if (fcb->list_entry.Flink) {
|
|
RemoveEntryList(&fcb->list_entry);
|
|
|
|
if (fcb->subvol && fcb->subvol->dropped && IsListEmpty(&fcb->subvol->fcbs)) {
|
|
ExDeleteResourceLite(&fcb->subvol->nonpaged->load_tree_lock);
|
|
ExFreePool(fcb->subvol->nonpaged);
|
|
ExFreePool(fcb->subvol);
|
|
}
|
|
}
|
|
|
|
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(&fcb->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);
|
|
|
|
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);
|
|
FsRtlUninitializeOplock(fcb_oplock(fcb));
|
|
|
|
if (fcb->pool_type == NonPagedPool)
|
|
ExFreePool(fcb);
|
|
else
|
|
ExFreeToPagedLookasideList(&fcb->Vcb->fcb_lookaside, fcb);
|
|
}
|
|
|
|
void reap_fcbs(device_extension* Vcb) {
|
|
LIST_ENTRY* le;
|
|
|
|
le = Vcb->all_fcbs.Flink;
|
|
while (le != &Vcb->all_fcbs) {
|
|
fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all);
|
|
LIST_ENTRY* le2 = le->Flink;
|
|
|
|
if (fcb->refcount == 0)
|
|
reap_fcb(fcb);
|
|
|
|
le = le2;
|
|
}
|
|
}
|
|
|
|
void free_fileref(_Inout_ file_ref* fr) {
|
|
LONG rc;
|
|
|
|
rc = InterlockedDecrement(&fr->refcount);
|
|
#ifdef __REACTOS__
|
|
(void)rc;
|
|
#endif
|
|
|
|
#ifdef DEBUG_FCB_REFCOUNTS
|
|
ERR("fileref %p: refcount now %i\n", fr, rc);
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
if (rc < 0) {
|
|
ERR("fileref %p: refcount now %li\n", fr, rc);
|
|
int3;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void reap_fileref(device_extension* Vcb, file_ref* fr) {
|
|
// FIXME - do we need a file_ref lock?
|
|
|
|
// FIXME - do delete if needed
|
|
|
|
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)
|
|
free_fileref(fr->parent);
|
|
|
|
free_fcb(fr->fcb);
|
|
|
|
if (fr->oldutf8.Buffer)
|
|
ExFreePool(fr->oldutf8.Buffer);
|
|
|
|
ExFreeToPagedLookasideList(&Vcb->fileref_lookaside, fr);
|
|
}
|
|
|
|
void reap_filerefs(device_extension* Vcb, file_ref* fr) {
|
|
LIST_ENTRY* le;
|
|
|
|
// FIXME - recursion is a bad idea in kernel mode
|
|
|
|
le = fr->children.Flink;
|
|
while (le != &fr->children) {
|
|
file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);
|
|
LIST_ENTRY* le2 = le->Flink;
|
|
|
|
reap_filerefs(Vcb, c);
|
|
|
|
le = le2;
|
|
}
|
|
|
|
if (fr->refcount == 0)
|
|
reap_fileref(Vcb, fr);
|
|
}
|
|
|
|
static NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp) {
|
|
fcb* fcb;
|
|
ccb* ccb;
|
|
file_ref* fileref = NULL;
|
|
LONG open_files;
|
|
|
|
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 fcb %p)\n", 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);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (!(fcb->Vcb->Vpb->Flags & VPB_MOUNTED))
|
|
return STATUS_SUCCESS;
|
|
|
|
if (fileref)
|
|
free_fileref(fileref);
|
|
else
|
|
free_fcb(fcb);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void uninit(_In_ device_extension* Vcb) {
|
|
uint64_t i;
|
|
KIRQL irql;
|
|
NTSTATUS Status;
|
|
LIST_ENTRY* le;
|
|
LARGE_INTEGER time;
|
|
|
|
if (!Vcb->removing) {
|
|
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);
|
|
Vcb->removing = true;
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
}
|
|
|
|
if (Vcb->vde && Vcb->vde->mounted_device == Vcb->devobj)
|
|
Vcb->vde->mounted_device = NULL;
|
|
|
|
IoAcquireVpbSpinLock(&irql);
|
|
Vcb->Vpb->Flags &= ~VPB_MOUNTED;
|
|
Vcb->Vpb->Flags |= VPB_DIRECT_WRITES_ALLOWED;
|
|
Vcb->Vpb->DeviceObject = NULL;
|
|
IoReleaseVpbSpinLock(irql);
|
|
|
|
// FIXME - needs global_loading_lock to be held
|
|
if (Vcb->list_entry.Flink)
|
|
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 %08lx\n", Status);
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
reap_fcb(Vcb->volume_fcb);
|
|
reap_fcb(Vcb->dummy_fcb);
|
|
|
|
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) {
|
|
reap_fcb(c->cache);
|
|
c->cache = NULL;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
while (!IsListEmpty(&Vcb->all_fcbs)) {
|
|
fcb* fcb = CONTAINING_RECORD(Vcb->all_fcbs.Flink, struct _fcb, list_entry_all);
|
|
|
|
reap_fcb(fcb);
|
|
}
|
|
|
|
while (!IsListEmpty(&Vcb->sys_chunks)) {
|
|
sys_chunk* sc = CONTAINING_RECORD(RemoveHeadList(&Vcb->sys_chunks), sys_chunk, list_entry);
|
|
|
|
if (sc->data)
|
|
ExFreePool(sc->data);
|
|
|
|
ExFreePool(sc);
|
|
}
|
|
|
|
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)
|
|
reap_fcb(c->cache);
|
|
|
|
ExDeleteResourceLite(&c->range_locks_lock);
|
|
ExDeleteResourceLite(&c->partial_stripes_lock);
|
|
ExDeleteResourceLite(&c->lock);
|
|
ExDeleteResourceLite(&c->changed_extents_lock);
|
|
|
|
ExFreePool(c->chunk_item);
|
|
ExFreePool(c);
|
|
}
|
|
|
|
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->fileref_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);
|
|
|
|
if (Vcb->devobj->AttachedDevice)
|
|
IoDetachDevice(Vcb->devobj);
|
|
|
|
IoDeleteDevice(Vcb->devobj);
|
|
}
|
|
|
|
static NTSTATUS delete_fileref_fcb(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
LIST_ENTRY* le;
|
|
|
|
// 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 %08lx\n", Status);
|
|
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 %08lx\n", Status);
|
|
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;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS delete_fileref(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_ bool make_orphan, _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 || make_orphan) {
|
|
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 {
|
|
Status = delete_fileref_fcb(fileref, FileObject, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref_fcb returned %08lx\n", Status);
|
|
ExReleaseResourceLite(fileref->fcb->Header.Resource);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
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", (int)(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 %I64x) was %I64x\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 %I64x) now %I64x\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 __stdcall 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;
|
|
}
|
|
|
|
FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);
|
|
|
|
// 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, refcount = %li, open_count = %li\n", fileref, 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);
|
|
|
|
FsRtlFastUnlockAll(&fcb->lock, FileObject, IoGetRequestorProcess(Irp), NULL);
|
|
|
|
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 || (fileref->delete_on_close && fileref->posix_delete))) {
|
|
if (!fcb->Vcb->removing) {
|
|
if (oc == 0 && fileref->fcb->inode_item.st_nlink == 0 && fileref != fcb->Vcb->root_fileref &&
|
|
fcb != fcb->Vcb->volume_fcb && !fcb->ads) { // last handle closed on POSIX-deleted file
|
|
LIST_ENTRY rollback;
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
Status = delete_fileref_fcb(fileref, FileObject, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref_fcb returned %08lx\n", Status);
|
|
do_rollback(fcb->Vcb, &rollback);
|
|
ExReleaseResourceLite(fileref->fcb->Header.Resource);
|
|
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
|
|
goto exit;
|
|
}
|
|
|
|
clear_rollback(&rollback);
|
|
|
|
mark_fcb_dirty(fileref->fcb);
|
|
} else if (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;
|
|
|
|
// fileref_lock needs to be acquired before fcb->Header.Resource
|
|
ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);
|
|
|
|
Status = delete_fileref(fileref, FileObject, oc > 0 && fileref->posix_delete, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08lx\n", Status);
|
|
do_rollback(fcb->Vcb, &rollback);
|
|
ExReleaseResourceLite(&fcb->Vcb->fileref_lock);
|
|
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
|
|
goto exit;
|
|
}
|
|
|
|
ExReleaseResourceLite(&fcb->Vcb->fileref_lock);
|
|
|
|
clear_rollback(&rollback);
|
|
} else if (FileObject->Flags & FO_CACHE_SUPPORTED && FileObject->SectionObjectPointer->DataSectionObject) {
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
if (locked) {
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
locked = false;
|
|
}
|
|
|
|
CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);
|
|
|
|
if (!NT_SUCCESS(iosb.Status))
|
|
ERR("CcFlushCache returned %08lx\n", iosb.Status);
|
|
|
|
if (!ExIsResourceAcquiredSharedLite(fcb->Header.PagingIoResource)) {
|
|
ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true);
|
|
ExReleaseResourceLite(fcb->Header.PagingIoResource);
|
|
}
|
|
|
|
CcPurgeCacheSection(FileObject->SectionObjectPointer, NULL, 0, false);
|
|
|
|
TRACE("flushed cache on close (FileObject = %p, fcb = %p, AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x)\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 %08lx\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_t 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: %08lx\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_t inode,
|
|
_In_ uint8_t type, _In_ bool dotfile, _In_ bool ignore_xa, _In_opt_ PIRP Irp) {
|
|
ULONG att;
|
|
char* eaval;
|
|
uint16_t ealen;
|
|
|
|
if (!ignore_xa && get_xattr(Vcb, r, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (uint8_t**)&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 || (r->id == BTRFS_ROOT_FSTREE && inode == SUBVOL_ROOT_INODE))
|
|
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_ PFILE_OBJECT FileObject, _In_ uint64_t 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;
|
|
IrpSp->FileObject = FileObject;
|
|
|
|
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 %08lx\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;
|
|
}
|
|
|
|
bool check_superblock_checksum(superblock* sb) {
|
|
switch (sb->csum_type) {
|
|
case CSUM_TYPE_CRC32C: {
|
|
uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));
|
|
|
|
if (crc32 == *((uint32_t*)sb->checksum))
|
|
return true;
|
|
|
|
WARN("crc32 was %08x, expected %08x\n", crc32, *((uint32_t*)sb->checksum));
|
|
|
|
break;
|
|
}
|
|
|
|
case CSUM_TYPE_XXHASH: {
|
|
uint64_t hash = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0);
|
|
|
|
if (hash == *((uint64_t*)sb->checksum))
|
|
return true;
|
|
|
|
WARN("superblock hash was %I64x, expected %I64x\n", hash, *((uint64_t*)sb->checksum));
|
|
|
|
break;
|
|
}
|
|
|
|
case CSUM_TYPE_SHA256: {
|
|
uint8_t hash[SHA256_HASH_SIZE];
|
|
|
|
calc_sha256(hash, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));
|
|
|
|
if (RtlCompareMemory(hash, sb, SHA256_HASH_SIZE) == SHA256_HASH_SIZE)
|
|
return true;
|
|
|
|
WARN("superblock hash was invalid\n");
|
|
|
|
break;
|
|
}
|
|
|
|
case CSUM_TYPE_BLAKE2: {
|
|
uint8_t hash[BLAKE2_HASH_SIZE];
|
|
|
|
blake2b(hash, sizeof(hash), &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));
|
|
|
|
if (RtlCompareMemory(hash, sb, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE)
|
|
return true;
|
|
|
|
WARN("superblock hash was invalid\n");
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
WARN("unrecognized csum type %x\n", sb->csum_type);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static NTSTATUS read_superblock(_In_ device_extension* Vcb, _In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t length) {
|
|
NTSTATUS Status;
|
|
superblock* sb;
|
|
ULONG i, to_read;
|
|
uint8_t 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) {
|
|
if (i > 0 && superblock_addrs[i] + to_read > length)
|
|
break;
|
|
|
|
Status = sync_read_phys(device, fileobj, superblock_addrs[i], to_read, (PUCHAR)sb, false);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("Failed to read superblock %lu: %08lx\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 %lu!\n", i);
|
|
|
|
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 (check_superblock_checksum(sb) && (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_ bool 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_t id, _In_ uint64_t addr,
|
|
_In_ uint64_t 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;
|
|
r->fcbs_version = 0;
|
|
r->checked_for_orphans = false;
|
|
r->dropped = false;
|
|
InitializeListHead(&r->fcbs);
|
|
RtlZeroMemory(r->fcbs_ptrs, sizeof(LIST_ENTRY*) * 256);
|
|
|
|
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_t*)&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 %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
do {
|
|
TRACE("(%I64x,%x,%I64x)\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("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(ROOT_ITEM, byte_limit));
|
|
} else {
|
|
TRACE("root %I64x - address %I64x\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 %08lx\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_t 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 %08lx\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 %08lx\n", Status);
|
|
ExFreePool(ii);
|
|
return Status;
|
|
}
|
|
|
|
irlen = (uint16_t)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 %08lx\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_t 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_t) * 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 %08lx\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 %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
lastaddr = tp.item->key.offset + de->length;
|
|
} else {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\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 %08lx\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 %I64x, 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 %I64x\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->fileobj = vc->fileobj;
|
|
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 %08lx\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 %08lx\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 %08lx\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 %08lx for IDENTIFY DEVICE\n", Status);
|
|
else {
|
|
IDENTIFY_DEVICE_DATA* idd = (IDENTIFY_DEVICE_DATA*)((uint8_t*)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);
|
|
|
|
#ifdef DEBUG_TRIM_EMULATION
|
|
dev->trim = true;
|
|
Vcb->trim = true;
|
|
#else
|
|
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");
|
|
}
|
|
#endif
|
|
|
|
RtlZeroMemory(dev->stats, sizeof(uint64_t) * 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 %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
do {
|
|
TRACE("(%I64x,%x,%I64x)\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("(%I64x,%x,%I64x) was %u bytes, expected %Iu\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;
|
|
dev->fileobj = vc->fileobj;
|
|
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 %I64x: DEV_ITEM says %I64x bytes, but Windows only reports %I64x\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 %I64x, 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 %I64x 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("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(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 %I64x: 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_t 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 %u = %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 %I64x: 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_t i = 0, j;
|
|
uint64_t 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_t 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_t startoff;
|
|
uint16_t startoffstripe;
|
|
#endif
|
|
|
|
TRACE("cut out superblock in chunk %I64x\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 = %I64x, superblock = %I64x\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_t 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 %I64x\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 %I64x, size %I64x\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_t 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 %I64x\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 %I64x, size %I64x\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, RAID1C3, RAID1C4
|
|
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 %I64x\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++;
|
|
}
|
|
}
|
|
|
|
uint64_t chunk_estimate_phys_size(device_extension* Vcb, chunk* c, uint64_t u) {
|
|
uint64_t nfactor, dfactor;
|
|
|
|
if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE || c->chunk_item->type & BLOCK_FLAG_RAID1 || c->chunk_item->type & BLOCK_FLAG_RAID10) {
|
|
nfactor = 1;
|
|
dfactor = 2;
|
|
} else if (c->chunk_item->type & BLOCK_FLAG_RAID5) {
|
|
nfactor = Vcb->superblock.num_devices - 1;
|
|
dfactor = Vcb->superblock.num_devices;
|
|
} else if (c->chunk_item->type & BLOCK_FLAG_RAID6) {
|
|
nfactor = Vcb->superblock.num_devices - 2;
|
|
dfactor = Vcb->superblock.num_devices;
|
|
} else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3) {
|
|
nfactor = 1;
|
|
dfactor = 3;
|
|
} else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4) {
|
|
nfactor = 1;
|
|
dfactor = 4;
|
|
} else {
|
|
nfactor = 1;
|
|
dfactor = 1;
|
|
}
|
|
|
|
return u * dfactor / nfactor;
|
|
}
|
|
|
|
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;
|
|
|
|
Vcb->superblock.bytes_used = 0;
|
|
|
|
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 %08lx\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 %I64x has %I64x bytes used\n", c->offset, c->used);
|
|
|
|
Vcb->superblock.bytes_used += chunk_estimate_phys_size(Vcb, c, bgi->used);
|
|
} else {
|
|
ERR("(%I64x;%I64x,%x,%I64x) is %u bytes, expected %Iu\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: %I64x,%x,%I64x\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 %I64x,%x,%I64x in bootstrap\n", key.obj_id, key.obj_type, key.offset);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_Ret_maybenull_
|
|
root* find_default_subvol(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {
|
|
LIST_ENTRY* le;
|
|
|
|
static const char fn[] = "default";
|
|
static uint32_t 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 %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
if (keycmp(tp.item->key, searchkey)) {
|
|
ERR("could not find (%I64x,%x,%I64x) in root tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
|
|
goto end;
|
|
}
|
|
|
|
if (tp.item->size < sizeof(DIR_ITEM)) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));
|
|
goto end;
|
|
}
|
|
|
|
di = (DIR_ITEM*)tp.item->data;
|
|
|
|
if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->n) {
|
|
ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\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 (%I64x,%x,%I64x), 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 %I64x, 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);
|
|
}
|
|
|
|
uint32_t get_num_of_processors() {
|
|
KAFFINITY p = KeQueryActiveProcessors();
|
|
uint32_t r = 0;
|
|
|
|
while (p != 0) {
|
|
if (p & 1)
|
|
r++;
|
|
|
|
p >>= 1;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static NTSTATUS create_calc_threads(_In_ PDEVICE_OBJECT DeviceObject) {
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
OBJECT_ATTRIBUTES oa;
|
|
ULONG i;
|
|
|
|
Vcb->calcthreads.num_threads = get_num_of_processors();
|
|
|
|
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);
|
|
KeInitializeSpinLock(&Vcb->calcthreads.spinlock);
|
|
KeInitializeEvent(&Vcb->calcthreads.event, NotificationEvent, false);
|
|
|
|
RtlZeroMemory(Vcb->calcthreads.threads, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads);
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
|
|
for (i = 0; i < Vcb->calcthreads.num_threads; i++) {
|
|
NTSTATUS Status;
|
|
|
|
Vcb->calcthreads.threads[i].DeviceObject = DeviceObject;
|
|
Vcb->calcthreads.threads[i].number = i;
|
|
KeInitializeEvent(&Vcb->calcthreads.threads[i].finished, NotificationEvent, false);
|
|
|
|
Status = PsCreateSystemThread(&Vcb->calcthreads.threads[i].handle, 0, &oa, NULL, NULL, calc_thread, &Vcb->calcthreads.threads[i]);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ULONG j;
|
|
|
|
ERR("PsCreateSystemThread returned %08lx\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 %08lx\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 %08lx\n", Status);
|
|
ExFreePool(mdn2);
|
|
return false;
|
|
}
|
|
|
|
if (mdn2->NameLength > (sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) &&
|
|
RtlCompareMemory(mdn2->Name, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) == sizeof(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 %08lx\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* pno_pnp) {
|
|
NTSTATUS Status;
|
|
ULONG to_read;
|
|
superblock* sb;
|
|
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, NULL, superblock_addrs[0], to_read, (PUCHAR)sb, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("sync_read_phys returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
if (sb->magic != BTRFS_MAGIC) {
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
if (!check_superblock_checksum(sb)) {
|
|
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 %08lx\n", Status);
|
|
pnp_name.Length = 0;
|
|
}
|
|
|
|
*pno_pnp = pnp_name.Length == 0;
|
|
|
|
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, _In_ PFILE_OBJECT fileobj) {
|
|
NTSTATUS Status;
|
|
ULONG to_read;
|
|
superblock* sb;
|
|
|
|
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, fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("Failed to read superblock: %08lx\n", Status);
|
|
ExFreePool(sb);
|
|
return false;
|
|
}
|
|
|
|
if (sb->magic != BTRFS_MAGIC) {
|
|
TRACE("not a BTRFS volume\n");
|
|
ExFreePool(sb);
|
|
return false;
|
|
} else {
|
|
if (!check_superblock_checksum(sb)) {
|
|
ExFreePool(sb);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ObReferenceObject(device);
|
|
|
|
while (device) {
|
|
PDEVICE_OBJECT device2 = IoGetLowerDeviceObject(device);
|
|
|
|
device->Flags &= ~DO_VERIFY_VOLUME;
|
|
|
|
ObDereferenceObject(device);
|
|
|
|
device = 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;
|
|
PFILE_OBJECT fileobj;
|
|
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;
|
|
uint64_t readobjsize;
|
|
OBJECT_ATTRIBUTES oa;
|
|
device_extension* real_devext;
|
|
KIRQL irql;
|
|
|
|
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;
|
|
|
|
real_devext = IrpSp->Parameters.MountVolume.Vpb->RealDevice->DeviceExtension;
|
|
|
|
// Make sure we're not trying to mount the PDO
|
|
if (IrpSp->Parameters.MountVolume.Vpb->RealDevice->DriverObject == drvobj && real_devext->type == VCB_TYPE_PDO)
|
|
return STATUS_UNRECOGNIZED_VOLUME;
|
|
|
|
if (!is_btrfs_volume(DeviceToMount)) {
|
|
bool not_pnp = false;
|
|
|
|
Status = check_mount_device(DeviceToMount, ¬_pnp);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("check_mount_device returned %08lx\n", Status);
|
|
|
|
if (!not_pnp) {
|
|
Status = STATUS_UNRECOGNIZED_VOLUME;
|
|
goto exit2;
|
|
}
|
|
} else {
|
|
PDEVICE_OBJECT pdo;
|
|
|
|
pdo = DeviceToMount;
|
|
|
|
ObReferenceObject(pdo);
|
|
|
|
while (true) {
|
|
PDEVICE_OBJECT pdo2 = IoGetLowerDeviceObject(pdo);
|
|
|
|
ObDereferenceObject(pdo);
|
|
|
|
if (!pdo2)
|
|
break;
|
|
else
|
|
pdo = pdo2;
|
|
}
|
|
|
|
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 == pdo) {
|
|
vde = pdode2->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, vc->fileobj)) {
|
|
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;
|
|
fileobj = vc->fileobj;
|
|
readobjsize = vc->size;
|
|
|
|
vde->device->Characteristics &= ~FILE_DEVICE_SECURE_OPEN;
|
|
} else {
|
|
GET_LENGTH_INFORMATION gli;
|
|
|
|
vc = NULL;
|
|
readobj = DeviceToMount;
|
|
fileobj = NULL;
|
|
|
|
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: %08lx\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 %08lx\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->fileref_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, fileobj, 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 %08lx\n", Status);
|
|
goto exit;
|
|
}
|
|
|
|
if (pdode && RtlCompareMemory(&boot_uuid, &pdode->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID) && boot_subvol != 0)
|
|
Vcb->options.subvol_id = boot_subvol;
|
|
|
|
if (pdode && pdode->children_loaded < pdode->num_children && (!Vcb->options.allow_degraded || !finished_probing || degraded_wait)) {
|
|
ERR("could not mount as %I64u 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 (%I64x)\n", Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED);
|
|
Status = STATUS_UNRECOGNIZED_VOLUME;
|
|
goto exit;
|
|
}
|
|
|
|
if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_METADATA_UUID))
|
|
Vcb->superblock.metadata_uuid = Vcb->superblock.uuid;
|
|
|
|
Vcb->readonly = false;
|
|
if (Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED) {
|
|
WARN("mounting read-only because of unsupported flags (%I64x)\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;
|
|
|
|
if (Vcb->superblock.log_tree_addr != 0) {
|
|
FIXME("FIXME - replay transaction log (clearing for now)\n");
|
|
Vcb->superblock.log_tree_addr = 0;
|
|
}
|
|
|
|
switch (Vcb->superblock.csum_type) {
|
|
case CSUM_TYPE_CRC32C:
|
|
Vcb->csum_size = sizeof(uint32_t);
|
|
break;
|
|
|
|
case CSUM_TYPE_XXHASH:
|
|
Vcb->csum_size = sizeof(uint64_t);
|
|
break;
|
|
|
|
case CSUM_TYPE_SHA256:
|
|
Vcb->csum_size = SHA256_HASH_SIZE;
|
|
break;
|
|
|
|
case CSUM_TYPE_BLAKE2:
|
|
Vcb->csum_size = BLAKE2_HASH_SIZE;
|
|
break;
|
|
|
|
default:
|
|
ERR("unrecognized csum type %x\n", Vcb->superblock.csum_type);
|
|
break;
|
|
}
|
|
|
|
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;
|
|
dev->fileobj = fileobj;
|
|
RtlCopyMemory(&dev->devitem, &Vcb->superblock.dev_item, sizeof(DEV_ITEM));
|
|
|
|
if (dev->devitem.num_bytes > readobjsize) {
|
|
WARN("device %I64x: DEV_ITEM says %I64x bytes, but Windows only reports %I64x\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 %08lx\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);
|
|
|
|
ExInitializeFastMutex(&Vcb->trees_list_mutex);
|
|
|
|
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 %08lx\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 %I64u 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 %08lx\n", Status);
|
|
goto exit;
|
|
}
|
|
|
|
if (!Vcb->readonly) {
|
|
Status = find_chunk_usage(Vcb, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("find_chunk_usage returned %08lx\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 %I64x, free-space cache generation was %I64x; 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 %08lx\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 %08lx\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->hash = calc_crc32c(0xffffffff, (uint8_t*)&root_fcb->inode, sizeof(uint64_t));
|
|
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 %08lx\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 %08lx\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);
|
|
|
|
if (root_fcb->subvol->id == BTRFS_ROOT_FSTREE)
|
|
root_fcb->atts &= ~FILE_ATTRIBUTE_HIDDEN;
|
|
|
|
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->subvol->fcbs_ptrs[root_fcb->hash >> 24] = &root_fcb->list_entry;
|
|
|
|
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;
|
|
}
|
|
|
|
Vcb->root_file = IoCreateStreamFileObject(NULL, DeviceToMount);
|
|
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 %08lx\n", Status);
|
|
goto exit;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
IoAcquireVpbSpinLock(&irql);
|
|
|
|
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++;
|
|
|
|
IoReleaseVpbSpinLock(irql);
|
|
|
|
KeInitializeEvent(&Vcb->flush_thread_finished, NotificationEvent, false);
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
|
|
Status = PsCreateSystemThread(&Vcb->flush_thread_handle, 0, &oa, NULL, NULL, flush_thread, NewDeviceObject);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("PsCreateSystemThread returned %08lx\n", Status);
|
|
goto exit;
|
|
}
|
|
|
|
Status = create_calc_threads(NewDeviceObject);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("create_calc_threads returned %08lx\n", Status);
|
|
goto exit;
|
|
}
|
|
|
|
Status = registry_mark_volume_mounted(&Vcb->superblock.uuid);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("registry_mark_volume_mounted returned %08lx\n", Status);
|
|
|
|
Status = look_for_balance_item(Vcb);
|
|
if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND)
|
|
WARN("look_for_balance_item returned %08lx\n", Status);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if (vde)
|
|
vde->mounted_device = NewDeviceObject;
|
|
|
|
Vcb->devobj = 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)
|
|
free_fileref(Vcb->root_fileref);
|
|
else if (root_fcb)
|
|
free_fcb(root_fcb);
|
|
|
|
if (root_fcb && root_fcb->refcount == 0)
|
|
reap_fcb(root_fcb);
|
|
|
|
if (Vcb->volume_fcb)
|
|
reap_fcb(Vcb->volume_fcb);
|
|
|
|
ExDeleteResourceLite(&Vcb->tree_lock);
|
|
ExDeleteResourceLite(&Vcb->load_lock);
|
|
ExDeleteResourceLite(&Vcb->fcb_lock);
|
|
ExDeleteResourceLite(&Vcb->fileref_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;
|
|
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 %08lx (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 %08lx\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, dev->fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("Failed to read superblock: %08lx\n", Status);
|
|
ExFreePool(sb);
|
|
return Status;
|
|
}
|
|
|
|
if (sb->magic != BTRFS_MAGIC) {
|
|
ERR("not a BTRFS volume\n");
|
|
ExFreePool(sb);
|
|
return STATUS_WRONG_VOLUME;
|
|
}
|
|
|
|
if (!check_superblock_checksum(sb)) {
|
|
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_t 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);
|
|
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 __stdcall 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 %08lx\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 __stdcall drv_lock_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
|
|
NTSTATUS Status;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
fcb* fcb = IrpSp->FileObject ? IrpSp->FileObject->FsContext : NULL;
|
|
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");
|
|
|
|
if (!fcb) {
|
|
ERR("fcb was NULL\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
}
|
|
|
|
FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);
|
|
|
|
Status = FsRtlProcessFileLock(&fcb->lock, Irp, NULL);
|
|
|
|
fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
|
|
|
|
exit:
|
|
TRACE("returning %08lx\n", Status);
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
void do_shutdown(PIRP Irp) {
|
|
LIST_ENTRY* le;
|
|
bus_device_extension* bde;
|
|
|
|
shutting_down = true;
|
|
KeSetEvent(&mountmgr_thread_event, 0, false);
|
|
|
|
le = VcbList.Flink;
|
|
while (le != &VcbList) {
|
|
LIST_ENTRY* le2 = le->Flink;
|
|
|
|
device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry);
|
|
volume_device_extension* vde = Vcb->vde;
|
|
PDEVICE_OBJECT devobj = vde ? vde->device : NULL;
|
|
|
|
TRACE("shutting down Vcb %p\n", Vcb);
|
|
|
|
if (vde)
|
|
InterlockedIncrement(&vde->open_count);
|
|
|
|
if (devobj)
|
|
ObReferenceObject(devobj);
|
|
|
|
dismount_volume(Vcb, true, Irp);
|
|
|
|
if (vde) {
|
|
NTSTATUS Status;
|
|
UNICODE_STRING mmdevpath;
|
|
PDEVICE_OBJECT mountmgr;
|
|
PFILE_OBJECT mountmgrfo;
|
|
KIRQL irql;
|
|
PVPB newvpb;
|
|
|
|
RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);
|
|
Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr);
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("IoGetDeviceObjectPointer returned %08lx\n", Status);
|
|
else {
|
|
remove_drive_letter(mountmgr, &vde->name);
|
|
|
|
ObDereferenceObject(mountmgrfo);
|
|
}
|
|
|
|
vde->removing = true;
|
|
|
|
newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG);
|
|
if (!newvpb) {
|
|
ERR("out of memory\n");
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(newvpb, sizeof(VPB));
|
|
|
|
newvpb->Type = IO_TYPE_VPB;
|
|
newvpb->Size = sizeof(VPB);
|
|
newvpb->RealDevice = newvpb->DeviceObject = vde->device;
|
|
newvpb->Flags = VPB_DIRECT_WRITES_ALLOWED;
|
|
|
|
IoAcquireVpbSpinLock(&irql);
|
|
vde->device->Vpb = newvpb;
|
|
IoReleaseVpbSpinLock(irql);
|
|
|
|
if (InterlockedDecrement(&vde->open_count) == 0)
|
|
free_vol(vde);
|
|
}
|
|
|
|
if (devobj)
|
|
ObDereferenceObject(devobj);
|
|
|
|
le = le2;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if (comfo) {
|
|
ObDereferenceObject(comfo);
|
|
comdo = NULL;
|
|
comfo = NULL;
|
|
}
|
|
#endif
|
|
|
|
IoUnregisterFileSystem(master_devobj);
|
|
|
|
if (notification_entry2) {
|
|
if (fIoUnregisterPlugPlayNotificationEx)
|
|
fIoUnregisterPlugPlayNotificationEx(notification_entry2);
|
|
else
|
|
IoUnregisterPlugPlayNotification(notification_entry2);
|
|
|
|
notification_entry2 = NULL;
|
|
}
|
|
|
|
if (notification_entry3) {
|
|
if (fIoUnregisterPlugPlayNotificationEx)
|
|
fIoUnregisterPlugPlayNotificationEx(notification_entry3);
|
|
else
|
|
IoUnregisterPlugPlayNotification(notification_entry3);
|
|
|
|
notification_entry3 = NULL;
|
|
}
|
|
|
|
if (notification_entry) {
|
|
if (fIoUnregisterPlugPlayNotificationEx)
|
|
fIoUnregisterPlugPlayNotificationEx(notification_entry);
|
|
else
|
|
IoUnregisterPlugPlayNotification(notification_entry);
|
|
|
|
notification_entry = NULL;
|
|
}
|
|
|
|
bde = busobj->DeviceExtension;
|
|
|
|
if (bde->attached_device)
|
|
IoDetachDevice(bde->attached_device);
|
|
|
|
IoDeleteDevice(busobj);
|
|
IoDeleteDevice(master_devobj);
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_SHUTDOWN)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
static NTSTATUS __stdcall 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;
|
|
|
|
do_shutdown(Irp);
|
|
|
|
end:
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest( Irp, IO_NO_INCREMENT );
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
static bool device_still_valid(device* dev, uint64_t expected_generation) {
|
|
NTSTATUS Status;
|
|
unsigned int to_read;
|
|
superblock* sb;
|
|
|
|
to_read = (unsigned int)(dev->devobj->SectorSize == 0 ? sizeof(superblock) : sector_align(sizeof(superblock), dev->devobj->SectorSize));
|
|
|
|
sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);
|
|
if (!sb) {
|
|
ERR("out of memory\n");
|
|
return false;
|
|
}
|
|
|
|
Status = sync_read_phys(dev->devobj, dev->fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, false);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("sync_read_phys returned %08lx\n", Status);
|
|
ExFreePool(sb);
|
|
return false;
|
|
}
|
|
|
|
if (sb->magic != BTRFS_MAGIC) {
|
|
ERR("magic not found\n");
|
|
ExFreePool(sb);
|
|
return false;
|
|
}
|
|
|
|
if (!check_superblock_checksum(sb)) {
|
|
ExFreePool(sb);
|
|
return false;
|
|
}
|
|
|
|
if (sb->generation > expected_generation) {
|
|
ERR("generation was %I64x, expected %I64x\n", sb->generation, expected_generation);
|
|
ExFreePool(sb);
|
|
return false;
|
|
}
|
|
|
|
ExFreePool(sb);
|
|
|
|
return true;
|
|
}
|
|
|
|
_Function_class_(IO_WORKITEM_ROUTINE)
|
|
static void __stdcall check_after_wakeup(PDEVICE_OBJECT DeviceObject, PVOID con) {
|
|
device_extension* Vcb = (device_extension*)con;
|
|
LIST_ENTRY* le;
|
|
|
|
UNUSED(DeviceObject);
|
|
|
|
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);
|
|
|
|
le = Vcb->devices.Flink;
|
|
|
|
// FIXME - do reads in parallel?
|
|
|
|
while (le != &Vcb->devices) {
|
|
device* dev = CONTAINING_RECORD(le, device, list_entry);
|
|
|
|
if (dev->devobj) {
|
|
if (!device_still_valid(dev, Vcb->superblock.generation - 1)) {
|
|
PDEVICE_OBJECT voldev = Vcb->Vpb->RealDevice;
|
|
KIRQL irql;
|
|
PVPB newvpb;
|
|
|
|
WARN("forcing remount\n");
|
|
|
|
newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG);
|
|
if (!newvpb) {
|
|
ERR("out of memory\n");
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(newvpb, sizeof(VPB));
|
|
|
|
newvpb->Type = IO_TYPE_VPB;
|
|
newvpb->Size = sizeof(VPB);
|
|
newvpb->RealDevice = voldev;
|
|
newvpb->Flags = VPB_DIRECT_WRITES_ALLOWED;
|
|
|
|
Vcb->removing = true;
|
|
|
|
IoAcquireVpbSpinLock(&irql);
|
|
voldev->Vpb = newvpb;
|
|
IoReleaseVpbSpinLock(irql);
|
|
|
|
Vcb->vde = NULL;
|
|
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
if (Vcb->open_files == 0)
|
|
uninit(Vcb);
|
|
else { // remove from VcbList
|
|
ExAcquireResourceExclusiveLite(&global_loading_lock, true);
|
|
RemoveEntryList(&Vcb->list_entry);
|
|
Vcb->list_entry.Flink = NULL;
|
|
ExReleaseResourceLite(&global_loading_lock);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_POWER)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
static NTSTATUS __stdcall drv_power(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
|
|
NTSTATUS Status;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
bool top_level;
|
|
|
|
// no need for FsRtlEnterFileSystem, as this only ever gets called in a system thread
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
|
|
volume_device_extension* vde = DeviceObject->DeviceExtension;
|
|
|
|
if (IrpSp->MinorFunction == IRP_MN_QUERY_POWER && IrpSp->Parameters.Power.Type == SystemPowerState &&
|
|
IrpSp->Parameters.Power.State.SystemState != PowerSystemWorking && vde->mounted_device) {
|
|
device_extension* Vcb2 = vde->mounted_device->DeviceExtension;
|
|
|
|
/* If power state is about to go to sleep or hibernate, do a flush. We do this on IRP_MJ_QUERY_POWER
|
|
* rather than IRP_MJ_SET_POWER because we know that the hard disks are still awake. */
|
|
|
|
if (Vcb2) {
|
|
ExAcquireResourceExclusiveLite(&Vcb2->tree_lock, true);
|
|
|
|
if (Vcb2->need_write && !Vcb2->readonly) {
|
|
TRACE("doing protective flush on power state change\n");
|
|
Status = do_write(Vcb2, NULL);
|
|
} else
|
|
Status = STATUS_SUCCESS;
|
|
|
|
free_trees(Vcb2);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("do_write returned %08lx\n", Status);
|
|
|
|
ExReleaseResourceLite(&Vcb2->tree_lock);
|
|
}
|
|
} else if (IrpSp->MinorFunction == IRP_MN_SET_POWER && IrpSp->Parameters.Power.Type == SystemPowerState &&
|
|
IrpSp->Parameters.Power.State.SystemState == PowerSystemWorking && vde->mounted_device) {
|
|
device_extension* Vcb2 = vde->mounted_device->DeviceExtension;
|
|
|
|
/* If waking up, make sure that the FS hasn't been changed while we've been out (e.g., by dual-boot Linux) */
|
|
|
|
if (Vcb2) {
|
|
PIO_WORKITEM work_item;
|
|
|
|
work_item = IoAllocateWorkItem(DeviceObject);
|
|
if (!work_item) {
|
|
ERR("out of memory\n");
|
|
} else
|
|
IoQueueWorkItem(work_item, check_after_wakeup, DelayedWorkQueue, Vcb2);
|
|
}
|
|
}
|
|
|
|
PoStartNextPowerIrp(Irp);
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
Status = PoCallDriver(vde->attached_device, Irp);
|
|
|
|
goto exit;
|
|
} else if (Vcb && Vcb->type == VCB_TYPE_FS) {
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
|
|
Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);
|
|
|
|
goto exit;
|
|
} else if (Vcb && Vcb->type == VCB_TYPE_BUS) {
|
|
bus_device_extension* bde = DeviceObject->DeviceExtension;
|
|
|
|
PoStartNextPowerIrp(Irp);
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
Status = PoCallDriver(bde->attached_device, Irp);
|
|
|
|
goto exit;
|
|
}
|
|
|
|
if (IrpSp->MinorFunction == IRP_MN_SET_POWER || IrpSp->MinorFunction == IRP_MN_QUERY_POWER)
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
Status = Irp->IoStatus.Status;
|
|
|
|
PoStartNextPowerIrp(Irp);
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
exit:
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
static NTSTATUS __stdcall 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->attached_device, Irp);
|
|
|
|
goto exit;
|
|
} else if (Vcb && Vcb->type == VCB_TYPE_FS) {
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
|
|
Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);
|
|
|
|
goto exit;
|
|
} else if (Vcb && Vcb->type == VCB_TYPE_BUS) {
|
|
bus_device_extension* bde = DeviceObject->DeviceExtension;
|
|
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
|
|
Status = IoCallDriver(bde->attached_device, 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, _In_ bool stream) {
|
|
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] == ':')) ||
|
|
(!posix && !stream && (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_t start, _In_ uint64_t 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_t start, _In_ uint64_t 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 __stdcall serial_thread(void* context) {
|
|
LARGE_INTEGER due_time;
|
|
KTIMER timer;
|
|
|
|
UNUSED(context);
|
|
|
|
KeInitializeTimer(&timer);
|
|
|
|
due_time.QuadPart = (uint64_t)-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 %08lx\n", Status);
|
|
|
|
if (first_time) {
|
|
OBJECT_ATTRIBUTES oa;
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
|
|
Status = PsCreateSystemThread(&serial_thread_handle, 0, &oa, NULL, NULL, serial_thread, NULL);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("PsCreateSystemThread returned %08lx\n", Status);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !defined(__REACTOS__) && (defined(_X86_) || defined(_AMD64_))
|
|
static void check_cpu() {
|
|
unsigned int cpuInfo[4];
|
|
bool have_sse42;
|
|
|
|
#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");
|
|
calc_crc32c = calc_crc32c_hw;
|
|
} 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 %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
if (iosb.Information == FILE_OPENED) { // already exists
|
|
FILE_STANDARD_INFORMATION fsi;
|
|
FILE_POSITION_INFORMATION fpi;
|
|
|
|
static const 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 %08lx\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 %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, (void*)delim, sizeof(delim) - 1, NULL, NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("ZwWriteFile returned %08lx\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 %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
end:
|
|
ExReleaseResourceLite(&log_lock);
|
|
}
|
|
#endif
|
|
|
|
_Function_class_(KSTART_ROUTINE)
|
|
static void __stdcall degraded_wait_thread(_In_ void* context) {
|
|
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);
|
|
}
|
|
|
|
_Function_class_(DRIVER_ADD_DEVICE)
|
|
NTSTATUS __stdcall AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject) {
|
|
LIST_ENTRY* le;
|
|
NTSTATUS Status;
|
|
UNICODE_STRING volname;
|
|
ULONG i;
|
|
WCHAR* s;
|
|
pdo_device_extension* pdode = NULL;
|
|
PDEVICE_OBJECT voldev;
|
|
volume_device_extension* vde;
|
|
UNICODE_STRING arc_name_us;
|
|
WCHAR* anp;
|
|
|
|
static const WCHAR arc_name_prefix[] = L"\\ArcName\\btrfs(";
|
|
|
|
WCHAR arc_name[(sizeof(arc_name_prefix) / sizeof(WCHAR)) - 1 + 37];
|
|
|
|
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;
|
|
}
|
|
|
|
ExAcquireResourceExclusiveLite(&pdode->child_lock, true);
|
|
|
|
if (pdode->vde) { // if already done, return success
|
|
Status = STATUS_SUCCESS;
|
|
goto end2;
|
|
}
|
|
|
|
volname.Length = volname.MaximumLength = (sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) + ((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, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR));
|
|
RtlCopyMemory(arc_name, arc_name_prefix, sizeof(arc_name_prefix) - sizeof(WCHAR));
|
|
|
|
anp = &arc_name[(sizeof(arc_name_prefix) / sizeof(WCHAR)) - 1];
|
|
s = &volname.Buffer[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1];
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
*s = *anp = hex_digit(pdode->uuid.uuid[i] >> 4);
|
|
s++;
|
|
anp++;
|
|
|
|
*s = *anp = hex_digit(pdode->uuid.uuid[i] & 0xf);
|
|
s++;
|
|
anp++;
|
|
|
|
if (i == 3 || i == 5 || i == 7 || i == 9) {
|
|
*s = *anp = '-';
|
|
s++;
|
|
anp++;
|
|
}
|
|
}
|
|
|
|
*s = '}';
|
|
*anp = ')';
|
|
|
|
Status = IoCreateDevice(drvobj, sizeof(volume_device_extension), &volname, FILE_DEVICE_DISK,
|
|
WdmlibRtlIsNtDdiVersionAvailable(NTDDI_WIN8) ? FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL : 0, false, &voldev);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("IoCreateDevice returned %08lx\n", Status);
|
|
goto end2;
|
|
}
|
|
|
|
arc_name_us.Buffer = arc_name;
|
|
arc_name_us.Length = arc_name_us.MaximumLength = sizeof(arc_name);
|
|
|
|
Status = IoCreateSymbolicLink(&arc_name_us, &volname);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("IoCreateSymbolicLink returned %08lx\n", Status);
|
|
|
|
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->dead = false;
|
|
vde->open_count = 0;
|
|
|
|
Status = IoRegisterDeviceInterface(PhysicalDeviceObject, &GUID_DEVINTERFACE_VOLUME, NULL, &vde->bus_name);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("IoRegisterDeviceInterface returned %08lx\n", Status);
|
|
|
|
vde->attached_device = IoAttachDeviceToDeviceStack(voldev, PhysicalDeviceObject);
|
|
|
|
pdode->vde = vde;
|
|
|
|
if (pdode->removable)
|
|
voldev->Characteristics |= FILE_REMOVABLE_MEDIA;
|
|
|
|
if (RtlCompareMemory(&boot_uuid, &pdode->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {
|
|
voldev->Flags |= DO_SYSTEM_BOOT_PARTITION;
|
|
PhysicalDeviceObject->Flags |= DO_SYSTEM_BOOT_PARTITION;
|
|
}
|
|
|
|
voldev->Flags &= ~DO_DEVICE_INITIALIZING;
|
|
|
|
Status = IoSetDeviceInterfaceState(&vde->bus_name, true);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("IoSetDeviceInterfaceState returned %08lx\n", Status);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end2:
|
|
ExReleaseResourceLite(&pdode->child_lock);
|
|
|
|
end:
|
|
ExReleaseResourceLite(&pdo_list_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Function_class_(DRIVER_INITIALIZE)
|
|
NTSTATUS __stdcall DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
|
|
NTSTATUS Status;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
UNICODE_STRING device_nameW;
|
|
UNICODE_STRING dosdevice_nameW;
|
|
control_device_extension* cde;
|
|
bus_device_extension* bde;
|
|
HANDLE regh;
|
|
OBJECT_ATTRIBUTES oa, system_thread_attributes;
|
|
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(®istry_path, false);
|
|
|
|
#ifdef _DEBUG
|
|
if (debug_log_level > 0)
|
|
init_logging();
|
|
|
|
log_started = true;
|
|
#endif
|
|
|
|
TRACE("DriverEntry\n");
|
|
|
|
#if !defined(__REACTOS__) && (defined(_X86_) || defined(_AMD64_))
|
|
check_cpu();
|
|
#endif
|
|
|
|
if (WdmlibRtlIsNtDdiVersionAvailable(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);
|
|
|
|
RtlInitUnicodeString(&name, L"FsRtlCheckLockForOplockRequest");
|
|
fFsRtlCheckLockForOplockRequest = (tFsRtlCheckLockForOplockRequest)MmGetSystemRoutineAddress(&name);
|
|
} else {
|
|
fPsUpdateDiskCounters = NULL;
|
|
fCcCopyReadEx = NULL;
|
|
fCcCopyWriteEx = NULL;
|
|
fCcSetAdditionalCacheAttributesEx = NULL;
|
|
fFsRtlUpdateDiskCounters = NULL;
|
|
fFsRtlCheckLockForOplockRequest = NULL;
|
|
}
|
|
|
|
if (WdmlibRtlIsNtDdiVersionAvailable(NTDDI_WIN7)) {
|
|
UNICODE_STRING name;
|
|
|
|
RtlInitUnicodeString(&name, L"IoUnregisterPlugPlayNotificationEx");
|
|
fIoUnregisterPlugPlayNotificationEx = (tIoUnregisterPlugPlayNotificationEx)MmGetSystemRoutineAddress(&name);
|
|
|
|
RtlInitUnicodeString(&name, L"FsRtlAreThereCurrentOrInProgressFileLocks");
|
|
fFsRtlAreThereCurrentOrInProgressFileLocks = (tFsRtlAreThereCurrentOrInProgressFileLocks)MmGetSystemRoutineAddress(&name);
|
|
} else {
|
|
fIoUnregisterPlugPlayNotificationEx = NULL;
|
|
fFsRtlAreThereCurrentOrInProgressFileLocks = NULL;
|
|
}
|
|
|
|
if (WdmlibRtlIsNtDdiVersionAvailable(NTDDI_VISTA)) {
|
|
UNICODE_STRING name;
|
|
|
|
RtlInitUnicodeString(&name, L"FsRtlGetEcpListFromIrp");
|
|
fFsRtlGetEcpListFromIrp = (tFsRtlGetEcpListFromIrp)MmGetSystemRoutineAddress(&name);
|
|
|
|
RtlInitUnicodeString(&name, L"FsRtlGetNextExtraCreateParameter");
|
|
fFsRtlGetNextExtraCreateParameter = (tFsRtlGetNextExtraCreateParameter)MmGetSystemRoutineAddress(&name);
|
|
|
|
RtlInitUnicodeString(&name, L"FsRtlValidateReparsePointBuffer");
|
|
fFsRtlValidateReparsePointBuffer = (tFsRtlValidateReparsePointBuffer)MmGetSystemRoutineAddress(&name);
|
|
} else {
|
|
fFsRtlGetEcpListFromIrp = NULL;
|
|
fFsRtlGetNextExtraCreateParameter = NULL;
|
|
fFsRtlValidateReparsePointBuffer = compat_FsRtlValidateReparsePointBuffer;
|
|
}
|
|
|
|
drvobj = DriverObject;
|
|
|
|
DriverObject->DriverUnload = DriverUnload;
|
|
|
|
DriverObject->DriverExtension->AddDevice = AddDevice;
|
|
|
|
DriverObject->MajorFunction[IRP_MJ_CREATE] = drv_create;
|
|
DriverObject->MajorFunction[IRP_MJ_CLOSE] = drv_close;
|
|
DriverObject->MajorFunction[IRP_MJ_READ] = drv_read;
|
|
DriverObject->MajorFunction[IRP_MJ_WRITE] = drv_write;
|
|
DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = drv_query_information;
|
|
DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = drv_set_information;
|
|
DriverObject->MajorFunction[IRP_MJ_QUERY_EA] = drv_query_ea;
|
|
DriverObject->MajorFunction[IRP_MJ_SET_EA] = drv_set_ea;
|
|
DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = drv_flush_buffers;
|
|
DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = drv_query_volume_information;
|
|
DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = drv_set_volume_information;
|
|
DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = drv_directory_control;
|
|
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = drv_file_system_control;
|
|
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = drv_device_control;
|
|
DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = drv_shutdown;
|
|
DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL] = drv_lock_control;
|
|
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = drv_cleanup;
|
|
DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY] = drv_query_security;
|
|
DriverObject->MajorFunction[IRP_MJ_SET_SECURITY] = drv_set_security;
|
|
DriverObject->MajorFunction[IRP_MJ_POWER] = drv_power;
|
|
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = drv_system_control;
|
|
DriverObject->MajorFunction[IRP_MJ_PNP] = drv_pnp;
|
|
|
|
init_fast_io_dispatch(&DriverObject->FastIoDispatch);
|
|
|
|
device_nameW.Buffer = (WCHAR*)device_name;
|
|
device_nameW.Length = device_nameW.MaximumLength = sizeof(device_name) - sizeof(WCHAR);
|
|
dosdevice_nameW.Buffer = (WCHAR*)dosdevice_name;
|
|
dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = sizeof(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 %08lx\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 %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
init_cache();
|
|
|
|
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(®h, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("ZwCreateKey returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
watch_registry(regh);
|
|
|
|
Status = IoCreateDevice(DriverObject, sizeof(bus_device_extension), NULL, FILE_DEVICE_UNKNOWN,
|
|
FILE_DEVICE_SECURE_OPEN, false, &busobj);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("IoCreateDevice returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
bde = (bus_device_extension*)busobj->DeviceExtension;
|
|
|
|
RtlZeroMemory(bde, sizeof(bus_device_extension));
|
|
|
|
bde->type = VCB_TYPE_BUS;
|
|
|
|
Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF,
|
|
NULL, NULL, 0, &bde->buspdo);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("IoReportDetectedDevice returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = IoRegisterDeviceInterface(bde->buspdo, &BtrfsBusInterface, NULL, &bde->bus_name);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("IoRegisterDeviceInterface returned %08lx\n", Status);
|
|
|
|
bde->attached_device = IoAttachDeviceToDeviceStack(busobj, bde->buspdo);
|
|
|
|
busobj->Flags &= ~DO_DEVICE_INITIALIZING;
|
|
|
|
Status = IoSetDeviceInterfaceState(&bde->bus_name, true);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("IoSetDeviceInterfaceState returned %08lx\n", Status);
|
|
|
|
IoInvalidateDeviceRelations(bde->buspdo, BusRelations);
|
|
|
|
InitializeObjectAttributes(&system_thread_attributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
|
|
Status = PsCreateSystemThread(°raded_wait_handle, 0, &system_thread_attributes, NULL, NULL, degraded_wait_thread, NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("PsCreateSystemThread returned %08lx\n", Status);
|
|
|
|
ExInitializeResourceLite(&boot_lock);
|
|
|
|
Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
|
|
(PVOID)&GUID_DEVINTERFACE_VOLUME, DriverObject, volume_notification, DriverObject, ¬ification_entry2);
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status);
|
|
|
|
Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
|
|
(PVOID)&GUID_DEVINTERFACE_HIDDEN_VOLUME, DriverObject, volume_notification, DriverObject, ¬ification_entry3);
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status);
|
|
|
|
Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
|
|
(PVOID)&GUID_DEVINTERFACE_DISK, DriverObject, pnp_notification, DriverObject, ¬ification_entry);
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status);
|
|
|
|
finished_probing = true;
|
|
|
|
KeInitializeEvent(&mountmgr_thread_event, NotificationEvent, false);
|
|
|
|
Status = PsCreateSystemThread(&mountmgr_thread_handle, 0, &system_thread_attributes, NULL, NULL, mountmgr_thread, NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("PsCreateSystemThread returned %08lx\n", Status);
|
|
|
|
IoRegisterFileSystem(DeviceObject);
|
|
|
|
IoRegisterBootDriverReinitialization(DriverObject, check_system_root, NULL);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|