2025-05-13 19:45:22 +03:00

458 lines
12 KiB
C

/*
* Process Hacker User Notes -
* database functions
*
* Copyright (C) 2011-2015 wj32
* Copyright (C) 2016 dmex
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
#include "usernotes.h"
BOOLEAN NTAPI ObjectDbEqualFunction(
_In_ PVOID Entry1,
_In_ PVOID Entry2
);
ULONG NTAPI ObjectDbHashFunction(
_In_ PVOID Entry
);
PPH_HASHTABLE ObjectDb;
PH_QUEUED_LOCK ObjectDbLock = PH_QUEUED_LOCK_INIT;
PPH_STRING ObjectDbPath;
VOID InitializeDb(
VOID
)
{
ObjectDb = PhCreateHashtable(
sizeof(PDB_OBJECT),
ObjectDbEqualFunction,
ObjectDbHashFunction,
64
);
}
BOOLEAN NTAPI ObjectDbEqualFunction(
_In_ PVOID Entry1,
_In_ PVOID Entry2
)
{
PDB_OBJECT object1 = *(PDB_OBJECT *)Entry1;
PDB_OBJECT object2 = *(PDB_OBJECT *)Entry2;
return object1->Tag == object2->Tag && PhEqualStringRef(&object1->Key, &object2->Key, TRUE);
}
ULONG NTAPI ObjectDbHashFunction(
_In_ PVOID Entry
)
{
PDB_OBJECT object = *(PDB_OBJECT *)Entry;
return object->Tag + PhHashStringRef(&object->Key, TRUE);
}
ULONG GetNumberOfDbObjects(
VOID
)
{
return ObjectDb->Count;
}
VOID LockDb(
VOID
)
{
PhAcquireQueuedLockExclusive(&ObjectDbLock);
}
VOID UnlockDb(
VOID
)
{
PhReleaseQueuedLockExclusive(&ObjectDbLock);
}
PDB_OBJECT FindDbObject(
_In_ ULONG Tag,
_In_ PPH_STRINGREF Name
)
{
DB_OBJECT lookupObject;
PDB_OBJECT lookupObjectPtr;
PDB_OBJECT *objectPtr;
lookupObject.Tag = Tag;
lookupObject.Key = *Name;
lookupObjectPtr = &lookupObject;
objectPtr = PhFindEntryHashtable(ObjectDb, &lookupObjectPtr);
if (objectPtr)
return *objectPtr;
else
return NULL;
}
PDB_OBJECT CreateDbObject(
_In_ ULONG Tag,
_In_ PPH_STRINGREF Name,
_In_opt_ PPH_STRING Comment
)
{
PDB_OBJECT object;
BOOLEAN added;
PDB_OBJECT *realObject;
object = PhAllocate(sizeof(DB_OBJECT));
memset(object, 0, sizeof(DB_OBJECT));
object->Tag = Tag;
object->Key = *Name;
object->BackColor = ULONG_MAX;
realObject = PhAddEntryHashtableEx(ObjectDb, &object, &added);
if (added)
{
object->Name = PhCreateStringEx(Name->Buffer, Name->Length);
object->Key = object->Name->sr;
if (Comment)
PhSetReference(&object->Comment, Comment);
else
object->Comment = PhReferenceEmptyString();
}
else
{
PhFree(object);
object = *realObject;
if (Comment)
PhSwapReference(&object->Comment, Comment);
}
return object;
}
VOID DeleteDbObject(
_In_ PDB_OBJECT Object
)
{
PhRemoveEntryHashtable(ObjectDb, &Object);
PhDereferenceObject(Object->Name);
PhDereferenceObject(Object->Comment);
PhFree(Object);
}
VOID SetDbPath(
_In_ PPH_STRING Path
)
{
PhSwapReference(&ObjectDbPath, Path);
}
mxml_type_t MxmlLoadCallback(
_In_ mxml_node_t *node
)
{
return MXML_OPAQUE;
}
PPH_STRING GetOpaqueXmlNodeText(
_In_ mxml_node_t *node
)
{
if (node->child && node->child->type == MXML_OPAQUE && node->child->value.opaque)
{
return PhConvertUtf8ToUtf16(node->child->value.opaque);
}
else
{
return PhReferenceEmptyString();
}
}
NTSTATUS LoadDb(
VOID
)
{
NTSTATUS status;
HANDLE fileHandle;
LARGE_INTEGER fileSize;
mxml_node_t *topNode;
mxml_node_t *currentNode;
status = PhCreateFileWin32(
&fileHandle,
ObjectDbPath->Buffer,
FILE_GENERIC_READ,
0,
FILE_SHARE_READ | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
);
if (!NT_SUCCESS(status))
return status;
if (NT_SUCCESS(PhGetFileSize(fileHandle, &fileSize)) && fileSize.QuadPart == 0)
{
// A blank file is OK. There are no objects to load.
NtClose(fileHandle);
return status;
}
topNode = mxmlLoadFd(NULL, fileHandle, MxmlLoadCallback);
NtClose(fileHandle);
if (!topNode)
return STATUS_FILE_CORRUPT_ERROR;
if (topNode->type != MXML_ELEMENT)
{
mxmlDelete(topNode);
return STATUS_FILE_CORRUPT_ERROR;
}
LockDb();
for (currentNode = topNode->child; currentNode; currentNode = currentNode->next)
{
PDB_OBJECT object = NULL;
PPH_STRING tag = NULL;
PPH_STRING name = NULL;
PPH_STRING priorityClass = NULL;
PPH_STRING ioPriorityPlusOne = NULL;
PPH_STRING comment = NULL;
PPH_STRING backColor = NULL;
PPH_STRING collapse = NULL;
if (currentNode->type == MXML_ELEMENT &&
currentNode->value.element.num_attrs >= 2)
{
for (INT i = 0; i < currentNode->value.element.num_attrs; i++)
{
if (_stricmp(currentNode->value.element.attrs[i].name, "tag") == 0)
PhMoveReference(&tag, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value));
else if (_stricmp(currentNode->value.element.attrs[i].name, "name") == 0)
PhMoveReference(&name, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value));
else if (_stricmp(currentNode->value.element.attrs[i].name, "priorityclass") == 0)
PhMoveReference(&priorityClass, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value));
else if (_stricmp(currentNode->value.element.attrs[i].name, "iopriorityplusone") == 0)
PhMoveReference(&ioPriorityPlusOne, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value));
else if (_stricmp(currentNode->value.element.attrs[i].name, "backcolor") == 0)
PhMoveReference(&backColor, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value));
else if (_stricmp(currentNode->value.element.attrs[i].name, "collapse") == 0)
PhMoveReference(&collapse, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value));
}
}
comment = GetOpaqueXmlNodeText(currentNode);
if (tag && name && comment)
{
ULONG64 tagInteger;
ULONG64 priorityClassInteger = 0;
ULONG64 ioPriorityPlusOneInteger = 0;
PhStringToInteger64(&tag->sr, 10, &tagInteger);
if (priorityClass)
PhStringToInteger64(&priorityClass->sr, 10, &priorityClassInteger);
if (ioPriorityPlusOne)
PhStringToInteger64(&ioPriorityPlusOne->sr, 10, &ioPriorityPlusOneInteger);
object = CreateDbObject((ULONG)tagInteger, &name->sr, comment);
object->PriorityClass = (ULONG)priorityClassInteger;
object->IoPriorityPlusOne = (ULONG)ioPriorityPlusOneInteger;
}
// NOTE: These items are handled separately to maintain compatibility with previous versions of the database.
if (object && backColor)
{
ULONG64 backColorInteger = ULONG_MAX;
PhStringToInteger64(&backColor->sr, 10, &backColorInteger);
object->BackColor = (COLORREF)backColorInteger;
}
if (object && collapse)
{
ULONG64 collapseInteger = 0;
PhStringToInteger64(&collapse->sr, 10, &collapseInteger);
object->Collapse = !!collapseInteger;
}
PhClearReference(&tag);
PhClearReference(&name);
PhClearReference(&priorityClass);
PhClearReference(&ioPriorityPlusOne);
PhClearReference(&comment);
PhClearReference(&backColor);
PhClearReference(&collapse);
}
UnlockDb();
mxmlDelete(topNode);
return STATUS_SUCCESS;
}
char *MxmlSaveCallback(
_In_ mxml_node_t *node,
_In_ int position
)
{
if (PhEqualBytesZ(node->value.element.name, "object", TRUE))
{
if (position == MXML_WS_BEFORE_OPEN)
return " ";
else if (position == MXML_WS_AFTER_CLOSE)
return "\r\n";
}
else if (PhEqualBytesZ(node->value.element.name, "objects", TRUE))
{
if (position == MXML_WS_AFTER_OPEN)
return "\r\n";
}
return NULL;
}
PPH_BYTES StringRefToUtf8(
_In_ PPH_STRINGREF String
)
{
return PH_AUTO(PhConvertUtf16ToUtf8Ex(String->Buffer, String->Length));
}
mxml_node_t *CreateObjectElement(
_Inout_ mxml_node_t *ParentNode,
_In_ PPH_STRINGREF Tag,
_In_ PPH_STRINGREF Name,
_In_ PPH_STRINGREF PriorityClass,
_In_ PPH_STRINGREF IoPriorityPlusOne,
_In_ PPH_STRINGREF Comment,
_In_ PPH_STRINGREF BackColor,
_In_ PPH_STRINGREF Collapse
)
{
mxml_node_t *objectNode;
mxml_node_t *textNode;
// Create the setting element.
objectNode = mxmlNewElement(ParentNode, "object");
// Set the attributes.
mxmlElementSetAttr(objectNode, "tag", StringRefToUtf8(Tag)->Buffer);
mxmlElementSetAttr(objectNode, "name", StringRefToUtf8(Name)->Buffer);
mxmlElementSetAttr(objectNode, "priorityclass", StringRefToUtf8(PriorityClass)->Buffer);
mxmlElementSetAttr(objectNode, "iopriorityplusone", StringRefToUtf8(IoPriorityPlusOne)->Buffer);
mxmlElementSetAttr(objectNode, "backcolor", StringRefToUtf8(BackColor)->Buffer);
mxmlElementSetAttr(objectNode, "collapse", StringRefToUtf8(Collapse)->Buffer);
// Set the value.
textNode = mxmlNewOpaque(objectNode, StringRefToUtf8(Comment)->Buffer);
return objectNode;
}
PPH_STRING UInt64ToBase10String(
_In_ ULONG64 Integer
)
{
return PH_AUTO(PhIntegerToString64(Integer, 10, FALSE));
}
NTSTATUS SaveDb(
VOID
)
{
PH_AUTO_POOL autoPool;
NTSTATUS status;
HANDLE fileHandle;
mxml_node_t *topNode;
ULONG enumerationKey = 0;
PDB_OBJECT *object;
PhInitializeAutoPool(&autoPool);
topNode = mxmlNewElement(MXML_NO_PARENT, "objects");
LockDb();
while (PhEnumHashtable(ObjectDb, (PVOID*)&object, &enumerationKey))
{
CreateObjectElement(
topNode,
&UInt64ToBase10String((*object)->Tag)->sr,
&(*object)->Name->sr,
&UInt64ToBase10String((*object)->PriorityClass)->sr,
&UInt64ToBase10String((*object)->IoPriorityPlusOne)->sr,
&(*object)->Comment->sr,
&UInt64ToBase10String((*object)->BackColor)->sr,
&UInt64ToBase10String((*object)->Collapse)->sr
);
PhDrainAutoPool(&autoPool);
}
UnlockDb();
// Create the directory if it does not exist.
{
PPH_STRING fullPath;
ULONG indexOfFileName;
if (fullPath = PH_AUTO(PhGetFullPath(ObjectDbPath->Buffer, &indexOfFileName)))
{
if (indexOfFileName != -1)
SHCreateDirectoryEx(NULL, PhaSubstring(fullPath, 0, indexOfFileName)->Buffer, NULL);
}
}
PhDeleteAutoPool(&autoPool);
status = PhCreateFileWin32(
&fileHandle,
ObjectDbPath->Buffer,
FILE_GENERIC_WRITE,
0,
FILE_SHARE_READ,
FILE_OVERWRITE_IF,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
);
if (!NT_SUCCESS(status))
{
mxmlDelete(topNode);
return status;
}
mxmlSaveFd(topNode, fileHandle, MxmlSaveCallback);
mxmlDelete(topNode);
NtClose(fileHandle);
return STATUS_SUCCESS;
}