[PARTMGR] Trigger mount points removal when a partition/volume is deleted (#7034)

CORE-18139

When a partition is created, PartMgr notifies the volume manager
(FTDisk on Windows <= 2003, VolMgr on Vista+) of its presence.
(Note that currently in ReactOS, our partmgr does the job of both
PartMgr *AND* VolMgr.)
The VolMgr then sends a `GUID_DEVINTERFACE_VOLUME` PnP notification,
which is handled by the mount manager (MountMgr) as part of a
`GUID_DEVICE_INTERFACE_ARRIVAL` notification:
```
MountMgr!MountMgrMountedDeviceNotification -> MountMgrMountedDeviceArrival
followed by
MountMgr!MountMgrTargetDeviceNotification
```

When a partition is deleted, via e.g. Disk Management or DiskPart,
it can be observed, on Windows, that the PartMgr gets notified by
the PnP manager, as part of QueryDeviceRelations. Before actually
removing the partition, it notifies the VolMgr. The latter invalidates
any volume mounted on that partition (`*PartitionRemoved*` functions
for basic volumes), then requests (`*DeleteMountPoints` function)
the MountMgr to delete all the mount points associated to the volume:
```
VolMgr!*DeleteMountPoints
-> MountMgr!MountMgrDeviceControl -> MountMgrDeletePoints
```
**** THIS is the new functionality that is implemented for ReactOS ****
**** in the present commit.                                        ****

Following this, a subsequent PnP notification is sent, which calls
```
MountMgr!MountMgrTargetDeviceNotification
-> MountMgr!MountMgrMountedDeviceRemoval
```
(Note that this observation somewhat invalidates the modification
made in ReactOS commit 62a4f9d42b : our MountMgr placed in Windows
*WOULD* receive a `GUID_TARGET_DEVICE_REMOVE_COMPLETE` target-device
notification...)

Finally, a `GUID_DEVICE_INTERFACE_REMOVAL` PnP notification is sent
to the MountMgr:
```
MountMgr!MountMgrMountedDeviceNotification
-> MountMgr!MountMgrMountedDeviceRemoval
```
This commit is contained in:
Hermès Bélusca-Maïto 2024-06-18 22:12:27 +02:00
parent 465b9cef25
commit 9e07d0cc74
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0

View file

@ -122,15 +122,15 @@ PartitionHandleStartDevice(
INFO("Symlink created %wZ -> %wZ\n", &partitionSymlink, &PartExt->DeviceName);
// our partition device will have two interfaces:
// Our partition device will have two interfaces:
// GUID_DEVINTERFACE_PARTITION and GUID_DEVINTERFACE_VOLUME
// the former one is used to notify mountmgr about new device
// (aka. MOUNTDEV_MOUNTED_DEVICE_GUID).
// The latter one is used to notify MountMgr about the new volume.
status = IoRegisterDeviceInterface(PartExt->DeviceObject,
&GUID_DEVINTERFACE_PARTITION,
NULL,
&interfaceName);
if (!NT_SUCCESS(status))
{
return status;
@ -152,7 +152,6 @@ PartitionHandleStartDevice(
&GUID_DEVINTERFACE_VOLUME,
NULL,
&interfaceName);
if (!NT_SUCCESS(status))
{
return status;
@ -173,6 +172,105 @@ PartitionHandleStartDevice(
return STATUS_SUCCESS;
}
/**
* @brief
* Notifies MountMgr to delete all mount points
* associated with the given volume.
*
* @note This should belong to volmgr.sys and act on a PVOLUME_EXTENSION.
**/
static
CODE_SEG("PAGE")
NTSTATUS
VolumeDeleteMountPoints(
_In_ PPARTITION_EXTENSION PartExt)
{
NTSTATUS Status;
UNICODE_STRING MountMgr;
ULONG InputSize, OutputSize;
LOGICAL Retry;
PUNICODE_STRING DeviceName;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject = NULL;
PMOUNTMGR_MOUNT_POINT InputBuffer = NULL;
PMOUNTMGR_MOUNT_POINTS OutputBuffer = NULL;
PAGED_CODE();
/* Get the device pointer to the MountMgr */
RtlInitUnicodeString(&MountMgr, MOUNTMGR_DEVICE_NAME);
Status = IoGetDeviceObjectPointer(&MountMgr,
FILE_READ_ATTRIBUTES,
&FileObject,
&DeviceObject);
if (!NT_SUCCESS(Status))
return Status;
/* Setup the volume device name for deleting its mount points */
DeviceName = &PartExt->DeviceName;
/* Allocate the input buffer */
InputSize = sizeof(*InputBuffer) + DeviceName->Length;
InputBuffer = ExAllocatePoolWithTag(PagedPool, InputSize, TAG_PARTMGR);
if (!InputBuffer)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Quit;
}
/* Fill it in */
RtlZeroMemory(InputBuffer, sizeof(*InputBuffer));
InputBuffer->DeviceNameOffset = sizeof(*InputBuffer);
InputBuffer->DeviceNameLength = DeviceName->Length;
RtlCopyMemory(&InputBuffer[1], DeviceName->Buffer, DeviceName->Length);
/*
* IOCTL_MOUNTMGR_DELETE_POINTS needs a large-enough scratch output buffer
* to work with. (It uses it to query the mount points, before deleting
* them.) Start with a guessed size and call the IOCTL. If the buffer is
* not big enough, use the value retrieved in MOUNTMGR_MOUNT_POINTS::Size
* to re-allocate a larger buffer and call the IOCTL once more.
*/
OutputSize = max(PAGE_SIZE, sizeof(*OutputBuffer));
for (Retry = 0; Retry < 2; ++Retry)
{
OutputBuffer = ExAllocatePoolWithTag(PagedPool, OutputSize, TAG_PARTMGR);
if (!OutputBuffer)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
/* Call the MountMgr to delete the drive letter */
Status = IssueSyncIoControlRequest(IOCTL_MOUNTMGR_DELETE_POINTS,
DeviceObject,
InputBuffer,
InputSize,
OutputBuffer,
OutputSize,
FALSE);
/* Adjust the allocation size if it was too small */
if (Status == STATUS_BUFFER_OVERFLOW)
{
OutputSize = OutputBuffer->Size;
ExFreePoolWithTag(OutputBuffer, TAG_PARTMGR);
continue;
}
/* Success or failure: stop the loop */
break;
}
Quit:
if (OutputBuffer)
ExFreePoolWithTag(OutputBuffer, TAG_PARTMGR);
if (InputBuffer)
ExFreePoolWithTag(InputBuffer, TAG_PARTMGR);
if (FileObject)
ObDereferenceObject(FileObject);
return Status;
}
CODE_SEG("PAGE")
NTSTATUS
PartitionHandleRemove(
@ -220,6 +318,19 @@ PartitionHandleRemove(
if (PartExt->VolumeInterfaceName.Buffer)
{
/* Notify MountMgr to delete all associated mount points.
* MountMgr does not automatically remove these in order to support
* drive letter persistence for online/offline volume transitions,
* or volumes arrival/removal on removable devices. */
status = VolumeDeleteMountPoints(PartExt);
if (!NT_SUCCESS(status))
{
ERR("VolumeDeleteMountPoints(%wZ) failed with status 0x%08lx\n",
&PartExt->DeviceName, status);
/* Failure isn't major, continue proceeding with volume removal */
}
/* Notify MountMgr of volume removal */
status = IoSetDeviceInterfaceState(&PartExt->VolumeInterfaceName, FALSE);
if (!NT_SUCCESS(status))
{