winfsp/src/sys/psbuffer.c
2024-01-08 12:38:44 +00:00

303 lines
8.7 KiB
C

/**
* @file sys/psbuffer.c
*
* @copyright 2015-2024 Bill Zissimopoulos
*/
/*
* This file is part of WinFsp.
*
* You can redistribute it and/or modify it under the terms of the GNU
* General Public License version 3 as published by the Free Software
* Foundation.
*
* Licensees holding a valid commercial license may use this software
* in accordance with the commercial license agreement provided in
* conjunction with the software. The terms and conditions of any such
* commercial license agreement shall govern, supersede, and render
* ineffective any application of the GPLv3 license to this software,
* notwithstanding of any reference thereto in the software or
* associated repository.
*/
#include <sys/driver.h>
#define SafeGetCurrentProcessId() (PsGetProcessId(PsGetCurrentProcess()))
#define FspProcessBufferCountMax (2 >= FspProcessorCount ? 2 : (8 <= FspProcessorCount ? 8 : FspProcessorCount))
#define ProcessBufferBucketCount 61 /* are you going to have that many file systems? */
typedef struct _FSP_PROCESS_BUFFER_ITEM
{
struct _FSP_PROCESS_BUFFER_ITEM *DictNext;
struct _FSP_PROCESS_BUFFER_LIST_ENTRY *BufferList;
ULONG BufferCount;
HANDLE ProcessId;
} FSP_PROCESS_BUFFER_ITEM;
typedef struct _FSP_PROCESS_BUFFER_LIST_ENTRY
{
struct _FSP_PROCESS_BUFFER_LIST_ENTRY *Next;
PVOID Buffer;
} FSP_PROCESS_BUFFER_LIST_ENTRY;
static KSPIN_LOCK ProcessBufferLock;
static FSP_PROCESS_BUFFER_ITEM *ProcessBufferBuckets[ProcessBufferBucketCount];
static VOID FspProcessBufferNotifyRoutine(HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create);
static inline FSP_PROCESS_BUFFER_ITEM *FspProcessBufferLookupItemAtDpcLevel(HANDLE ProcessId)
{
FSP_PROCESS_BUFFER_ITEM *Item = 0;
ULONG HashIndex = FspHashMixPointer(ProcessId) % ProcessBufferBucketCount;
for (FSP_PROCESS_BUFFER_ITEM *ItemX = ProcessBufferBuckets[HashIndex]; ItemX; ItemX = ItemX->DictNext)
if (ItemX->ProcessId == ProcessId)
{
Item = ItemX;
break;
}
return Item;
}
static inline VOID FspProcessBufferAddItemAtDpcLevel(FSP_PROCESS_BUFFER_ITEM *Item)
{
ULONG HashIndex = FspHashMixPointer(Item->ProcessId) % ProcessBufferBucketCount;
#if DBG
for (FSP_PROCESS_BUFFER_ITEM *ItemX = ProcessBufferBuckets[HashIndex]; ItemX; ItemX = ItemX->DictNext)
ASSERT(ItemX->ProcessId != Item->ProcessId);
#endif
Item->DictNext = ProcessBufferBuckets[HashIndex];
ProcessBufferBuckets[HashIndex] = Item;
}
static inline FSP_PROCESS_BUFFER_ITEM *FspProcessBufferRemoveItemAtDpcLevel(HANDLE ProcessId)
{
FSP_PROCESS_BUFFER_ITEM *Item = 0;
ULONG HashIndex = FspHashMixPointer(ProcessId) % ProcessBufferBucketCount;
for (FSP_PROCESS_BUFFER_ITEM **P = &ProcessBufferBuckets[HashIndex]; *P; P = &(*P)->DictNext)
if ((*P)->ProcessId == ProcessId)
{
Item = *P;
*P = (*P)->DictNext;
break;
}
return Item;
}
static inline VOID FspProcessBufferReuseEntry(HANDLE ProcessId,
FSP_PROCESS_BUFFER_LIST_ENTRY *BufferEntry)
{
KIRQL Irql;
FSP_PROCESS_BUFFER_ITEM *Item;
KeAcquireSpinLock(&ProcessBufferLock, &Irql);
Item = FspProcessBufferLookupItemAtDpcLevel(ProcessId);
if (0 != Item)
{
BufferEntry->Next = Item->BufferList;
Item->BufferList = BufferEntry;
}
KeReleaseSpinLock(&ProcessBufferLock, Irql);
if (0 == Item)
{
if (0 != BufferEntry->Buffer)
{
SIZE_T BufferSize = 0;
ZwFreeVirtualMemory(ZwCurrentProcess(), &BufferEntry->Buffer, &BufferSize, MEM_RELEASE);
}
FspFree(BufferEntry);
}
}
NTSTATUS FspProcessBufferInitialize(VOID)
{
KeInitializeSpinLock(&ProcessBufferLock);
return PsSetCreateProcessNotifyRoutine(FspProcessBufferNotifyRoutine, FALSE);
}
VOID FspProcessBufferFinalize(VOID)
{
PsSetCreateProcessNotifyRoutine(FspProcessBufferNotifyRoutine, TRUE);
/*
* Free any items in our process hash table.
* Any virtual memory was released when the corresponding processes went away.
*/
for (ULONG HashIndex = 0; ProcessBufferBucketCount > HashIndex; HashIndex++)
{
for (FSP_PROCESS_BUFFER_ITEM *Item = ProcessBufferBuckets[HashIndex], *DictNext; Item; Item = DictNext)
{
for (FSP_PROCESS_BUFFER_LIST_ENTRY *P = Item->BufferList, *Next; P; P = Next)
{
Next = P->Next;
FspFree(P);
}
DictNext = Item->DictNext;
FspFree(Item);
}
ProcessBufferBuckets[HashIndex] = 0;
}
}
static VOID FspProcessBufferNotifyRoutine(HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create)
{
if (!Create)
FspProcessBufferCollect(ProcessId);
}
VOID FspProcessBufferCollect(HANDLE ProcessId)
{
KIRQL Irql;
FSP_PROCESS_BUFFER_ITEM *Item = 0;
KeAcquireSpinLock(&ProcessBufferLock, &Irql);
Item = FspProcessBufferRemoveItemAtDpcLevel(ProcessId);
KeReleaseSpinLock(&ProcessBufferLock, Irql);
if (0 != Item)
{
DEBUGLOG("pid=%ld", (ULONG)(UINT_PTR)ProcessId);
for (FSP_PROCESS_BUFFER_LIST_ENTRY *P = Item->BufferList, *Next; P; P = Next)
{
Next = P->Next;
FspFree(P);
}
FspFree(Item);
}
}
NTSTATUS FspProcessBufferAcquire(SIZE_T BufferSize, PVOID *PBufferCookie, PVOID *PBuffer)
{
if (FspProcessBufferSizeMax >= BufferSize)
{
HANDLE ProcessId = SafeGetCurrentProcessId();
KIRQL Irql;
FSP_PROCESS_BUFFER_ITEM *Item, *NewItem;
FSP_PROCESS_BUFFER_LIST_ENTRY *BufferEntry = 0;
BOOLEAN AllocNoReuse;
NTSTATUS Result;
KeAcquireSpinLock(&ProcessBufferLock, &Irql);
Item = FspProcessBufferLookupItemAtDpcLevel(ProcessId);
if (0 != Item)
{
BufferEntry = Item->BufferList;
if (0 != BufferEntry)
Item->BufferList = BufferEntry->Next;
}
AllocNoReuse = 0 == BufferEntry &&
(0 != Item && FspProcessBufferCountMax <= Item->BufferCount);
KeReleaseSpinLock(&ProcessBufferLock, Irql);
if (AllocNoReuse)
goto alloc_no_reuse;
if (0 == BufferEntry)
{
*PBufferCookie = 0;
*PBuffer = 0;
BufferEntry = FspAllocNonPaged(sizeof *BufferEntry);
if (0 == BufferEntry)
return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(BufferEntry, sizeof *BufferEntry);
NewItem = FspAllocNonPaged(sizeof *NewItem);
if (0 == NewItem)
{
FspFree(BufferEntry);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(NewItem, sizeof *NewItem);
KeAcquireSpinLock(&ProcessBufferLock, &Irql);
Item = FspProcessBufferLookupItemAtDpcLevel(ProcessId);
if (0 == Item)
{
Item = NewItem;
NewItem = 0;
Item->BufferCount = 1;
Item->ProcessId = ProcessId;
FspProcessBufferAddItemAtDpcLevel(Item);
}
else if (FspProcessBufferCountMax > Item->BufferCount)
Item->BufferCount++;
else
AllocNoReuse = TRUE;
KeReleaseSpinLock(&ProcessBufferLock, Irql);
if (0 != NewItem)
FspFree(NewItem);
if (AllocNoReuse)
{
FspFree(BufferEntry);
goto alloc_no_reuse;
}
}
if (0 == BufferEntry->Buffer)
{
BufferSize = FspProcessBufferSizeMax;
Result = ZwAllocateVirtualMemory(ZwCurrentProcess(),
&BufferEntry->Buffer, 0, &BufferSize, MEM_COMMIT, PAGE_READWRITE);
if (!NT_SUCCESS(Result))
{
/* failed to allocate actual buffer; reuse BufferEntry */
FspProcessBufferReuseEntry(ProcessId, BufferEntry);
return Result;
}
}
*PBufferCookie = BufferEntry;
*PBuffer = BufferEntry->Buffer;
return STATUS_SUCCESS;
}
else
{
alloc_no_reuse:
*PBufferCookie = 0;
*PBuffer = 0;
return ZwAllocateVirtualMemory(ZwCurrentProcess(),
PBuffer, 0, &BufferSize, MEM_COMMIT, PAGE_READWRITE);
}
}
VOID FspProcessBufferRelease(PVOID BufferCookie, PVOID Buffer)
{
if (0 != BufferCookie)
{
HANDLE ProcessId = SafeGetCurrentProcessId();
FSP_PROCESS_BUFFER_LIST_ENTRY *BufferEntry = BufferCookie;
ASSERT(Buffer == BufferEntry->Buffer);
FspProcessBufferReuseEntry(ProcessId, BufferEntry);
}
else
{
SIZE_T BufferSize = 0;
ZwFreeVirtualMemory(ZwCurrentProcess(), &Buffer, &BufferSize, MEM_RELEASE);
}
}