reactos/drivers/filesystems/btrfs/pnp.c

562 lines
15 KiB
C

/* Copyright (c) Mark Harmstone 2016-17
*
* This file is part of WinBtrfs.
*
* WinBtrfs is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public Licence as published by
* the Free Software Foundation, either version 3 of the Licence, or
* (at your option) any later version.
*
* WinBtrfs is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public Licence for more details.
*
* You should have received a copy of the GNU Lesser General Public Licence
* along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */
#include "btrfs_drv.h"
struct pnp_context;
typedef struct {
struct pnp_context* context;
PIRP Irp;
IO_STATUS_BLOCK iosb;
NTSTATUS Status;
device* dev;
} pnp_stripe;
typedef struct {
KEVENT Event;
NTSTATUS Status;
LONG left;
pnp_stripe* stripes;
} pnp_context;
extern ERESOURCE pdo_list_lock;
extern LIST_ENTRY pdo_list;
_Function_class_(IO_COMPLETION_ROUTINE)
#ifdef __REACTOS__
static NTSTATUS NTAPI pnp_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
#else
static NTSTATUS pnp_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {
#endif
pnp_stripe* stripe = conptr;
pnp_context* context = (pnp_context*)stripe->context;
UNUSED(DeviceObject);
stripe->Status = Irp->IoStatus.Status;
InterlockedDecrement(&context->left);
if (context->left == 0)
KeSetEvent(&context->Event, 0, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
static NTSTATUS send_disks_pnp_message(device_extension* Vcb, UCHAR minor) {
pnp_context context;
ULONG num_devices, i;
NTSTATUS Status;
LIST_ENTRY* le;
RtlZeroMemory(&context, sizeof(pnp_context));
KeInitializeEvent(&context.Event, NotificationEvent, FALSE);
num_devices = (ULONG)min(0xffffffff, Vcb->superblock.num_devices);
context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(pnp_stripe) * num_devices, ALLOC_TAG);
if (!context.stripes) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(context.stripes, sizeof(pnp_stripe) * num_devices);
i = 0;
le = Vcb->devices.Flink;
while (le != &Vcb->devices) {
PIO_STACK_LOCATION IrpSp;
device* dev = CONTAINING_RECORD(le, device, list_entry);
if (dev->devobj) {
context.stripes[i].context = (struct pnp_context*)&context;
context.stripes[i].Irp = IoAllocateIrp(dev->devobj->StackSize, FALSE);
if (!context.stripes[i].Irp) {
UINT64 j;
ERR("IoAllocateIrp failed\n");
for (j = 0; j < i; j++) {
if (context.stripes[j].dev->devobj) {
IoFreeIrp(context.stripes[j].Irp);
}
}
ExFreePool(context.stripes);
return STATUS_INSUFFICIENT_RESOURCES;
}
IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp);
IrpSp->MajorFunction = IRP_MJ_PNP;
IrpSp->MinorFunction = minor;
context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb;
IoSetCompletionRoutine(context.stripes[i].Irp, pnp_completion, &context.stripes[i], TRUE, TRUE, TRUE);
context.stripes[i].Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
context.stripes[i].dev = dev;
context.left++;
}
le = le->Flink;
}
if (context.left == 0) {
Status = STATUS_SUCCESS;
goto end;
}
for (i = 0; i < num_devices; i++) {
if (context.stripes[i].Irp) {
IoCallDriver(context.stripes[i].dev->devobj, context.stripes[i].Irp);
}
}
KeWaitForSingleObject(&context.Event, Executive, KernelMode, FALSE, NULL);
Status = STATUS_SUCCESS;
for (i = 0; i < num_devices; i++) {
if (context.stripes[i].Irp) {
if (context.stripes[i].Status != STATUS_SUCCESS)
Status = context.stripes[i].Status;
}
}
end:
for (i = 0; i < num_devices; i++) {
if (context.stripes[i].Irp) {
IoFreeIrp(context.stripes[i].Irp);
}
}
ExFreePool(context.stripes);
return Status;
}
static NTSTATUS pnp_cancel_remove_device(PDEVICE_OBJECT DeviceObject) {
device_extension* Vcb = DeviceObject->DeviceExtension;
NTSTATUS Status;
ExAcquireResourceSharedLite(&Vcb->tree_lock, TRUE);
ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->open_count > 0 || has_open_children(Vcb->root_fileref))) {
Status = STATUS_ACCESS_DENIED;
goto end;
}
Status = send_disks_pnp_message(Vcb, IRP_MN_CANCEL_REMOVE_DEVICE);
if (!NT_SUCCESS(Status)) {
WARN("send_disks_pnp_message returned %08x\n", Status);
goto end;
}
end:
ExReleaseResourceLite(&Vcb->fcb_lock);
ExReleaseResourceLite(&Vcb->tree_lock);
return STATUS_SUCCESS;
}
NTSTATUS pnp_query_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
device_extension* Vcb = DeviceObject->DeviceExtension;
NTSTATUS Status;
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, TRUE);
if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->open_count > 0 || has_open_children(Vcb->root_fileref))) {
Status = STATUS_ACCESS_DENIED;
goto end;
}
Status = send_disks_pnp_message(Vcb, IRP_MN_QUERY_REMOVE_DEVICE);
if (!NT_SUCCESS(Status)) {
WARN("send_disks_pnp_message returned %08x\n", Status);
goto end;
}
Vcb->removing = TRUE;
if (Vcb->need_write && !Vcb->readonly) {
Status = do_write(Vcb, Irp);
free_trees(Vcb);
if (!NT_SUCCESS(Status)) {
ERR("do_write returned %08x\n", Status);
goto end;
}
}
Status = STATUS_SUCCESS;
end:
ExReleaseResourceLite(&Vcb->fcb_lock);
ExReleaseResourceLite(&Vcb->tree_lock);
return Status;
}
static NTSTATUS pnp_remove_device(PDEVICE_OBJECT DeviceObject) {
device_extension* Vcb = DeviceObject->DeviceExtension;
NTSTATUS Status;
ExAcquireResourceSharedLite(&Vcb->tree_lock, TRUE);
Status = send_disks_pnp_message(Vcb, IRP_MN_REMOVE_DEVICE);
if (!NT_SUCCESS(Status))
WARN("send_disks_pnp_message returned %08x\n", Status);
ExReleaseResourceLite(&Vcb->tree_lock);
if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {
Status = FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_DISMOUNT);
if (!NT_SUCCESS(Status)) {
WARN("FsRtlNotifyVolumeEvent returned %08x\n", Status);
}
if (Vcb->vde)
Vcb->vde->mounted_device = NULL;
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
Vcb->removing = TRUE;
Vcb->Vpb->Flags &= ~VPB_MOUNTED;
Vcb->Vpb->Flags |= VPB_DIRECT_WRITES_ALLOWED;
ExReleaseResourceLite(&Vcb->tree_lock);
if (Vcb->open_files == 0)
uninit(Vcb, FALSE);
}
return STATUS_SUCCESS;
}
NTSTATUS pnp_surprise_removal(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
device_extension* Vcb = DeviceObject->DeviceExtension;
TRACE("(%p, %p)\n", DeviceObject, Irp);
if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {
ExAcquireResourceExclusiveLite(&Vcb->tree_lock, TRUE);
if (Vcb->vde)
Vcb->vde->mounted_device = NULL;
Vcb->removing = TRUE;
Vcb->Vpb->Flags &= ~VPB_MOUNTED;
Vcb->Vpb->Flags |= VPB_DIRECT_WRITES_ALLOWED;
ExReleaseResourceLite(&Vcb->tree_lock);
if (Vcb->open_files == 0)
uninit(Vcb, FALSE);
}
return STATUS_SUCCESS;
}
static void bus_query_capabilities(PIRP Irp) {
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_CAPABILITIES dc = IrpSp->Parameters.DeviceCapabilities.Capabilities;
dc->UniqueID = TRUE;
dc->SilentInstall = TRUE;
Irp->IoStatus.Status = STATUS_SUCCESS;
}
static NTSTATUS bus_query_device_relations(PIRP Irp) {
NTSTATUS Status;
ULONG num_children;
LIST_ENTRY* le;
ULONG drsize, i;
DEVICE_RELATIONS* dr;
ExAcquireResourceSharedLite(&pdo_list_lock, TRUE);
num_children = 0;
le = pdo_list.Flink;
while (le != &pdo_list) {
num_children++;
le = le->Flink;
}
drsize = offsetof(DEVICE_RELATIONS, Objects[0]) + (num_children * sizeof(PDEVICE_OBJECT));
dr = ExAllocatePoolWithTag(PagedPool, drsize, ALLOC_TAG);
if (!dr) {
ERR("out of memory\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
dr->Count = num_children;
i = 0;
le = pdo_list.Flink;
while (le != &pdo_list) {
pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);
ObReferenceObject(pdode->pdo);
dr->Objects[i] = pdode->pdo;
i++;
le = le->Flink;
}
Irp->IoStatus.Information = (ULONG_PTR)dr;
Status = STATUS_SUCCESS;
end:
ExReleaseResourceLite(&pdo_list_lock);
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
static NTSTATUS bus_query_hardware_ids(PIRP Irp) {
WCHAR* out;
static WCHAR ids[] = L"ROOT\\btrfs\0";
out = ExAllocatePoolWithTag(PagedPool, sizeof(ids), ALLOC_TAG);
if (!out) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(out, ids, sizeof(ids));
Irp->IoStatus.Information = (ULONG_PTR)out;
return STATUS_SUCCESS;
}
static NTSTATUS bus_pnp(control_device_extension* cde, PIRP Irp) {
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
switch (IrpSp->MinorFunction) {
case IRP_MN_QUERY_CAPABILITIES:
bus_query_capabilities(Irp);
break;
case IRP_MN_QUERY_DEVICE_RELATIONS:
if (IrpSp->Parameters.QueryDeviceRelations.Type != BusRelations || no_pnp)
break;
return bus_query_device_relations(Irp);
case IRP_MN_QUERY_ID:
{
NTSTATUS Status;
if (IrpSp->Parameters.QueryId.IdType != BusQueryHardwareIDs)
break;
Status = bus_query_hardware_ids(Irp);
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
}
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(cde->attached_device, Irp);
}
static NTSTATUS pdo_query_device_id(pdo_device_extension* pdode, PIRP Irp) {
WCHAR name[100], *noff, *out;
int i;
static WCHAR pref[] = L"Btrfs\\";
RtlCopyMemory(name, pref, wcslen(pref) * sizeof(WCHAR));
noff = &name[wcslen(pref)];
for (i = 0; i < 16; i++) {
*noff = hex_digit(pdode->uuid.uuid[i] >> 4); noff++;
*noff = hex_digit(pdode->uuid.uuid[i] & 0xf); noff++;
if (i == 3 || i == 5 || i == 7 || i == 9) {
*noff = '-';
noff++;
}
}
*noff = 0;
out = ExAllocatePoolWithTag(PagedPool, (wcslen(name) + 1) * sizeof(WCHAR), ALLOC_TAG);
if (!out) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(out, name, (wcslen(name) + 1) * sizeof(WCHAR));
Irp->IoStatus.Information = (ULONG_PTR)out;
return STATUS_SUCCESS;
}
static NTSTATUS pdo_query_hardware_ids(PIRP Irp) {
WCHAR* out;
static WCHAR ids[] = L"BtrfsVolume\0";
out = ExAllocatePoolWithTag(PagedPool, sizeof(ids), ALLOC_TAG);
if (!out) {
ERR("out of memory\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(out, ids, sizeof(ids));
Irp->IoStatus.Information = (ULONG_PTR)out;
return STATUS_SUCCESS;
}
static NTSTATUS pdo_query_id(pdo_device_extension* pdode, PIRP Irp) {
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
switch (IrpSp->Parameters.QueryId.IdType) {
case BusQueryDeviceID:
TRACE("BusQueryDeviceID\n");
return pdo_query_device_id(pdode, Irp);
case BusQueryHardwareIDs:
TRACE("BusQueryHardwareIDs\n");
return pdo_query_hardware_ids(Irp);
default:
break;
}
return Irp->IoStatus.Status;
}
static NTSTATUS pdo_pnp(PDEVICE_OBJECT pdo, PIRP Irp) {
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
pdo_device_extension* pdode = pdo->DeviceExtension;
switch (IrpSp->MinorFunction) {
case IRP_MN_QUERY_ID:
return pdo_query_id(pdode, Irp);
case IRP_MN_START_DEVICE:
case IRP_MN_CANCEL_REMOVE_DEVICE:
case IRP_MN_SURPRISE_REMOVAL:
case IRP_MN_REMOVE_DEVICE:
return STATUS_SUCCESS;
case IRP_MN_QUERY_REMOVE_DEVICE:
return STATUS_UNSUCCESSFUL;
}
return Irp->IoStatus.Status;
}
_Dispatch_type_(IRP_MJ_PNP)
_Function_class_(DRIVER_DISPATCH)
NTSTATUS drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
device_extension* Vcb = DeviceObject->DeviceExtension;
NTSTATUS Status;
BOOL top_level;
FsRtlEnterFileSystem();
top_level = is_top_level(Irp);
if (Vcb && Vcb->type == VCB_TYPE_CONTROL) {
Status = bus_pnp(DeviceObject->DeviceExtension, Irp);
goto exit;
} else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
volume_device_extension* vde = DeviceObject->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
Status = IoCallDriver(vde->pdo, Irp);
goto exit;
} else if (Vcb && Vcb->type == VCB_TYPE_PDO) {
Status = pdo_pnp(DeviceObject, Irp);
goto end;
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
Status = STATUS_INVALID_PARAMETER;
goto end;
}
Status = STATUS_NOT_IMPLEMENTED;
switch (IrpSp->MinorFunction) {
case IRP_MN_CANCEL_REMOVE_DEVICE:
Status = pnp_cancel_remove_device(DeviceObject);
break;
case IRP_MN_QUERY_REMOVE_DEVICE:
Status = pnp_query_remove_device(DeviceObject, Irp);
break;
case IRP_MN_REMOVE_DEVICE:
Status = pnp_remove_device(DeviceObject);
break;
case IRP_MN_SURPRISE_REMOVAL:
Status = pnp_surprise_removal(DeviceObject, Irp);
break;
default:
TRACE("passing minor function 0x%x on\n", IrpSp->MinorFunction);
IoSkipCurrentIrpStackLocation(Irp);
Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);
goto exit;
}
end:
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
exit:
TRACE("returning %08x\n", Status);
if (top_level)
IoSetTopLevelIrp(NULL);
FsRtlExitFileSystem();
return Status;
}