mirror of
https://github.com/reactos/reactos.git
synced 2024-07-05 12:15:46 +00:00
[NPFS]
- Implement NpQueryClientProcess and NpSetClientProcess - Add a fast I/O dispatch table and implement NpFastRead and NpFastWrite The NPFS driver is now good enough to boot Windows 2003 to desktop! svn path=/trunk/; revision=61906
This commit is contained in:
parent
e61f3d956b
commit
005703c2dc
|
@ -53,10 +53,86 @@ NpInternalWrite(IN PDEVICE_OBJECT DeviceObject,
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
NpQueryClientProcess(IN PDEVICE_OBJECT DeviceObject,
|
NpQueryClientProcess(IN PDEVICE_OBJECT DeviceObject,
|
||||||
IN PIRP Irp)
|
IN PIRP Irp)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED;
|
PIO_STACK_LOCATION IoStackLocation;
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
NODE_TYPE_CODE NodeTypeCode;
|
||||||
|
PNP_CCB Ccb;
|
||||||
|
PNP_CLIENT_PROCESS ClientSession, QueryBuffer;
|
||||||
|
ULONG Length;
|
||||||
|
PAGED_CODE();
|
||||||
|
|
||||||
|
/* Get the current stack location */
|
||||||
|
IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
|
||||||
|
|
||||||
|
/* Decode the file object and check the node type */
|
||||||
|
NodeTypeCode = NpDecodeFileObject(IoStackLocation->FileObject, 0, &Ccb, 0);
|
||||||
|
if (NodeTypeCode != NPFS_NTC_CCB)
|
||||||
|
{
|
||||||
|
return STATUS_PIPE_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the length of the query buffer */
|
||||||
|
Length = IoStackLocation->Parameters.QueryFile.Length;
|
||||||
|
if (Length < 8)
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuffer = Irp->AssociatedIrp.SystemBuffer;
|
||||||
|
|
||||||
|
/* Lock the Ccb */
|
||||||
|
ExAcquireResourceExclusiveLite(&Ccb->NonPagedCcb->Lock, TRUE);
|
||||||
|
|
||||||
|
/* Get the CCBs client session and check if it's set */
|
||||||
|
ClientSession = Ccb->ClientSession;
|
||||||
|
if (ClientSession != NULL)
|
||||||
|
{
|
||||||
|
/* Copy first 2 fields */
|
||||||
|
QueryBuffer->Unknown = ClientSession->Unknown;
|
||||||
|
QueryBuffer->Process = ClientSession->Process;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Copy the process from the CCB */
|
||||||
|
QueryBuffer->Unknown = NULL;
|
||||||
|
QueryBuffer->Process = Ccb->Process;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Does the caller provide a large enough buffer for the full data? */
|
||||||
|
if (Length >= sizeof(NP_CLIENT_PROCESS))
|
||||||
|
{
|
||||||
|
Irp->IoStatus.Information = sizeof(NP_CLIENT_PROCESS);
|
||||||
|
|
||||||
|
/* Do we have a ClientSession structure? */
|
||||||
|
if (ClientSession != NULL)
|
||||||
|
{
|
||||||
|
/* Copy length and the data */
|
||||||
|
QueryBuffer->DataLength = ClientSession->DataLength;
|
||||||
|
RtlCopyMemory(QueryBuffer->Buffer,
|
||||||
|
ClientSession->Buffer,
|
||||||
|
ClientSession->DataLength);
|
||||||
|
|
||||||
|
/* NULL terminate the buffer */
|
||||||
|
NT_ASSERT(QueryBuffer->DataLength <= 30);
|
||||||
|
QueryBuffer->Buffer[QueryBuffer->DataLength / sizeof(WCHAR)] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* No data */
|
||||||
|
QueryBuffer->DataLength = 0;
|
||||||
|
QueryBuffer->Buffer[0] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Irp->IoStatus.Information = FIELD_OFFSET(NP_CLIENT_PROCESS, DataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unlock the Ccb */
|
||||||
|
ExReleaseResourceLite(&Ccb->NonPagedCcb->Lock);
|
||||||
|
|
||||||
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
|
@ -64,8 +140,72 @@ NTAPI
|
||||||
NpSetClientProcess(IN PDEVICE_OBJECT DeviceObject,
|
NpSetClientProcess(IN PDEVICE_OBJECT DeviceObject,
|
||||||
IN PIRP Irp)
|
IN PIRP Irp)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED;
|
PIO_STACK_LOCATION IoStackLocation;
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
NODE_TYPE_CODE NodeTypeCode;
|
||||||
|
PNP_CCB Ccb;
|
||||||
|
ULONG Length;
|
||||||
|
PNP_CLIENT_PROCESS InputBuffer, ClientSession, OldClientSession;
|
||||||
|
PAGED_CODE();
|
||||||
|
|
||||||
|
/* Get the current stack location */
|
||||||
|
IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
|
||||||
|
|
||||||
|
/* Only kernel calls are allowed! */
|
||||||
|
if (IoStackLocation->MinorFunction != IRP_MN_KERNEL_CALL)
|
||||||
|
{
|
||||||
|
return STATUS_ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode the file object and check the node type */
|
||||||
|
NodeTypeCode = NpDecodeFileObject(IoStackLocation->FileObject, 0, &Ccb, 0);
|
||||||
|
if (NodeTypeCode != NPFS_NTC_CCB)
|
||||||
|
{
|
||||||
|
return STATUS_PIPE_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the length of the query buffer and check if it's valid */
|
||||||
|
Length = IoStackLocation->Parameters.QueryFile.Length;
|
||||||
|
if (Length != sizeof(NP_CLIENT_PROCESS))
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the buffer and check if the data Length is valid */
|
||||||
|
InputBuffer = Irp->AssociatedIrp.SystemBuffer;
|
||||||
|
if (InputBuffer->DataLength > 30)
|
||||||
|
{
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate a new structure */
|
||||||
|
ClientSession = ExAllocatePoolWithQuotaTag(PagedPool,
|
||||||
|
sizeof(NP_CLIENT_PROCESS),
|
||||||
|
'iFpN');
|
||||||
|
|
||||||
|
/* Copy the full input buffer */
|
||||||
|
RtlCopyMemory(ClientSession, InputBuffer, sizeof(NP_CLIENT_PROCESS));
|
||||||
|
|
||||||
|
/* Lock the Ccb */
|
||||||
|
ExAcquireResourceExclusiveLite(&Ccb->NonPagedCcb->Lock, TRUE);
|
||||||
|
|
||||||
|
/* Get the old ClientSession and set the new */
|
||||||
|
OldClientSession = Ccb->ClientSession;
|
||||||
|
Ccb->ClientSession = ClientSession;
|
||||||
|
|
||||||
|
/* Copy the process to the CCB */
|
||||||
|
Ccb->Process = ClientSession->Process;
|
||||||
|
|
||||||
|
/* Unlock the Ccb */
|
||||||
|
ExReleaseResourceLite(&Ccb->NonPagedCcb->Lock);
|
||||||
|
|
||||||
|
/* Check if there was already a ClientSession */
|
||||||
|
if (OldClientSession != NULL)
|
||||||
|
{
|
||||||
|
/* Free it */
|
||||||
|
ExFreePoolWithTag(OldClientSession, 'iFpN');
|
||||||
|
}
|
||||||
|
|
||||||
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
|
@ -201,7 +341,7 @@ NpListen(IN PDEVICE_OBJECT DeviceObject,
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
NpPeek(IN PDEVICE_OBJECT DeviceObject,
|
NpPeek(IN PDEVICE_OBJECT DeviceObject,
|
||||||
IN PIRP Irp,
|
IN PIRP Irp,
|
||||||
IN PLIST_ENTRY List)
|
IN PLIST_ENTRY List)
|
||||||
{
|
{
|
||||||
PIO_STACK_LOCATION IoStack;
|
PIO_STACK_LOCATION IoStack;
|
||||||
|
@ -490,7 +630,7 @@ NpTransceive(IN PDEVICE_OBJECT DeviceObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NT_SUCCESS(Status)) goto Quickie;
|
if (!NT_SUCCESS(Status)) goto Quickie;
|
||||||
|
|
||||||
if (EventBuffer) KeSetEvent(EventBuffer->Event, IO_NO_INCREMENT, FALSE);
|
if (EventBuffer) KeSetEvent(EventBuffer->Event, IO_NO_INCREMENT, FALSE);
|
||||||
ASSERT(ReadQueue->QueueState == Empty);
|
ASSERT(ReadQueue->QueueState == Empty);
|
||||||
Status = NpAddDataQueueEntry(NamedPipeEnd,
|
Status = NpAddDataQueueEntry(NamedPipeEnd,
|
||||||
|
@ -648,7 +788,7 @@ NpCommonFileSystemControl(IN PDEVICE_OBJECT DeviceObject,
|
||||||
NpAcquireSharedVcb();
|
NpAcquireSharedVcb();
|
||||||
Status = NpInternalTransceive(DeviceObject, Irp, &DeferredList);
|
Status = NpInternalTransceive(DeviceObject, Irp, &DeferredList);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FSCTL_PIPE_INTERNAL_READ_OVFLOW:
|
case FSCTL_PIPE_INTERNAL_READ_OVFLOW:
|
||||||
Overflow = TRUE;
|
Overflow = TRUE;
|
||||||
// on purpose
|
// on purpose
|
||||||
|
|
|
@ -20,6 +20,14 @@ PVOID NpAliases;
|
||||||
PNPFS_ALIAS NpAliasList;
|
PNPFS_ALIAS NpAliasList;
|
||||||
PNPFS_ALIAS NpAliasListByLength[MAX_INDEXED_LENGTH + 1 - MIN_INDEXED_LENGTH];
|
PNPFS_ALIAS NpAliasListByLength[MAX_INDEXED_LENGTH + 1 - MIN_INDEXED_LENGTH];
|
||||||
|
|
||||||
|
FAST_IO_DISPATCH NpFastIoDispatch =
|
||||||
|
{
|
||||||
|
sizeof(FAST_IO_DISPATCH),
|
||||||
|
NULL,
|
||||||
|
NpFastRead,
|
||||||
|
NpFastWrite,
|
||||||
|
};
|
||||||
|
|
||||||
/* FUNCTIONS ******************************************************************/
|
/* FUNCTIONS ******************************************************************/
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
|
@ -311,6 +319,7 @@ NpFsdDirectoryControl(IN PDEVICE_OBJECT DeviceObject,
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
return STATUS_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
DriverEntry(IN PDRIVER_OBJECT DriverObject,
|
DriverEntry(IN PDRIVER_OBJECT DriverObject,
|
||||||
|
@ -321,7 +330,7 @@ DriverEntry(IN PDRIVER_OBJECT DriverObject,
|
||||||
NTSTATUS Status;
|
NTSTATUS Status;
|
||||||
UNREFERENCED_PARAMETER(RegistryPath);
|
UNREFERENCED_PARAMETER(RegistryPath);
|
||||||
|
|
||||||
DPRINT1("Next-Generation NPFS-Lite\n");
|
DPRINT1("Next-Generation NPFS-Advanced\n");
|
||||||
|
|
||||||
Status = NpInitializeAliases();
|
Status = NpInitializeAliases();
|
||||||
if (!NT_SUCCESS(Status))
|
if (!NT_SUCCESS(Status))
|
||||||
|
@ -347,6 +356,8 @@ DriverEntry(IN PDRIVER_OBJECT DriverObject,
|
||||||
|
|
||||||
DriverObject->DriverUnload = NULL;
|
DriverObject->DriverUnload = NULL;
|
||||||
|
|
||||||
|
DriverObject->FastIoDispatch = &NpFastIoDispatch;
|
||||||
|
|
||||||
RtlInitUnicodeString(&DeviceName, L"\\Device\\NamedPipe");
|
RtlInitUnicodeString(&DeviceName, L"\\Device\\NamedPipe");
|
||||||
Status = IoCreateDevice(DriverObject,
|
Status = IoCreateDevice(DriverObject,
|
||||||
sizeof(NP_VCB),
|
sizeof(NP_VCB),
|
||||||
|
|
|
@ -360,6 +360,18 @@ typedef struct _NPFS_QUERY_VALUE_CONTEXT
|
||||||
extern PNPFS_ALIAS NpAliasList;
|
extern PNPFS_ALIAS NpAliasList;
|
||||||
extern PNPFS_ALIAS NpAliasListByLength[MAX_INDEXED_LENGTH + 1 - MIN_INDEXED_LENGTH];
|
extern PNPFS_ALIAS NpAliasListByLength[MAX_INDEXED_LENGTH + 1 - MIN_INDEXED_LENGTH];
|
||||||
|
|
||||||
|
//
|
||||||
|
// This structure is actually a user-mode structure and should go into a share header
|
||||||
|
//
|
||||||
|
typedef struct _NP_CLIENT_PROCESS
|
||||||
|
{
|
||||||
|
PVOID Unknown;
|
||||||
|
PVOID Process;
|
||||||
|
USHORT DataLength;
|
||||||
|
WCHAR Buffer[17];
|
||||||
|
} NP_CLIENT_PROCESS, *PNP_CLIENT_PROCESS;
|
||||||
|
|
||||||
|
|
||||||
/* FUNCTIONS ******************************************************************/
|
/* FUNCTIONS ******************************************************************/
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -674,6 +686,34 @@ NTAPI
|
||||||
NpFsdRead(IN PDEVICE_OBJECT DeviceObject,
|
NpFsdRead(IN PDEVICE_OBJECT DeviceObject,
|
||||||
IN PIRP Irp);
|
IN PIRP Irp);
|
||||||
|
|
||||||
|
_Function_class_(FAST_IO_READ)
|
||||||
|
_IRQL_requires_same_
|
||||||
|
BOOLEAN
|
||||||
|
NTAPI
|
||||||
|
NpFastRead(
|
||||||
|
_In_ PFILE_OBJECT FileObject,
|
||||||
|
_In_ PLARGE_INTEGER FileOffset,
|
||||||
|
_In_ ULONG Length,
|
||||||
|
_In_ BOOLEAN Wait,
|
||||||
|
_In_ ULONG LockKey,
|
||||||
|
_Out_ PVOID Buffer,
|
||||||
|
_Out_ PIO_STATUS_BLOCK IoStatus,
|
||||||
|
_In_ PDEVICE_OBJECT DeviceObject);
|
||||||
|
|
||||||
|
_Function_class_(FAST_IO_WRITE)
|
||||||
|
_IRQL_requires_same_
|
||||||
|
BOOLEAN
|
||||||
|
NTAPI
|
||||||
|
NpFastWrite(
|
||||||
|
_In_ PFILE_OBJECT FileObject,
|
||||||
|
_In_ PLARGE_INTEGER FileOffset,
|
||||||
|
_In_ ULONG Length,
|
||||||
|
_In_ BOOLEAN Wait,
|
||||||
|
_In_ ULONG LockKey,
|
||||||
|
_In_ PVOID Buffer,
|
||||||
|
_Out_ PIO_STATUS_BLOCK IoStatus,
|
||||||
|
_In_ PDEVICE_OBJECT DeviceObject);
|
||||||
|
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
/* GLOBALS ********************************************************************/
|
/* GLOBALS ********************************************************************/
|
||||||
|
|
||||||
LONG NpSlowReadCalls;
|
LONG NpSlowReadCalls;
|
||||||
|
ULONG NpFastReadTrue;
|
||||||
|
ULONG NpFastReadFalse;
|
||||||
|
|
||||||
/* FUNCTIONS ******************************************************************/
|
/* FUNCTIONS ******************************************************************/
|
||||||
|
|
||||||
|
@ -23,8 +25,8 @@ BOOLEAN
|
||||||
NTAPI
|
NTAPI
|
||||||
NpCommonRead(IN PFILE_OBJECT FileObject,
|
NpCommonRead(IN PFILE_OBJECT FileObject,
|
||||||
IN PVOID Buffer,
|
IN PVOID Buffer,
|
||||||
IN ULONG BufferSize,
|
IN ULONG BufferSize,
|
||||||
OUT PIO_STATUS_BLOCK IoStatus,
|
OUT PIO_STATUS_BLOCK IoStatus,
|
||||||
IN PIRP Irp,
|
IN PIRP Irp,
|
||||||
IN PLIST_ENTRY List)
|
IN PLIST_ENTRY List)
|
||||||
{
|
{
|
||||||
|
@ -190,4 +192,46 @@ NpFsdRead(IN PDEVICE_OBJECT DeviceObject,
|
||||||
return IoStatus.Status;
|
return IoStatus.Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_Function_class_(FAST_IO_READ)
|
||||||
|
_IRQL_requires_same_
|
||||||
|
BOOLEAN
|
||||||
|
NTAPI
|
||||||
|
NpFastRead(
|
||||||
|
_In_ PFILE_OBJECT FileObject,
|
||||||
|
_In_ PLARGE_INTEGER FileOffset,
|
||||||
|
_In_ ULONG Length,
|
||||||
|
_In_ BOOLEAN Wait,
|
||||||
|
_In_ ULONG LockKey,
|
||||||
|
_Out_ PVOID Buffer,
|
||||||
|
_Out_ PIO_STATUS_BLOCK IoStatus,
|
||||||
|
_In_ PDEVICE_OBJECT DeviceObject)
|
||||||
|
{
|
||||||
|
LIST_ENTRY DeferredList;
|
||||||
|
BOOLEAN Result;
|
||||||
|
PAGED_CODE();
|
||||||
|
|
||||||
|
InitializeListHead(&DeferredList);
|
||||||
|
|
||||||
|
FsRtlEnterFileSystem();
|
||||||
|
NpAcquireSharedVcb();
|
||||||
|
|
||||||
|
Result = NpCommonRead(FileObject,
|
||||||
|
Buffer,
|
||||||
|
Length,
|
||||||
|
IoStatus,
|
||||||
|
NULL,
|
||||||
|
&DeferredList);
|
||||||
|
if (Result)
|
||||||
|
++NpFastReadTrue;
|
||||||
|
else
|
||||||
|
++NpFastReadFalse;
|
||||||
|
|
||||||
|
NpReleaseVcb();
|
||||||
|
NpCompleteDeferredIrps(&DeferredList);
|
||||||
|
FsRtlExitFileSystem();
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
/* GLOBALS ********************************************************************/
|
/* GLOBALS ********************************************************************/
|
||||||
|
|
||||||
LONG NpSlowWriteCalls;
|
LONG NpSlowWriteCalls;
|
||||||
|
ULONG NpFastWriteTrue;
|
||||||
|
ULONG NpFastWriteFalse;
|
||||||
|
|
||||||
/* FUNCTIONS ******************************************************************/
|
/* FUNCTIONS ******************************************************************/
|
||||||
|
|
||||||
|
@ -23,10 +25,10 @@ BOOLEAN
|
||||||
NTAPI
|
NTAPI
|
||||||
NpCommonWrite(IN PFILE_OBJECT FileObject,
|
NpCommonWrite(IN PFILE_OBJECT FileObject,
|
||||||
IN PVOID Buffer,
|
IN PVOID Buffer,
|
||||||
IN ULONG DataSize,
|
IN ULONG DataSize,
|
||||||
IN PETHREAD Thread,
|
IN PETHREAD Thread,
|
||||||
IN PIO_STATUS_BLOCK IoStatus,
|
IN PIO_STATUS_BLOCK IoStatus,
|
||||||
IN PIRP Irp,
|
IN PIRP Irp,
|
||||||
IN PLIST_ENTRY List)
|
IN PLIST_ENTRY List)
|
||||||
{
|
{
|
||||||
NODE_TYPE_CODE NodeType;
|
NODE_TYPE_CODE NodeType;
|
||||||
|
@ -207,4 +209,47 @@ NpFsdWrite(IN PDEVICE_OBJECT DeviceObject,
|
||||||
return IoStatus.Status;
|
return IoStatus.Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_Function_class_(FAST_IO_WRITE)
|
||||||
|
_IRQL_requires_same_
|
||||||
|
BOOLEAN
|
||||||
|
NTAPI
|
||||||
|
NpFastWrite(
|
||||||
|
_In_ PFILE_OBJECT FileObject,
|
||||||
|
_In_ PLARGE_INTEGER FileOffset,
|
||||||
|
_In_ ULONG Length,
|
||||||
|
_In_ BOOLEAN Wait,
|
||||||
|
_In_ ULONG LockKey,
|
||||||
|
_In_ PVOID Buffer,
|
||||||
|
_Out_ PIO_STATUS_BLOCK IoStatus,
|
||||||
|
_In_ PDEVICE_OBJECT DeviceObject)
|
||||||
|
{
|
||||||
|
LIST_ENTRY DeferredList;
|
||||||
|
BOOLEAN Result;
|
||||||
|
PAGED_CODE();
|
||||||
|
|
||||||
|
InitializeListHead(&DeferredList);
|
||||||
|
|
||||||
|
FsRtlEnterFileSystem();
|
||||||
|
NpAcquireSharedVcb();
|
||||||
|
|
||||||
|
Result = NpCommonWrite(FileObject,
|
||||||
|
Buffer,
|
||||||
|
Length,
|
||||||
|
PsGetCurrentThread(),
|
||||||
|
IoStatus,
|
||||||
|
NULL,
|
||||||
|
&DeferredList);
|
||||||
|
if (Result)
|
||||||
|
++NpFastWriteTrue;
|
||||||
|
else
|
||||||
|
++NpFastWriteFalse;
|
||||||
|
|
||||||
|
NpReleaseVcb();
|
||||||
|
NpCompleteDeferredIrps(&DeferredList);
|
||||||
|
FsRtlExitFileSystem();
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
Loading…
Reference in a new issue