mirror of
https://github.com/reactos/reactos.git
synced 2024-08-19 18:02:55 +00:00
- Add some more functions that just build IRPs to send to devices into iofunc.c from vpb.c
- Add IoCheckQuerySetvolumeInformation to util.c from vpb.c with other similar functions - Add the actual VPB functions in vpc.c to fs.c, and rename fs.c to volume.c, because it already contained Volume APIs, and now with VPB, even more so. It also still contains the FileSystem stuff, which is of course also generically part of "volumes". - Delete vpb.c, rename fs.c to volume.c. svn path=/trunk/; revision=22748
This commit is contained in:
parent
9bb57a0fc7
commit
3ea25ef417
|
@ -402,6 +402,86 @@ IoQueryFileInformation(IN PFILE_OBJECT FileObject,
|
||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @implemented
|
||||||
|
*/
|
||||||
|
NTSTATUS STDCALL
|
||||||
|
IoQueryVolumeInformation(IN PFILE_OBJECT FileObject,
|
||||||
|
IN FS_INFORMATION_CLASS FsInformationClass,
|
||||||
|
IN ULONG Length,
|
||||||
|
OUT PVOID FsInformation,
|
||||||
|
OUT PULONG ReturnedLength)
|
||||||
|
{
|
||||||
|
IO_STATUS_BLOCK IoStatusBlock;
|
||||||
|
PIO_STACK_LOCATION StackPtr;
|
||||||
|
PDEVICE_OBJECT DeviceObject;
|
||||||
|
PIRP Irp;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
ASSERT(FsInformation != NULL);
|
||||||
|
|
||||||
|
DPRINT("FsInformation %p\n", FsInformation);
|
||||||
|
|
||||||
|
Status = ObReferenceObjectByPointer(FileObject,
|
||||||
|
FILE_READ_ATTRIBUTES,
|
||||||
|
IoFileObjectType,
|
||||||
|
KernelMode);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
return(Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceObject = FileObject->DeviceObject;
|
||||||
|
|
||||||
|
Irp = IoAllocateIrp(DeviceObject->StackSize,
|
||||||
|
TRUE);
|
||||||
|
if (Irp == NULL)
|
||||||
|
{
|
||||||
|
ObDereferenceObject(FileObject);
|
||||||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trigger FileObject/Event dereferencing */
|
||||||
|
Irp->Tail.Overlay.OriginalFileObject = FileObject;
|
||||||
|
Irp->RequestorMode = KernelMode;
|
||||||
|
Irp->AssociatedIrp.SystemBuffer = FsInformation;
|
||||||
|
KeResetEvent( &FileObject->Event );
|
||||||
|
Irp->UserEvent = &FileObject->Event;
|
||||||
|
Irp->UserIosb = &IoStatusBlock;
|
||||||
|
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
||||||
|
|
||||||
|
StackPtr = IoGetNextIrpStackLocation(Irp);
|
||||||
|
StackPtr->MajorFunction = IRP_MJ_QUERY_VOLUME_INFORMATION;
|
||||||
|
StackPtr->MinorFunction = 0;
|
||||||
|
StackPtr->Flags = 0;
|
||||||
|
StackPtr->Control = 0;
|
||||||
|
StackPtr->DeviceObject = DeviceObject;
|
||||||
|
StackPtr->FileObject = FileObject;
|
||||||
|
StackPtr->Parameters.QueryVolume.Length = Length;
|
||||||
|
StackPtr->Parameters.QueryVolume.FsInformationClass =
|
||||||
|
FsInformationClass;
|
||||||
|
|
||||||
|
Status = IoCallDriver(DeviceObject,
|
||||||
|
Irp);
|
||||||
|
if (Status == STATUS_PENDING)
|
||||||
|
{
|
||||||
|
KeWaitForSingleObject(&FileObject->Event,
|
||||||
|
UserRequest,
|
||||||
|
KernelMode,
|
||||||
|
FALSE,
|
||||||
|
NULL);
|
||||||
|
Status = IoStatusBlock.Status;
|
||||||
|
}
|
||||||
|
DPRINT("Status %x\n", Status);
|
||||||
|
|
||||||
|
if (ReturnedLength != NULL)
|
||||||
|
{
|
||||||
|
*ReturnedLength = IoStatusBlock.Information;
|
||||||
|
}
|
||||||
|
|
||||||
|
return(Status);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @unimplemented
|
* @unimplemented
|
||||||
*/
|
*/
|
||||||
|
@ -2421,3 +2501,328 @@ NtWriteFileGather(IN HANDLE FileHandle,
|
||||||
return(STATUS_NOT_IMPLEMENTED);
|
return(STATUS_NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FUNCTION: Queries the volume information
|
||||||
|
* ARGUMENTS:
|
||||||
|
* FileHandle = Handle to a file object on the target volume
|
||||||
|
* ReturnLength = DataWritten
|
||||||
|
* FsInformation = Caller should supply storage for the information
|
||||||
|
* structure.
|
||||||
|
* Length = Size of the information structure
|
||||||
|
* FsInformationClass = Index to a information structure
|
||||||
|
*
|
||||||
|
* FileFsVolumeInformation FILE_FS_VOLUME_INFORMATION
|
||||||
|
* FileFsLabelInformation FILE_FS_LABEL_INFORMATION
|
||||||
|
* FileFsSizeInformation FILE_FS_SIZE_INFORMATION
|
||||||
|
* FileFsDeviceInformation FILE_FS_DEVICE_INFORMATION
|
||||||
|
* FileFsAttributeInformation FILE_FS_ATTRIBUTE_INFORMATION
|
||||||
|
* FileFsControlInformation
|
||||||
|
* FileFsQuotaQueryInformation --
|
||||||
|
* FileFsQuotaSetInformation --
|
||||||
|
* FileFsMaximumInformation
|
||||||
|
*
|
||||||
|
* RETURNS: Status
|
||||||
|
*
|
||||||
|
* @implemented
|
||||||
|
*/
|
||||||
|
|
||||||
|
NTSTATUS STDCALL
|
||||||
|
NtQueryVolumeInformationFile(IN HANDLE FileHandle,
|
||||||
|
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
||||||
|
OUT PVOID FsInformation,
|
||||||
|
IN ULONG Length,
|
||||||
|
IN FS_INFORMATION_CLASS FsInformationClass)
|
||||||
|
{
|
||||||
|
PFILE_OBJECT FileObject;
|
||||||
|
PDEVICE_OBJECT DeviceObject;
|
||||||
|
PIRP Irp;
|
||||||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||||||
|
PIO_STACK_LOCATION StackPtr;
|
||||||
|
PVOID SystemBuffer;
|
||||||
|
KPROCESSOR_MODE PreviousMode;
|
||||||
|
|
||||||
|
DPRINT("FsInformation %p\n", FsInformation);
|
||||||
|
|
||||||
|
PreviousMode = ExGetPreviousMode();
|
||||||
|
|
||||||
|
if (PreviousMode != KernelMode)
|
||||||
|
{
|
||||||
|
_SEH_TRY
|
||||||
|
{
|
||||||
|
if (IoStatusBlock != NULL)
|
||||||
|
{
|
||||||
|
ProbeForWrite(IoStatusBlock,
|
||||||
|
sizeof(IO_STATUS_BLOCK),
|
||||||
|
sizeof(ULONG));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Length != 0)
|
||||||
|
{
|
||||||
|
ProbeForWrite(FsInformation,
|
||||||
|
Length,
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_SEH_HANDLE
|
||||||
|
{
|
||||||
|
Status = _SEH_GetExceptionCode();
|
||||||
|
}
|
||||||
|
_SEH_END;
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ASSERT(IoStatusBlock != NULL);
|
||||||
|
ASSERT(FsInformation != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ObReferenceObjectByHandle(FileHandle,
|
||||||
|
0, /* FIXME - depends on the information class! */
|
||||||
|
IoFileObjectType,
|
||||||
|
PreviousMode,
|
||||||
|
(PVOID*)&FileObject,
|
||||||
|
NULL);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
return(Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceObject = FileObject->DeviceObject;
|
||||||
|
|
||||||
|
Irp = IoAllocateIrp(DeviceObject->StackSize,
|
||||||
|
TRUE);
|
||||||
|
if (Irp == NULL)
|
||||||
|
{
|
||||||
|
ObDereferenceObject(FileObject);
|
||||||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemBuffer = ExAllocatePoolWithTag(NonPagedPool,
|
||||||
|
Length,
|
||||||
|
TAG_SYSB);
|
||||||
|
if (SystemBuffer == NULL)
|
||||||
|
{
|
||||||
|
IoFreeIrp(Irp);
|
||||||
|
ObDereferenceObject(FileObject);
|
||||||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trigger FileObject/Event dereferencing */
|
||||||
|
Irp->Tail.Overlay.OriginalFileObject = FileObject;
|
||||||
|
|
||||||
|
Irp->RequestorMode = PreviousMode;
|
||||||
|
Irp->AssociatedIrp.SystemBuffer = SystemBuffer;
|
||||||
|
KeResetEvent( &FileObject->Event );
|
||||||
|
Irp->UserEvent = &FileObject->Event;
|
||||||
|
Irp->UserIosb = IoStatusBlock;
|
||||||
|
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
||||||
|
|
||||||
|
StackPtr = IoGetNextIrpStackLocation(Irp);
|
||||||
|
StackPtr->MajorFunction = IRP_MJ_QUERY_VOLUME_INFORMATION;
|
||||||
|
StackPtr->MinorFunction = 0;
|
||||||
|
StackPtr->Flags = 0;
|
||||||
|
StackPtr->Control = 0;
|
||||||
|
StackPtr->DeviceObject = DeviceObject;
|
||||||
|
StackPtr->FileObject = FileObject;
|
||||||
|
StackPtr->Parameters.QueryVolume.Length = Length;
|
||||||
|
StackPtr->Parameters.QueryVolume.FsInformationClass =
|
||||||
|
FsInformationClass;
|
||||||
|
|
||||||
|
Status = IoCallDriver(DeviceObject,
|
||||||
|
Irp);
|
||||||
|
if (Status == STATUS_PENDING)
|
||||||
|
{
|
||||||
|
KeWaitForSingleObject(&FileObject->Event,
|
||||||
|
UserRequest,
|
||||||
|
PreviousMode,
|
||||||
|
FALSE,
|
||||||
|
NULL);
|
||||||
|
Status = IoStatusBlock->Status;
|
||||||
|
}
|
||||||
|
DPRINT("Status %x\n", Status);
|
||||||
|
|
||||||
|
if (NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
_SEH_TRY
|
||||||
|
{
|
||||||
|
DPRINT("Information %lu\n", IoStatusBlock->Information);
|
||||||
|
RtlCopyMemory(FsInformation,
|
||||||
|
SystemBuffer,
|
||||||
|
IoStatusBlock->Information);
|
||||||
|
}
|
||||||
|
_SEH_HANDLE
|
||||||
|
{
|
||||||
|
Status = _SEH_GetExceptionCode();
|
||||||
|
}
|
||||||
|
_SEH_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExFreePool(SystemBuffer);
|
||||||
|
|
||||||
|
return(Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @implemented
|
||||||
|
*/
|
||||||
|
NTSTATUS STDCALL
|
||||||
|
NtSetVolumeInformationFile(IN HANDLE FileHandle,
|
||||||
|
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
||||||
|
IN PVOID FsInformation,
|
||||||
|
IN ULONG Length,
|
||||||
|
IN FS_INFORMATION_CLASS FsInformationClass)
|
||||||
|
{
|
||||||
|
PFILE_OBJECT FileObject;
|
||||||
|
PDEVICE_OBJECT DeviceObject;
|
||||||
|
PIRP Irp;
|
||||||
|
NTSTATUS Status;
|
||||||
|
PIO_STACK_LOCATION StackPtr;
|
||||||
|
PVOID SystemBuffer;
|
||||||
|
KPROCESSOR_MODE PreviousMode;
|
||||||
|
|
||||||
|
PreviousMode = ExGetPreviousMode();
|
||||||
|
|
||||||
|
if (PreviousMode != KernelMode)
|
||||||
|
{
|
||||||
|
Status = STATUS_SUCCESS;
|
||||||
|
_SEH_TRY
|
||||||
|
{
|
||||||
|
if (IoStatusBlock != NULL)
|
||||||
|
{
|
||||||
|
ProbeForWrite(IoStatusBlock,
|
||||||
|
sizeof(IO_STATUS_BLOCK),
|
||||||
|
sizeof(ULONG));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Length != 0)
|
||||||
|
{
|
||||||
|
ProbeForRead(FsInformation,
|
||||||
|
Length,
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_SEH_HANDLE
|
||||||
|
{
|
||||||
|
Status = _SEH_GetExceptionCode();
|
||||||
|
}
|
||||||
|
_SEH_END;
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ASSERT(IoStatusBlock != NULL);
|
||||||
|
ASSERT(FsInformation != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = ObReferenceObjectByHandle(FileHandle,
|
||||||
|
FILE_WRITE_ATTRIBUTES,
|
||||||
|
NULL,
|
||||||
|
PreviousMode,
|
||||||
|
(PVOID*)&FileObject,
|
||||||
|
NULL);
|
||||||
|
if (Status != STATUS_SUCCESS)
|
||||||
|
{
|
||||||
|
return(Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceObject = FileObject->DeviceObject;
|
||||||
|
|
||||||
|
Irp = IoAllocateIrp(DeviceObject->StackSize,TRUE);
|
||||||
|
if (Irp == NULL)
|
||||||
|
{
|
||||||
|
ObDereferenceObject(FileObject);
|
||||||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemBuffer = ExAllocatePoolWithTag(NonPagedPool,
|
||||||
|
Length,
|
||||||
|
TAG_SYSB);
|
||||||
|
if (SystemBuffer == NULL)
|
||||||
|
{
|
||||||
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
goto failfreeirp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreviousMode != KernelMode)
|
||||||
|
{
|
||||||
|
_SEH_TRY
|
||||||
|
{
|
||||||
|
/* no need to probe again */
|
||||||
|
RtlCopyMemory(SystemBuffer,
|
||||||
|
FsInformation,
|
||||||
|
Length);
|
||||||
|
}
|
||||||
|
_SEH_HANDLE
|
||||||
|
{
|
||||||
|
Status = _SEH_GetExceptionCode();
|
||||||
|
}
|
||||||
|
_SEH_END;
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
ExFreePoolWithTag(SystemBuffer,
|
||||||
|
TAG_SYSB);
|
||||||
|
failfreeirp:
|
||||||
|
IoFreeIrp(Irp);
|
||||||
|
ObDereferenceObject(FileObject);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RtlCopyMemory(SystemBuffer,
|
||||||
|
FsInformation,
|
||||||
|
Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trigger FileObject/Event dereferencing */
|
||||||
|
Irp->Tail.Overlay.OriginalFileObject = FileObject;
|
||||||
|
Irp->RequestorMode = PreviousMode;
|
||||||
|
Irp->AssociatedIrp.SystemBuffer = SystemBuffer;
|
||||||
|
KeResetEvent( &FileObject->Event );
|
||||||
|
Irp->UserEvent = &FileObject->Event;
|
||||||
|
Irp->UserIosb = IoStatusBlock;
|
||||||
|
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
||||||
|
|
||||||
|
StackPtr = IoGetNextIrpStackLocation(Irp);
|
||||||
|
StackPtr->MajorFunction = IRP_MJ_SET_VOLUME_INFORMATION;
|
||||||
|
StackPtr->MinorFunction = 0;
|
||||||
|
StackPtr->Flags = 0;
|
||||||
|
StackPtr->Control = 0;
|
||||||
|
StackPtr->DeviceObject = DeviceObject;
|
||||||
|
StackPtr->FileObject = FileObject;
|
||||||
|
StackPtr->Parameters.SetVolume.Length = Length;
|
||||||
|
StackPtr->Parameters.SetVolume.FsInformationClass =
|
||||||
|
FsInformationClass;
|
||||||
|
|
||||||
|
Status = IoCallDriver(DeviceObject,Irp);
|
||||||
|
if (Status == STATUS_PENDING)
|
||||||
|
{
|
||||||
|
KeWaitForSingleObject(&FileObject->Event,
|
||||||
|
UserRequest,
|
||||||
|
PreviousMode,
|
||||||
|
FALSE,
|
||||||
|
NULL);
|
||||||
|
_SEH_TRY
|
||||||
|
{
|
||||||
|
Status = IoStatusBlock->Status;
|
||||||
|
}
|
||||||
|
_SEH_HANDLE
|
||||||
|
{
|
||||||
|
Status = _SEH_GetExceptionCode();
|
||||||
|
}
|
||||||
|
_SEH_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExFreePool(SystemBuffer);
|
||||||
|
|
||||||
|
return(Status);
|
||||||
|
}
|
||||||
|
|
|
@ -195,4 +195,16 @@ IoGetDeviceToVerify(IN PETHREAD Thread)
|
||||||
return Thread->DeviceToVerify;
|
return Thread->DeviceToVerify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @unimplemented
|
||||||
|
*/
|
||||||
|
NTSTATUS
|
||||||
|
NTAPI
|
||||||
|
IoCheckQuerySetVolumeInformation(IN FS_INFORMATION_CLASS FsInformationClass,
|
||||||
|
IN ULONG Length,
|
||||||
|
IN BOOLEAN SetOperation)
|
||||||
|
{
|
||||||
|
UNIMPLEMENTED;
|
||||||
|
return STATUS_NOT_IMPLEMENTED;
|
||||||
|
}
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
|
@ -33,6 +33,10 @@ typedef struct _FS_CHANGE_NOTIFY_ENTRY
|
||||||
PDRIVER_FS_NOTIFICATION FSDNotificationProc;
|
PDRIVER_FS_NOTIFICATION FSDNotificationProc;
|
||||||
} FS_CHANGE_NOTIFY_ENTRY, *PFS_CHANGE_NOTIFY_ENTRY;
|
} FS_CHANGE_NOTIFY_ENTRY, *PFS_CHANGE_NOTIFY_ENTRY;
|
||||||
|
|
||||||
|
#if defined (ALLOC_PRAGMA)
|
||||||
|
#pragma alloc_text(INIT, IoInitVpbImplementation)
|
||||||
|
#endif
|
||||||
|
|
||||||
/* GLOBALS ******************************************************************/
|
/* GLOBALS ******************************************************************/
|
||||||
|
|
||||||
static ERESOURCE FileSystemListLock;
|
static ERESOURCE FileSystemListLock;
|
||||||
|
@ -45,9 +49,40 @@ static VOID
|
||||||
IopNotifyFileSystemChange(PDEVICE_OBJECT DeviceObject,
|
IopNotifyFileSystemChange(PDEVICE_OBJECT DeviceObject,
|
||||||
BOOLEAN DriverActive);
|
BOOLEAN DriverActive);
|
||||||
|
|
||||||
|
static KSPIN_LOCK IoVpbLock;
|
||||||
/* FUNCTIONS *****************************************************************/
|
/* FUNCTIONS *****************************************************************/
|
||||||
|
|
||||||
|
VOID INIT_FUNCTION
|
||||||
|
IoInitVpbImplementation(VOID)
|
||||||
|
{
|
||||||
|
KeInitializeSpinLock(&IoVpbLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
STDCALL
|
||||||
|
IopAttachVpb(PDEVICE_OBJECT DeviceObject)
|
||||||
|
{
|
||||||
|
PVPB Vpb;
|
||||||
|
|
||||||
|
/* Allocate the Vpb */
|
||||||
|
Vpb = ExAllocatePoolWithTag(NonPagedPool,
|
||||||
|
sizeof(VPB),
|
||||||
|
TAG_VPB);
|
||||||
|
if (Vpb == NULL) return(STATUS_UNSUCCESSFUL);
|
||||||
|
|
||||||
|
/* Clear it so we don't waste time manually */
|
||||||
|
RtlZeroMemory(Vpb, sizeof(VPB));
|
||||||
|
|
||||||
|
/* Set the Header and Device Field */
|
||||||
|
Vpb->Type = IO_TYPE_VPB;
|
||||||
|
Vpb->Size = sizeof(VPB);
|
||||||
|
Vpb->RealDevice = DeviceObject;
|
||||||
|
|
||||||
|
/* link it to the Device Object */
|
||||||
|
DeviceObject->Vpb = Vpb;
|
||||||
|
return(STATUS_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
VOID INIT_FUNCTION
|
VOID INIT_FUNCTION
|
||||||
IoInitFileSystemImplementation(VOID)
|
IoInitFileSystemImplementation(VOID)
|
||||||
{
|
{
|
||||||
|
@ -603,4 +638,25 @@ IoUnregisterFsRegistrationChange(IN PDRIVER_OBJECT DriverObject,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @implemented
|
||||||
|
*/
|
||||||
|
VOID STDCALL
|
||||||
|
IoAcquireVpbSpinLock(OUT PKIRQL Irql)
|
||||||
|
{
|
||||||
|
KeAcquireSpinLock(&IoVpbLock,
|
||||||
|
Irql);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @implemented
|
||||||
|
*/
|
||||||
|
VOID STDCALL
|
||||||
|
IoReleaseVpbSpinLock(IN KIRQL Irql)
|
||||||
|
{
|
||||||
|
KeReleaseSpinLock(&IoVpbLock,
|
||||||
|
Irql);
|
||||||
|
}
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
|
@ -1,502 +0,0 @@
|
||||||
/* $Id$
|
|
||||||
*
|
|
||||||
* COPYRIGHT: See COPYING in the top level directory
|
|
||||||
* PROJECT: ReactOS kernel
|
|
||||||
* FILE: ntoskrnl/io/vpb.c
|
|
||||||
* PURPOSE: Volume Parameter Block managment
|
|
||||||
*
|
|
||||||
* PROGRAMMERS: David Welch (welch@mcmail.com)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* INCLUDES *****************************************************************/
|
|
||||||
|
|
||||||
#include <ntoskrnl.h>
|
|
||||||
#define NDEBUG
|
|
||||||
#include <internal/debug.h>
|
|
||||||
|
|
||||||
#if defined (ALLOC_PRAGMA)
|
|
||||||
#pragma alloc_text(INIT, IoInitVpbImplementation)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* GLOBALS *******************************************************************/
|
|
||||||
|
|
||||||
static KSPIN_LOCK IoVpbLock;
|
|
||||||
|
|
||||||
/* FUNCTIONS *****************************************************************/
|
|
||||||
|
|
||||||
VOID INIT_FUNCTION
|
|
||||||
IoInitVpbImplementation(VOID)
|
|
||||||
{
|
|
||||||
KeInitializeSpinLock(&IoVpbLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
NTSTATUS
|
|
||||||
STDCALL
|
|
||||||
IopAttachVpb(PDEVICE_OBJECT DeviceObject)
|
|
||||||
{
|
|
||||||
PVPB Vpb;
|
|
||||||
|
|
||||||
/* Allocate the Vpb */
|
|
||||||
Vpb = ExAllocatePoolWithTag(NonPagedPool,
|
|
||||||
sizeof(VPB),
|
|
||||||
TAG_VPB);
|
|
||||||
if (Vpb == NULL) return(STATUS_UNSUCCESSFUL);
|
|
||||||
|
|
||||||
/* Clear it so we don't waste time manually */
|
|
||||||
RtlZeroMemory(Vpb, sizeof(VPB));
|
|
||||||
|
|
||||||
/* Set the Header and Device Field */
|
|
||||||
Vpb->Type = IO_TYPE_VPB;
|
|
||||||
Vpb->Size = sizeof(VPB);
|
|
||||||
Vpb->RealDevice = DeviceObject;
|
|
||||||
|
|
||||||
/* link it to the Device Object */
|
|
||||||
DeviceObject->Vpb = Vpb;
|
|
||||||
return(STATUS_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* FUNCTION: Queries the volume information
|
|
||||||
* ARGUMENTS:
|
|
||||||
* FileHandle = Handle to a file object on the target volume
|
|
||||||
* ReturnLength = DataWritten
|
|
||||||
* FsInformation = Caller should supply storage for the information
|
|
||||||
* structure.
|
|
||||||
* Length = Size of the information structure
|
|
||||||
* FsInformationClass = Index to a information structure
|
|
||||||
*
|
|
||||||
* FileFsVolumeInformation FILE_FS_VOLUME_INFORMATION
|
|
||||||
* FileFsLabelInformation FILE_FS_LABEL_INFORMATION
|
|
||||||
* FileFsSizeInformation FILE_FS_SIZE_INFORMATION
|
|
||||||
* FileFsDeviceInformation FILE_FS_DEVICE_INFORMATION
|
|
||||||
* FileFsAttributeInformation FILE_FS_ATTRIBUTE_INFORMATION
|
|
||||||
* FileFsControlInformation
|
|
||||||
* FileFsQuotaQueryInformation --
|
|
||||||
* FileFsQuotaSetInformation --
|
|
||||||
* FileFsMaximumInformation
|
|
||||||
*
|
|
||||||
* RETURNS: Status
|
|
||||||
*
|
|
||||||
* @implemented
|
|
||||||
*/
|
|
||||||
|
|
||||||
NTSTATUS STDCALL
|
|
||||||
NtQueryVolumeInformationFile(IN HANDLE FileHandle,
|
|
||||||
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
|
||||||
OUT PVOID FsInformation,
|
|
||||||
IN ULONG Length,
|
|
||||||
IN FS_INFORMATION_CLASS FsInformationClass)
|
|
||||||
{
|
|
||||||
PFILE_OBJECT FileObject;
|
|
||||||
PDEVICE_OBJECT DeviceObject;
|
|
||||||
PIRP Irp;
|
|
||||||
NTSTATUS Status = STATUS_SUCCESS;
|
|
||||||
PIO_STACK_LOCATION StackPtr;
|
|
||||||
PVOID SystemBuffer;
|
|
||||||
KPROCESSOR_MODE PreviousMode;
|
|
||||||
|
|
||||||
DPRINT("FsInformation %p\n", FsInformation);
|
|
||||||
|
|
||||||
PreviousMode = ExGetPreviousMode();
|
|
||||||
|
|
||||||
if (PreviousMode != KernelMode)
|
|
||||||
{
|
|
||||||
_SEH_TRY
|
|
||||||
{
|
|
||||||
if (IoStatusBlock != NULL)
|
|
||||||
{
|
|
||||||
ProbeForWrite(IoStatusBlock,
|
|
||||||
sizeof(IO_STATUS_BLOCK),
|
|
||||||
sizeof(ULONG));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Length != 0)
|
|
||||||
{
|
|
||||||
ProbeForWrite(FsInformation,
|
|
||||||
Length,
|
|
||||||
1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_SEH_HANDLE
|
|
||||||
{
|
|
||||||
Status = _SEH_GetExceptionCode();
|
|
||||||
}
|
|
||||||
_SEH_END;
|
|
||||||
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
return Status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ASSERT(IoStatusBlock != NULL);
|
|
||||||
ASSERT(FsInformation != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
Status = ObReferenceObjectByHandle(FileHandle,
|
|
||||||
0, /* FIXME - depends on the information class! */
|
|
||||||
IoFileObjectType,
|
|
||||||
PreviousMode,
|
|
||||||
(PVOID*)&FileObject,
|
|
||||||
NULL);
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
return(Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceObject = FileObject->DeviceObject;
|
|
||||||
|
|
||||||
Irp = IoAllocateIrp(DeviceObject->StackSize,
|
|
||||||
TRUE);
|
|
||||||
if (Irp == NULL)
|
|
||||||
{
|
|
||||||
ObDereferenceObject(FileObject);
|
|
||||||
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemBuffer = ExAllocatePoolWithTag(NonPagedPool,
|
|
||||||
Length,
|
|
||||||
TAG_SYSB);
|
|
||||||
if (SystemBuffer == NULL)
|
|
||||||
{
|
|
||||||
IoFreeIrp(Irp);
|
|
||||||
ObDereferenceObject(FileObject);
|
|
||||||
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trigger FileObject/Event dereferencing */
|
|
||||||
Irp->Tail.Overlay.OriginalFileObject = FileObject;
|
|
||||||
|
|
||||||
Irp->RequestorMode = PreviousMode;
|
|
||||||
Irp->AssociatedIrp.SystemBuffer = SystemBuffer;
|
|
||||||
KeResetEvent( &FileObject->Event );
|
|
||||||
Irp->UserEvent = &FileObject->Event;
|
|
||||||
Irp->UserIosb = IoStatusBlock;
|
|
||||||
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
|
||||||
|
|
||||||
StackPtr = IoGetNextIrpStackLocation(Irp);
|
|
||||||
StackPtr->MajorFunction = IRP_MJ_QUERY_VOLUME_INFORMATION;
|
|
||||||
StackPtr->MinorFunction = 0;
|
|
||||||
StackPtr->Flags = 0;
|
|
||||||
StackPtr->Control = 0;
|
|
||||||
StackPtr->DeviceObject = DeviceObject;
|
|
||||||
StackPtr->FileObject = FileObject;
|
|
||||||
StackPtr->Parameters.QueryVolume.Length = Length;
|
|
||||||
StackPtr->Parameters.QueryVolume.FsInformationClass =
|
|
||||||
FsInformationClass;
|
|
||||||
|
|
||||||
Status = IoCallDriver(DeviceObject,
|
|
||||||
Irp);
|
|
||||||
if (Status == STATUS_PENDING)
|
|
||||||
{
|
|
||||||
KeWaitForSingleObject(&FileObject->Event,
|
|
||||||
UserRequest,
|
|
||||||
PreviousMode,
|
|
||||||
FALSE,
|
|
||||||
NULL);
|
|
||||||
Status = IoStatusBlock->Status;
|
|
||||||
}
|
|
||||||
DPRINT("Status %x\n", Status);
|
|
||||||
|
|
||||||
if (NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
_SEH_TRY
|
|
||||||
{
|
|
||||||
DPRINT("Information %lu\n", IoStatusBlock->Information);
|
|
||||||
RtlCopyMemory(FsInformation,
|
|
||||||
SystemBuffer,
|
|
||||||
IoStatusBlock->Information);
|
|
||||||
}
|
|
||||||
_SEH_HANDLE
|
|
||||||
{
|
|
||||||
Status = _SEH_GetExceptionCode();
|
|
||||||
}
|
|
||||||
_SEH_END;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExFreePool(SystemBuffer);
|
|
||||||
|
|
||||||
return(Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @implemented
|
|
||||||
*/
|
|
||||||
NTSTATUS STDCALL
|
|
||||||
IoQueryVolumeInformation(IN PFILE_OBJECT FileObject,
|
|
||||||
IN FS_INFORMATION_CLASS FsInformationClass,
|
|
||||||
IN ULONG Length,
|
|
||||||
OUT PVOID FsInformation,
|
|
||||||
OUT PULONG ReturnedLength)
|
|
||||||
{
|
|
||||||
IO_STATUS_BLOCK IoStatusBlock;
|
|
||||||
PIO_STACK_LOCATION StackPtr;
|
|
||||||
PDEVICE_OBJECT DeviceObject;
|
|
||||||
PIRP Irp;
|
|
||||||
NTSTATUS Status;
|
|
||||||
|
|
||||||
ASSERT(FsInformation != NULL);
|
|
||||||
|
|
||||||
DPRINT("FsInformation %p\n", FsInformation);
|
|
||||||
|
|
||||||
Status = ObReferenceObjectByPointer(FileObject,
|
|
||||||
FILE_READ_ATTRIBUTES,
|
|
||||||
IoFileObjectType,
|
|
||||||
KernelMode);
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
return(Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceObject = FileObject->DeviceObject;
|
|
||||||
|
|
||||||
Irp = IoAllocateIrp(DeviceObject->StackSize,
|
|
||||||
TRUE);
|
|
||||||
if (Irp == NULL)
|
|
||||||
{
|
|
||||||
ObDereferenceObject(FileObject);
|
|
||||||
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trigger FileObject/Event dereferencing */
|
|
||||||
Irp->Tail.Overlay.OriginalFileObject = FileObject;
|
|
||||||
Irp->RequestorMode = KernelMode;
|
|
||||||
Irp->AssociatedIrp.SystemBuffer = FsInformation;
|
|
||||||
KeResetEvent( &FileObject->Event );
|
|
||||||
Irp->UserEvent = &FileObject->Event;
|
|
||||||
Irp->UserIosb = &IoStatusBlock;
|
|
||||||
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
|
||||||
|
|
||||||
StackPtr = IoGetNextIrpStackLocation(Irp);
|
|
||||||
StackPtr->MajorFunction = IRP_MJ_QUERY_VOLUME_INFORMATION;
|
|
||||||
StackPtr->MinorFunction = 0;
|
|
||||||
StackPtr->Flags = 0;
|
|
||||||
StackPtr->Control = 0;
|
|
||||||
StackPtr->DeviceObject = DeviceObject;
|
|
||||||
StackPtr->FileObject = FileObject;
|
|
||||||
StackPtr->Parameters.QueryVolume.Length = Length;
|
|
||||||
StackPtr->Parameters.QueryVolume.FsInformationClass =
|
|
||||||
FsInformationClass;
|
|
||||||
|
|
||||||
Status = IoCallDriver(DeviceObject,
|
|
||||||
Irp);
|
|
||||||
if (Status == STATUS_PENDING)
|
|
||||||
{
|
|
||||||
KeWaitForSingleObject(&FileObject->Event,
|
|
||||||
UserRequest,
|
|
||||||
KernelMode,
|
|
||||||
FALSE,
|
|
||||||
NULL);
|
|
||||||
Status = IoStatusBlock.Status;
|
|
||||||
}
|
|
||||||
DPRINT("Status %x\n", Status);
|
|
||||||
|
|
||||||
if (ReturnedLength != NULL)
|
|
||||||
{
|
|
||||||
*ReturnedLength = IoStatusBlock.Information;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @implemented
|
|
||||||
*/
|
|
||||||
NTSTATUS STDCALL
|
|
||||||
NtSetVolumeInformationFile(IN HANDLE FileHandle,
|
|
||||||
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
|
||||||
IN PVOID FsInformation,
|
|
||||||
IN ULONG Length,
|
|
||||||
IN FS_INFORMATION_CLASS FsInformationClass)
|
|
||||||
{
|
|
||||||
PFILE_OBJECT FileObject;
|
|
||||||
PDEVICE_OBJECT DeviceObject;
|
|
||||||
PIRP Irp;
|
|
||||||
NTSTATUS Status;
|
|
||||||
PIO_STACK_LOCATION StackPtr;
|
|
||||||
PVOID SystemBuffer;
|
|
||||||
KPROCESSOR_MODE PreviousMode;
|
|
||||||
|
|
||||||
PreviousMode = ExGetPreviousMode();
|
|
||||||
|
|
||||||
if (PreviousMode != KernelMode)
|
|
||||||
{
|
|
||||||
Status = STATUS_SUCCESS;
|
|
||||||
_SEH_TRY
|
|
||||||
{
|
|
||||||
if (IoStatusBlock != NULL)
|
|
||||||
{
|
|
||||||
ProbeForWrite(IoStatusBlock,
|
|
||||||
sizeof(IO_STATUS_BLOCK),
|
|
||||||
sizeof(ULONG));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Length != 0)
|
|
||||||
{
|
|
||||||
ProbeForRead(FsInformation,
|
|
||||||
Length,
|
|
||||||
1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_SEH_HANDLE
|
|
||||||
{
|
|
||||||
Status = _SEH_GetExceptionCode();
|
|
||||||
}
|
|
||||||
_SEH_END;
|
|
||||||
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
return Status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ASSERT(IoStatusBlock != NULL);
|
|
||||||
ASSERT(FsInformation != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
Status = ObReferenceObjectByHandle(FileHandle,
|
|
||||||
FILE_WRITE_ATTRIBUTES,
|
|
||||||
NULL,
|
|
||||||
PreviousMode,
|
|
||||||
(PVOID*)&FileObject,
|
|
||||||
NULL);
|
|
||||||
if (Status != STATUS_SUCCESS)
|
|
||||||
{
|
|
||||||
return(Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceObject = FileObject->DeviceObject;
|
|
||||||
|
|
||||||
Irp = IoAllocateIrp(DeviceObject->StackSize,TRUE);
|
|
||||||
if (Irp == NULL)
|
|
||||||
{
|
|
||||||
ObDereferenceObject(FileObject);
|
|
||||||
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemBuffer = ExAllocatePoolWithTag(NonPagedPool,
|
|
||||||
Length,
|
|
||||||
TAG_SYSB);
|
|
||||||
if (SystemBuffer == NULL)
|
|
||||||
{
|
|
||||||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
||||||
goto failfreeirp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreviousMode != KernelMode)
|
|
||||||
{
|
|
||||||
_SEH_TRY
|
|
||||||
{
|
|
||||||
/* no need to probe again */
|
|
||||||
RtlCopyMemory(SystemBuffer,
|
|
||||||
FsInformation,
|
|
||||||
Length);
|
|
||||||
}
|
|
||||||
_SEH_HANDLE
|
|
||||||
{
|
|
||||||
Status = _SEH_GetExceptionCode();
|
|
||||||
}
|
|
||||||
_SEH_END;
|
|
||||||
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
ExFreePoolWithTag(SystemBuffer,
|
|
||||||
TAG_SYSB);
|
|
||||||
failfreeirp:
|
|
||||||
IoFreeIrp(Irp);
|
|
||||||
ObDereferenceObject(FileObject);
|
|
||||||
return Status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RtlCopyMemory(SystemBuffer,
|
|
||||||
FsInformation,
|
|
||||||
Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trigger FileObject/Event dereferencing */
|
|
||||||
Irp->Tail.Overlay.OriginalFileObject = FileObject;
|
|
||||||
Irp->RequestorMode = PreviousMode;
|
|
||||||
Irp->AssociatedIrp.SystemBuffer = SystemBuffer;
|
|
||||||
KeResetEvent( &FileObject->Event );
|
|
||||||
Irp->UserEvent = &FileObject->Event;
|
|
||||||
Irp->UserIosb = IoStatusBlock;
|
|
||||||
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
|
||||||
|
|
||||||
StackPtr = IoGetNextIrpStackLocation(Irp);
|
|
||||||
StackPtr->MajorFunction = IRP_MJ_SET_VOLUME_INFORMATION;
|
|
||||||
StackPtr->MinorFunction = 0;
|
|
||||||
StackPtr->Flags = 0;
|
|
||||||
StackPtr->Control = 0;
|
|
||||||
StackPtr->DeviceObject = DeviceObject;
|
|
||||||
StackPtr->FileObject = FileObject;
|
|
||||||
StackPtr->Parameters.SetVolume.Length = Length;
|
|
||||||
StackPtr->Parameters.SetVolume.FsInformationClass =
|
|
||||||
FsInformationClass;
|
|
||||||
|
|
||||||
Status = IoCallDriver(DeviceObject,Irp);
|
|
||||||
if (Status == STATUS_PENDING)
|
|
||||||
{
|
|
||||||
KeWaitForSingleObject(&FileObject->Event,
|
|
||||||
UserRequest,
|
|
||||||
PreviousMode,
|
|
||||||
FALSE,
|
|
||||||
NULL);
|
|
||||||
_SEH_TRY
|
|
||||||
{
|
|
||||||
Status = IoStatusBlock->Status;
|
|
||||||
}
|
|
||||||
_SEH_HANDLE
|
|
||||||
{
|
|
||||||
Status = _SEH_GetExceptionCode();
|
|
||||||
}
|
|
||||||
_SEH_END;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExFreePool(SystemBuffer);
|
|
||||||
|
|
||||||
return(Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @implemented
|
|
||||||
*/
|
|
||||||
VOID STDCALL
|
|
||||||
IoAcquireVpbSpinLock(OUT PKIRQL Irql)
|
|
||||||
{
|
|
||||||
KeAcquireSpinLock(&IoVpbLock,
|
|
||||||
Irql);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @implemented
|
|
||||||
*/
|
|
||||||
VOID STDCALL
|
|
||||||
IoReleaseVpbSpinLock(IN KIRQL Irql)
|
|
||||||
{
|
|
||||||
KeReleaseSpinLock(&IoVpbLock,
|
|
||||||
Irql);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @unimplemented
|
|
||||||
*/
|
|
||||||
NTSTATUS
|
|
||||||
STDCALL
|
|
||||||
IoCheckQuerySetVolumeInformation(IN FS_INFORMATION_CLASS FsInformationClass,
|
|
||||||
IN ULONG Length,
|
|
||||||
IN BOOLEAN SetOperation)
|
|
||||||
{
|
|
||||||
UNIMPLEMENTED;
|
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* EOF */
|
|
|
@ -175,7 +175,6 @@
|
||||||
<file>error.c</file>
|
<file>error.c</file>
|
||||||
<file>event.c</file>
|
<file>event.c</file>
|
||||||
<file>file.c</file>
|
<file>file.c</file>
|
||||||
<file>fs.c</file>
|
|
||||||
<file>iocomp.c</file>
|
<file>iocomp.c</file>
|
||||||
<file>iofunc.c</file>
|
<file>iofunc.c</file>
|
||||||
<file>iomgr.c</file>
|
<file>iomgr.c</file>
|
||||||
|
@ -189,7 +188,7 @@
|
||||||
<file>util.c</file>
|
<file>util.c</file>
|
||||||
<file>symlink.c</file>
|
<file>symlink.c</file>
|
||||||
<file>timer.c</file>
|
<file>timer.c</file>
|
||||||
<file>vpb.c</file>
|
<file>volume.c</file>
|
||||||
</directory>
|
</directory>
|
||||||
<directory name="pnpmgr">
|
<directory name="pnpmgr">
|
||||||
<file>plugplay.c</file>
|
<file>plugplay.c</file>
|
||||||
|
|
Loading…
Reference in a new issue