mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-23 17:03:12 -05:00
sys: IRP_MJ_CREATE: refactoring to support Overwrite
This commit is contained in:
parent
16895a5b68
commit
a382db8b01
@ -61,7 +61,7 @@ static NTSTATUS FspFsvolCleanup(
|
|||||||
UINT64 UserContext2 = (UINT_PTR)FileObject->FsContext2;
|
UINT64 UserContext2 = (UINT_PTR)FileObject->FsContext2;
|
||||||
FSP_FSCTL_TRANSACT_REQ *Request;
|
FSP_FSCTL_TRANSACT_REQ *Request;
|
||||||
|
|
||||||
FspFileContextClose(FsvolDeviceObject, FsContext);
|
FspFileContextClose(FsContext, FileObject);
|
||||||
|
|
||||||
/* create the user-mode file system request; MustSucceed because IRP_MJ_CLEANUP cannot fail */
|
/* create the user-mode file system request; MustSucceed because IRP_MJ_CLEANUP cannot fail */
|
||||||
FspIopCreateRequestMustSucceed(Irp, FileNameRequired ? &FsContext->FileName : 0, 0, &Request);
|
FspIopCreateRequestMustSucceed(Irp, FileNameRequired ? &FsContext->FileName : 0, 0, &Request);
|
||||||
|
106
src/sys/create.c
106
src/sys/create.c
@ -14,6 +14,9 @@ static NTSTATUS FspFsvolCreate(
|
|||||||
PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
|
PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
|
||||||
FSP_IOPREP_DISPATCH FspFsvolCreatePrepare;
|
FSP_IOPREP_DISPATCH FspFsvolCreatePrepare;
|
||||||
FSP_IOCMPL_DISPATCH FspFsvolCreateComplete;
|
FSP_IOCMPL_DISPATCH FspFsvolCreateComplete;
|
||||||
|
static VOID FspFsvolCreatePostClose(
|
||||||
|
PDEVICE_OBJECT FsvolDeviceObject, PUNICODE_STRING FileName,
|
||||||
|
UINT64 UserContext, UINT64 UserContext2, NTSTATUS CloseStatus);
|
||||||
static FSP_IOP_REQUEST_FINI FspFsvolCreateRequestFini;
|
static FSP_IOP_REQUEST_FINI FspFsvolCreateRequestFini;
|
||||||
FSP_DRIVER_DISPATCH FspCreate;
|
FSP_DRIVER_DISPATCH FspCreate;
|
||||||
|
|
||||||
@ -23,6 +26,7 @@ FSP_DRIVER_DISPATCH FspCreate;
|
|||||||
#pragma alloc_text(PAGE, FspFsvolCreate)
|
#pragma alloc_text(PAGE, FspFsvolCreate)
|
||||||
#pragma alloc_text(PAGE, FspFsvolCreatePrepare)
|
#pragma alloc_text(PAGE, FspFsvolCreatePrepare)
|
||||||
#pragma alloc_text(PAGE, FspFsvolCreateComplete)
|
#pragma alloc_text(PAGE, FspFsvolCreateComplete)
|
||||||
|
#pragma alloc_text(PAGE, FspFsvolCreatePostClose)
|
||||||
#pragma alloc_text(PAGE, FspFsvolCreateRequestFini)
|
#pragma alloc_text(PAGE, FspFsvolCreateRequestFini)
|
||||||
#pragma alloc_text(PAGE, FspCreate)
|
#pragma alloc_text(PAGE, FspCreate)
|
||||||
#endif
|
#endif
|
||||||
@ -368,10 +372,10 @@ VOID FspFsvolCreateComplete(
|
|||||||
PDEVICE_OBJECT FsvolDeviceObject = IrpSp->DeviceObject;
|
PDEVICE_OBJECT FsvolDeviceObject = IrpSp->DeviceObject;
|
||||||
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
|
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
|
||||||
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
||||||
SHARE_ACCESS TemporaryShareAccess;
|
|
||||||
UNICODE_STRING ReparseFileName;
|
|
||||||
FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp);
|
FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp);
|
||||||
FSP_FILE_CONTEXT *FsContext = FspIopRequestContext(Request, RequestFsContext);
|
FSP_FILE_CONTEXT *FsContext = FspIopRequestContext(Request, RequestFsContext);
|
||||||
|
UNICODE_STRING ReparseFileName;
|
||||||
|
BOOLEAN DeleteOnClose;
|
||||||
|
|
||||||
if (FspFsctlTransactCreateKind == Request->Kind)
|
if (FspFsctlTransactCreateKind == Request->Kind)
|
||||||
{
|
{
|
||||||
@ -445,15 +449,7 @@ VOID FspFsvolCreateComplete(
|
|||||||
FsContext->Header.FileSize.QuadPart = Response->Rsp.Create.Opened.AllocationSize;
|
FsContext->Header.FileSize.QuadPart = Response->Rsp.Create.Opened.AllocationSize;
|
||||||
FsContext->UserContext = Response->Rsp.Create.Opened.UserContext;
|
FsContext->UserContext = Response->Rsp.Create.Opened.UserContext;
|
||||||
|
|
||||||
/* open the FsContext */
|
/* set up the FileObject */
|
||||||
FspIopRequestContext(Request, RequestFsContext) =
|
|
||||||
FspFileContextOpen(FsvolDeviceObject, FsContext);
|
|
||||||
|
|
||||||
/* set up share access on FileObject; user-mode file system assumed to have done share check */
|
|
||||||
IoSetShareAccess(Response->Rsp.Create.Opened.GrantedAccess, IrpSp->Parameters.Create.ShareAccess,
|
|
||||||
FileObject, &TemporaryShareAccess);
|
|
||||||
|
|
||||||
/* finish setting up the FileObject */
|
|
||||||
if (0 != FsvolDeviceExtension->FsvrtDeviceObject)
|
if (0 != FsvolDeviceExtension->FsvrtDeviceObject)
|
||||||
FileObject->Vpb = FsvolDeviceExtension->FsvrtDeviceObject->Vpb;
|
FileObject->Vpb = FsvolDeviceExtension->FsvrtDeviceObject->Vpb;
|
||||||
FileObject->SectionObjectPointer = &FsContext->NonPaged->SectionObjectPointers;
|
FileObject->SectionObjectPointer = &FsContext->NonPaged->SectionObjectPointers;
|
||||||
@ -461,6 +457,59 @@ VOID FspFsvolCreateComplete(
|
|||||||
FileObject->FsContext = FsContext;
|
FileObject->FsContext = FsContext;
|
||||||
FileObject->FsContext2 = (PVOID)(UINT_PTR)Response->Rsp.Create.Opened.UserContext2;
|
FileObject->FsContext2 = (PVOID)(UINT_PTR)Response->Rsp.Create.Opened.UserContext2;
|
||||||
|
|
||||||
|
DeleteOnClose = BooleanFlagOn(Request->Req.Create.CreateOptions, FILE_DELETE_ON_CLOSE);
|
||||||
|
|
||||||
|
/* open the FsContext */
|
||||||
|
FsContext = FspFileContextOpen(FsContext, FileObject,
|
||||||
|
Response->Rsp.Create.Opened.GrantedAccess, IrpSp->Parameters.Create.ShareAccess,
|
||||||
|
&Result);
|
||||||
|
FspIopRequestContext(Request, RequestFsContext) = FsContext;
|
||||||
|
if (0 == FsContext)
|
||||||
|
{
|
||||||
|
/* unable to open the FsContext; post a close Create2 request */
|
||||||
|
FspFsvolCreatePostClose(FsvolDeviceObject,
|
||||||
|
FsvolDeviceExtension->VolumeParams.FileNameRequired ? &FsContext->FileName : 0,
|
||||||
|
Response->Rsp.Create.Opened.UserContext,
|
||||||
|
Response->Rsp.Create.Opened.UserContext2,
|
||||||
|
Result);
|
||||||
|
|
||||||
|
FSP_RETURN();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FILE_OPENED == Response->IoStatus.Information)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* FastFat quote:
|
||||||
|
* If the user wants write access access to the file make sure there
|
||||||
|
* is not a process mapping this file as an image. Any attempt to
|
||||||
|
* delete the file will be stopped in fileinfo.c
|
||||||
|
*
|
||||||
|
* If the user wants to delete on close, we must check at this
|
||||||
|
* point though.
|
||||||
|
*/
|
||||||
|
if (FlagOn(Response->Rsp.Create.Opened.GrantedAccess, FILE_WRITE_DATA) ||
|
||||||
|
DeleteOnClose)
|
||||||
|
{
|
||||||
|
if (!MmFlushImageSection(&FsContext->NonPaged->SectionObjectPointers,
|
||||||
|
MmFlushForWrite))
|
||||||
|
{
|
||||||
|
Result = DeleteOnClose ? STATUS_CANNOT_DELETE : STATUS_SHARING_VIOLATION;
|
||||||
|
|
||||||
|
FspFsvolCreatePostClose(FsvolDeviceObject,
|
||||||
|
FsvolDeviceExtension->VolumeParams.FileNameRequired ? &FsContext->FileName : 0,
|
||||||
|
Response->Rsp.Create.Opened.UserContext,
|
||||||
|
Response->Rsp.Create.Opened.UserContext2,
|
||||||
|
Result);
|
||||||
|
|
||||||
|
FSP_RETURN();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SUCCESS! */
|
||||||
|
Irp->IoStatus.Information = (ULONG_PTR)Response->IoStatus.Information;
|
||||||
|
Result = STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
else
|
||||||
if (FILE_SUPERSEDED == Response->IoStatus.Information ||
|
if (FILE_SUPERSEDED == Response->IoStatus.Information ||
|
||||||
FILE_OVERWRITTEN == Response->IoStatus.Information)
|
FILE_OVERWRITTEN == Response->IoStatus.Information)
|
||||||
{
|
{
|
||||||
@ -525,7 +574,7 @@ VOID FspFsvolCreateComplete(
|
|||||||
if (!NT_SUCCESS(Response->IoStatus.Status))
|
if (!NT_SUCCESS(Response->IoStatus.Status))
|
||||||
{
|
{
|
||||||
FspFileContextPgioUnlock(FsContext);
|
FspFileContextPgioUnlock(FsContext);
|
||||||
FspFileContextClose(FsvolDeviceObject, FsContext);
|
FspFileContextClose(FsContext, FileObject);
|
||||||
|
|
||||||
Irp->IoStatus.Information = 0;
|
Irp->IoStatus.Information = 0;
|
||||||
Result = Response->IoStatus.Status;
|
Result = Response->IoStatus.Status;
|
||||||
@ -551,6 +600,39 @@ VOID FspFsvolCreateComplete(
|
|||||||
IrpSp->FileObject, IrpSp->FileObject->RelatedFileObject, IrpSp->FileObject->FileName);
|
IrpSp->FileObject, IrpSp->FileObject->RelatedFileObject, IrpSp->FileObject->FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VOID FspFsvolCreatePostClose(
|
||||||
|
PDEVICE_OBJECT FsvolDeviceObject, PUNICODE_STRING FileName,
|
||||||
|
UINT64 UserContext, UINT64 UserContext2, NTSTATUS CloseStatus)
|
||||||
|
{
|
||||||
|
PAGED_CODE();
|
||||||
|
|
||||||
|
ASSERT(STATUS_SUCCESS != CloseStatus);
|
||||||
|
|
||||||
|
FSP_FSCTL_TRANSACT_REQ *Request;
|
||||||
|
|
||||||
|
/* create the user-mode file system request; MustSucceed because we cannot fail */
|
||||||
|
FspIopCreateRequestMustSucceed(0, FileName, 0, &Request);
|
||||||
|
|
||||||
|
/* populate the Create2 request */
|
||||||
|
Request->Kind = FspFsctlTransactCreate2Kind;
|
||||||
|
Request->Req.Create2.UserContext = UserContext;
|
||||||
|
Request->Req.Create2.UserContext2 = UserContext2;
|
||||||
|
Request->Req.Create2.CloseStatus = CloseStatus;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Post as a BestEffort work request. This allows us to complete our own IRP
|
||||||
|
* and return immediately.
|
||||||
|
*/
|
||||||
|
FspIopPostWorkRequestBestEffort(FsvolDeviceObject, Request);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that it is still possible for this request to not be delivered,
|
||||||
|
* if the volume device Ioq is stopped. But such failures are benign
|
||||||
|
* from our perspective, because they mean that the file system is going
|
||||||
|
* away and should correctly tear things down.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
static VOID FspFsvolCreateRequestFini(PVOID Context[3])
|
static VOID FspFsvolCreateRequestFini(PVOID Context[3])
|
||||||
{
|
{
|
||||||
PAGED_CODE();
|
PAGED_CODE();
|
||||||
|
@ -517,6 +517,8 @@ typedef struct
|
|||||||
/* interlocked access */
|
/* interlocked access */
|
||||||
LONG RefCount;
|
LONG RefCount;
|
||||||
LONG OpenCount;
|
LONG OpenCount;
|
||||||
|
/* locked access */
|
||||||
|
SHARE_ACCESS ShareAccess;
|
||||||
/* read-only after creation (and insertion in the GenericTable) */
|
/* read-only after creation (and insertion in the GenericTable) */
|
||||||
PDEVICE_OBJECT FsvolDeviceObject;
|
PDEVICE_OBJECT FsvolDeviceObject;
|
||||||
UINT64 UserContext;
|
UINT64 UserContext;
|
||||||
@ -527,21 +529,15 @@ typedef struct
|
|||||||
NTSTATUS FspFileContextCreate(PDEVICE_OBJECT DeviceObject,
|
NTSTATUS FspFileContextCreate(PDEVICE_OBJECT DeviceObject,
|
||||||
ULONG ExtraSize, FSP_FILE_CONTEXT **PFsContext);
|
ULONG ExtraSize, FSP_FILE_CONTEXT **PFsContext);
|
||||||
VOID FspFileContextDelete(FSP_FILE_CONTEXT *FsContext);
|
VOID FspFileContextDelete(FSP_FILE_CONTEXT *FsContext);
|
||||||
FSP_FILE_CONTEXT *FspFileContextOpen(PDEVICE_OBJECT FsvolDeviceObject,
|
FSP_FILE_CONTEXT *FspFileContextOpen(FSP_FILE_CONTEXT *FsContext, PFILE_OBJECT FileObject,
|
||||||
FSP_FILE_CONTEXT *FsContext);
|
DWORD GrantedAccess, DWORD ShareAccess, NTSTATUS *PResult);
|
||||||
VOID FspFileContextClose(PDEVICE_OBJECT FsvolDeviceObject,
|
VOID FspFileContextClose(FSP_FILE_CONTEXT *FsContext, PFILE_OBJECT FileObject);
|
||||||
FSP_FILE_CONTEXT *FsContext);
|
|
||||||
static inline
|
static inline
|
||||||
VOID FspFileContextRetain(FSP_FILE_CONTEXT *FsContext)
|
VOID FspFileContextRetain(FSP_FILE_CONTEXT *FsContext)
|
||||||
{
|
{
|
||||||
InterlockedIncrement(&FsContext->RefCount);
|
InterlockedIncrement(&FsContext->RefCount);
|
||||||
}
|
}
|
||||||
static inline
|
static inline
|
||||||
VOID FspFileContextRetain2(FSP_FILE_CONTEXT *FsContext)
|
|
||||||
{
|
|
||||||
InterlockedAdd(&FsContext->RefCount, 2);
|
|
||||||
}
|
|
||||||
static inline
|
|
||||||
VOID FspFileContextRelease(FSP_FILE_CONTEXT *FsContext)
|
VOID FspFileContextRelease(FSP_FILE_CONTEXT *FsContext)
|
||||||
{
|
{
|
||||||
LONG RefCount = InterlockedDecrement(&FsContext->RefCount);
|
LONG RefCount = InterlockedDecrement(&FsContext->RefCount);
|
||||||
|
@ -73,8 +73,8 @@ VOID FspFileContextDelete(FSP_FILE_CONTEXT *FsContext)
|
|||||||
FspFree(FsContext);
|
FspFree(FsContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
FSP_FILE_CONTEXT *FspFileContextOpen(PDEVICE_OBJECT FsvolDeviceObject,
|
FSP_FILE_CONTEXT *FspFileContextOpen(FSP_FILE_CONTEXT *FsContext, PFILE_OBJECT FileObject,
|
||||||
FSP_FILE_CONTEXT *FsContext)
|
DWORD GrantedAccess, DWORD ShareAccess, NTSTATUS *PResult)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Attempt to insert our FsContext into the volume device's generic table.
|
* Attempt to insert our FsContext into the volume device's generic table.
|
||||||
@ -82,6 +82,7 @@ FSP_FILE_CONTEXT *FspFileContextOpen(PDEVICE_OBJECT FsvolDeviceObject,
|
|||||||
* FsContext instead.
|
* FsContext instead.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
PDEVICE_OBJECT FsvolDeviceObject = FsContext->FsvolDeviceObject;
|
||||||
FSP_FILE_CONTEXT *OpenedFsContext;
|
FSP_FILE_CONTEXT *OpenedFsContext;
|
||||||
BOOLEAN Inserted;
|
BOOLEAN Inserted;
|
||||||
|
|
||||||
@ -94,29 +95,58 @@ FSP_FILE_CONTEXT *FspFileContextOpen(PDEVICE_OBJECT FsvolDeviceObject,
|
|||||||
if (Inserted)
|
if (Inserted)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The new FsContext was inserted into the Context table.
|
* The new FsContext was inserted into the Context table. Set its share access
|
||||||
* Retain it. There should be (at least) two references to this FsContext,
|
* and retain and open it. There should be (at least) two references to this
|
||||||
* one from our caller and one from the Context table.
|
* FsContext, one from our caller and one from the Context table.
|
||||||
*/
|
*/
|
||||||
ASSERT(OpenedFsContext == FsContext);
|
ASSERT(OpenedFsContext == FsContext);
|
||||||
|
|
||||||
|
IoSetShareAccess(GrantedAccess, ShareAccess, FileObject, &FsContext->ShareAccess);
|
||||||
FspFileContextRetain(OpenedFsContext);
|
FspFileContextRetain(OpenedFsContext);
|
||||||
|
OpenedFsContext->OpenCount++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The new FsContext was NOT inserted into the Context table,
|
* The new FsContext was NOT inserted into the Context table. Instead we are
|
||||||
* instead a prior FsContext is being opened.
|
* opening a prior FsContext that we found in the table.
|
||||||
* Release the new FsContext since the caller will no longer reference it,
|
*
|
||||||
* and retain the prior FsContext TWICE, once for our caller and once for
|
* First check and update the share access. If successful then retain the
|
||||||
* the Context table.
|
* prior FsContext for our caller and release the original FsContext.
|
||||||
*/
|
*/
|
||||||
ASSERT(OpenedFsContext != FsContext);
|
ASSERT(OpenedFsContext != FsContext);
|
||||||
|
|
||||||
FspFileContextRetain2(OpenedFsContext);
|
/*
|
||||||
}
|
* FastFat says to do the following on Vista and above.
|
||||||
|
*
|
||||||
|
* Quote:
|
||||||
|
* Do an extra test for writeable user sections if the user did not allow
|
||||||
|
* write sharing - this is neccessary since a section may exist with no handles
|
||||||
|
* open to the file its based against.
|
||||||
|
*/
|
||||||
|
NTSTATUS Result = STATUS_SUCCESS;
|
||||||
|
if (!FlagOn(ShareAccess, FILE_SHARE_WRITE) &&
|
||||||
|
FlagOn(GrantedAccess,
|
||||||
|
FILE_EXECUTE | FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE) &&
|
||||||
|
MmDoesFileHaveUserWritableReferences(&FsContext->NonPaged->SectionObjectPointers))
|
||||||
|
Result = STATUS_SHARING_VIOLATION;
|
||||||
|
|
||||||
InterlockedIncrement(&OpenedFsContext->OpenCount);
|
/* share access check */
|
||||||
|
if (NT_SUCCESS(Result))
|
||||||
|
Result = IoCheckShareAccess(GrantedAccess, ShareAccess, FileObject, &FsContext->ShareAccess, TRUE);
|
||||||
|
if (NT_SUCCESS(Result))
|
||||||
|
{
|
||||||
|
FspFileContextRetain(OpenedFsContext);
|
||||||
|
OpenedFsContext->OpenCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (0 != PResult)
|
||||||
|
*PResult = Result;
|
||||||
|
|
||||||
|
OpenedFsContext = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FspFsvolDeviceUnlockContextTable(FsvolDeviceObject);
|
FspFsvolDeviceUnlockContextTable(FsvolDeviceObject);
|
||||||
|
|
||||||
@ -126,20 +156,24 @@ FSP_FILE_CONTEXT *FspFileContextOpen(PDEVICE_OBJECT FsvolDeviceObject,
|
|||||||
return OpenedFsContext;
|
return OpenedFsContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID FspFileContextClose(PDEVICE_OBJECT FsvolDeviceObject,
|
VOID FspFileContextClose(FSP_FILE_CONTEXT *FsContext, PFILE_OBJECT FileObject)
|
||||||
FSP_FILE_CONTEXT *FsContext)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Close the FsContext. If the OpenCount becomes zero remove it
|
* Close the FsContext. If the OpenCount becomes zero remove it
|
||||||
* from the Context table.
|
* from the Context table.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (0 == InterlockedDecrement(&FsContext->OpenCount))
|
PDEVICE_OBJECT FsvolDeviceObject = FsContext->FsvolDeviceObject;
|
||||||
{
|
BOOLEAN Deleted = FALSE;
|
||||||
FspFsvolDeviceLockContextTable(FsvolDeviceObject);
|
|
||||||
FspFsvolDeviceDeleteContext(FsvolDeviceObject, FsContext->UserContext, 0);
|
|
||||||
FspFsvolDeviceUnlockContextTable(FsvolDeviceObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
FspFsvolDeviceLockContextTable(FsvolDeviceObject);
|
||||||
|
|
||||||
|
IoRemoveShareAccess(FileObject, &FsContext->ShareAccess);
|
||||||
|
if (0 == --FsContext->OpenCount)
|
||||||
|
FspFsvolDeviceDeleteContext(FsvolDeviceObject, FsContext->UserContext, &Deleted);
|
||||||
|
|
||||||
|
FspFsvolDeviceUnlockContextTable(FsvolDeviceObject);
|
||||||
|
|
||||||
|
if (Deleted)
|
||||||
FspFileContextRelease(FsContext);
|
FspFileContextRelease(FsContext);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user