mirror of
https://github.com/ufrisk/MemProcFS.git
synced 2026-10-11 22:40:01 +00:00
Version 3.8.1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
@@ -7,17 +7,15 @@
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@@ -72,7 +60,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program 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
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
@@ -109,7 +109,12 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="memprocfs.c" />
|
||||
<ClCompile Include="vfs.c" />
|
||||
<ClCompile Include="ob\ob_cachemap.c" />
|
||||
<ClCompile Include="ob\ob_core.c" />
|
||||
<ClCompile Include="ob\ob_map.c" />
|
||||
<ClCompile Include="ob\ob_set.c" />
|
||||
<ClCompile Include="vfsdokan.c" />
|
||||
<ClCompile Include="vfslist.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\includes\dokan.h" />
|
||||
@@ -117,6 +122,7 @@
|
||||
<ClInclude Include="..\includes\leechcore.h" />
|
||||
<ClInclude Include="..\includes\public.h" />
|
||||
<ClInclude Include="..\includes\vmmdll.h" />
|
||||
<ClInclude Include="ob\ob.h" />
|
||||
<ClInclude Include="version.h" />
|
||||
<ClInclude Include="vfs.h" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -16,12 +16,33 @@
|
||||
<Filter Include="Header Files\includes">
|
||||
<UniqueIdentifier>{81e2f737-13dd-4ab5-8d2d-f8f822c46c53}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\ob">
|
||||
<UniqueIdentifier>{b9ed75de-f5b0-49ac-8690-6322fbe7421d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\ob">
|
||||
<UniqueIdentifier>{d7ea25e4-3035-471b-9608-4fe0b262a79b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="memprocfs.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vfs.c">
|
||||
<ClCompile Include="ob\ob_cachemap.c">
|
||||
<Filter>Source Files\ob</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ob\ob_core.c">
|
||||
<Filter>Source Files\ob</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ob\ob_map.c">
|
||||
<Filter>Source Files\ob</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ob\ob_set.c">
|
||||
<Filter>Source Files\ob</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vfsdokan.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vfslist.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
@@ -47,6 +68,9 @@
|
||||
<ClInclude Include="..\includes\vmmdll.h">
|
||||
<Filter>Header Files\includes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ob\ob.h">
|
||||
<Filter>Header Files\ob</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="MemProcFS.rc">
|
||||
|
||||
@@ -45,7 +45,8 @@ VOID MemProcFsCtrlHandler_TryShutdownThread(PVOID pv)
|
||||
HMODULE hModuleVmm;
|
||||
BOOL(*VMMDLL_Close)();
|
||||
__try {
|
||||
VfsClose(g_VfsMountPoint);
|
||||
VfsDokan_Close(g_VfsMountPoint);
|
||||
VfsList_Close();
|
||||
} __except(EXCEPTION_EXECUTE_HANDLER) { ; }
|
||||
__try {
|
||||
hModuleVmm = GetModuleHandleA("vmm.dll");
|
||||
@@ -138,9 +139,10 @@ int main(_In_ int argc, _In_ char* argv[])
|
||||
printf("MemProcFS: Error file system plugins in vmm.dll!\n");
|
||||
return 1;
|
||||
}
|
||||
VfsList_Initialize(hVMM, 500, 128);
|
||||
SetConsoleCtrlHandler(MemProcFsCtrlHandler, TRUE);
|
||||
g_VfsMountPoint = GetMountPoint(argc, argv);
|
||||
VfsInitializeAndMount(g_VfsMountPoint, &VmmDll);
|
||||
VfsDokan_InitializeAndMount(g_VfsMountPoint, &VmmDll);
|
||||
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MemProcFsCtrlHandler_TryShutdownThread, NULL, 0, NULL);
|
||||
Sleep(250);
|
||||
TerminateProcess(GetCurrentProcess(), 1);
|
||||
|
||||
@@ -0,0 +1,841 @@
|
||||
// ob.h : definitions related to the object manager and object manager collections.
|
||||
//
|
||||
// (c) Ulf Frisk, 2018-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#ifndef __OB_H__
|
||||
#define __OB_H__
|
||||
#include <windows.h>
|
||||
|
||||
typedef unsigned __int64 QWORD, *PQWORD;
|
||||
#define OB_DEBUG
|
||||
#define OB_HEADER_MAGIC 0x0c0efefe
|
||||
|
||||
#define OB_TAG_CORE_CONTAINER 'ObCo'
|
||||
#define OB_TAG_CORE_COMPRESSED 'ObCp'
|
||||
#define OB_TAG_CORE_DATA 'ObDa'
|
||||
#define OB_TAG_CORE_SET 'ObSe'
|
||||
#define OB_TAG_CORE_MAP 'ObMa'
|
||||
#define OB_TAG_CORE_CACHEMAP 'ObMc'
|
||||
#define OB_TAG_CORE_STRMAP 'ObMs'
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OBJECT MANAGER CORE FUNCTIONALITY BELOW:
|
||||
//
|
||||
// The object manager is a minimal non-threaded way of allocating objects with
|
||||
// reference counts. When reference count reach zero the object is deallocated
|
||||
// automatically.
|
||||
//
|
||||
// All Ob functions are thread-safe and performs only minimum locking.
|
||||
//
|
||||
// A thread calls Ob_Alloc to allocate an object of a specific length. The
|
||||
// object initially have reference count 1. Reference counts may be increased
|
||||
// by calling Ob_INCREF and decreased by calling Ob_DECREF. If the refcount
|
||||
// reach one or zero in a call to Ob_DECREF optional callbacks may be made
|
||||
// (specified at Ob_Alloc time). Callbacks may be useful for cleanup tasks
|
||||
// - such as decreasing reference count of sub-objects contained in the object
|
||||
// that is to be deallocated.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB {
|
||||
// internal object manager functionality below: (= do not use unless absolutely necessary)
|
||||
DWORD _magic; // magic value - OB_HEADER_MAGIC
|
||||
union {
|
||||
DWORD _tag; // tag - 2 chars, no null terminator
|
||||
CHAR _tagCh[4];
|
||||
};
|
||||
VOID(*_pfnRef_0)(_In_ PVOID pOb); // callback - object specific cleanup before free
|
||||
VOID(*_pfnRef_1)(_In_ PVOID pOb); // callback - when object reach refcount 1 (not initial)
|
||||
DWORD _count; // reference count
|
||||
// external object manager functionality below: (= ok to use)
|
||||
DWORD cbData;
|
||||
} OB, *POB;
|
||||
|
||||
/*
|
||||
* Allocate a new object manager memory object.
|
||||
* -- tag = tag identifying the type of object.
|
||||
* -- uFlags = flags as given by LocalAlloc.
|
||||
* -- uBytes = bytes of object (_including_ object headers).
|
||||
* -- pfnRef_0 = optional callback for cleanup o be called before object is destroyed.
|
||||
* (if object contains objects which references should be decremented
|
||||
before destruction of this 'parent' object).
|
||||
* -- pfnRef_1 = optional callback for when object reach refcount = 1 at DECREF.
|
||||
* -- return = allocated object on success, with refcount = 1, - NULL on fail.
|
||||
*/
|
||||
PVOID Ob_Alloc(_In_ DWORD tag, _In_ UINT uFlags, _In_ SIZE_T uBytes, _In_opt_ VOID(*pfnRef_0)(_In_ PVOID pOb), _In_opt_ VOID(*pfnRef_1)(_In_ PVOID pOb));
|
||||
|
||||
/*
|
||||
* Increase the reference count of a object by one.
|
||||
* -- pOb
|
||||
* -- return
|
||||
*/
|
||||
PVOID Ob_INCREF(_In_opt_ PVOID pOb);
|
||||
|
||||
/*
|
||||
* Decrease the reference count of an object manager object by one.
|
||||
* NB! Do not use object after DECREF - other threads might have also DECREF'ed
|
||||
* the object at same time making it to be free'd - making the memory invalid.
|
||||
* -- pOb
|
||||
* -- return = pObIn if pObIn is valid and refcount > 0 after decref.
|
||||
*/
|
||||
PVOID Ob_DECREF(_In_opt_ PVOID pOb);
|
||||
|
||||
/*
|
||||
* Decrease the reference count of a object manager object. If the reference
|
||||
* count reaches zero the object will be cleaned up.
|
||||
* Also set the incoming pointer to NULL.
|
||||
* -- ppOb
|
||||
*/
|
||||
inline VOID Ob_DECREF_NULL(_In_opt_ PVOID *ppOb)
|
||||
{
|
||||
if(ppOb) {
|
||||
Ob_DECREF(*ppOb);
|
||||
*ppOb = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if pObIn is a valid object manager object with the specified tag.
|
||||
* -- pObIn
|
||||
* -- tag
|
||||
* -- return
|
||||
*/
|
||||
BOOL Ob_VALID_TAG(_In_ PVOID pObIn, _In_ DWORD tag);
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OBJECT MANAGER COMMON/GENERIC OBJECTS BELOW:
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_DATA {
|
||||
OB ObHdr;
|
||||
union {
|
||||
BYTE pb[];
|
||||
CHAR sz[];
|
||||
DWORD pdw[];
|
||||
QWORD pqw[];
|
||||
};
|
||||
} OB_DATA, *POB_DATA;
|
||||
|
||||
/*
|
||||
* Create a new object manager data object in which the ObHdr->cbData is equal
|
||||
* to the number of bytes in the data buffer supplied to this function.
|
||||
* May also be created with Ob_Alloc with size: sizeof(OB_HDR) + length of data.
|
||||
* CALLER DECREF: return
|
||||
* -- pb
|
||||
* -- cb
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_DATA ObData_New(_In_ PBYTE pb, _In_ DWORD cb);
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OBJECT CONTAINER FUNCTIONALITY BELOW:
|
||||
//
|
||||
// A container provides atomic access to a single Ob object. This is useful
|
||||
// if a Ob object is to frequently be replaced by a new object in an atomic
|
||||
// way. An example of this is the process list object containing the process
|
||||
// information. The container holds a reference count to the object that is
|
||||
// contained. The object container itself is an object manager object and
|
||||
// must be DECREF'ed when required.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_CONTAINER {
|
||||
OB ObHdr;
|
||||
CRITICAL_SECTION Lock;
|
||||
POB pOb;
|
||||
} OB_CONTAINER, *POB_CONTAINER;
|
||||
|
||||
/*
|
||||
* Create a new object container object without an initial contained object.
|
||||
* An object container provides atomic access to its contained object in a
|
||||
* multithreaded environment. The object container is in itself an object
|
||||
* manager object and must be DECREF'ed by the caller when use is complete.
|
||||
* CALLER DECREF: return
|
||||
* -- return
|
||||
*/
|
||||
POB_CONTAINER ObContainer_New();
|
||||
|
||||
/*
|
||||
* Retrieve an enclosed object from the given pObContainer.
|
||||
* CALLER DECREF: return
|
||||
* -- pObContainer
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObContainer_GetOb(_In_ POB_CONTAINER pObContainer);
|
||||
|
||||
/*
|
||||
* Set or Replace an object in the object container.
|
||||
* -- pObContainer
|
||||
* -- pOb
|
||||
*/
|
||||
VOID ObContainer_SetOb(_In_ POB_CONTAINER pObContainer, _In_opt_ PVOID pOb);
|
||||
|
||||
/*
|
||||
* Check if the object container is valid and contains an object.
|
||||
* -- pObContainer
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObContainer_Exists(_In_opt_ POB_CONTAINER pObContainer);
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// HASHED VALUE SET FUNCTIONALITY BELOW:
|
||||
//
|
||||
// The hashed value set (ObSet) provides thread safe efficient access to a set
|
||||
// which is containing _NON_ZERO_ values (64-bit unsigned integers). The ObSet
|
||||
// may hold a maximum capacity of 0x01000000 (~16M) entries - which are UNIQUE
|
||||
// and _NON_ZERO_.
|
||||
// The hashed value set (ObSet) guarantees order amongst values unless the
|
||||
// function ObSet_Remove is called - in which order may change and on-going
|
||||
// iterations of the set with ObSet_Get/ObSet_GetNext may fail.
|
||||
// The ObSet is an object manager object and must be DECREF'ed when required.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_SET *POB_SET;
|
||||
|
||||
/*
|
||||
* Create a new hashed value set. A hashed value set (ObSet) provides atomic
|
||||
* ways to store unique 64-bit (or smaller) numbers as a set.
|
||||
* The ObSet is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- return
|
||||
*/
|
||||
POB_SET ObSet_New();
|
||||
|
||||
/*
|
||||
* Retrieve the number of items in the given ObSet.
|
||||
* -- pvs
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObSet_Size(_In_opt_ POB_SET pvs);
|
||||
|
||||
/*
|
||||
* Check if a value already exists in the ObSet.
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObSet_Exists(_In_opt_ POB_SET pvs, _In_ QWORD value);
|
||||
|
||||
/*
|
||||
* Push / Insert a non-zero value into the ObSet.
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if value already
|
||||
* exists or if the max capacity of the set is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_Push(_In_opt_ POB_SET pvs, _In_ QWORD value);
|
||||
|
||||
/*
|
||||
* Push/Merge/Insert all values from the ObSet pvsSrc into the ObSet pvs.
|
||||
* The source set is kept intact.
|
||||
* -- pvs
|
||||
* -- pvsSrc
|
||||
* -- return = TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_PushSet(_In_opt_ POB_SET pvs, _In_opt_ POB_SET pvsSrc);
|
||||
|
||||
/*
|
||||
* Push/Merge/Insert all QWORD values from the ObData pDataSrc into the ObSet pvs.
|
||||
* The source data is kept intact.
|
||||
* -- pvs
|
||||
* -- pDataSrc
|
||||
* -- return = TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_PushData(_In_opt_ POB_SET pvs, _In_opt_ POB_DATA pDataSrc);
|
||||
|
||||
/*
|
||||
* Insert a value representing an address into the ObSet. If the length of the
|
||||
* data read from the start of the address a traverses page boundries all the
|
||||
* pages are inserted into the set.
|
||||
* -- pvs
|
||||
* -- a
|
||||
* -- cb
|
||||
*/
|
||||
VOID ObSet_Push_PageAlign(_In_opt_ POB_SET pvs, _In_ QWORD a, _In_ DWORD cb);
|
||||
|
||||
/*
|
||||
* Remove an existing value from the ObSet.
|
||||
* NB! must not be called simultaneously while iterating with ObSet_Get/ObSet_GetNext.
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return = removal was successful (i.e. the value was found and removed).
|
||||
*/
|
||||
BOOL ObSet_Remove(_In_opt_ POB_SET pvs, _In_ QWORD value);
|
||||
|
||||
/*
|
||||
* Clear the ObSet by removing all values.
|
||||
* NB! underlying allocated memory will remain unchanged.
|
||||
* -- pvs
|
||||
*/
|
||||
VOID ObSet_Clear(_In_opt_ POB_SET pvs);
|
||||
|
||||
/*
|
||||
* Save the contents of an ObSet to a disk file.
|
||||
* The resulting disk file may be read with ObSet_FileLoad().
|
||||
* -- pvs
|
||||
* -- wszFileName = save file to create.
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_FileSave(_In_opt_ POB_SET pvs, _In_ LPWSTR wszFileName);
|
||||
|
||||
/*
|
||||
* Load the contents of an ObSet disk file into the supplied set.
|
||||
* -- pvs
|
||||
* -- wszFileName = file previously saved by ObSet_FileSave().
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_FileLoad(_In_opt_ POB_SET pvs, _In_ LPWSTR wszFileName);
|
||||
|
||||
/*
|
||||
* Remove the "last" value in a way that is safe for concurrent iterations of
|
||||
* values in the set.
|
||||
* -- pvs
|
||||
* -- return = success: value, fail: 0.
|
||||
*/
|
||||
QWORD ObSet_Pop(_In_opt_ POB_SET pvs);
|
||||
|
||||
/*
|
||||
* Retrieve the next value given a value. The start value and end value are the
|
||||
* ZERO value (which is a special reserved non-valid value).
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT_ guaranteed if the
|
||||
* ObSet_Remove function is called while iterating over the ObSet - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return
|
||||
*/
|
||||
QWORD ObSet_GetNext(_In_opt_ POB_SET pvs, _In_ QWORD value);
|
||||
|
||||
/*
|
||||
* Retrieve a value given a value index (which is less than the amount of items
|
||||
* in the Set).
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT- guaranteed if the
|
||||
* ObSet_Remove function is called while iterating over the ObSet - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* -- pvs
|
||||
* -- index
|
||||
* -- return
|
||||
*/
|
||||
QWORD ObSet_Get(_In_opt_ POB_SET pvs, _In_ DWORD index);
|
||||
|
||||
/*
|
||||
* Retrieve all values in the Set as a POB_DATA object containing the values
|
||||
* in a QWORD table.
|
||||
* -- CALLER DECREF: return
|
||||
* -- pvs
|
||||
* -- return
|
||||
*/
|
||||
POB_DATA ObSet_GetAll(_In_opt_ POB_SET pvs);
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// MAP FUNCTIONALITY BELOW:
|
||||
//
|
||||
// The map is a key-value map that may, as an option, contain object manager
|
||||
// objects in its value field. They key may be user-defined, generated by a
|
||||
// function or absent. The ObMap may hold a maximum capacity of 0x02000000
|
||||
// (~32M) entries which are UNIQUE and non-NULL.
|
||||
//
|
||||
// The map (ObMap) is thread safe and implement efficient access to the data
|
||||
// via internal hashing functionality.
|
||||
// The map (ObMap) guarantees order amongst values unless the ObMap_Remove*
|
||||
// functions are called - in which order may change and on-going iterations
|
||||
// of the set with ObMap_Get/ObMap_GetNext may fail.
|
||||
// The ObMap is an object manager object and must be DECREF'ed when required.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_MAP *POB_MAP;
|
||||
|
||||
#define OB_MAP_FLAGS_OBJECT_VOID 0x00
|
||||
#define OB_MAP_FLAGS_OBJECT_OB 0x01
|
||||
#define OB_MAP_FLAGS_OBJECT_LOCALFREE 0x02
|
||||
#define OB_MAP_FLAGS_NOKEY 0x04
|
||||
|
||||
/*
|
||||
* Create a new map. A map (ObMap) provides atomic map operations and ways
|
||||
* to optionally map key values to values, pointers or object manager objects.
|
||||
* The ObSet is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- flags = defined by OB_MAP_FLAGS_*
|
||||
* -- return
|
||||
*/
|
||||
POB_MAP ObMap_New(_In_ QWORD flags);
|
||||
|
||||
/*
|
||||
* Retrieve the number of objects in the ObMap.
|
||||
* -- pm
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObMap_Size(_In_opt_ POB_MAP pm);
|
||||
|
||||
/*
|
||||
* Check if an object exists in the ObMap.
|
||||
* -- pm
|
||||
* -- qwKey/pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObMap_Exists(_In_opt_ POB_MAP pm, _In_ PVOID pvObject);
|
||||
|
||||
/*
|
||||
* Check if a key exists in the ObMap.
|
||||
* -- pm
|
||||
* -- qwKey/pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObMap_ExistsKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey);
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObMap.
|
||||
* If pvObject is OB the map performs Ob_INCREF on its own reference.
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if the key or object
|
||||
* already exists or if the max capacity of the map is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_Push(_In_opt_ POB_MAP pm, _In_ QWORD qwKey, _In_ PVOID pvObject);
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObMap by making a shallow copy of the object.
|
||||
* NB! only valid for OB_MAP_FLAGS_OBJECT_LOCALFREE initialized maps.
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- cbObject
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if the key or object
|
||||
* already exists or if the max capacity of the map is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_PushCopy(_In_opt_ POB_MAP pm, _In_ QWORD qwKey, _In_ PVOID pvObject, _In_ SIZE_T cbObject);
|
||||
|
||||
/*
|
||||
* Remove the "last" object.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
PVOID ObMap_Pop(_In_opt_ POB_MAP pm);
|
||||
|
||||
/*
|
||||
* Remove the "last" object and return it and its key.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- pKey
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
PVOID ObMap_PopWithKey(_In_opt_ POB_MAP pm, _Out_ PQWORD pKey);
|
||||
|
||||
/*
|
||||
* Remove an object from the ObMap.
|
||||
* NB! must not be called simultaneously while iterating with ObMap_GetByIndex/ObMap_GetNext.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- value
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObMap_Remove(_In_opt_ POB_MAP pm, _In_ PVOID pvObject);
|
||||
|
||||
/*
|
||||
* Remove an object from the ObMap by using its key.
|
||||
* NB! must not be called simultaneously while iterating with ObMap_GetByIndex/ObMap_GetNext.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObMap_RemoveByKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey);
|
||||
|
||||
/*
|
||||
* Clear the ObMap by removing all objects and their keys.
|
||||
* NB! underlying allocated memory will remain unchanged.
|
||||
* -- pm
|
||||
* -- return = clear was successful - always true.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_Clear(_In_opt_ POB_MAP pm);
|
||||
|
||||
/*
|
||||
* Peek the "last" object.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObMap_Peek(_In_opt_ POB_MAP pm);
|
||||
|
||||
/*
|
||||
* Peek the key of the "last" object.
|
||||
* -- pm
|
||||
* -- return = the key, otherwise 0.
|
||||
*/
|
||||
QWORD ObMap_PeekKey(_In_opt_ POB_MAP pm);
|
||||
|
||||
/*
|
||||
* Retrieve the next object given an object. Start and end objects are NULL.
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT_ guaranteed if the
|
||||
* ObMap_Remove* functions are called while iterating over the ObMap - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* FUNCTION DECREF(if OB): pvObject
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetNext(_In_opt_ POB_MAP pm, _In_opt_ PVOID pvObject);
|
||||
|
||||
/*
|
||||
* Retrieve the next object given a key. To start iterating supply NULL in the
|
||||
* pvObject parameter (this overrides qwKey). When no more objects are found
|
||||
* NULL will be returned. This function may ideally be used when object maps
|
||||
* may be refreshed between function calls. Key may be more stable than object.
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT_ guaranteed if the
|
||||
* ObMap_Remove* functions are called while iterating over the ObMap - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* FUNCTION DECREF(if OB): pvObject
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetNextByKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey, _In_opt_ PVOID pvObject);
|
||||
|
||||
/*
|
||||
* Retrieve a value given a key.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetByKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey);
|
||||
|
||||
/*
|
||||
* Retrieve an object given an index (which is less than the amount of items
|
||||
* in the ObMap).
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT- guaranteed if the
|
||||
* ObMap_Remove* functions are called while iterating over the ObSet - items
|
||||
* may be skipped or iterated over multiple times!
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- index
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetByIndex(_In_opt_ POB_MAP pm, _In_ DWORD index);
|
||||
|
||||
/*
|
||||
* Retrieve the key for an existing object in the ObMap.
|
||||
* -- pm
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != 0)
|
||||
QWORD ObMap_GetKey(_In_opt_ POB_MAP pm, _In_ PVOID pvObject);
|
||||
|
||||
/*
|
||||
* Common filter function related to ObMap_FilterSet.
|
||||
*/
|
||||
VOID ObMap_FilterSet_FilterAllKey(_In_ QWORD k, _In_ PVOID v, _Inout_ POB_SET ps);
|
||||
|
||||
/*
|
||||
* Filter map objects into a generic context by using a user-supplied filter function.
|
||||
* -- pm
|
||||
* -- ctx = optional context to pass on to the filter function.
|
||||
* -- pfnFilter
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_Filter(_In_opt_ POB_MAP pm, _Inout_opt_ PVOID ctx, _In_opt_ VOID(*pfnFilter)(_In_ QWORD k, _In_ PVOID v, _Inout_opt_ PVOID ctx));
|
||||
|
||||
/*
|
||||
* Filter map objects into a POB_SET by using a user-supplied filter function.
|
||||
* CALLER DECREF: return
|
||||
* -- pm
|
||||
* -- pfnFilter
|
||||
* -- return = POB_SET consisting of values gathered by the pfnFilter function.
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_SET ObMap_FilterSet(_In_opt_ POB_MAP pm, _In_opt_ VOID(*pfnFilter)(_In_ QWORD k, _In_ PVOID v, _Inout_ POB_SET ps));
|
||||
|
||||
/*
|
||||
* Remove map objects using a user-supplied filter function.
|
||||
* -- pm
|
||||
* -- pfnFilter = decision making function: [pfnFilter(k,v)->TRUE(remove)|FALSE(keep)]
|
||||
* -- return = number of entries removed.
|
||||
*/
|
||||
DWORD ObMap_RemoveByFilter(_In_opt_ POB_MAP pm, _In_opt_ BOOL(*pfnFilter)(_In_ QWORD k, _In_ PVOID v));
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CACHE MAP FUNCTIONALITY BELOW:
|
||||
//
|
||||
// The map (ObCacheMap) implements an efficient caching of objects stored in
|
||||
// an internal hash map. The cached object are retrieved and cleared according
|
||||
// to rules implemented by callback functions.
|
||||
//
|
||||
// If the max number of map entries are reached the least recently accessed
|
||||
// entry will be removed if required to make room for a new entry.
|
||||
//
|
||||
// The map (ObCacheMap) is thread safe.
|
||||
// The ObCacheMap is an object manager object and must be DECREF'ed when required.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_CACHEMAP *POB_CACHEMAP;
|
||||
|
||||
#define OB_CACHEMAP_FLAGS_OBJECT_VOID 0x00
|
||||
#define OB_CACHEMAP_FLAGS_OBJECT_OB 0x01
|
||||
#define OB_CACHEMAP_FLAGS_OBJECT_LOCALFREE 0x02
|
||||
|
||||
/*
|
||||
* Create a new cached map. A cached map (ObCacheMap) provides atomic map
|
||||
* operations on cached objects.
|
||||
* The ObCacheMap is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- cMaxEntries = max entries in the cache, if more entries are added the
|
||||
* least recently accessed item will be removed from the cache map.
|
||||
* -- pfnValidEntry = validation callback function (if any).
|
||||
* -- flags = defined by OB_CACHEMAP_FLAGS_*
|
||||
* -- return
|
||||
*/
|
||||
POB_CACHEMAP ObCacheMap_New(
|
||||
_In_ DWORD cMaxEntries,
|
||||
_In_opt_ BOOL(*pfnValidEntry)(_Inout_ PQWORD qwContext, _In_ QWORD qwKey, _In_ PVOID pvObject),
|
||||
_In_ QWORD flags
|
||||
);
|
||||
|
||||
/*
|
||||
* Clear the ObCacheMap by removing all objects and their keys.
|
||||
* -- pcm
|
||||
* -- return = clear was successful - always true.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObCacheMap_Clear(_In_opt_ POB_CACHEMAP pcm);
|
||||
|
||||
/*
|
||||
* Check if a key exists in the ObCacheMap.
|
||||
* -- pcm
|
||||
* -- qwKey/pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObCacheMap_ExistsKey(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey);
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObCacheMap. If an object with the same key already
|
||||
* exists it's removed from the cache map before the new object is inserted.
|
||||
* If pvObject is OB the map performs Ob_INCREF on its own reference.
|
||||
* -- pcm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- qwContextInitial = initial context (passed on to pfnValidEntry callback).
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if the key or object
|
||||
* already exists or if the max capacity of the map is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObCacheMap_Push(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey, _In_ PVOID pvObject, _In_ QWORD qwContextInitial);
|
||||
|
||||
/*
|
||||
* Retrieve the number of objects in the ObCacheMap.
|
||||
* -- pcm
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObCacheMap_Size(_In_opt_ POB_CACHEMAP pcm);
|
||||
|
||||
/*
|
||||
* Retrieve a value given a key.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pcm
|
||||
* -- qwKey
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObCacheMap_GetByKey(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey);
|
||||
|
||||
/*
|
||||
* Remove an object from the ObCacheMap by using its key.
|
||||
* NB! Object is removed and returned even if valid critera is not matched.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pcm
|
||||
* -- qwKey
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObCacheMap_RemoveByKey(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey);
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// STRMAP FUNCTIONALITY BELOW:
|
||||
//
|
||||
// The strmap is created and populated with strings (ascii and wide-char)
|
||||
// in an optimal way removing duplicates. Upon finalization the string map
|
||||
// results in a multi-string and an update of string references will happen.
|
||||
//
|
||||
// References to the strings will only be valid after a successful call to
|
||||
// finalize_DECREF_NULL().
|
||||
//
|
||||
// The strmap is only meant to be an interim object to be used for creation
|
||||
// of multi-string values and should not be kept as a long-lived object.
|
||||
//
|
||||
// The ObStrMap is an object manager object and must be DECREF'ed when required.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_STRMAP *POB_STRMAP;
|
||||
|
||||
// Strings in OB_STRMAP are considered to be CASE SENSITIVE.
|
||||
#define OB_STRMAP_FLAGS_CASE_SENSITIVE 0x00
|
||||
|
||||
// Strings in OB_STRMAP are considered to be CASE INSENSITIVE. The case is
|
||||
// preserved for 1st unique entry added; subsequent entries will use 1st entry.
|
||||
#define OB_STRMAP_FLAGS_CASE_INSENSITIVE 0x01
|
||||
|
||||
// Assign temporary string values to destinations at time of push.
|
||||
// NB! values will become invalid after OB_STRMAP DECREF/FINALIZE!
|
||||
#define OB_STRMAP_FLAGS_STR_ASSIGN_TEMPORARY 0x02
|
||||
|
||||
/*
|
||||
* Create a new strmap. A strmap (ObStrMap) provides an easy way to add new
|
||||
* strings to a multi-string in an efficient way. The ObStrMap is not meant
|
||||
* to be a long-term object - it's supposed to be finalized and decommissioned
|
||||
* by calling ObStrMap_Finalize_DECREF_NULL().
|
||||
* The ObStrMap is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- flags = defined by OB_STRMAP_FLAGS_*
|
||||
* -- return
|
||||
*/
|
||||
POB_STRMAP ObStrMap_New(_In_ QWORD flags);
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObStrMap.
|
||||
* -- psm
|
||||
* -- sz
|
||||
* -- pwszDst
|
||||
* -- pcchDst
|
||||
* -- return = TRUE on insertion, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObStrMap_PushA(_In_opt_ POB_STRMAP psm, _In_opt_ LPSTR sz, _Out_opt_ LPWSTR *pwszDst, _Out_opt_ PDWORD pcchDst);
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObStrMap.
|
||||
* -- psm
|
||||
* -- wsz
|
||||
* -- pwszDst
|
||||
* -- pcchDst
|
||||
* -- return = TRUE on insertion, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObStrMap_Push(_In_opt_ POB_STRMAP psm, _In_opt_ LPWSTR wsz, _Out_opt_ LPWSTR *pwszDst, _Out_opt_ PDWORD pcchDst);
|
||||
|
||||
/*
|
||||
* Push / Insert max 2048 characters into ObStrMap using a swprintf_s syntax.
|
||||
* -- psm
|
||||
* -- pwszDst
|
||||
* -- pcchDst
|
||||
* -- wszFormat
|
||||
* -- ...
|
||||
* -- return = TRUE on insertion, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObStrMap_Push_swprintf_s(_In_opt_ POB_STRMAP psm, _Out_opt_ LPWSTR *pwszDst, _Out_opt_ PDWORD pcchDst, _In_z_ _Printf_format_string_ wchar_t const *const wszFormat, ...);
|
||||
|
||||
/*
|
||||
* Push a UNICODE_OBJECT Pointer for delayed resolve at finalize stage.
|
||||
* NB! Incompatible with: OB_STRMAP_FLAGS_STR_ASSIGN_TEMPORARY create flag.
|
||||
* -- psm
|
||||
* -- f32 = 32-bit/64-bit unicode object.
|
||||
* -- vaUnicodeObject
|
||||
* -- pwszDst
|
||||
* -- pcchDst
|
||||
* -- return = TRUE on initial validation success (NB! no guarantee for success).
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObStrMap_Push_UnicodeObject(_In_opt_ POB_STRMAP psm, _In_ BOOL f32, _In_ QWORD vaUnicodeObject, _Out_opt_ LPWSTR *pwszDst, _Out_opt_ PDWORD pcchDst);
|
||||
|
||||
/*
|
||||
* Push a UNICODE_OBJECT Buffer for delayed resolve at finalize stage.
|
||||
* NB! Incompatible with: OB_STRMAP_FLAGS_STR_ASSIGN_TEMPORARY create flag.
|
||||
* -- psm
|
||||
* -- cbUnicodeBuffer.
|
||||
* -- vaUnicodeBuffer
|
||||
* -- pwszDst
|
||||
* -- pcchDst
|
||||
* -- return = TRUE on initial validation success (NB! no guarantee for success).
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObStrMap_Push_UnicodeBuffer(_In_opt_ POB_STRMAP psm, _In_ WORD cbUnicodeBuffer, _In_ QWORD vaUnicodeBuffer, _Out_opt_ LPWSTR *pwszDst, _Out_opt_ PDWORD pcchDst);
|
||||
|
||||
/*
|
||||
* Finalize the ObStrMap. Create and assign the MultiStr and assign each
|
||||
* previously added string reference to a pointer location within the MultiStr.
|
||||
* ---
|
||||
* Also decrease the reference count of the object. If the reference count
|
||||
* reaches zero the object will be cleaned up.
|
||||
* Also set the incoming pointer to NULL.
|
||||
* CALLER LOCALFREE: *pwszMultiStr
|
||||
* -- ppsm
|
||||
* -- pwszMultiStr
|
||||
* -- pcbMultiStr
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObStrMap_Finalize_DECREF_NULL(_In_opt_ PVOID *ppsm, _Out_ LPWSTR *pwszMultiStr, _Out_ PDWORD pcbMultiStr);
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// COMPRESSED DATA OBJECT FUNCTIONALITY BELOW:
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdOB_COMPRESSED *POB_COMPRESSED;
|
||||
|
||||
/*
|
||||
* Create a new compressed buffer object from a byte buffer.
|
||||
* CALLER DECREF: return
|
||||
* -- pcm
|
||||
* -- pb
|
||||
* -- cb
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_COMPRESSED ObCompressed_NewFromByte(_In_reads_(cb) PBYTE pb, _In_ DWORD cb);
|
||||
|
||||
/*
|
||||
* Create a new compressed buffer object from a zero terminated string.
|
||||
* CALLER DECREF: return
|
||||
* -- pcm
|
||||
* -- sz
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_COMPRESSED ObCompress_NewFromStrA(_In_ LPSTR sz);
|
||||
|
||||
/*
|
||||
* Retrieve the uncompressed size of the compressed data object.
|
||||
* -- pdc
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObCompress_Size(_In_opt_ POB_COMPRESSED pdc);
|
||||
|
||||
/*
|
||||
* Retrieve uncompressed from a compressed data object.
|
||||
* CALLER DECREF: return
|
||||
* -- pdc
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_DATA ObCompressed_GetData(_In_opt_ POB_COMPRESSED pdc);
|
||||
|
||||
#endif /* __OB_H__ */
|
||||
@@ -0,0 +1,255 @@
|
||||
// ob_cachemap.c : implementation of object manager cached map functionality.
|
||||
//
|
||||
// The map (ObCacheMap) implements an efficient caching of objects stored in
|
||||
// an internal hash map. The cached object are retrieved and cleared according
|
||||
// to rules implemented by callback functions.
|
||||
//
|
||||
// If the max number of map entries are reached the least recently accessed
|
||||
// entry will be removed if required to make room for a new entry.
|
||||
//
|
||||
// The map (ObCacheMap) is thread safe.
|
||||
// The ObCacheMap is an object manager object and must be DECREF'ed when required.
|
||||
//
|
||||
// (c) Ulf Frisk, 2020-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include "ob.h"
|
||||
|
||||
#define OB_CACHEMAP_IS_VALID(p) (p && (p->ObHdr._magic == OB_HEADER_MAGIC) && (p->ObHdr._tag == OB_TAG_CORE_CACHEMAP))
|
||||
|
||||
#define OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, RetTp, RetValFail, fn) { \
|
||||
if(!OB_CACHEMAP_IS_VALID(pcm)) { return RetValFail; } \
|
||||
RetTp retVal; \
|
||||
AcquireSRWLockExclusive(&pcm->LockSRW); \
|
||||
retVal = fn; \
|
||||
ReleaseSRWLockExclusive(&pcm->LockSRW); \
|
||||
return retVal; \
|
||||
}
|
||||
|
||||
typedef struct tdOB_CACHEMAPENTRY {
|
||||
struct tdOB_CACHEMAPENTRY *FLink;
|
||||
struct tdOB_CACHEMAPENTRY *BLink;
|
||||
PVOID pvObject;
|
||||
QWORD qwContext;
|
||||
} OB_CACHEMAPENTRY, *POB_CACHEMAPENTRY;
|
||||
|
||||
typedef struct tdOB_CACHEMAP {
|
||||
OB ObHdr;
|
||||
SRWLOCK LockSRW;
|
||||
DWORD c;
|
||||
DWORD cMax;
|
||||
BOOL fObjectsOb;
|
||||
BOOL fObjectsLocalFree;
|
||||
POB_MAP pm;
|
||||
POB_CACHEMAPENTRY AgeListHead;
|
||||
BOOL(*pfnValidEntry)(_Inout_ PQWORD qwData, _In_ QWORD qwKey, _In_ PVOID pvObject);
|
||||
} OB_CACHEMAP, *POB_CACHEMAP;
|
||||
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObCacheMap_Clear(_In_ POB_CACHEMAP pcm)
|
||||
{
|
||||
POB_CACHEMAPENTRY pe, peNext;
|
||||
if(!(peNext = pcm->AgeListHead)) { return TRUE; }
|
||||
peNext->BLink->FLink = NULL;
|
||||
while((pe = peNext)) {
|
||||
peNext = pe->FLink;
|
||||
if(pcm->fObjectsOb) {
|
||||
Ob_DECREF(pe->pvObject);
|
||||
} else if(pcm->fObjectsLocalFree) {
|
||||
LocalFree(pe->pvObject);
|
||||
}
|
||||
LocalFree(pe);
|
||||
}
|
||||
ObMap_Clear(pcm->pm);
|
||||
pcm->AgeListHead = NULL;
|
||||
pcm->c = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the ObCacheMap by removing all objects and their keys.
|
||||
* -- pcm
|
||||
* -- return = clear was successful - always true.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObCacheMap_Clear(_In_opt_ POB_CACHEMAP pcm)
|
||||
{
|
||||
OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, BOOL, TRUE, _ObCacheMap_Clear(pcm));
|
||||
}
|
||||
|
||||
PVOID _ObCacheMap_RemoveByKey(_In_ POB_CACHEMAP pcm, _In_ QWORD qwKey, _In_ BOOL fNoReturn)
|
||||
{
|
||||
PVOID pvRemovedObject;
|
||||
POB_CACHEMAPENTRY pe;
|
||||
if(!(pe = ObMap_RemoveByKey(pcm->pm, qwKey))) { return NULL; }
|
||||
pe->FLink->BLink = pe->BLink;
|
||||
pe->BLink->FLink = pe->FLink;
|
||||
pcm->c--;
|
||||
if(pcm->c == 0) {
|
||||
pcm->AgeListHead = NULL;
|
||||
} else if(pcm->AgeListHead == pe) {
|
||||
pcm->AgeListHead = pe->FLink;
|
||||
}
|
||||
pvRemovedObject = pe->pvObject;
|
||||
LocalFree(pe);
|
||||
if(fNoReturn && pvRemovedObject) {
|
||||
if(pcm->fObjectsOb) {
|
||||
Ob_DECREF(pvRemovedObject);
|
||||
} else if(pcm->fObjectsLocalFree) {
|
||||
LocalFree(pvRemovedObject);
|
||||
}
|
||||
pvRemovedObject = NULL;
|
||||
}
|
||||
return pvRemovedObject;
|
||||
}
|
||||
|
||||
PVOID _ObCacheMap_GetByKey(_In_ POB_CACHEMAP pcm, _In_ QWORD qwKey)
|
||||
{
|
||||
POB_CACHEMAPENTRY pe;
|
||||
if(!(pe = ObMap_GetByKey(pcm->pm, qwKey))) { return NULL; }
|
||||
if(pcm->pfnValidEntry && !pcm->pfnValidEntry(&pe->qwContext, qwKey, pe->pvObject)) {
|
||||
// invalid - remove object from map and return NULL
|
||||
_ObCacheMap_RemoveByKey(pcm, qwKey, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
// valid - move to front of age list and return object
|
||||
if(pcm->AgeListHead != pe) {
|
||||
pe->FLink->BLink = pe->BLink;
|
||||
pe->BLink->FLink = pe->FLink;
|
||||
pe->BLink = pcm->AgeListHead->BLink;
|
||||
pe->FLink = pcm->AgeListHead;
|
||||
pcm->AgeListHead->BLink->FLink = pe;
|
||||
pcm->AgeListHead->BLink = pe;
|
||||
pcm->AgeListHead = pe;
|
||||
}
|
||||
if(pcm->fObjectsOb) { Ob_INCREF(pe->pvObject); }
|
||||
return pe->pvObject;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObCacheMap_Push(_In_ POB_CACHEMAP pcm, _In_ QWORD qwKey, _In_ PVOID pvObject, _In_ QWORD qwContextInitial)
|
||||
{
|
||||
QWORD qwRemovedKey;
|
||||
POB_CACHEMAPENTRY pe;
|
||||
if(!qwKey || !pvObject) { return FALSE; }
|
||||
// 1: remove existing object with same key
|
||||
_ObCacheMap_RemoveByKey(pcm, qwKey, TRUE);
|
||||
// 2: remove least recently accessed object (if required)
|
||||
if(pcm->c >= pcm->cMax) {
|
||||
pe = pcm->AgeListHead->BLink;
|
||||
qwRemovedKey = ObMap_GetKey(pcm->pm, pe);
|
||||
_ObCacheMap_RemoveByKey(pcm, qwRemovedKey, TRUE);
|
||||
}
|
||||
// 3: add new object
|
||||
if(!(pe = LocalAlloc(0, sizeof(OB_CACHEMAPENTRY)))) { return FALSE; }
|
||||
if(pcm->fObjectsOb) { Ob_INCREF(pvObject); }
|
||||
pe->pvObject = pvObject;
|
||||
pe->qwContext = qwContextInitial;
|
||||
if(pcm->AgeListHead) {
|
||||
pe->BLink = pcm->AgeListHead->BLink;
|
||||
pe->FLink = pcm->AgeListHead;
|
||||
pcm->AgeListHead->BLink->FLink = pe;
|
||||
pcm->AgeListHead->BLink = pe;
|
||||
} else {
|
||||
pe->BLink = pe->FLink = pe;
|
||||
}
|
||||
ObMap_Push(pcm->pm, qwKey, pe);
|
||||
pcm->AgeListHead = pe;
|
||||
pcm->c++;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve a value given a key.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pcm
|
||||
* -- qwKey
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObCacheMap_GetByKey(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey)
|
||||
{
|
||||
OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, PVOID, NULL, _ObCacheMap_GetByKey(pcm, qwKey));
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove an object from the ObCacheMap by using its key.
|
||||
* NB! Object is removed and returned even if valid critera is not matched.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pcm
|
||||
* -- qwKey
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObCacheMap_RemoveByKey(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey)
|
||||
{
|
||||
OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, PVOID, NULL, _ObCacheMap_RemoveByKey(pcm, qwKey, FALSE));
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a key exists in the ObCacheMap.
|
||||
* -- pcm
|
||||
* -- qwKey/pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObCacheMap_ExistsKey(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey)
|
||||
{
|
||||
OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, DWORD, 0, ObMap_ExistsKey(pcm->pm, qwKey))
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the number of objects in the ObCacheMap.
|
||||
* -- pcm
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObCacheMap_Size(_In_opt_ POB_CACHEMAP pcm)
|
||||
{
|
||||
OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, DWORD, 0, pcm->c);
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL ObCacheMap_Push(_In_opt_ POB_CACHEMAP pcm, _In_ QWORD qwKey, _In_ PVOID pvObject, _In_ QWORD qwContextInitial)
|
||||
{
|
||||
OB_CACHEMAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pcm, BOOL, FALSE, _ObCacheMap_Push(pcm, qwKey, pvObject, qwContextInitial))
|
||||
}
|
||||
|
||||
/*
|
||||
* Object Map object manager cleanup function to be called when reference
|
||||
* count reaches zero.
|
||||
* -- pObMap
|
||||
*/
|
||||
VOID _ObCacheMap_ObCloseCallback(_In_ POB_CACHEMAP pObCacheMap)
|
||||
{
|
||||
_ObCacheMap_Clear(pObCacheMap);
|
||||
Ob_DECREF(pObCacheMap->pm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new cached map. A cached map (ObCacheMap) provides atomic map
|
||||
* operations on cached objects.
|
||||
* The ObCacheMap is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- cMaxEntries = max entries in the cache, if more entries are added the
|
||||
* least recently accessed item will be removed from the cache map.
|
||||
* -- pfnValidEntry = optional validation callback function.
|
||||
* -- flags = defined by OB_CACHEMAP_FLAGS_*
|
||||
* -- return
|
||||
*/
|
||||
POB_CACHEMAP ObCacheMap_New(_In_ DWORD cMaxEntries, _In_opt_ BOOL(*pfnValidEntry)(_Inout_ PQWORD qwContext, _In_ QWORD qwKey, _In_ PVOID pvObject), _In_ QWORD flags)
|
||||
{
|
||||
POB_CACHEMAP pObCacheMap;
|
||||
if(!cMaxEntries) { return NULL; }
|
||||
if((flags & OB_MAP_FLAGS_OBJECT_OB) && (flags & OB_MAP_FLAGS_OBJECT_LOCALFREE)) { return NULL; }
|
||||
pObCacheMap = Ob_Alloc(OB_TAG_CORE_CACHEMAP, LMEM_ZEROINIT, sizeof(OB_CACHEMAP), _ObCacheMap_ObCloseCallback, NULL);
|
||||
if(!pObCacheMap) { return NULL; }
|
||||
InitializeSRWLock(&pObCacheMap->LockSRW);
|
||||
pObCacheMap->cMax = cMaxEntries;
|
||||
pObCacheMap->pfnValidEntry = pfnValidEntry;
|
||||
pObCacheMap->fObjectsOb = (flags & OB_CACHEMAP_FLAGS_OBJECT_OB) ? TRUE : FALSE;
|
||||
pObCacheMap->fObjectsLocalFree = (flags & OB_CACHEMAP_FLAGS_OBJECT_LOCALFREE) ? TRUE : FALSE;
|
||||
pObCacheMap->pm = ObMap_New(OB_MAP_FLAGS_OBJECT_VOID);
|
||||
if(!pObCacheMap->pm) {
|
||||
Ob_DECREF(pObCacheMap);
|
||||
return NULL;
|
||||
}
|
||||
return pObCacheMap;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// ob_core.c : implementation of object manager core functionality.
|
||||
//
|
||||
// The object manager is a minimal non-threaded way of allocating objects with
|
||||
// reference counts. When reference count reach zero the object is deallocated
|
||||
// automatically.
|
||||
//
|
||||
// All Ob functions are thread-safe and performs only minimum locking.
|
||||
//
|
||||
// A thread calls Ob_Alloc to allocate an object of a specific length. The
|
||||
// object initially have reference count 1. Reference counts may be increased
|
||||
// by calling Ob_INCREF and decreased by calling Ob_DECREF. If the refcount
|
||||
// reach one or zero in a call to Ob_DECREF optional callbacks may be made
|
||||
// (specified at Ob_Alloc time). Callbacks may be useful for cleanup tasks
|
||||
// - such as decreasing reference count of sub-objects contained in the object
|
||||
// that is to be deallocated.
|
||||
//
|
||||
// (c) Ulf Frisk, 2018-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include "ob.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define obprintf_fn(format, ...) printf("%s: "format, __func__, ##__VA_ARGS__);
|
||||
#define OB_DEBUG_FOOTER_SIZE 0x20
|
||||
#define OB_DEBUG_FOOTER_MAGIC 0x001122334455667788
|
||||
|
||||
/*
|
||||
* Allocate a new object manager memory object.
|
||||
* -- tag = tag of the object to be allocated.
|
||||
* -- uFlags = flags as given by LocalAlloc.
|
||||
* -- uBytes = bytes of object (_including_ object headers).
|
||||
* -- pfnRef_0 = optional callback for cleanup o be called before object is destroyed.
|
||||
* (if object has references that should be decremented before destruction).
|
||||
* -- pfnRef_1 = optional callback for when object reach refcount = 1 (excl. initial).
|
||||
* -- return = allocated object on success, with refcount = 1, - NULL on fail.
|
||||
*/
|
||||
PVOID Ob_Alloc(_In_ DWORD tag, _In_ UINT uFlags, _In_ SIZE_T uBytes, _In_opt_ VOID(*pfnRef_0)(_In_ PVOID pOb), _In_opt_ VOID(*pfnRef_1)(_In_ PVOID pOb))
|
||||
{
|
||||
POB pOb;
|
||||
if((uBytes > 0x40000000) || (uBytes < sizeof(OB))) { return NULL; }
|
||||
pOb = (POB)LocalAlloc(uFlags, uBytes + OB_DEBUG_FOOTER_SIZE);
|
||||
if(!pOb) { return NULL; }
|
||||
pOb->_magic = OB_HEADER_MAGIC;
|
||||
pOb->_count = 1;
|
||||
pOb->_tag = tag;
|
||||
pOb->_pfnRef_0 = pfnRef_0;
|
||||
pOb->_pfnRef_1 = pfnRef_1;
|
||||
pOb->cbData = (DWORD)uBytes - sizeof(OB);
|
||||
#ifdef OB_DEBUG
|
||||
DWORD i, cb = sizeof(OB) + pOb->cbData;
|
||||
PBYTE pb = (PBYTE)pOb;
|
||||
for(i = 0; i < OB_DEBUG_FOOTER_SIZE; i += 8) {
|
||||
*(PQWORD)(pb + cb + i) = OB_DEBUG_FOOTER_MAGIC;
|
||||
}
|
||||
#endif /* OB_DEBUG */
|
||||
return pOb;
|
||||
}
|
||||
|
||||
/*
|
||||
* Increase the reference count of a object manager object.
|
||||
* -- pOb
|
||||
* -- return
|
||||
*/
|
||||
PVOID Ob_INCREF(_In_opt_ PVOID pObIn)
|
||||
{
|
||||
POB pOb = (POB)pObIn;
|
||||
if(pOb) {
|
||||
if(pOb->_magic == OB_HEADER_MAGIC) {
|
||||
InterlockedIncrement(&pOb->_count);
|
||||
return (POB)pOb;
|
||||
} else {
|
||||
obprintf_fn("ObCORE: CRITICAL: INCREF OF NON OBJECT MANAGER OBJECT!\n")
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decrease the reference count of a object manager object. If the reference
|
||||
* count reaches zero the object will be cleaned up.
|
||||
* -- pObIn
|
||||
* -- return = pObIn if pObIn is valid and refcount > 0 after decref.
|
||||
*/
|
||||
PVOID Ob_DECREF(_In_opt_ PVOID pObIn)
|
||||
{
|
||||
POB pOb = (POB)pObIn;
|
||||
DWORD c;
|
||||
if(pOb) {
|
||||
if(pOb->_magic == OB_HEADER_MAGIC) {
|
||||
c = InterlockedDecrement(&pOb->_count);
|
||||
#ifdef OB_DEBUG
|
||||
DWORD i, cb = sizeof(OB) + pOb->cbData;
|
||||
PBYTE pb = (PBYTE)pOb;
|
||||
for(i = 0; i < OB_DEBUG_FOOTER_SIZE; i += 8) {
|
||||
if(*(PQWORD)(pb + cb + i) != OB_DEBUG_FOOTER_MAGIC) {
|
||||
obprintf_fn("ObCORE: CRITICAL: FOOTER OVERWRITTEN - MEMORY CORRUPTION? REFCNT: %i TAG: %04X\n", c, pOb->_tag)
|
||||
}
|
||||
}
|
||||
#endif /* OB_DEBUG */
|
||||
if(c == 0) {
|
||||
if(pOb->_pfnRef_0) { pOb->_pfnRef_0(pOb); }
|
||||
pOb->_magic = 0;
|
||||
LocalFree(pOb);
|
||||
} else if((c == 1) && pOb->_pfnRef_1) {
|
||||
pOb->_pfnRef_1(pOb);
|
||||
return pOb;
|
||||
} else {
|
||||
return pOb;
|
||||
}
|
||||
} else {
|
||||
obprintf_fn("ObCORE: CRITICAL: DECREF OF NON OBJECT MANAGER OBJECT!\n")
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if pObIn is a valid object manager object with the specified tag.
|
||||
* -- pObIn
|
||||
* -- tag
|
||||
* -- return
|
||||
*/
|
||||
BOOL Ob_VALID_TAG(_In_ PVOID pObIn, _In_ DWORD tag)
|
||||
{
|
||||
POB pOb = (POB)pObIn;
|
||||
return pOb && (pOb->_magic == OB_HEADER_MAGIC) && (pOb->_tag = tag);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new object manager data object in which the ObHdr->cbData is equal
|
||||
* to the number of bytes in the data buffer supplied to this function.
|
||||
* May also be created with Ob_Alloc with size: sizeof(OB_HDR) + length of data.
|
||||
* CALLER DECREF: return
|
||||
* -- pb
|
||||
* -- cb
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_DATA ObData_New(_In_ PBYTE pb, _In_ DWORD cb)
|
||||
{
|
||||
POB_DATA pObData = NULL;
|
||||
if(pObData = Ob_Alloc(OB_TAG_CORE_DATA, 0, sizeof(OB) + cb, NULL, NULL)) {
|
||||
memcpy(pObData->pb, pb, cb);
|
||||
}
|
||||
return pObData;
|
||||
}
|
||||
@@ -0,0 +1,733 @@
|
||||
// ob_map.c : implementation of object manager hashed map functionality.
|
||||
//
|
||||
// The map is a key-value map that may, as an option, contain object manager
|
||||
// objects in its value field. They key may be user-defined, generated by a
|
||||
// function or absent. The ObMap may hold a maximum capacity of 0x02000000
|
||||
// (~32M) entries which are UNIQUE and non-NULL.
|
||||
//
|
||||
// The map (ObMap) is thread safe and implement efficient access to the data
|
||||
// via internal hashing functionality.
|
||||
// The map (ObMap) guarantees order amongst values unless the ObMap_Remove*
|
||||
// functions are called - in which order may change and on-going iterations
|
||||
// of the set with ObMap_Get/ObMap_GetNext may fail.
|
||||
// The ObMap is an object manager object and must be DECREF'ed when required.
|
||||
//
|
||||
// (c) Ulf Frisk, 2019-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include "ob.h"
|
||||
|
||||
#define OB_MAP_ENTRIES_DIRECTORY 0x100
|
||||
#define OB_MAP_ENTRIES_TABLE 0x200
|
||||
#define OB_MAP_ENTRIES_STORE 0x100
|
||||
#define OB_MAP_IS_VALID(p) (p && (p->ObHdr._magic == OB_HEADER_MAGIC) && (p->ObHdr._tag == OB_TAG_CORE_MAP))
|
||||
#define OB_MAP_TABLE_MAX_CAPACITY OB_MAP_ENTRIES_DIRECTORY * OB_MAP_ENTRIES_TABLE * OB_MAP_ENTRIES_STORE
|
||||
#define OB_MAP_HASH_FUNCTION(v) (13 * (v + _rotr16((WORD)v, 9) + _rotr((DWORD)v, 17) + _rotr64(v, 31)))
|
||||
|
||||
#define OB_MAP_INDEX_DIRECTORY(i) ((i >> 17) & (OB_MAP_ENTRIES_DIRECTORY - 1))
|
||||
#define OB_MAP_INDEX_TABLE(i) ((i >> 8) & (OB_MAP_ENTRIES_TABLE - 1))
|
||||
#define OB_MAP_INDEX_STORE(i) (i & (OB_MAP_ENTRIES_STORE - 1))
|
||||
|
||||
typedef struct tdOB_MAP_ENTRY {
|
||||
QWORD k;
|
||||
PVOID v;
|
||||
} OB_MAP_ENTRY, *POB_MAP_ENTRY, **PPOB_MAP_ENTRY;
|
||||
|
||||
typedef struct tdOB_MAP {
|
||||
OB ObHdr;
|
||||
SRWLOCK LockSRW;
|
||||
DWORD c;
|
||||
DWORD cHashMax;
|
||||
DWORD cHashGrowThreshold;
|
||||
BOOL fLargeMode;
|
||||
BOOL fKey;
|
||||
BOOL fObjectsOb;
|
||||
BOOL fObjectsLocalFree;
|
||||
PDWORD pHashMapKey;
|
||||
PDWORD pHashMapValue;
|
||||
union {
|
||||
PPOB_MAP_ENTRY Directory[OB_MAP_ENTRIES_DIRECTORY];
|
||||
struct {
|
||||
PPOB_MAP_ENTRY _SmallDirectory[1];
|
||||
DWORD _SmallHashMap[0x200];
|
||||
};
|
||||
};
|
||||
POB_MAP_ENTRY _SmallTable[1];
|
||||
OB_MAP_ENTRY Store00[OB_MAP_ENTRIES_STORE];
|
||||
} OB_MAP, *POB_MAP;
|
||||
|
||||
#define OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, RetTp, RetValFail, fn) { \
|
||||
if(!OB_MAP_IS_VALID(pm)) { return RetValFail; } \
|
||||
RetTp retVal; \
|
||||
AcquireSRWLockExclusive(&pm->LockSRW); \
|
||||
retVal = fn; \
|
||||
ReleaseSRWLockExclusive(&pm->LockSRW); \
|
||||
return retVal; \
|
||||
}
|
||||
|
||||
#define OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, RetTp, RetValFail, fn) { \
|
||||
if(!OB_MAP_IS_VALID(pm)) { return RetValFail; } \
|
||||
RetTp retVal; \
|
||||
AcquireSRWLockShared(&pm->LockSRW); \
|
||||
retVal = fn; \
|
||||
ReleaseSRWLockShared(&pm->LockSRW); \
|
||||
return retVal; \
|
||||
}
|
||||
|
||||
/*
|
||||
* Ob_DECREF / LocalFree all objects in the map (if required)
|
||||
* -- pObMap
|
||||
*/
|
||||
VOID _ObMap_ObFreeAllObjects(_In_ POB_MAP pObMap)
|
||||
{
|
||||
DWORD i;
|
||||
POB_MAP_ENTRY pe;
|
||||
if(pObMap->fObjectsOb) {
|
||||
for(i = 1; i < pObMap->c; i++) {
|
||||
pe = &pObMap->Directory[OB_MAP_INDEX_DIRECTORY(i)][OB_MAP_INDEX_TABLE(i)][OB_MAP_INDEX_STORE(i)];
|
||||
Ob_DECREF(pe->v);
|
||||
}
|
||||
} else if(pObMap->fObjectsLocalFree) {
|
||||
for(i = 1; i < pObMap->c; i++) {
|
||||
pe = &pObMap->Directory[OB_MAP_INDEX_DIRECTORY(i)][OB_MAP_INDEX_TABLE(i)][OB_MAP_INDEX_STORE(i)];
|
||||
LocalFree(pe->v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Object Map object manager cleanup function to be called when reference
|
||||
* count reaches zero.
|
||||
* -- pObMap
|
||||
*/
|
||||
VOID _ObMap_ObCloseCallback(_In_ POB_MAP pObMap)
|
||||
{
|
||||
DWORD iDirectory, iTable;
|
||||
_ObMap_ObFreeAllObjects(pObMap);
|
||||
if(pObMap->fLargeMode) {
|
||||
for(iDirectory = 0; iDirectory < OB_MAP_ENTRIES_DIRECTORY; iDirectory++) {
|
||||
if(!pObMap->Directory[iDirectory]) { break; }
|
||||
for(iTable = 0; iTable < OB_MAP_ENTRIES_TABLE; iTable++) {
|
||||
if(!pObMap->Directory[iDirectory][iTable]) { break; }
|
||||
if(iDirectory || iTable) {
|
||||
LocalFree(pObMap->Directory[iDirectory][iTable]);
|
||||
}
|
||||
}
|
||||
LocalFree(pObMap->Directory[iDirectory]);
|
||||
}
|
||||
LocalFree(pObMap->pHashMapValue);
|
||||
}
|
||||
}
|
||||
|
||||
inline POB_MAP_ENTRY _ObMap_GetFromIndex(_In_ POB_MAP pm, _In_ DWORD iEntry)
|
||||
{
|
||||
if(!iEntry || (iEntry >= pm->c)) { return NULL; }
|
||||
return &pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)][OB_MAP_INDEX_TABLE(iEntry)][OB_MAP_INDEX_STORE(iEntry)];
|
||||
}
|
||||
|
||||
inline QWORD _ObMap_GetFromEntryIndex(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ DWORD iEntry)
|
||||
{
|
||||
POB_MAP_ENTRY pe = _ObMap_GetFromIndex(pm, iEntry);
|
||||
return pe ? (fValueHash ? (QWORD)pe->v : pe->k) : 0;
|
||||
}
|
||||
|
||||
inline VOID _ObMap_SetHashIndex(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ DWORD iHash, _In_ DWORD iEntry)
|
||||
{
|
||||
if(fValueHash) {
|
||||
pm->pHashMapValue[iHash] = iEntry;
|
||||
} else if(pm->fKey) {
|
||||
pm->pHashMapKey[iHash] = iEntry;
|
||||
}
|
||||
}
|
||||
|
||||
VOID _ObMap_InsertHash(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ DWORD iEntry)
|
||||
{
|
||||
QWORD qwValueToHash;
|
||||
DWORD iHash, dwHashMask = pm->cHashMax - 1;
|
||||
if(!fValueHash && !pm->fKey) { return; }
|
||||
qwValueToHash = _ObMap_GetFromEntryIndex(pm, fValueHash, iEntry);
|
||||
iHash = OB_MAP_HASH_FUNCTION(qwValueToHash) & dwHashMask;
|
||||
while(fValueHash ? pm->pHashMapValue[iHash] : pm->pHashMapKey[iHash]) {
|
||||
iHash = (iHash + 1) & dwHashMask;
|
||||
}
|
||||
_ObMap_SetHashIndex(pm, fValueHash, iHash, iEntry);
|
||||
}
|
||||
|
||||
VOID _ObMap_RemoveHash(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ QWORD kv, _In_ DWORD iEntry)
|
||||
{
|
||||
DWORD iHash, dwHashMask = pm->cHashMax - 1;
|
||||
DWORD iNextHash, iNextEntry, iNextHashPreferred;
|
||||
QWORD qwNextEntry;
|
||||
if(!fValueHash && !pm->fKey) { return; }
|
||||
// search for hash index and clear
|
||||
iHash = OB_MAP_HASH_FUNCTION(kv) & dwHashMask;
|
||||
while(TRUE) {
|
||||
if(iEntry == (fValueHash ? pm->pHashMapValue[iHash] : pm->pHashMapKey[iHash])) { break; }
|
||||
iHash = (iHash + 1) & dwHashMask;
|
||||
}
|
||||
_ObMap_SetHashIndex(pm, fValueHash, iHash, 0);
|
||||
// re-hash any entries following (value)
|
||||
iNextHash = iHash;
|
||||
while(TRUE) {
|
||||
iNextHash = (iNextHash + 1) & dwHashMask;
|
||||
iNextEntry = fValueHash ? pm->pHashMapValue[iNextHash] : pm->pHashMapKey[iNextHash];
|
||||
if(0 == iNextEntry) { return; }
|
||||
qwNextEntry = _ObMap_GetFromEntryIndex(pm, fValueHash, iNextEntry);
|
||||
iNextHashPreferred = OB_MAP_HASH_FUNCTION(qwNextEntry) & dwHashMask;
|
||||
if(iNextHash == iNextHashPreferred) { continue; }
|
||||
_ObMap_SetHashIndex(pm, fValueHash, iNextHash, 0);
|
||||
_ObMap_InsertHash(pm, fValueHash, iNextEntry);
|
||||
}
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObMap_GetEntryIndexFromKeyOrValue(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ QWORD kv, _Out_opt_ PDWORD piEntry)
|
||||
{
|
||||
DWORD iEntry;
|
||||
DWORD dwHashMask = pm->cHashMax - 1;
|
||||
DWORD iHash = OB_MAP_HASH_FUNCTION(kv) & dwHashMask;
|
||||
if(!fValueHash && !pm->fKey) { return FALSE; }
|
||||
// scan hash table to find entry
|
||||
while(TRUE) {
|
||||
iEntry = fValueHash ? pm->pHashMapValue[iHash] : pm->pHashMapKey[iHash];
|
||||
if(0 == iEntry) { return FALSE; }
|
||||
if(kv == _ObMap_GetFromEntryIndex(pm, fValueHash, iEntry)) {
|
||||
if(piEntry) { *piEntry = iEntry; }
|
||||
return TRUE;
|
||||
}
|
||||
iHash = (iHash + 1) & dwHashMask;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// RETRIEVE/GET FUNCTIONALITY BELOW:
|
||||
// ObMap_Size, ObMap_Exists, ObMap_ExistsKey, ObMap_GetByIndex,
|
||||
// ObMap_Peek, ObMap_PeekKey, ObMap_GetByKey, ObMap_GetNext,
|
||||
// ObMap_GetKey,
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
inline BOOL _ObMap_Exists(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ QWORD kv)
|
||||
{
|
||||
if((!fValueHash && !pm->fKey) || (fValueHash && !kv)) { return FALSE; }
|
||||
return _ObMap_GetEntryIndexFromKeyOrValue(pm, fValueHash, kv, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the number of objects in the ObMap.
|
||||
* -- pm
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObMap_Size(_In_opt_ POB_MAP pm)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, DWORD, 0, pm->c - 1)
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if an object exists in the ObMap.
|
||||
* -- pm
|
||||
* -- qwKey/pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObMap_Exists(_In_opt_ POB_MAP pm, _In_ PVOID pvObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, BOOL, FALSE, _ObMap_Exists(pm, TRUE, (QWORD)pvObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a key exists in the ObMap.
|
||||
* -- pm
|
||||
* -- qwKey/pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObMap_ExistsKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, BOOL, FALSE, _ObMap_Exists(pm, FALSE, qwKey))
|
||||
}
|
||||
|
||||
PVOID _ObMap_GetByEntryIndex(_In_ POB_MAP pm, _In_ DWORD iEntry)
|
||||
{
|
||||
PVOID pvObObject = (PVOID)_ObMap_GetFromEntryIndex(pm, TRUE, iEntry);
|
||||
if(pm->fObjectsOb) { Ob_INCREF(pvObObject); }
|
||||
return pvObObject;
|
||||
}
|
||||
|
||||
PVOID _ObMap_GetByKey(_In_ POB_MAP pm, _In_ QWORD qwKey)
|
||||
{
|
||||
DWORD iEntry;
|
||||
return _ObMap_GetEntryIndexFromKeyOrValue(pm, FALSE, qwKey, &iEntry) ? _ObMap_GetByEntryIndex(pm, iEntry) : NULL;
|
||||
}
|
||||
|
||||
PVOID _ObMap_GetNext(_In_ POB_MAP pm, _In_opt_ PVOID pvObject)
|
||||
{
|
||||
DWORD iEntry;
|
||||
if(!pvObject) {
|
||||
return _ObMap_GetByEntryIndex(pm, 1);
|
||||
}
|
||||
if(pm->fObjectsOb) { Ob_DECREF(pvObject); }
|
||||
if(!_ObMap_GetEntryIndexFromKeyOrValue(pm, TRUE, (QWORD)pvObject, &iEntry)) { return NULL; }
|
||||
return _ObMap_GetByEntryIndex(pm, iEntry + 1);
|
||||
}
|
||||
|
||||
PVOID _ObMap_GetNextByKey(_In_ POB_MAP pm, _In_ QWORD qwKey, _In_opt_ PVOID pvObject)
|
||||
{
|
||||
DWORD iEntry;
|
||||
if(!pvObject) {
|
||||
return _ObMap_GetByEntryIndex(pm, 1);
|
||||
}
|
||||
if(pm->fObjectsOb) { Ob_DECREF(pvObject); }
|
||||
if(!_ObMap_GetEntryIndexFromKeyOrValue(pm, FALSE, qwKey, &iEntry)) { return NULL; }
|
||||
return _ObMap_GetByEntryIndex(pm, iEntry + 1);
|
||||
}
|
||||
|
||||
_Success_(return != 0)
|
||||
QWORD _ObMap_GetKey(_In_ POB_MAP pm, _In_ PVOID pvObject)
|
||||
{
|
||||
DWORD iEntry;
|
||||
if(!_ObMap_GetEntryIndexFromKeyOrValue(pm, TRUE, (QWORD)pvObject, &iEntry)) { return 0; }
|
||||
return _ObMap_GetFromIndex(pm, iEntry)->k;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObMap_Filter(_In_ POB_MAP pm, _Inout_opt_ PVOID ctx, _In_ VOID(*pfnFilter)(_In_ QWORD k, _In_ PVOID v, _Inout_opt_ PVOID ctx))
|
||||
{
|
||||
DWORD iEntry;
|
||||
POB_MAP_ENTRY pEntry;
|
||||
for(iEntry = 1; iEntry < pm->c; iEntry++) {
|
||||
pEntry = &pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)][OB_MAP_INDEX_TABLE(iEntry)][OB_MAP_INDEX_STORE(iEntry)];
|
||||
pfnFilter(pEntry->k, pEntry->v, ctx);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Success_(return != NULL)
|
||||
POB_SET _ObMap_FilterSet(_In_ POB_MAP pm, _In_opt_ VOID(*pfnFilter)(_In_ QWORD k, _In_ PVOID v, _Inout_ POB_SET ps))
|
||||
{
|
||||
DWORD iEntry;
|
||||
POB_MAP_ENTRY pEntry;
|
||||
POB_SET pObSet;
|
||||
if(!(pObSet = ObSet_New())) { return NULL; }
|
||||
if(!pfnFilter) { return pObSet; }
|
||||
for(iEntry = 1; iEntry < pm->c; iEntry++) {
|
||||
pEntry = &pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)][OB_MAP_INDEX_TABLE(iEntry)][OB_MAP_INDEX_STORE(iEntry)];
|
||||
pfnFilter(pEntry->k, pEntry->v, pObSet);
|
||||
}
|
||||
return pObSet;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve an object given an index (which is less than the amount of items
|
||||
* in the ObMap).
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT- guaranteed if the
|
||||
* ObMap_Remove* functions are called while iterating over the ObSet - items
|
||||
* may be skipped or iterated over multiple times!
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- index
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetByIndex(_In_opt_ POB_MAP pm, _In_ DWORD index)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, PVOID, NULL, _ObMap_GetByEntryIndex(pm, index + 1)) // (+1 == account/adjust for index 0 (reserved))
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve a value given a key.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetByKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, PVOID, NULL, _ObMap_GetByKey(pm, qwKey))
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the next object given an object. Start and end objects are NULL.
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT_ guaranteed if the
|
||||
* ObMap_Remove* functions are called while iterating over the ObMap - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* FUNCTION DECREF(if OB): pvObject
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetNext(_In_opt_ POB_MAP pm, _In_opt_ PVOID pvObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, PVOID, NULL, _ObMap_GetNext(pm, pvObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the next object given a key. To start iterating supply NULL in the
|
||||
* pvObject parameter (this overrides qwKey). When no more objects are found
|
||||
* NULL will be returned. This function may ideally be used when object maps
|
||||
* may be refreshed between function calls. Key may be more stable than object.
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT_ guaranteed if the
|
||||
* ObMap_Remove* functions are called while iterating over the ObMap - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* FUNCTION DECREF(if OB): pvObject
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
PVOID ObMap_GetNextByKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey, _In_opt_ PVOID pvObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, PVOID, NULL, _ObMap_GetNextByKey(pm, qwKey, pvObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the key for an existing object in the ObMap.
|
||||
* -- pm
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return != 0)
|
||||
QWORD ObMap_GetKey(_In_opt_ POB_MAP pm, _In_ PVOID pvObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, QWORD, 0, _ObMap_GetKey(pm, pvObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Peek the "last" object.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObMap_Peek(_In_opt_ POB_MAP pm)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, PVOID, NULL, _ObMap_GetByEntryIndex(pm, pm->c - 1))
|
||||
}
|
||||
|
||||
/*
|
||||
* Peek the key of the "last" object.
|
||||
* -- pm
|
||||
* -- return = the key, otherwise 0.
|
||||
*/
|
||||
QWORD ObMap_PeekKey(_In_opt_ POB_MAP pm)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, QWORD, 0, _ObMap_GetFromEntryIndex(pm, FALSE, pm->c - 1))
|
||||
}
|
||||
|
||||
/*
|
||||
* Filter map objects into a generic context by using a user-supplied filter function.
|
||||
* -- pm
|
||||
* -- ctx = optional context to pass on to the filter function.
|
||||
* -- pfnFilter
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_Filter(_In_opt_ POB_MAP pm, _Inout_opt_ PVOID ctx, _In_opt_ VOID(*pfnFilter)(_In_ QWORD k, _In_ PVOID v, _Inout_opt_ PVOID ctx))
|
||||
{
|
||||
if(!pfnFilter) { return FALSE; }
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, BOOL, FALSE, _ObMap_Filter(pm, ctx, pfnFilter));
|
||||
}
|
||||
|
||||
/*
|
||||
* Filter map objects into a POB_SET by using a user-supplied filter function.
|
||||
* CALLER DECREF: return
|
||||
* -- pm
|
||||
* -- pfnFilter
|
||||
* -- return = POB_SET consisting of values gathered by the pfnFilter function.
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
POB_SET ObMap_FilterSet(_In_opt_ POB_MAP pm, _In_opt_ VOID(*pfnFilter)(_In_ QWORD k, _In_ PVOID v, _Inout_ POB_SET ps))
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pm, POB_SET, NULL, _ObMap_FilterSet(pm, pfnFilter));
|
||||
}
|
||||
|
||||
/*
|
||||
* Common filter function related to ObMap_FilterSet.
|
||||
*/
|
||||
VOID ObMap_FilterSet_FilterAllKey(_In_ QWORD k, _In_ PVOID v, _Inout_ POB_SET ps)
|
||||
{
|
||||
ObSet_Push(ps, k);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// REMOVAL FUNCTIONALITY BELOW:
|
||||
// ObMap_Pop, ObMap_Remove, ObMap_RemoveByKey, ObMap_RemoveByFilter
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* CALLER DECREF: return
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
PVOID _ObMap_RetrieveAndRemoveByEntryIndex(_In_ POB_MAP pm, _In_ DWORD iEntry, _Out_opt_ PQWORD pKey)
|
||||
{
|
||||
POB_MAP_ENTRY pRemoveEntry, pLastEntry;
|
||||
QWORD qwRemoveKey, qwRemoveValue;
|
||||
if(!(pRemoveEntry = _ObMap_GetFromIndex(pm, iEntry))) { return NULL; }
|
||||
qwRemoveKey = pRemoveEntry->k;
|
||||
qwRemoveValue = (QWORD)pRemoveEntry->v;
|
||||
_ObMap_RemoveHash(pm, FALSE, qwRemoveKey, iEntry);
|
||||
_ObMap_RemoveHash(pm, TRUE, qwRemoveValue, iEntry);
|
||||
if(iEntry < pm->c - 1) {
|
||||
// not last item removed -> move last item into empty bucket
|
||||
pLastEntry = _ObMap_GetFromIndex(pm, pm->c - 1);
|
||||
_ObMap_RemoveHash(pm, FALSE, pLastEntry->k, pm->c - 1);
|
||||
_ObMap_RemoveHash(pm, TRUE, (QWORD)pLastEntry->v, pm->c - 1);
|
||||
pRemoveEntry->k = pLastEntry->k;
|
||||
pRemoveEntry->v = pLastEntry->v;
|
||||
_ObMap_InsertHash(pm, FALSE, iEntry);
|
||||
_ObMap_InsertHash(pm, TRUE, iEntry);
|
||||
}
|
||||
pm->c--;
|
||||
if(pKey) { *pKey = qwRemoveKey; }
|
||||
return (PVOID)qwRemoveValue;
|
||||
}
|
||||
|
||||
PVOID _ObMap_RemoveOrRemoveByKey(_In_ POB_MAP pm, _In_ BOOL fValueHash, _In_ QWORD kv)
|
||||
{
|
||||
DWORD iEntry;
|
||||
if(fValueHash && !kv) { return NULL; }
|
||||
if(!_ObMap_GetEntryIndexFromKeyOrValue(pm, fValueHash, kv, &iEntry)) { return NULL; }
|
||||
return _ObMap_RetrieveAndRemoveByEntryIndex(pm, iEntry, NULL);
|
||||
}
|
||||
|
||||
DWORD _ObMap_RemoveByFilter(_In_ POB_MAP pm, _In_opt_ BOOL(*pfnFilter)(_In_ QWORD k, _In_ PVOID v))
|
||||
{
|
||||
DWORD cRemove = 0;
|
||||
DWORD iEntry;
|
||||
PVOID pv;
|
||||
POB_MAP_ENTRY pEntry;
|
||||
if(!pfnFilter) { return 0; }
|
||||
for(iEntry = pm->c - 1; iEntry; iEntry--) {
|
||||
pEntry = &pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)][OB_MAP_INDEX_TABLE(iEntry)][OB_MAP_INDEX_STORE(iEntry)];
|
||||
if(pfnFilter(pEntry->k, pEntry->v)) {
|
||||
cRemove++;
|
||||
pv = _ObMap_RetrieveAndRemoveByEntryIndex(pm, iEntry, NULL);
|
||||
if(pm->fObjectsOb) { Ob_DECREF(pv); }
|
||||
if(pm->fObjectsLocalFree) { LocalFree(pv); }
|
||||
}
|
||||
}
|
||||
return cRemove;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the "last" object.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
PVOID ObMap_Pop(_In_opt_ POB_MAP pm)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, PVOID, NULL, _ObMap_RetrieveAndRemoveByEntryIndex(pm, pm->c - 1, NULL))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the "last" object and return it and its key.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- pKey
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
_Success_(return != NULL)
|
||||
PVOID ObMap_PopWithKey(_In_opt_ POB_MAP pm, _Out_ PQWORD pKey)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, PVOID, NULL, _ObMap_RetrieveAndRemoveByEntryIndex(pm, pm->c - 1, pKey))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove an object from the ObMap.
|
||||
* NB! must not be called simultaneously while iterating with ObMap_GetByIndex/ObMap_GetNext.
|
||||
* -- pm
|
||||
* -- value
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObMap_Remove(_In_opt_ POB_MAP pm, _In_ PVOID pvObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, PVOID, NULL, _ObMap_RemoveOrRemoveByKey(pm, TRUE, (QWORD)pvObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove an object from the ObMap by using its key.
|
||||
* NB! must not be called simultaneously while iterating with ObMap_GetByIndex/ObMap_GetNext.
|
||||
* CALLER DECREF(if OB): return
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- return = success: object, fail: NULL.
|
||||
*/
|
||||
PVOID ObMap_RemoveByKey(_In_opt_ POB_MAP pm, _In_ QWORD qwKey)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, PVOID, NULL, _ObMap_RemoveOrRemoveByKey(pm, FALSE, qwKey))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove map objects using a user-supplied filter function.
|
||||
* -- pm
|
||||
* -- pfnFilter = decision making function, TRUE = keep, FALSE = remove.
|
||||
* -- return = number of entries removed.
|
||||
*/
|
||||
DWORD ObMap_RemoveByFilter(_In_opt_ POB_MAP pm, _In_opt_ BOOL(*pfnFilter)(_In_ QWORD k, _In_ PVOID v))
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, DWORD, 0, _ObMap_RemoveByFilter(pm, pfnFilter));
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the ObMap by removing all objects and their keys.
|
||||
* NB! underlying allocated memory will remain unchanged.
|
||||
* -- pm
|
||||
* -- return = clear was successful - always true.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_Clear(_In_opt_ POB_MAP pm)
|
||||
{
|
||||
if(!OB_MAP_IS_VALID(pm) || (pm->c <= 1)) { return TRUE; }
|
||||
AcquireSRWLockExclusive(&pm->LockSRW);
|
||||
if(pm->c <= 1) {
|
||||
ReleaseSRWLockExclusive(&pm->LockSRW);
|
||||
return TRUE;
|
||||
}
|
||||
_ObMap_ObFreeAllObjects(pm);
|
||||
ZeroMemory(pm->pHashMapValue, 4ULL * pm->cHashMax);
|
||||
if(pm->pHashMapKey) { ZeroMemory(pm->pHashMapKey, 4ULL * pm->cHashMax); }
|
||||
pm->c = 1; // item zero is reserved - hence the initialization of count to 1
|
||||
ReleaseSRWLockExclusive(&pm->LockSRW);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CREATE / INSERT FUNCTIONALITY BELOW:
|
||||
// ObMap_New, ObMap_Push
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Grow the Tables for hash lookups by a factor of *2.
|
||||
* -- pvs
|
||||
* -- pm
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL _ObMap_Grow(_In_ POB_MAP pm)
|
||||
{
|
||||
DWORD iEntry;
|
||||
PDWORD pdwNewAllocHashMap;
|
||||
if(!(pdwNewAllocHashMap = LocalAlloc(LMEM_ZEROINIT, 2 * sizeof(DWORD) * pm->cHashMax * (pm->fKey ? 2 : 1)))) { return FALSE; }
|
||||
if(!pm->fLargeMode) {
|
||||
if(!(pm->Directory[0] = LocalAlloc(LMEM_ZEROINIT, sizeof(POB_MAP_ENTRY) * OB_MAP_ENTRIES_TABLE))) { return FALSE; }
|
||||
pm->Directory[0][0] = pm->Store00;
|
||||
ZeroMemory(pm->_SmallHashMap, sizeof(pm->_SmallHashMap));
|
||||
pm->pHashMapKey = NULL;
|
||||
pm->pHashMapValue = NULL;
|
||||
pm->fLargeMode = TRUE;
|
||||
}
|
||||
pm->cHashMax *= 2;
|
||||
pm->cHashGrowThreshold *= 2;
|
||||
LocalFree(pm->pHashMapValue);
|
||||
pm->pHashMapValue = pdwNewAllocHashMap;
|
||||
if(pm->fKey) {
|
||||
pm->pHashMapKey = pm->pHashMapValue + pm->cHashMax;
|
||||
}
|
||||
for(iEntry = 1; iEntry < pm->c; iEntry++) {
|
||||
_ObMap_InsertHash(pm, TRUE, iEntry);
|
||||
_ObMap_InsertHash(pm, FALSE, iEntry);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObMap_Push(_In_ POB_MAP pm, _In_ QWORD qwKey, _In_ PVOID pvObject)
|
||||
{
|
||||
POB_MAP_ENTRY pe;
|
||||
DWORD iEntry = pm->c;
|
||||
if(!pvObject || _ObMap_Exists(pm, TRUE, (QWORD)pvObject) || _ObMap_Exists(pm, FALSE, qwKey)) { return FALSE; }
|
||||
if(iEntry == OB_MAP_ENTRIES_DIRECTORY * OB_MAP_ENTRIES_TABLE * OB_MAP_ENTRIES_STORE) { return FALSE; }
|
||||
if(iEntry == pm->cHashGrowThreshold) {
|
||||
if(!_ObMap_Grow(pm)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
if(!pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)]) { // allocate "table" if required
|
||||
if(!(pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)] = LocalAlloc(LMEM_ZEROINIT, sizeof(POB_MAP_ENTRY) * OB_MAP_ENTRIES_TABLE))) { return FALSE; }
|
||||
}
|
||||
if(!pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)][OB_MAP_INDEX_TABLE(iEntry)]) { // allocate "store" if required
|
||||
if(!(pm->Directory[OB_MAP_INDEX_DIRECTORY(iEntry)][OB_MAP_INDEX_TABLE(iEntry)] = LocalAlloc(LMEM_ZEROINIT, sizeof(OB_MAP_ENTRY) * OB_MAP_ENTRIES_STORE))) { return FALSE; }
|
||||
}
|
||||
if(pm->fObjectsOb) {
|
||||
Ob_INCREF(pvObject);
|
||||
}
|
||||
pm->c++;
|
||||
pe = _ObMap_GetFromIndex(pm, iEntry);
|
||||
pe->k = qwKey;
|
||||
pe->v = pvObject;
|
||||
_ObMap_InsertHash(pm, TRUE, iEntry);
|
||||
_ObMap_InsertHash(pm, FALSE, iEntry);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObMap_PushCopy(_In_ POB_MAP pm, _In_ QWORD qwKey, _In_ PVOID pvObject, _In_ SIZE_T cbObject)
|
||||
{
|
||||
PVOID pvObjectCopy;
|
||||
if(!pm->fObjectsLocalFree) { return FALSE; }
|
||||
if(!(pvObjectCopy = LocalAlloc(0, cbObject))) { return FALSE; }
|
||||
memcpy(pvObjectCopy, pvObject, cbObject);
|
||||
if(_ObMap_Push(pm, qwKey, pvObjectCopy)) { return TRUE; }
|
||||
LocalFree(pvObjectCopy);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObMap.
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if the key or object
|
||||
* already exists or if the max capacity of the map is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_Push(_In_opt_ POB_MAP pm, _In_ QWORD qwKey, _In_ PVOID pvObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, BOOL, FALSE, _ObMap_Push(pm, qwKey, pvObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Push / Insert into the ObMap by making a shallow copy of the object.
|
||||
* NB! only valid for OB_MAP_FLAGS_OBJECT_LOCALFREE initialized maps.
|
||||
* -- pm
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- cbObject
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if the key or object
|
||||
* already exists or if the max capacity of the map is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObMap_PushCopy(_In_opt_ POB_MAP pm, _In_ QWORD qwKey, _In_ PVOID pvObject, _In_ SIZE_T cbObject)
|
||||
{
|
||||
OB_MAP_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pm, BOOL, FALSE, _ObMap_PushCopy(pm, qwKey, pvObject, cbObject))
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new map. A map (ObMap) provides atomic map operations and ways
|
||||
* to optionally map key values to values, pointers or object manager objects.
|
||||
* The ObMap is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- flags = defined by OB_MAP_FLAGS_*
|
||||
* -- return
|
||||
*/
|
||||
POB_MAP ObMap_New(_In_ QWORD flags)
|
||||
{
|
||||
POB_MAP pObMap;
|
||||
if((flags & OB_MAP_FLAGS_OBJECT_OB) && (flags & OB_MAP_FLAGS_OBJECT_LOCALFREE)) { return NULL; }
|
||||
pObMap = Ob_Alloc(OB_TAG_CORE_MAP, LMEM_ZEROINIT, sizeof(OB_MAP), _ObMap_ObCloseCallback, NULL);
|
||||
if(!pObMap) { return NULL; }
|
||||
InitializeSRWLock(&pObMap->LockSRW);
|
||||
pObMap->c = 1; // item zero is reserved - hence the initialization of count to 1
|
||||
pObMap->fKey = (flags & OB_MAP_FLAGS_NOKEY) ? FALSE : TRUE;
|
||||
pObMap->fObjectsOb = (flags & OB_MAP_FLAGS_OBJECT_OB) ? TRUE : FALSE;
|
||||
pObMap->fObjectsLocalFree = (flags & OB_MAP_FLAGS_OBJECT_LOCALFREE) ? TRUE : FALSE;
|
||||
pObMap->_SmallTable[0] = pObMap->Store00;
|
||||
pObMap->Directory[0] = pObMap->_SmallTable;
|
||||
pObMap->pHashMapValue = pObMap->_SmallHashMap;
|
||||
pObMap->cHashMax = 0x100;
|
||||
pObMap->cHashGrowThreshold = 0xc0;
|
||||
pObMap->pHashMapKey = pObMap->pHashMapValue + pObMap->cHashMax;
|
||||
return pObMap;
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
// ob_set.c : implementation of object manager hashed value set functionality.
|
||||
//
|
||||
// The hashed value set (ObSet) provides thread safe efficient access to a set
|
||||
// which is containing _NON_ZERO_ values (64-bit unsigned integers). The ObSet
|
||||
// may hold a maximum capacity of 0x01000000 (~16M) entries - which are UNIQUE
|
||||
// and _NON_ZERO_.
|
||||
// The hashed value set (ObSet) guarantees order amongst values unless the
|
||||
// function ObSet_Remove is called - in which order may change and on-going
|
||||
// iterations of the set with ObSet_Get/ObSet_GetNext may fail.
|
||||
// The ObSet is an object manager object and must be DECREF'ed when required.
|
||||
//
|
||||
// (c) Ulf Frisk, 2019-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include "ob.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define OB_SET_ENTRIES_DIRECTORY 0x100
|
||||
#define OB_SET_ENTRIES_TABLE 0x80
|
||||
#define OB_SET_ENTRIES_STORE 0x200
|
||||
|
||||
typedef struct tdOB_SET_TABLE_ENTRY {
|
||||
PQWORD pValues; // ptr to QWORD[0x200]
|
||||
} OB_SET_TABLE_ENTRY, *POB_SET_TABLE_ENTRY;
|
||||
|
||||
typedef struct tdOB_SET_TABLE_DIRECTORY_ENTRY {
|
||||
POB_SET_TABLE_ENTRY pTable; // ptr to OB_SET_TABLE_ENTRY[0x20]
|
||||
} OB_SET_TABLE_DIRECTORY_ENTRY, *POB_SET_TABLE_DIRECTORY_ENTRY;
|
||||
|
||||
typedef struct tdOB_SET {
|
||||
OB ObHdr;
|
||||
SRWLOCK LockSRW;
|
||||
DWORD c;
|
||||
DWORD cHashMax;
|
||||
DWORD cHashGrowThreshold;
|
||||
BOOL fLargeMode;
|
||||
PDWORD pHashMapLarge;
|
||||
union {
|
||||
WORD pHashMapSmall[0x400];
|
||||
OB_SET_TABLE_DIRECTORY_ENTRY pDirectory[OB_SET_ENTRIES_DIRECTORY];
|
||||
};
|
||||
OB_SET_TABLE_ENTRY pTable0[OB_SET_ENTRIES_TABLE];
|
||||
QWORD pStore00[OB_SET_ENTRIES_STORE];
|
||||
} OB_SET, *POB_SET;
|
||||
|
||||
#define OB_SET_IS_VALID(p) (p && (p->ObHdr._magic == OB_HEADER_MAGIC) && (p->ObHdr._tag == OB_TAG_CORE_SET))
|
||||
#define TABLE_MAX_CAPACITY OB_SET_ENTRIES_DIRECTORY * OB_SET_ENTRIES_TABLE * OB_SET_ENTRIES_STORE
|
||||
#define HASH_FUNCTION(v) (13 * (v + _rotr16((WORD)v, 9) + _rotr((DWORD)v, 17) + _rotr64(v, 31)))
|
||||
|
||||
#define OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pvs, RetTp, RetValFail, fn) { \
|
||||
if(!OB_SET_IS_VALID(pvs)) { return RetValFail; } \
|
||||
RetTp retVal; \
|
||||
AcquireSRWLockExclusive(&pvs->LockSRW); \
|
||||
retVal = fn; \
|
||||
ReleaseSRWLockExclusive(&pvs->LockSRW); \
|
||||
return retVal; \
|
||||
}
|
||||
|
||||
#define OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, RetTp, RetValFail, fn) { \
|
||||
if(!OB_SET_IS_VALID(pvs)) { return RetValFail; } \
|
||||
RetTp retVal; \
|
||||
AcquireSRWLockShared(&pvs->LockSRW); \
|
||||
retVal = fn; \
|
||||
ReleaseSRWLockShared(&pvs->LockSRW); \
|
||||
return retVal; \
|
||||
}
|
||||
|
||||
/*
|
||||
* Object Container object manager cleanup function to be called when reference
|
||||
* count reaches zero.
|
||||
* -- pObSet
|
||||
*/
|
||||
VOID _ObSet_ObCloseCallback(_In_ POB_SET pObSet)
|
||||
{
|
||||
DWORD iDirectory, iTable;
|
||||
if(pObSet->fLargeMode) {
|
||||
for(iDirectory = 0; iDirectory < OB_SET_ENTRIES_DIRECTORY; iDirectory++) {
|
||||
if(!pObSet->pDirectory[iDirectory].pTable) { break; }
|
||||
for(iTable = 0; iTable < OB_SET_ENTRIES_TABLE; iTable++) {
|
||||
if(!pObSet->pDirectory[iDirectory].pTable[iTable].pValues) { break; }
|
||||
if(iDirectory || iTable) {
|
||||
LocalFree(pObSet->pDirectory[iDirectory].pTable[iTable].pValues);
|
||||
}
|
||||
}
|
||||
if(iDirectory) {
|
||||
LocalFree(pObSet->pDirectory[iDirectory].pTable);
|
||||
}
|
||||
}
|
||||
LocalFree(pObSet->pHashMapLarge);
|
||||
} else {
|
||||
for(iTable = 1; iTable < OB_SET_ENTRIES_TABLE; iTable++) {
|
||||
if(!pObSet->pTable0[iTable].pValues) { break; }
|
||||
LocalFree(pObSet->pTable0[iTable].pValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new hashed value set. A hashed value set (ObSet) provides atomic
|
||||
* ways to store unique 64-bit (or smaller) numbers as a set.
|
||||
* The ObSet is an object manager object and must be DECREF'ed when required.
|
||||
* CALLER DECREF: return
|
||||
* -- return
|
||||
*/
|
||||
POB_SET ObSet_New()
|
||||
{
|
||||
POB_SET pObSet = Ob_Alloc(OB_TAG_CORE_SET, LMEM_ZEROINIT, sizeof(OB_SET), _ObSet_ObCloseCallback, NULL);
|
||||
if(!pObSet) { return NULL; }
|
||||
InitializeSRWLock(&pObSet->LockSRW);
|
||||
pObSet->c = 1; // item zero is reserved - hence the initialization of count to 1
|
||||
pObSet->cHashMax = 0x400;
|
||||
pObSet->cHashGrowThreshold = 0x300;
|
||||
pObSet->pTable0[0].pValues = pObSet->pStore00;
|
||||
return pObSet;
|
||||
}
|
||||
|
||||
inline QWORD _ObSet_GetValueFromIndex(_In_ POB_SET pvs, _In_ DWORD iValue)
|
||||
{
|
||||
WORD iDirectory = (iValue >> 14) & (OB_SET_ENTRIES_DIRECTORY - 1);
|
||||
WORD iTable = (iValue >> 9) & (OB_SET_ENTRIES_TABLE - 1);
|
||||
WORD iValueStore = iValue & (OB_SET_ENTRIES_STORE - 1);
|
||||
if(!iValue || (iValue >= pvs->c)) { return 0; }
|
||||
return pvs->fLargeMode ?
|
||||
pvs->pDirectory[iDirectory].pTable[iTable].pValues[iValueStore] :
|
||||
pvs->pTable0[iTable].pValues[iValueStore];
|
||||
}
|
||||
|
||||
inline VOID _ObSet_SetValueFromIndex(_In_ POB_SET pvs, _In_ DWORD iValue, _In_ QWORD qwValue)
|
||||
{
|
||||
WORD iDirectory = (iValue >> 14) & (OB_SET_ENTRIES_DIRECTORY - 1);
|
||||
WORD iTable = (iValue >> 9) & (OB_SET_ENTRIES_TABLE - 1);
|
||||
WORD iValueStore = iValue & (OB_SET_ENTRIES_STORE - 1);
|
||||
if(pvs->fLargeMode) {
|
||||
pvs->pDirectory[iDirectory].pTable[iTable].pValues[iValueStore] = qwValue;
|
||||
} else {
|
||||
pvs->pTable0[iTable].pValues[iValueStore] = qwValue;
|
||||
}
|
||||
}
|
||||
|
||||
inline DWORD _ObSet_GetIndexFromHash(_In_ POB_SET pvs, _In_ DWORD iHash)
|
||||
{
|
||||
return pvs->fLargeMode ? pvs->pHashMapLarge[iHash] : pvs->pHashMapSmall[iHash];
|
||||
}
|
||||
|
||||
inline VOID _ObSet_SetHashIndex(_In_ POB_SET pvs, _In_ DWORD iHash, _In_ DWORD iValue)
|
||||
{
|
||||
if(pvs->fLargeMode) {
|
||||
pvs->pHashMapLarge[iHash] = iValue;
|
||||
} else {
|
||||
pvs->pHashMapSmall[iHash] = (WORD)iValue;
|
||||
}
|
||||
}
|
||||
|
||||
VOID _ObSet_InsertHash(_In_ POB_SET pvs, _In_ DWORD iValue)
|
||||
{
|
||||
DWORD iHash;
|
||||
DWORD dwHashMask = pvs->cHashMax - 1;
|
||||
QWORD qwValueToHash = _ObSet_GetValueFromIndex(pvs, iValue);
|
||||
if(!qwValueToHash) { return; }
|
||||
iHash = HASH_FUNCTION(qwValueToHash) & dwHashMask;
|
||||
while(_ObSet_GetIndexFromHash(pvs, iHash)) {
|
||||
iHash = (iHash + 1) & dwHashMask;
|
||||
}
|
||||
_ObSet_SetHashIndex(pvs, iHash, iValue);
|
||||
}
|
||||
|
||||
VOID _ObSet_RemoveHash(_In_ POB_SET pvs, _In_ DWORD iHash)
|
||||
{
|
||||
DWORD dwHashMask = pvs->cHashMax - 1;
|
||||
DWORD iNextHash, iNextEntry, iNextHashPreferred;
|
||||
// clear existing hash entry
|
||||
_ObSet_SetHashIndex(pvs, iHash, 0);
|
||||
// re-hash any entries following
|
||||
iNextHash = iHash;
|
||||
while(TRUE) {
|
||||
iNextHash = (iNextHash + 1) & dwHashMask;
|
||||
iNextEntry = _ObSet_GetIndexFromHash(pvs, iNextHash);
|
||||
if(0 == iNextEntry) { return; }
|
||||
iNextHashPreferred = HASH_FUNCTION(_ObSet_GetValueFromIndex(pvs, iNextEntry)) & dwHashMask;
|
||||
if(iNextHash == iNextHashPreferred) { continue; }
|
||||
if(pvs->fLargeMode) {
|
||||
pvs->pHashMapLarge[iNextHash] = 0;
|
||||
} else {
|
||||
pvs->pHashMapSmall[iNextHash] = 0;
|
||||
}
|
||||
_ObSet_InsertHash(pvs, iNextEntry);
|
||||
}
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObSet_GetIndexFromValue(_In_ POB_SET pvs, _In_ QWORD v, _Out_opt_ PDWORD pdwIndexValue, _Out_opt_ PDWORD pdwIndexHash)
|
||||
{
|
||||
DWORD dwIndex;
|
||||
DWORD dwHashMask = pvs->cHashMax - 1;
|
||||
DWORD dwHash = HASH_FUNCTION(v) & dwHashMask;
|
||||
// scan hash table to find entry
|
||||
while(TRUE) {
|
||||
dwIndex = _ObSet_GetIndexFromHash(pvs, dwHash);
|
||||
if(0 == dwIndex) { return FALSE; }
|
||||
if(v == _ObSet_GetValueFromIndex(pvs, dwIndex)) {
|
||||
if(pdwIndexValue) { *pdwIndexValue = dwIndex; }
|
||||
if(pdwIndexHash) { *pdwIndexHash = dwHash; }
|
||||
return TRUE;
|
||||
}
|
||||
dwHash = (dwHash + 1) & dwHashMask;
|
||||
}
|
||||
}
|
||||
|
||||
inline BOOL _ObSet_Exists(_In_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
return _ObSet_GetIndexFromValue(pvs, value, NULL, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a value already exists in the ObSet.
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return
|
||||
*/
|
||||
BOOL ObSet_Exists(_In_opt_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, BOOL, FALSE, _ObSet_Exists(pvs, value))
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve a value given a value index (which is less than the amount of items
|
||||
* in the Set).
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT- guaranteed if the
|
||||
* ObSet_Remove function is called while iterating over the ObSet - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* -- pvs
|
||||
* -- index
|
||||
* -- return
|
||||
*/
|
||||
QWORD ObSet_Get(_In_opt_ POB_SET pvs, _In_ DWORD index)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, QWORD, 0, _ObSet_GetValueFromIndex(pvs, index + 1)) // (+1 == account/adjust for index 0 (reserved))
|
||||
}
|
||||
|
||||
QWORD _ObSet_GetNext(_In_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
DWORD iValue;
|
||||
if(value == 0) {
|
||||
return _ObSet_GetValueFromIndex(pvs, 1); // (+1 == account/adjust for index 0 (reserved))
|
||||
}
|
||||
if(!_ObSet_GetIndexFromValue(pvs, value, &iValue, NULL)) { return 0; }
|
||||
return _ObSet_GetValueFromIndex(pvs, iValue + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the next value given a value. The start value and end value are the
|
||||
* ZERO value (which is a special reserved non-valid value).
|
||||
* NB! Correctness of the Get/GetNext functionality is _NOT_ guaranteed if the
|
||||
* ObSet_Remove function is called while iterating over the ObSet - items may
|
||||
* be skipped or iterated over multiple times!
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return
|
||||
*/
|
||||
QWORD ObSet_GetNext(_In_opt_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, QWORD, 0, _ObSet_GetNext(pvs, value))
|
||||
}
|
||||
|
||||
POB_DATA _ObSet_GetAll(_In_ POB_SET pvs)
|
||||
{
|
||||
DWORD iValue;
|
||||
POB_DATA pObData;
|
||||
if(!(pObData = Ob_Alloc(OB_TAG_CORE_DATA, 0, sizeof(OB_DATA) + (pvs->c - 1) * sizeof(QWORD), NULL, NULL))) { return NULL; }
|
||||
for(iValue = pvs->c - 1; iValue; iValue--) {
|
||||
pObData->pqw[iValue - 1] = _ObSet_GetValueFromIndex(pvs, iValue);
|
||||
}
|
||||
return pObData;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve all values in the Set as a POB_DATA object containing the values
|
||||
* in a QWORD table.
|
||||
* -- CALLER DECREF: return
|
||||
* -- pvs
|
||||
* -- return
|
||||
*/
|
||||
POB_DATA ObSet_GetAll(_In_opt_ POB_SET pvs)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, POB_DATA, NULL, _ObSet_GetAll(pvs))
|
||||
}
|
||||
|
||||
BOOL _ObSet_Remove(_In_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
QWORD qwLastValue;
|
||||
DWORD iRemoveValue, iRemoveHash;
|
||||
DWORD iLastValue, iLastHash;
|
||||
DWORD dwHashMask = pvs->cHashMax - 1;
|
||||
if(value == 0) { return FALSE; }
|
||||
if(!_ObSet_GetIndexFromValue(pvs, value, &iRemoveValue, &iRemoveHash)) { return FALSE; }
|
||||
qwLastValue = _ObSet_GetValueFromIndex(pvs, pvs->c - 1);
|
||||
if(qwLastValue == 0) { return FALSE; }
|
||||
if(!_ObSet_GetIndexFromValue(pvs, qwLastValue, &iLastValue, &iLastHash)) { return FALSE; }
|
||||
_ObSet_SetValueFromIndex(pvs, iLastValue, 0);
|
||||
_ObSet_RemoveHash(pvs, iLastHash);
|
||||
pvs->c--;
|
||||
if(iLastValue != iRemoveValue) { // overwrite value to remove with last value if required.
|
||||
_ObSet_RemoveHash(pvs, iRemoveHash);
|
||||
_ObSet_SetValueFromIndex(pvs, iRemoveValue, qwLastValue);
|
||||
_ObSet_InsertHash(pvs, iRemoveValue);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove an existing value from the ObSet.
|
||||
* NB! must not be called simultaneously while iterating with ObSet_Get/ObSet_GetNext.
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return = removal was successful (i.e. the value was found and removed).
|
||||
*/
|
||||
BOOL ObSet_Remove(_In_opt_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pvs, BOOL, FALSE, _ObSet_Remove(pvs, value))
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the ObSet by removing all values.
|
||||
* NB! underlying allocated memory will remain unchanged.
|
||||
* -- pvs
|
||||
*/
|
||||
VOID ObSet_Clear(_In_opt_ POB_SET pvs)
|
||||
{
|
||||
if(!OB_SET_IS_VALID(pvs) || (pvs->c <= 1)) { return; }
|
||||
AcquireSRWLockExclusive(&pvs->LockSRW);
|
||||
if(pvs->c <= 1) {
|
||||
ReleaseSRWLockExclusive(&pvs->LockSRW);
|
||||
return;
|
||||
}
|
||||
if(pvs->fLargeMode) {
|
||||
ZeroMemory(pvs->pHashMapLarge, pvs->cHashMax * sizeof(DWORD));
|
||||
} else {
|
||||
ZeroMemory(pvs->pHashMapSmall, sizeof(pvs->pHashMapSmall));
|
||||
}
|
||||
pvs->c = 1; // item zero is reserved - hence the initialization of count to 1
|
||||
ReleaseSRWLockExclusive(&pvs->LockSRW);
|
||||
}
|
||||
|
||||
QWORD _ObSet_Pop(_In_ POB_SET pvs)
|
||||
{
|
||||
QWORD qwLastValue;
|
||||
DWORD iLastValue, iLastHash;
|
||||
qwLastValue = _ObSet_GetValueFromIndex(pvs, pvs->c - 1);
|
||||
if(qwLastValue == 0) { return 0; }
|
||||
if(!_ObSet_GetIndexFromValue(pvs, qwLastValue, &iLastValue, &iLastHash)) { return 0; }
|
||||
_ObSet_SetValueFromIndex(pvs, iLastValue, 0);
|
||||
_ObSet_RemoveHash(pvs, iLastHash);
|
||||
pvs->c--;
|
||||
return qwLastValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the "last" value in a way that is safe for concurrent iterations of
|
||||
* values in the set.
|
||||
* -- pvs
|
||||
* -- return = success: value, fail: 0.
|
||||
*
|
||||
*/
|
||||
QWORD ObSet_Pop(_In_opt_ POB_SET pvs)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pvs, QWORD, 0, _ObSet_Pop(pvs))
|
||||
}
|
||||
|
||||
/*
|
||||
* Grow the Table for hash lookups by a factor of *2.
|
||||
* -- pvs
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL _ObSet_Grow(_In_ POB_SET pvs)
|
||||
{
|
||||
DWORD iValue;
|
||||
PDWORD pdwNewAllocHashMap;
|
||||
if(!(pdwNewAllocHashMap = LocalAlloc(LMEM_ZEROINIT, 2 * sizeof(DWORD) * pvs->cHashMax))) { return FALSE; }
|
||||
if(!pvs->fLargeMode) {
|
||||
ZeroMemory(pvs->pDirectory, OB_SET_ENTRIES_DIRECTORY * sizeof(OB_SET_TABLE_DIRECTORY_ENTRY));
|
||||
pvs->pDirectory[0].pTable = pvs->pTable0;
|
||||
pvs->fLargeMode = TRUE;
|
||||
}
|
||||
pvs->cHashMax *= 2;
|
||||
pvs->cHashGrowThreshold *= 2;
|
||||
LocalFree(pvs->pHashMapLarge);
|
||||
pvs->pHashMapLarge = pdwNewAllocHashMap;
|
||||
for(iValue = 1; iValue < pvs->c; iValue++) {
|
||||
_ObSet_InsertHash(pvs, iValue);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObSet_Push(_In_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
POB_SET_TABLE_ENTRY pTable = NULL;
|
||||
DWORD iValue = pvs->c;
|
||||
WORD iDirectory = (iValue >> 14) & (OB_SET_ENTRIES_DIRECTORY - 1);
|
||||
WORD iTable = (iValue >> 9) & (OB_SET_ENTRIES_TABLE - 1);
|
||||
WORD iValueStore = iValue & (OB_SET_ENTRIES_STORE - 1);
|
||||
if((value == 0) || _ObSet_Exists(pvs, value)) { return FALSE; }
|
||||
if(iValue == OB_SET_ENTRIES_DIRECTORY * OB_SET_ENTRIES_TABLE * OB_SET_ENTRIES_STORE) { return FALSE; }
|
||||
if(iValue == pvs->cHashGrowThreshold) {
|
||||
if(!_ObSet_Grow(pvs)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
if(iDirectory && !pvs->pDirectory[iDirectory].pTable) { // Ensure Table Exists
|
||||
pvs->pDirectory[iDirectory].pTable = LocalAlloc(LMEM_ZEROINIT, OB_SET_ENTRIES_TABLE * sizeof(OB_SET_TABLE_ENTRY));
|
||||
if(!pvs->pDirectory[iDirectory].pTable) { return FALSE; }
|
||||
}
|
||||
pTable = iDirectory ? pvs->pDirectory[iDirectory].pTable : pvs->pTable0;
|
||||
if(!pTable[iTable].pValues) { // Ensure Store Exists
|
||||
pTable[iTable].pValues = LocalAlloc(0, OB_SET_ENTRIES_STORE * sizeof(OB_SET_TABLE_ENTRY));
|
||||
if(!pTable[iTable].pValues) { return FALSE; }
|
||||
}
|
||||
pvs->c++;
|
||||
_ObSet_SetValueFromIndex(pvs, iValue, value);
|
||||
_ObSet_InsertHash(pvs, iValue);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObSet_PushSet(_In_ POB_SET pvs, _In_opt_ POB_SET pvsSrc)
|
||||
{
|
||||
DWORD iValue;
|
||||
if(pvsSrc) {
|
||||
AcquireSRWLockShared(&pvsSrc->LockSRW);
|
||||
for(iValue = pvsSrc->c - 1; iValue; iValue--) {
|
||||
QWORD qwValue = _ObSet_GetValueFromIndex(pvsSrc, iValue);
|
||||
_ObSet_Push(pvs, _ObSet_GetValueFromIndex(pvsSrc, iValue));
|
||||
}
|
||||
ReleaseSRWLockShared(&pvsSrc->LockSRW);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObSet_PushData(_In_ POB_SET pvs, _In_opt_ POB_DATA pDataSrc)
|
||||
{
|
||||
DWORD i, iMax;
|
||||
if(pDataSrc) {
|
||||
for(i = 0, iMax = pDataSrc->ObHdr.cbData / sizeof(QWORD); i < iMax; i++) {
|
||||
_ObSet_Push(pvs, pDataSrc->pqw[i]);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Push / Insert a non-zero value into the ObSet.
|
||||
* -- pvs
|
||||
* -- value
|
||||
* -- return = TRUE on insertion, FALSE otherwise - i.e. if value already
|
||||
* exists or if the max capacity of the set is reached.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_Push(_In_opt_ POB_SET pvs, _In_ QWORD value)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pvs, BOOL, FALSE, _ObSet_Push(pvs, value))
|
||||
}
|
||||
|
||||
/*
|
||||
* Push/Merge/Insert all values from the ObSet pvsSrc into the ObSet pvs.
|
||||
* The source set is kept intact.
|
||||
* -- pvs
|
||||
* -- pvsSrc
|
||||
* -- return = TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_PushSet(_In_opt_ POB_SET pvs, _In_opt_ POB_SET pvsSrc)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pvs, BOOL, FALSE, _ObSet_PushSet(pvs, pvsSrc))
|
||||
}
|
||||
|
||||
/*
|
||||
* Push/Merge/Insert all QWORD values from the ObData pDataSrc into the ObSet pvs.
|
||||
* The source data is kept intact.
|
||||
* -- pvs
|
||||
* -- pDataSrc
|
||||
* -- return = TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_PushData(_In_opt_ POB_SET pvs, _In_opt_ POB_DATA pDataSrc)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_WRITE(pvs, BOOL, FALSE, _ObSet_PushData(pvs, pDataSrc))
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a value representing an address into the ObSet. If the length of the
|
||||
* data read from the start of the address a traverses page boundries all the
|
||||
* pages are inserted into the set.
|
||||
* -- pvs
|
||||
* -- a
|
||||
* -- cb
|
||||
*/
|
||||
VOID ObSet_Push_PageAlign(_In_opt_ POB_SET pvs, _In_ QWORD a, _In_ DWORD cb)
|
||||
{
|
||||
QWORD qwA;
|
||||
if(!OB_SET_IS_VALID(pvs)) { return; }
|
||||
qwA = a & ~0xfff;
|
||||
if(qwA == 0xfffffffffffff000) { return; }
|
||||
while(qwA < a + cb) {
|
||||
ObSet_Push(pvs, qwA);
|
||||
qwA += 0x1000;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the number of items in the given ObSet.
|
||||
* -- pvs
|
||||
* -- return
|
||||
*/
|
||||
DWORD ObSet_Size(_In_opt_ POB_SET pvs)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, DWORD, 0, pvs->c - 1)
|
||||
}
|
||||
|
||||
|
||||
_Success_(return)
|
||||
BOOL _ObSet_FileSave(_In_ POB_SET pvs, _In_ LPWSTR wszFileName)
|
||||
{
|
||||
QWORD v = 0;
|
||||
CHAR szBuffer[18] = { 0 };
|
||||
HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
hFile = CreateFileW(wszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if(hFile == INVALID_HANDLE_VALUE) { return FALSE; }
|
||||
while(v = ObSet_GetNext(pvs, v)) {
|
||||
snprintf(szBuffer, 18, "%016llx\n", v);
|
||||
WriteFile(hFile, szBuffer, 17, NULL, NULL);
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save the contents of an ObSet to a disk file.
|
||||
* The resulting disk file may be read with ObSet_FileLoad().
|
||||
* -- pvs
|
||||
* -- wszFileName = save file to create.
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_FileSave(_In_opt_ POB_SET pvs, _In_ LPWSTR wszFileName)
|
||||
{
|
||||
OB_SET_CALL_SYNCHRONIZED_IMPLEMENTATION_READ(pvs, BOOL, FALSE, _ObSet_FileSave(pvs, wszFileName))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load the contents of an ObSet disk file into the supplied set.
|
||||
* -- pvs
|
||||
* -- wszFileName = file previously saved by ObSet_FileSave().
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL ObSet_FileLoad(_In_opt_ POB_SET pvs, _In_ LPWSTR wszFileName)
|
||||
{
|
||||
QWORD v;
|
||||
DWORD cbRead;
|
||||
HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
CHAR szBuffer[18] = { 0 };
|
||||
if(!OB_SET_IS_VALID(pvs)) { return FALSE; }
|
||||
hFile = CreateFileW(wszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if(hFile == INVALID_HANDLE_VALUE) { return FALSE; }
|
||||
while(ReadFile(hFile, szBuffer, 17, &cbRead, NULL) && (cbRead == 17)) {
|
||||
if((v = strtoull(szBuffer, NULL, 16))) {
|
||||
ObSet_Push(pvs, v);
|
||||
}
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
return TRUE;
|
||||
}
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
|
||||
#define VERSION_MAJOR 3
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_REVISION 0
|
||||
#define VERSION_BUILD 23
|
||||
#define VERSION_REVISION 1
|
||||
#define VERSION_BUILD 24
|
||||
|
||||
#define VER_FILE_DESCRIPTION_STR "MemProcFS"
|
||||
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
|
||||
|
||||
-553
@@ -1,553 +0,0 @@
|
||||
// vfs.c : implementation of functions related to virtual file system support.
|
||||
//
|
||||
// (c) Ulf Frisk, 2018-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
#include <vmmdll.h>
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4005 )
|
||||
#include <dokan.h>
|
||||
#pragma warning( pop )
|
||||
#include "vfs.h"
|
||||
|
||||
DWORD g_dbg_c = 0;
|
||||
|
||||
//#define dbg_GetTickCount64() GetTickCount64()
|
||||
//#define dbg_wprintf_init(format, ...) { wprintf(format, ++g_dbg_c, ##__VA_ARGS__); }
|
||||
//#define dbg_wprintf(format, ...) { wprintf(format, ++g_dbg_c, ##__VA_ARGS__); }
|
||||
#define dbg_wprintf_init(format, ...) {}
|
||||
#define dbg_wprintf(format, ...) {}
|
||||
#define dbg_GetTickCount64() 0
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// DEFINES, TYPEDEFS AND FORWARD DECLARATIONS BELOW:
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
#define VFS_CONFIG_FILELIST_ITEMS 12
|
||||
#define VFS_CONFIG_FILELIST_MAGIC 0x7f646555caffee66
|
||||
typedef struct tdVFS_FILELIST {
|
||||
QWORD magic;
|
||||
struct tdVFS_FILELIST* FLink;
|
||||
DWORD cFiles;
|
||||
WIN32_FIND_DATAW pFiles[VFS_CONFIG_FILELIST_ITEMS];
|
||||
} VFS_FILELIST, *PVFS_FILELIST;
|
||||
|
||||
BOOL VfsListVmmDirectory(_In_ LPWSTR wszDirectoryName);
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// FILELIST FUNCTIONALITY BELOW:
|
||||
// (directory listing functions/structs for communicating between vfs and vfsproc).
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
PVFS_FILELIST VfsFileList_Alloc()
|
||||
{
|
||||
PVFS_FILELIST pFileList = LocalAlloc(LMEM_ZEROINIT, sizeof(VFS_FILELIST));
|
||||
if(pFileList) {
|
||||
pFileList->magic = VFS_CONFIG_FILELIST_MAGIC;
|
||||
}
|
||||
return pFileList;
|
||||
}
|
||||
|
||||
VOID VfsFileList_Free(_Frees_ptr_opt_ PVFS_FILELIST pFileList)
|
||||
{
|
||||
PVFS_FILELIST pFileListFlink;
|
||||
while(pFileList) {
|
||||
pFileListFlink = pFileList->FLink;
|
||||
LocalFree(pFileList);
|
||||
pFileList = pFileListFlink;
|
||||
}
|
||||
}
|
||||
|
||||
#define VFSFILELIST_ASCII "________________________________ !_#$%&'()_+,-._0123456789_;_=__@ABCDEFGHIJKLMNOPQRSTUVWXYZ[_]^_`abcdefghijklmnopqrstuvwxyz{_}~ "
|
||||
|
||||
VOID VfsFileList_AddDirectoryFileInternal(_Inout_ PVFS_FILELIST pFileList, _In_ DWORD dwFileAttributes, _In_ FILETIME ftCreationTime, _In_ FILETIME ftLastAccessTime, _In_ FILETIME ftLastWriteTime, _In_ DWORD nFileSizeHigh, _In_ DWORD nFileSizeLow, _In_ LPWSTR wszName)
|
||||
{
|
||||
WCHAR c;
|
||||
DWORD i = 0;
|
||||
PWIN32_FIND_DATAW pFindData;
|
||||
// 1: check if required to allocate more FileList items
|
||||
while(pFileList->cFiles == VFS_CONFIG_FILELIST_ITEMS) {
|
||||
if(pFileList->FLink) {
|
||||
pFileList = pFileList->FLink;
|
||||
continue;
|
||||
}
|
||||
pFileList->FLink = VfsFileList_Alloc();
|
||||
if(!pFileList->FLink) { return; }
|
||||
pFileList = pFileList->FLink;
|
||||
}
|
||||
// 2: locate item to fill into
|
||||
pFindData = pFileList->pFiles + pFileList->cFiles;
|
||||
pFileList->cFiles++;
|
||||
// 3: fill
|
||||
pFindData->dwFileAttributes = dwFileAttributes;
|
||||
pFindData->ftCreationTime = ftCreationTime;
|
||||
pFindData->ftLastAccessTime = ftLastAccessTime;
|
||||
pFindData->ftLastWriteTime = ftLastWriteTime;
|
||||
pFindData->nFileSizeHigh = nFileSizeHigh;
|
||||
pFindData->nFileSizeLow = nFileSizeLow;
|
||||
while(i < MAX_PATH && (c = wszName[i])) {
|
||||
pFindData->cFileName[i++] = (c < 128) ? VFSFILELIST_ASCII[c] : c;
|
||||
}
|
||||
pFindData->cFileName[min(i, MAX_PATH - 1)] = 0;
|
||||
}
|
||||
|
||||
VOID VfsFileList_AddFile(_Inout_ HANDLE hFileList, _In_ LPWSTR wszName, _In_ QWORD cb, _In_opt_ PVMMDLL_VFS_FILELIST_EXINFO pExInfo)
|
||||
{
|
||||
PVFS_FILELIST pFileList2 = (PVFS_FILELIST)hFileList;
|
||||
BOOL fExInfo = pExInfo && (pExInfo->dwVersion == VMMDLL_VFS_FILELIST_EXINFO_VERSION);
|
||||
if(pFileList2 && (pFileList2->magic == VFS_CONFIG_FILELIST_MAGIC)) {
|
||||
VfsFileList_AddDirectoryFileInternal(
|
||||
pFileList2,
|
||||
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | ((fExInfo && pExInfo->fCompressed) ? FILE_ATTRIBUTE_COMPRESSED : 0),
|
||||
(fExInfo && pExInfo->qwCreationTime) ? pExInfo->ftCreationTime : ctxVfs->ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastAccessTime) ? pExInfo->ftLastAccessTime : ctxVfs->ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastWriteTime) ? pExInfo->ftLastWriteTime : ctxVfs->ftDefaultTime,
|
||||
(DWORD)(cb >> 32),
|
||||
(DWORD)cb,
|
||||
wszName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VOID VfsFileList_AddDirectory(_Inout_ HANDLE hFileList, _In_ LPWSTR wszName, _In_opt_ PVMMDLL_VFS_FILELIST_EXINFO pExInfo)
|
||||
{
|
||||
PVFS_FILELIST pFileList2 = (PVFS_FILELIST)hFileList;
|
||||
BOOL fExInfo = pExInfo && (pExInfo->dwVersion == VMMDLL_VFS_FILELIST_EXINFO_VERSION);
|
||||
if(pFileList2 && (pFileList2->magic == VFS_CONFIG_FILELIST_MAGIC)) {
|
||||
VfsFileList_AddDirectoryFileInternal(
|
||||
pFileList2,
|
||||
FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | ((fExInfo && pExInfo->fCompressed) ? FILE_ATTRIBUTE_COMPRESSED : 0),
|
||||
(fExInfo && pExInfo->qwCreationTime) ? pExInfo->ftCreationTime : ctxVfs->ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastAccessTime) ? pExInfo->ftLastAccessTime : ctxVfs->ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastWriteTime) ? pExInfo->ftLastWriteTime : ctxVfs->ftDefaultTime,
|
||||
0,
|
||||
0,
|
||||
wszName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VOID VfsFileList_DokanFillAll(PVFS_FILELIST pFileList, PDOKAN_FILE_INFO DokanFileInfo, PFillFindData FillFindData)
|
||||
{
|
||||
DWORD i;
|
||||
do {
|
||||
for(i = 0; i < pFileList->cFiles; i++) {
|
||||
FillFindData(pFileList->pFiles + i, DokanFileInfo);
|
||||
}
|
||||
pFileList = pFileList->FLink;
|
||||
} while(pFileList);
|
||||
}
|
||||
|
||||
PWIN32_FIND_DATAW VfsFileList_FindSingle(_In_ PVFS_FILELIST pFileList, _In_ LPWSTR wszFile)
|
||||
{
|
||||
DWORD i;
|
||||
do {
|
||||
for(i = 0; i < pFileList->cFiles; i++) {
|
||||
if(!wcscmp(wszFile, pFileList->pFiles[i].cFileName)) {
|
||||
return pFileList->pFiles + i;
|
||||
}
|
||||
}
|
||||
pFileList = pFileList->FLink;
|
||||
} while(pFileList);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// DIRECTORY LISTINGS READ CACHE BELOW:
|
||||
// (caching is used to cache vmmproc directory listings for performance reasons)
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
_Success_(return)
|
||||
BOOL VfsCacheDirectory_GetSingle2(_In_ LPWSTR wszPath, _In_ LPWSTR wszFile, _Out_ PWIN32_FIND_DATAW pFindData, _Out_ PBOOL pIsDirectoryExisting)
|
||||
{
|
||||
QWORD i, qwCurrentTickCount;
|
||||
PWIN32_FIND_DATAW pFindDataCache;
|
||||
qwCurrentTickCount = GetTickCount64();
|
||||
*pIsDirectoryExisting = FALSE;
|
||||
EnterCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
for(i = 0; i < VMMVFS_CACHE_DIRECTORY_ENTRIES; i++) {
|
||||
if(wcscmp(wszPath, ctxVfs->CacheDirectory[i].wszDirectoryName)) { continue; }
|
||||
if(qwCurrentTickCount > ctxVfs->CacheDirectory[i].qwExpireTickCount64) { continue; }
|
||||
*pIsDirectoryExisting = TRUE;
|
||||
pFindDataCache = VfsFileList_FindSingle(ctxVfs->CacheDirectory[i].pFileList, wszFile);
|
||||
if(!pFindDataCache) {
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
return FALSE;
|
||||
}
|
||||
if(pFindData) {
|
||||
memcpy(pFindData, pFindDataCache, sizeof(WIN32_FIND_DATAW));
|
||||
}
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
return TRUE;
|
||||
}
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL VfsCacheDirectory_GetSingle(_In_ LPWSTR wszPath, _In_ LPWSTR wszFile, _Out_ PWIN32_FIND_DATAW pFindData, _Out_ PBOOL pfIsDirectoryExisting)
|
||||
{
|
||||
BOOL result;
|
||||
result = VfsCacheDirectory_GetSingle2(wszPath, wszFile, pFindData, pfIsDirectoryExisting);
|
||||
if(result) { return TRUE; }
|
||||
if(*pfIsDirectoryExisting) { return FALSE; }
|
||||
return VfsListVmmDirectory(wszPath) && VfsCacheDirectory_GetSingle2(wszPath, wszFile, pFindData, pfIsDirectoryExisting);
|
||||
}
|
||||
|
||||
BOOL VfsCacheDirectory_DokanFillDirectory(_In_ LPCWSTR wcsPathFileName, _In_ PFillFindData FillFindData, _Inout_ PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
QWORD i, qwCurrentTickCount;
|
||||
qwCurrentTickCount = GetTickCount64();
|
||||
EnterCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
for(i = 0; i < VMMVFS_CACHE_DIRECTORY_ENTRIES; i++) {
|
||||
if(wcscmp(wcsPathFileName, ctxVfs->CacheDirectory[i].wszDirectoryName)) { continue; }
|
||||
if(qwCurrentTickCount > ctxVfs->CacheDirectory[i].qwExpireTickCount64) { continue; }
|
||||
VfsFileList_DokanFillAll(ctxVfs->CacheDirectory[i].pFileList, DokanFileInfo, FillFindData);
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
return TRUE;
|
||||
}
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
VOID VfsCacheDirectory_Put(_In_ LPCWSTR wcsDirectoryName, _In_ PVFS_FILELIST pFileList)
|
||||
{
|
||||
EnterCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].qwExpireTickCount64 = GetTickCount64() + VMMVFS_CACHE_DIRECTORY_LIFETIME_PROC_MS;
|
||||
wcsncpy_s(ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].wszDirectoryName, MAX_PATH, wcsDirectoryName, _TRUNCATE);
|
||||
VfsFileList_Free(ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].pFileList);
|
||||
ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].pFileList = pFileList;
|
||||
ctxVfs->CacheDirectoryIndex = (ctxVfs->CacheDirectoryIndex + 1) % VMMVFS_CACHE_DIRECTORY_ENTRIES;
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
}
|
||||
|
||||
VOID VfsCacheDirectory_Close()
|
||||
{
|
||||
DWORD i;
|
||||
EnterCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
for(i = 0; i < VMMVFS_CACHE_DIRECTORY_ENTRIES; i++) {
|
||||
ctxVfs->CacheDirectory[i].qwExpireTickCount64 = 0;
|
||||
VfsFileList_Free(ctxVfs->CacheDirectory[i].pFileList);
|
||||
ctxVfs->CacheDirectory[i].pFileList = NULL;
|
||||
}
|
||||
LeaveCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// UTILITY FUNCTIONS BELOW:
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
BOOL VfsListVmmDirectory(_In_ LPWSTR wszDirectoryName)
|
||||
{
|
||||
BOOL result;
|
||||
PVFS_FILELIST pFileList = VfsFileList_Alloc(ctxVfs->ftDefaultTime);
|
||||
VMMDLL_VFS_FILELIST VfsFileList;
|
||||
if(!pFileList) { return FALSE; }
|
||||
VfsFileList.dwVersion = VMMDLL_VFS_FILELIST_VERSION;
|
||||
VfsFileList.h = (HANDLE)pFileList;
|
||||
VfsFileList.pfnAddFile = VfsFileList_AddFile;
|
||||
VfsFileList.pfnAddDirectory = VfsFileList_AddDirectory;
|
||||
result = ctxVfs->pVmmDll->VfsList(wszDirectoryName, &VfsFileList);
|
||||
if(!result) {
|
||||
VfsFileList_Free(pFileList);
|
||||
return FALSE;
|
||||
}
|
||||
VfsCacheDirectory_Put(wszDirectoryName, pFileList); // do not free pFileList since it's put into the cache
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
VOID Vfs_UtilSplitPathFile(_Out_writes_(MAX_PATH) PWCHAR wszPath, _Out_ LPWSTR *pwcsFile, _In_ LPCWSTR wcsFileName)
|
||||
{
|
||||
DWORD i, iSplitFilePath = 0;
|
||||
wcsncpy_s(wszPath, MAX_PATH, wcsFileName, _TRUNCATE);
|
||||
for(i = 0; i < MAX_PATH; i++) {
|
||||
if(wszPath[i] == '\\') {
|
||||
iSplitFilePath = i;
|
||||
}
|
||||
if(wszPath[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
wszPath[iSplitFilePath] = 0;
|
||||
*pwcsFile = wszPath + iSplitFilePath + 1;
|
||||
}
|
||||
|
||||
DWORD Vfs_UtilHashStringUpperW(_In_opt_ LPCWSTR wsz)
|
||||
{
|
||||
WCHAR c;
|
||||
DWORD i = 0, dwHash = 0;
|
||||
if(!wsz) { return 0; }
|
||||
while(TRUE) {
|
||||
c = wsz[i++];
|
||||
if(!c) { return dwHash; }
|
||||
if(c >= 'a' && c <= 'z') {
|
||||
c += 'A' - 'a';
|
||||
}
|
||||
dwHash = ((dwHash >> 13) | (dwHash << 19)) + c;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// DOKAN CALLBACK FUNCTIONS BELOW:
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
NTSTATUS
|
||||
VfsCallback_CreateFile_Impl(LPCWSTR wcsFileName, PDOKAN_IO_SECURITY_CONTEXT SecurityContext, ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
BOOL result;
|
||||
WIN32_FIND_DATAW FindData;
|
||||
WCHAR wszPath[MAX_PATH];
|
||||
LPWSTR wszFile;
|
||||
BOOL fIsDirectoryExisting = FALSE;
|
||||
UNREFERENCED_PARAMETER(SecurityContext);
|
||||
UNREFERENCED_PARAMETER(FileAttributes);
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
// root directory
|
||||
if(!wcscmp(wcsFileName, L"\\")) {
|
||||
if(CreateDisposition == CREATE_ALWAYS) { return ctxVfs->DokanNtStatusFromWin32(ERROR_ACCESS_DENIED); }
|
||||
DokanFileInfo->IsDirectory = TRUE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
// other files
|
||||
if(CreateDisposition == CREATE_ALWAYS) { return ctxVfs->DokanNtStatusFromWin32(ERROR_ACCESS_DENIED); }
|
||||
Vfs_UtilSplitPathFile(wszPath, &wszFile, wcsFileName);
|
||||
result = VfsCacheDirectory_GetSingle(wszPath[0] ? wszPath : L"\\", wszFile, &FindData, &fIsDirectoryExisting);
|
||||
if(!result) { return fIsDirectoryExisting ? STATUS_OBJECT_NAME_NOT_FOUND : STATUS_OBJECT_PATH_NOT_FOUND; }
|
||||
DokanFileInfo->IsDirectory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
|
||||
DokanFileInfo->Nocache = TRUE;
|
||||
if(!DokanFileInfo->IsDirectory && (CreateOptions & FILE_DIRECTORY_FILE)) { return STATUS_NOT_A_DIRECTORY; } // fail upon open normal file as directory
|
||||
return (CreateDisposition == OPEN_ALWAYS) ? STATUS_OBJECT_NAME_COLLISION : STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsCallback_CreateFile(LPCWSTR FileName, PDOKAN_IO_SECURITY_CONTEXT SecurityContext, ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
NTSTATUS nt;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
nt = VfsCallback_CreateFile_Impl(FileName, SecurityContext, DesiredAccess, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, DokanFileInfo);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_CreateFile:\t\t 0x%08x %s [ %08x %08x %08x %08x ]\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, FileName, FileAttributes, ShareAccess, CreateDisposition, CreateOptions);
|
||||
return nt;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsCallback_GetFileInformation(_In_ LPCWSTR wcsFileName, _Inout_ LPBY_HANDLE_FILE_INFORMATION hfi, _In_ PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
BOOL result;
|
||||
WIN32_FIND_DATAW FindData;
|
||||
WCHAR wszPath[MAX_PATH];
|
||||
LPWSTR wszFile;
|
||||
BOOL fIsDirectoryExisting = FALSE;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_GetFileInformation:\t 0x%08x %s\n", 0, wcsFileName);
|
||||
// matches: root directory
|
||||
if(!wcscmp(wcsFileName, L"\\")) {
|
||||
hfi->ftCreationTime = ctxVfs->ftDefaultTime;
|
||||
hfi->ftLastWriteTime = ctxVfs->ftDefaultTime;
|
||||
hfi->ftLastAccessTime = ctxVfs->ftDefaultTime;
|
||||
hfi->nFileSizeHigh = 0;
|
||||
hfi->nFileSizeLow = 0;
|
||||
hfi->dwFileAttributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
Vfs_UtilSplitPathFile(wszPath, &wszFile, wcsFileName);
|
||||
result = VfsCacheDirectory_GetSingle((wszPath[0] ? wszPath : L"\\"), wszFile, &FindData, &fIsDirectoryExisting);
|
||||
if(!result) {
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_FILE_NOT_AVAILABLE, wcsFileName);
|
||||
return STATUS_FILE_NOT_AVAILABLE;
|
||||
}
|
||||
hfi->dwFileAttributes = FindData.dwFileAttributes;
|
||||
hfi->ftCreationTime = FindData.ftCreationTime;
|
||||
hfi->ftLastAccessTime = FindData.ftLastAccessTime;
|
||||
hfi->ftLastWriteTime = FindData.ftLastWriteTime;
|
||||
hfi->nFileSizeHigh = FindData.nFileSizeHigh;
|
||||
hfi->nFileSizeLow = FindData.nFileSizeLow;
|
||||
hfi->nFileIndexHigh = Vfs_UtilHashStringUpperW(wcsFileName);
|
||||
hfi->nFileIndexLow = Vfs_UtilHashStringUpperW(FindData.cFileName);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_GetFileInformation:\t 0x%08x %s\t [ %08x %08x%08x %016llx %016llx %016llx ]\n",
|
||||
(DWORD)(dbg_GetTickCount64() - tmStart),
|
||||
STATUS_SUCCESS,
|
||||
wcsFileName,
|
||||
hfi->dwFileAttributes,
|
||||
hfi->nFileSizeHigh, hfi->nFileSizeLow,
|
||||
*(PQWORD)&hfi->ftCreationTime,
|
||||
*(PQWORD)&hfi->ftLastAccessTime,
|
||||
*(PQWORD)&hfi->ftLastWriteTime
|
||||
);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsCallback_FindFiles(LPCWSTR wcsFileName, PFillFindData FillFindData, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
BOOL result;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", 0, wcsFileName);
|
||||
result = VfsCacheDirectory_DokanFillDirectory(wcsFileName, FillFindData, DokanFileInfo);
|
||||
if(result) {
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
VfsListVmmDirectory((LPWSTR)wcsFileName);
|
||||
VfsCacheDirectory_DokanFillDirectory(wcsFileName, FillFindData, DokanFileInfo);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsCallback_ReadFile(LPCWSTR wcsFileName, LPVOID Buffer, DWORD BufferLength, LPDWORD ReadLength, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
NTSTATUS nt;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_ReadFile:\t\t\t 0x%08x %s\n", 0, wcsFileName);
|
||||
nt = ctxVfs->pVmmDll->VfsRead(wcsFileName, Buffer, BufferLength, ReadLength, Offset);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_ReadFile:\t\t\t 0x%08x %s\t [ %016llx %08x %08x ]\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName, Offset, BufferLength, *ReadLength);
|
||||
return nt;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsCallback_WriteFile(LPCWSTR wcsFileName, LPCVOID Buffer, DWORD NumberOfBytesToWrite, LPDWORD NumberOfBytesWritten, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
NTSTATUS nt;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_WriteFile:\t\t\t 0x%08x %s\n", 0, wcsFileName);
|
||||
nt = ctxVfs->pVmmDll->VfsWrite(wcsFileName, (PBYTE)Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, Offset);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_WriteFile:\t\t\t 0x%08x %s\t [ %016llx %08x %08x ]\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName, Offset, NumberOfBytesToWrite, *NumberOfBytesWritten);
|
||||
return nt;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// VFS INITIALIZATION FUNCTIONALITY BELOW:
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
VOID VfsClose(_In_ CHAR chMountPoint)
|
||||
{
|
||||
HMODULE hModuleDokan = NULL;
|
||||
WCHAR wchMountPoint = chMountPoint;
|
||||
BOOL(*pfnDokanUnmount)(WCHAR DriveLetter);
|
||||
if(ctxVfs && ctxVfs->fInitialized) {
|
||||
ctxVfs->fInitialized = FALSE;
|
||||
if(wchMountPoint) {
|
||||
hModuleDokan = LoadLibraryExA("dokan1.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if(hModuleDokan) {
|
||||
pfnDokanUnmount = (BOOL(*)(WCHAR))GetProcAddress(hModuleDokan, "DokanUnmount");
|
||||
if(pfnDokanUnmount) {
|
||||
pfnDokanUnmount(wchMountPoint);
|
||||
}
|
||||
FreeLibrary(hModuleDokan);
|
||||
}
|
||||
}
|
||||
VfsCacheDirectory_Close();
|
||||
DeleteCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
}
|
||||
LocalFree(ctxVfs);
|
||||
ctxVfs = NULL;
|
||||
}
|
||||
|
||||
VOID VfsInitializeAndMount_DisplayInfo(LPWSTR wszMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll)
|
||||
{
|
||||
ULONG64 qwVersionVmmMajor = 0, qwVersionVmmMinor = 0, qwVersionVmmRevision = 0;
|
||||
ULONG64 qwVersionWinMajor = 0, qwVersionWinMinor = 0, qwVersionWinBuild = 0, iMemoryModel;
|
||||
// get vmm.dll versions
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR, &qwVersionVmmMajor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR, &qwVersionVmmMinor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION, &qwVersionVmmRevision);
|
||||
// get operating system versions
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CORE_MEMORYMODEL, &iMemoryModel);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_WIN_VERSION_MAJOR, &qwVersionWinMajor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_WIN_VERSION_MINOR, &qwVersionWinMinor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_WIN_VERSION_BUILD, &qwVersionWinBuild);
|
||||
printf("\n" \
|
||||
"=============== MemProcFS - THE MEMORY PROCESS FILE SYSTEM ===============\n" \
|
||||
" - Author: Ulf Frisk - [email protected] - https://frizk.net \n" \
|
||||
" - Info: https://github.com/ufrisk/MemProcFS \n" \
|
||||
" - VmmDll Version: %i.%i.%i \n" \
|
||||
" - Mount Point: %S \n",
|
||||
(DWORD)qwVersionVmmMajor, (DWORD)qwVersionVmmMinor, (DWORD)qwVersionVmmRevision, wszMountPoint);
|
||||
if(qwVersionWinMajor && (iMemoryModel < (sizeof(VMMDLL_MEMORYMODEL_TOSTRING) / sizeof(LPSTR)))) {
|
||||
printf(" - Operating System: Windows %i.%i.%i (%s)\n",
|
||||
(DWORD)qwVersionWinMajor, (DWORD)qwVersionWinMinor, (DWORD)qwVersionWinBuild, VMMDLL_MEMORYMODEL_TOSTRING[iMemoryModel]);
|
||||
} else {
|
||||
printf(" - Operating System: Unknown\n");
|
||||
}
|
||||
printf("==========================================================================\n\n");
|
||||
}
|
||||
|
||||
VOID VfsInitializeAndMount(_In_ CHAR chMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll)
|
||||
{
|
||||
int status;
|
||||
HMODULE hModuleDokan = NULL;
|
||||
PDOKAN_OPTIONS pDokanOptions = NULL;
|
||||
PDOKAN_OPERATIONS pDokanOperations = NULL;
|
||||
WCHAR wszMountPoint[] = { 'M', ':', '\\', 0 };
|
||||
SYSTEMTIME SystemTimeNow;
|
||||
int(*fnDokanMain)(PDOKAN_OPTIONS, PDOKAN_OPERATIONS);
|
||||
// allocate
|
||||
hModuleDokan = LoadLibraryExA("dokan1.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if(!hModuleDokan) {
|
||||
printf("MOUNT: Failed. The required DOKANY file system library is not installed. \n");
|
||||
printf("Please download from : https://github.com/dokan-dev/dokany/releases/latest\n");
|
||||
goto fail;
|
||||
}
|
||||
fnDokanMain = (int(*)(PDOKAN_OPTIONS, PDOKAN_OPERATIONS))GetProcAddress(hModuleDokan, "DokanMain");
|
||||
if(!fnDokanMain) {
|
||||
printf("MOUNT: Failed. The required DOKANY file system library is not installed. \n");
|
||||
printf("Please download from : https://github.com/dokan-dev/dokany/releases/latest\n");
|
||||
goto fail;
|
||||
}
|
||||
pDokanOptions = (PDOKAN_OPTIONS)LocalAlloc(LMEM_ZEROINIT, sizeof(DOKAN_OPTIONS));
|
||||
pDokanOperations = (PDOKAN_OPERATIONS)LocalAlloc(LMEM_ZEROINIT, sizeof(DOKAN_OPERATIONS));
|
||||
if(!pDokanOptions || !pDokanOperations) {
|
||||
printf("MOUNT: Failed (out of memory).\n");
|
||||
goto fail;
|
||||
}
|
||||
// allocate empty vfs context
|
||||
ctxVfs = (PVMMVFS_CONFIG)LocalAlloc(LMEM_ZEROINIT, sizeof(VMMVFS_CONFIG));
|
||||
if(!ctxVfs) { goto fail; }
|
||||
ctxVfs->pVmmDll = pVmmDll;
|
||||
// set vfs context
|
||||
GetSystemTime(&SystemTimeNow);
|
||||
SystemTimeToFileTime(&SystemTimeNow, &ctxVfs->ftDefaultTime);
|
||||
InitializeCriticalSection(&ctxVfs->CacheDirectoryLock);
|
||||
ctxVfs->DokanNtStatusFromWin32 = (NTSTATUS(*)(DWORD))GetProcAddress(hModuleDokan, "DokanNtStatusFromWin32");
|
||||
ctxVfs->fInitialized = TRUE;
|
||||
// set options
|
||||
pDokanOptions->Version = DOKAN_VERSION;
|
||||
pDokanOptions->Options |= DOKAN_OPTION_NETWORK;
|
||||
pDokanOptions->UNCName = L"MemProcFS";
|
||||
pDokanOptions->ThreadCount = 10;
|
||||
wszMountPoint[0] = chMountPoint;
|
||||
pDokanOptions->MountPoint = wszMountPoint;
|
||||
pDokanOptions->Timeout = 60000;
|
||||
// set callbacks
|
||||
pDokanOperations->ZwCreateFile = VfsCallback_CreateFile;
|
||||
pDokanOperations->GetFileInformation = VfsCallback_GetFileInformation;
|
||||
pDokanOperations->FindFiles = VfsCallback_FindFiles;
|
||||
pDokanOperations->ReadFile = VfsCallback_ReadFile;
|
||||
pDokanOperations->WriteFile = VfsCallback_WriteFile;
|
||||
// print system information to console
|
||||
VfsInitializeAndMount_DisplayInfo(wszMountPoint, pVmmDll);
|
||||
// mount file system
|
||||
status = fnDokanMain(pDokanOptions, pDokanOperations);
|
||||
while(ctxVfs && ctxVfs->fInitialized && (status == DOKAN_SUCCESS)) {
|
||||
printf("MOUNT: ReMounting as drive %S\n", pDokanOptions->MountPoint);
|
||||
status = fnDokanMain(pDokanOptions, pDokanOperations);
|
||||
}
|
||||
if(status == -5) {
|
||||
printf("MOUNT: Failed: drive busy/already mounted.\n");
|
||||
} else {
|
||||
printf("MOUNT: Failed. Status Code: %i\n", status);
|
||||
}
|
||||
fail:
|
||||
if(hModuleDokan) { FreeLibrary(hModuleDokan); }
|
||||
LocalFree(pDokanOptions);
|
||||
LocalFree(pDokanOperations);
|
||||
VfsClose(0);
|
||||
}
|
||||
+54
-12
@@ -6,12 +6,61 @@
|
||||
#ifndef __VFS_H__
|
||||
#define __VFS_H__
|
||||
#include <windows.h>
|
||||
#include "vmmdll.h"
|
||||
#include <vmmdll.h>
|
||||
|
||||
typedef unsigned __int64 QWORD, *PQWORD;
|
||||
|
||||
#define VMMVFS_CACHE_DIRECTORY_ENTRIES 15
|
||||
#define VMMVFS_CACHE_DIRECTORY_LIFETIME_PROC_MS 500
|
||||
typedef int(*PFN_VFSLIST_CALLBACK)(_In_ PWIN32_FIND_DATAW pFindData, _In_opt_ PVOID ctx);
|
||||
|
||||
/*
|
||||
* Split a path into path + filename.
|
||||
* -- wszPath
|
||||
* -- pwcsFile
|
||||
* -- wcsFileName
|
||||
*/
|
||||
VOID Util_SplitPathFile(_Out_writes_(MAX_PATH) PWCHAR wszPath, _Out_ LPWSTR *pwcsFile, _In_ LPCWSTR wcsFileName);
|
||||
|
||||
/*
|
||||
* Hash a string in uppercase.
|
||||
* -- wsz
|
||||
* -- return
|
||||
*/
|
||||
DWORD Util_HashStringUpperW(_In_opt_ LPWSTR wsz);
|
||||
|
||||
/*
|
||||
* Retrieve information about a single entry inside a directory.
|
||||
* -- wszPath
|
||||
* -- wszFile
|
||||
* -- pFindData
|
||||
* -- pfPathValid = receives if wszPath is valid or not.
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL VfsList_GetSingle(_In_ LPWSTR wszPath, _In_ LPWSTR wszFile, _Out_ PWIN32_FIND_DATAW pFindData, _Out_ PBOOL pfPathValid);
|
||||
|
||||
/*
|
||||
* List a directory using a callback function
|
||||
* -- wszPath
|
||||
* -- ctx = optional context to pass along to callback function.
|
||||
* -- pfnListCallback = callback function called one time per directory entry.
|
||||
* -- return = TRUE if directory exists, otherwise FALSE.
|
||||
*/
|
||||
BOOL VfsList_ListDirectory(_In_ LPWSTR wszPath, _In_opt_ PVOID ctx, _In_opt_ PFN_VFSLIST_CALLBACK pfnListCallback);
|
||||
|
||||
/*
|
||||
* Initialize the vfs list functionality.
|
||||
* -- hModuleVmm
|
||||
* -- dwCacheValidMs
|
||||
* -- cCacheMaxEntries
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL VfsList_Initialize(_In_ HMODULE hModuleVmm, _In_ DWORD dwCacheValidMs, _In_ DWORD dwCacheMaxEntries);
|
||||
|
||||
/*
|
||||
* Close and clean up the vfs list functionality.
|
||||
*/
|
||||
VOID VfsList_Close();
|
||||
|
||||
typedef struct tdVMMDLL_FUNCTIONS {
|
||||
BOOL(*Initialize)(_In_ DWORD argc, _In_ LPSTR argv[]);
|
||||
@@ -27,14 +76,7 @@ typedef struct tdVMMVFS_CONFIG {
|
||||
PVMMDLL_FUNCTIONS pVmmDll;
|
||||
FILETIME ftDefaultTime;
|
||||
NTSTATUS(*DokanNtStatusFromWin32)(DWORD Error);
|
||||
CRITICAL_SECTION CacheDirectoryLock;
|
||||
BOOL fInitialized;
|
||||
QWORD CacheDirectoryIndex;
|
||||
struct {
|
||||
QWORD qwExpireTickCount64;
|
||||
WCHAR wszDirectoryName[MAX_PATH];
|
||||
PVOID pFileList;
|
||||
} CacheDirectory[VMMVFS_CACHE_DIRECTORY_ENTRIES];
|
||||
} VMMVFS_CONFIG, *PVMMVFS_CONFIG;
|
||||
|
||||
PVMMVFS_CONFIG ctxVfs;
|
||||
@@ -50,12 +92,12 @@ PVMMVFS_CONFIG ctxVfs;
|
||||
* -- chMountPoint
|
||||
* -- pVmmDll
|
||||
*/
|
||||
VOID VfsInitializeAndMount(_In_ CHAR chMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll);
|
||||
VOID VfsDokan_InitializeAndMount(_In_ CHAR chMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll);
|
||||
|
||||
/*
|
||||
* Close a vfs sub-context in ctxVfs - if exists.
|
||||
* -- chMountPoint
|
||||
*/
|
||||
VOID VfsClose(_In_ CHAR chMountPoint);
|
||||
VOID VfsDokan_Close(_In_ CHAR chMountPoint);
|
||||
|
||||
#endif /* __VFS_H__ */
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
// vfsdokan.c : implementation of functions related to virtual file system support based on dokan.
|
||||
//
|
||||
// (c) Ulf Frisk, 2018-2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
#include <vmmdll.h>
|
||||
#include "vfs.h"
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4005 )
|
||||
#include <dokan.h>
|
||||
#pragma warning( pop )
|
||||
|
||||
DWORD g_dbg_c = 0;
|
||||
|
||||
//#define dbg_GetTickCount64() GetTickCount64()
|
||||
//#define dbg_wprintf_init(format, ...) { wprintf(format, ++g_dbg_c, ##__VA_ARGS__); }
|
||||
//#define dbg_wprintf(format, ...) { wprintf(format, ++g_dbg_c, ##__VA_ARGS__); }
|
||||
#define dbg_wprintf_init(format, ...) {}
|
||||
#define dbg_wprintf(format, ...) {}
|
||||
#define dbg_GetTickCount64() 0
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// DOKAN CALLBACK FUNCTIONS BELOW:
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
NTSTATUS
|
||||
VfsDokanCallback_CreateFile_Impl(LPCWSTR wcsFileName, PDOKAN_IO_SECURITY_CONTEXT SecurityContext, ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
BOOL result;
|
||||
WIN32_FIND_DATAW FindData;
|
||||
WCHAR wszPath[MAX_PATH];
|
||||
LPWSTR wszFile;
|
||||
BOOL fIsDirectoryExisting = FALSE;
|
||||
UNREFERENCED_PARAMETER(SecurityContext);
|
||||
UNREFERENCED_PARAMETER(FileAttributes);
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
// root directory
|
||||
if(!wcscmp(wcsFileName, L"\\")) {
|
||||
if(CreateDisposition == CREATE_ALWAYS) { return ctxVfs->DokanNtStatusFromWin32(ERROR_ACCESS_DENIED); }
|
||||
DokanFileInfo->IsDirectory = TRUE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
// other files
|
||||
if(CreateDisposition == CREATE_ALWAYS) { return ctxVfs->DokanNtStatusFromWin32(ERROR_ACCESS_DENIED); }
|
||||
Util_SplitPathFile(wszPath, &wszFile, wcsFileName);
|
||||
result = VfsList_GetSingle(wszPath[0] ? wszPath : L"\\", wszFile, &FindData, &fIsDirectoryExisting);
|
||||
if(!result) { return fIsDirectoryExisting ? STATUS_OBJECT_NAME_NOT_FOUND : STATUS_OBJECT_PATH_NOT_FOUND; }
|
||||
DokanFileInfo->IsDirectory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
|
||||
DokanFileInfo->Nocache = TRUE;
|
||||
if(!DokanFileInfo->IsDirectory && (CreateOptions & FILE_DIRECTORY_FILE)) { return STATUS_NOT_A_DIRECTORY; } // fail upon open normal file as directory
|
||||
return (CreateDisposition == OPEN_ALWAYS) ? STATUS_OBJECT_NAME_COLLISION : STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsDokanCallback_CreateFile(LPCWSTR FileName, PDOKAN_IO_SECURITY_CONTEXT SecurityContext, ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
NTSTATUS nt;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
nt = VfsDokanCallback_CreateFile_Impl(FileName, SecurityContext, DesiredAccess, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, DokanFileInfo);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_CreateFile:\t\t 0x%08x %s [ %08x %08x %08x %08x ]\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, FileName, FileAttributes, ShareAccess, CreateDisposition, CreateOptions);
|
||||
return nt;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsDokanCallback_GetFileInformation(_In_ LPCWSTR wcsFileName, _Inout_ LPBY_HANDLE_FILE_INFORMATION hfi, _In_ PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
BOOL result;
|
||||
WIN32_FIND_DATAW FindData;
|
||||
WCHAR wszPath[MAX_PATH];
|
||||
LPWSTR wszFile;
|
||||
BOOL fIsDirectoryExisting = FALSE;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_GetFileInformation:\t 0x%08x %s\n", 0, wcsFileName);
|
||||
// matches: root directory
|
||||
if(!wcscmp(wcsFileName, L"\\")) {
|
||||
hfi->ftCreationTime = ctxVfs->ftDefaultTime;
|
||||
hfi->ftLastWriteTime = ctxVfs->ftDefaultTime;
|
||||
hfi->ftLastAccessTime = ctxVfs->ftDefaultTime;
|
||||
hfi->nFileSizeHigh = 0;
|
||||
hfi->nFileSizeLow = 0;
|
||||
hfi->dwFileAttributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
Util_SplitPathFile(wszPath, &wszFile, wcsFileName);
|
||||
result = VfsList_GetSingle((wszPath[0] ? wszPath : L"\\"), wszFile, &FindData, &fIsDirectoryExisting);
|
||||
if(!result) {
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_FILE_NOT_AVAILABLE, wcsFileName);
|
||||
return STATUS_FILE_NOT_AVAILABLE;
|
||||
}
|
||||
hfi->dwFileAttributes = FindData.dwFileAttributes;
|
||||
hfi->ftCreationTime = FindData.ftCreationTime;
|
||||
hfi->ftLastAccessTime = FindData.ftLastAccessTime;
|
||||
hfi->ftLastWriteTime = FindData.ftLastWriteTime;
|
||||
hfi->nFileSizeHigh = FindData.nFileSizeHigh;
|
||||
hfi->nFileSizeLow = FindData.nFileSizeLow;
|
||||
hfi->nFileIndexHigh = Util_HashStringUpperW((LPWSTR)wcsFileName);
|
||||
hfi->nFileIndexLow = Util_HashStringUpperW((LPWSTR)FindData.cFileName);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_GetFileInformation:\t 0x%08x %s\t [ %08x %08x%08x %016llx %016llx %016llx ]\n",
|
||||
(DWORD)(dbg_GetTickCount64() - tmStart),
|
||||
STATUS_SUCCESS,
|
||||
wcsFileName,
|
||||
hfi->dwFileAttributes,
|
||||
hfi->nFileSizeHigh, hfi->nFileSizeLow,
|
||||
*(PQWORD)&hfi->ftCreationTime,
|
||||
*(PQWORD)&hfi->ftLastAccessTime,
|
||||
*(PQWORD)&hfi->ftLastWriteTime
|
||||
);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsDokanCallback_FindFiles(LPCWSTR wcsFileName, PFillFindData FillFindData, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", 0, wcsFileName);
|
||||
VfsList_ListDirectory((LPWSTR)wcsFileName, DokanFileInfo, FillFindData);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsDokanCallback_ReadFile(LPCWSTR wcsFileName, LPVOID Buffer, DWORD BufferLength, LPDWORD ReadLength, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
NTSTATUS nt;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_ReadFile:\t\t\t 0x%08x %s\n", 0, wcsFileName);
|
||||
nt = ctxVfs->pVmmDll->VfsRead(wcsFileName, Buffer, BufferLength, ReadLength, Offset);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_ReadFile:\t\t\t 0x%08x %s\t [ %016llx %08x %08x ]\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName, Offset, BufferLength, *ReadLength);
|
||||
return nt;
|
||||
}
|
||||
|
||||
NTSTATUS DOKAN_CALLBACK
|
||||
VfsDokanCallback_WriteFile(LPCWSTR wcsFileName, LPCVOID Buffer, DWORD NumberOfBytesToWrite, LPDWORD NumberOfBytesWritten, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo)
|
||||
{
|
||||
UINT64 tmStart = dbg_GetTickCount64();
|
||||
NTSTATUS nt;
|
||||
if(!ctxVfs || !ctxVfs->fInitialized) { return STATUS_FILE_INVALID; }
|
||||
dbg_wprintf_init(L"DEBUG::%08x -------- VfsCallback_WriteFile:\t\t\t 0x%08x %s\n", 0, wcsFileName);
|
||||
nt = ctxVfs->pVmmDll->VfsWrite(wcsFileName, (PBYTE)Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, Offset);
|
||||
dbg_wprintf(L"DEBUG::%08x %8x VfsCallback_WriteFile:\t\t\t 0x%08x %s\t [ %016llx %08x %08x ]\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName, Offset, NumberOfBytesToWrite, *NumberOfBytesWritten);
|
||||
return nt;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// VFS INITIALIZATION FUNCTIONALITY BELOW:
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
VOID VfsDokan_Close(_In_ CHAR chMountPoint)
|
||||
{
|
||||
HMODULE hModuleDokan = NULL;
|
||||
WCHAR wchMountPoint = chMountPoint;
|
||||
BOOL(*pfnDokanUnmount)(WCHAR DriveLetter);
|
||||
if(ctxVfs && ctxVfs->fInitialized) {
|
||||
ctxVfs->fInitialized = FALSE;
|
||||
if(wchMountPoint) {
|
||||
hModuleDokan = LoadLibraryExA("dokan1.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if(hModuleDokan) {
|
||||
pfnDokanUnmount = (BOOL(*)(WCHAR))GetProcAddress(hModuleDokan, "DokanUnmount");
|
||||
if(pfnDokanUnmount) {
|
||||
pfnDokanUnmount(wchMountPoint);
|
||||
}
|
||||
FreeLibrary(hModuleDokan);
|
||||
}
|
||||
}
|
||||
}
|
||||
LocalFree(ctxVfs);
|
||||
ctxVfs = NULL;
|
||||
}
|
||||
|
||||
VOID VfsDokan_InitializeAndMount_DisplayInfo(LPWSTR wszMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll)
|
||||
{
|
||||
ULONG64 qwVersionVmmMajor = 0, qwVersionVmmMinor = 0, qwVersionVmmRevision = 0;
|
||||
ULONG64 qwVersionWinMajor = 0, qwVersionWinMinor = 0, qwVersionWinBuild = 0, iMemoryModel;
|
||||
// get vmm.dll versions
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR, &qwVersionVmmMajor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR, &qwVersionVmmMinor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION, &qwVersionVmmRevision);
|
||||
// get operating system versions
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_CORE_MEMORYMODEL, &iMemoryModel);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_WIN_VERSION_MAJOR, &qwVersionWinMajor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_WIN_VERSION_MINOR, &qwVersionWinMinor);
|
||||
pVmmDll->ConfigGet(VMMDLL_OPT_WIN_VERSION_BUILD, &qwVersionWinBuild);
|
||||
printf("\n" \
|
||||
"=============== MemProcFS - THE MEMORY PROCESS FILE SYSTEM ===============\n" \
|
||||
" - Author: Ulf Frisk - [email protected] \n" \
|
||||
" - Info: https://github.com/ufrisk/MemProcFS \n" \
|
||||
" - License: GNU Affero General Public License v3.0 \n" \
|
||||
" -------------------------------------------------------------------- \n" \
|
||||
" MemProcFS is free open source software. If you find it useful please \n" \
|
||||
" become a sponsor at: https://github.com/sponsors/ufrisk Thank You! \n" \
|
||||
" -------------------------------------------------------------------- \n" \
|
||||
" - Version: %i.%i.%i \n" \
|
||||
" - Mount Point: %S \n",
|
||||
(DWORD)qwVersionVmmMajor, (DWORD)qwVersionVmmMinor, (DWORD)qwVersionVmmRevision, wszMountPoint);
|
||||
if(qwVersionWinMajor && (iMemoryModel < (sizeof(VMMDLL_MEMORYMODEL_TOSTRING) / sizeof(LPSTR)))) {
|
||||
printf(" - Operating System: Windows %i.%i.%i (%s)\n",
|
||||
(DWORD)qwVersionWinMajor, (DWORD)qwVersionWinMinor, (DWORD)qwVersionWinBuild, VMMDLL_MEMORYMODEL_TOSTRING[iMemoryModel]);
|
||||
} else {
|
||||
printf(" - Operating System: Unknown\n");
|
||||
}
|
||||
printf("==========================================================================\n\n");
|
||||
}
|
||||
|
||||
VOID VfsDokan_InitializeAndMount(_In_ CHAR chMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll)
|
||||
{
|
||||
int status;
|
||||
HMODULE hModuleDokan = NULL;
|
||||
PDOKAN_OPTIONS pDokanOptions = NULL;
|
||||
PDOKAN_OPERATIONS pDokanOperations = NULL;
|
||||
WCHAR wszMountPoint[] = { 'M', ':', '\\', 0 };
|
||||
SYSTEMTIME SystemTimeNow;
|
||||
int(*fnDokanMain)(PDOKAN_OPTIONS, PDOKAN_OPERATIONS);
|
||||
// allocate
|
||||
hModuleDokan = LoadLibraryExA("dokan1.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if(!hModuleDokan) {
|
||||
printf("MOUNT: Failed. The required DOKANY file system library is not installed. \n");
|
||||
printf("Please download from : https://github.com/dokan-dev/dokany/releases/latest\n");
|
||||
goto fail;
|
||||
}
|
||||
fnDokanMain = (int(*)(PDOKAN_OPTIONS, PDOKAN_OPERATIONS))GetProcAddress(hModuleDokan, "DokanMain");
|
||||
if(!fnDokanMain) {
|
||||
printf("MOUNT: Failed. The required DOKANY file system library is not installed. \n");
|
||||
printf("Please download from : https://github.com/dokan-dev/dokany/releases/latest\n");
|
||||
goto fail;
|
||||
}
|
||||
pDokanOptions = (PDOKAN_OPTIONS)LocalAlloc(LMEM_ZEROINIT, sizeof(DOKAN_OPTIONS));
|
||||
pDokanOperations = (PDOKAN_OPERATIONS)LocalAlloc(LMEM_ZEROINIT, sizeof(DOKAN_OPERATIONS));
|
||||
if(!pDokanOptions || !pDokanOperations) {
|
||||
printf("MOUNT: Failed (out of memory).\n");
|
||||
goto fail;
|
||||
}
|
||||
// allocate empty vfs context
|
||||
ctxVfs = (PVMMVFS_CONFIG)LocalAlloc(LMEM_ZEROINIT, sizeof(VMMVFS_CONFIG));
|
||||
if(!ctxVfs) { goto fail; }
|
||||
ctxVfs->pVmmDll = pVmmDll;
|
||||
// set vfs context
|
||||
GetSystemTime(&SystemTimeNow);
|
||||
SystemTimeToFileTime(&SystemTimeNow, &ctxVfs->ftDefaultTime);
|
||||
ctxVfs->DokanNtStatusFromWin32 = (NTSTATUS(*)(DWORD))GetProcAddress(hModuleDokan, "DokanNtStatusFromWin32");
|
||||
ctxVfs->fInitialized = TRUE;
|
||||
// set options
|
||||
pDokanOptions->Version = DOKAN_VERSION;
|
||||
pDokanOptions->Options |= DOKAN_OPTION_NETWORK;
|
||||
pDokanOptions->UNCName = L"MemProcFS";
|
||||
pDokanOptions->ThreadCount = 10;
|
||||
wszMountPoint[0] = chMountPoint;
|
||||
pDokanOptions->MountPoint = wszMountPoint;
|
||||
pDokanOptions->Timeout = 60000;
|
||||
// set callbacks
|
||||
pDokanOperations->ZwCreateFile = VfsDokanCallback_CreateFile;
|
||||
pDokanOperations->GetFileInformation = VfsDokanCallback_GetFileInformation;
|
||||
pDokanOperations->FindFiles = VfsDokanCallback_FindFiles;
|
||||
pDokanOperations->ReadFile = VfsDokanCallback_ReadFile;
|
||||
pDokanOperations->WriteFile = VfsDokanCallback_WriteFile;
|
||||
// print system information to console
|
||||
VfsDokan_InitializeAndMount_DisplayInfo(wszMountPoint, pVmmDll);
|
||||
// mount file system
|
||||
status = fnDokanMain(pDokanOptions, pDokanOperations);
|
||||
while(ctxVfs && ctxVfs->fInitialized && (status == DOKAN_SUCCESS)) {
|
||||
printf("MOUNT: ReMounting as drive %S\n", pDokanOptions->MountPoint);
|
||||
status = fnDokanMain(pDokanOptions, pDokanOperations);
|
||||
}
|
||||
if(status == -5) {
|
||||
printf("MOUNT: Failed: drive busy/already mounted.\n");
|
||||
} else {
|
||||
printf("MOUNT: Failed. Status Code: %i\n", status);
|
||||
}
|
||||
fail:
|
||||
if(hModuleDokan) { FreeLibrary(hModuleDokan); }
|
||||
LocalFree(pDokanOptions);
|
||||
LocalFree(pDokanOperations);
|
||||
VfsDokan_Close(0);
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
// vfslist.h : definitions related to vfs directory listings.
|
||||
//
|
||||
// (c) Ulf Frisk, 2021
|
||||
// Author: Ulf Frisk, [email protected]
|
||||
//
|
||||
#include "vfs.h"
|
||||
#include "ob/ob.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// UTILITY FUNCTIONS BELOW:
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
VOID Util_SplitPathFile(_Out_writes_(MAX_PATH) PWCHAR wszPath, _Out_ LPWSTR *pwcsFile, _In_ LPCWSTR wcsFileName)
|
||||
{
|
||||
DWORD i, iSplitFilePath = 0;
|
||||
wcsncpy_s(wszPath, MAX_PATH, wcsFileName, _TRUNCATE);
|
||||
for(i = 0; i < MAX_PATH; i++) {
|
||||
if(wszPath[i] == '\\') {
|
||||
iSplitFilePath = i;
|
||||
}
|
||||
if(wszPath[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
wszPath[iSplitFilePath] = 0;
|
||||
*pwcsFile = wszPath + iSplitFilePath + 1;
|
||||
}
|
||||
|
||||
LPWSTR Util_PathSplit2_ExWCHAR(_In_ LPWSTR wsz, _Out_writes_(cwsz1) LPWSTR wsz1, _In_ DWORD cwsz1)
|
||||
{
|
||||
WCHAR wch;
|
||||
DWORD i = 0;
|
||||
while((wch = wsz[i]) && (wch != '\\') && (i < cwsz1 - 1)) {
|
||||
wsz1[i++] = wch;
|
||||
}
|
||||
wsz1[i] = 0;
|
||||
return wsz[i] ? &wsz[i + 1] : L"";
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash a string in uppercase.
|
||||
* -- wsz
|
||||
* -- return
|
||||
*/
|
||||
DWORD Util_HashStringUpperW(_In_opt_ LPWSTR wsz)
|
||||
{
|
||||
WCHAR c;
|
||||
DWORD i = 0, dwHash = 0;
|
||||
if(!wsz) { return 0; }
|
||||
while(TRUE) {
|
||||
c = wsz[i++];
|
||||
if(!c) { return dwHash; }
|
||||
if(c >= 'a' && c <= 'z') {
|
||||
c += 'A' - 'a';
|
||||
}
|
||||
dwHash = ((dwHash >> 13) | (dwHash << 19)) + c;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash a path in uppercase.
|
||||
* -- wszPath
|
||||
* -- return
|
||||
*/
|
||||
QWORD Util_HashPathW(_In_ LPWSTR wszPath)
|
||||
{
|
||||
DWORD dwHashName;
|
||||
QWORD qwHashTotal = 0;
|
||||
WCHAR wsz1[MAX_PATH];
|
||||
while(wszPath && wszPath[0]) {
|
||||
wszPath = Util_PathSplit2_ExWCHAR(wszPath, wsz1, _countof(wsz1));
|
||||
dwHashName = Util_HashStringUpperW(wsz1);
|
||||
qwHashTotal = dwHashName + ((qwHashTotal >> 13) | (qwHashTotal << 51));
|
||||
}
|
||||
return qwHashTotal;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// VFS LIST FUNCTIONALITY BELOW:
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
typedef struct tdVFSLIST_CONTEXT {
|
||||
BOOL(*pfnVfsList)(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList);
|
||||
QWORD qwCacheValidMs;
|
||||
FILETIME ftDefaultTime;
|
||||
POB_CACHEMAP pcm;
|
||||
} VFSLIST_CONTEXT, *PVFSLIST_CONTEXT;
|
||||
|
||||
VFSLIST_CONTEXT g_ctxVfsList = { 0 };
|
||||
|
||||
#define VFSLIST_CONFIG_FILELIST_ITEMS 12
|
||||
#define VFSLIST_CONFIG_FILELIST_MAGIC 0x7f646555caffee77
|
||||
|
||||
typedef struct tdVFSLIST_DIRECTORY {
|
||||
QWORD magic;
|
||||
struct tdVFSLIST_DIRECTORY *FLink;
|
||||
DWORD cFiles;
|
||||
WIN32_FIND_DATAW pFiles[VFSLIST_CONFIG_FILELIST_ITEMS];
|
||||
} VFSLIST_DIRECTORY, *PVFSLIST_DIRECTORY;
|
||||
|
||||
typedef struct tdVFSLISTOB_DIRECTORY {
|
||||
OB ObHdr;
|
||||
QWORD tc64;
|
||||
QWORD qwHash;
|
||||
VFSLIST_DIRECTORY Dir;
|
||||
} VFSLISTOB_DIRECTORY, *PVFSLISTOB_DIRECTORY;
|
||||
|
||||
/*
|
||||
* Object Manager cleanup callback function.
|
||||
*/
|
||||
VOID VfsList_CallbackCleanup_ObDirectory(PVFSLISTOB_DIRECTORY pObDir)
|
||||
{
|
||||
PVFSLIST_DIRECTORY pDirNext, pDir = pObDir->Dir.FLink;
|
||||
while(pDir) {
|
||||
pDirNext = pDir->FLink;
|
||||
LocalFree(pDir);
|
||||
pDir = pDirNext;
|
||||
}
|
||||
}
|
||||
|
||||
#define VFSLIST_ASCII "________________________________ !_#$%&'()_+,-._0123456789_;_=__@ABCDEFGHIJKLMNOPQRSTUVWXYZ[_]^_`abcdefghijklmnopqrstuvwxyz{_}~ "
|
||||
|
||||
VOID VfsList_AddDirectoryFileInternal(_Inout_ PVFSLIST_DIRECTORY pFileList, _In_ DWORD dwFileAttributes, _In_ FILETIME ftCreationTime, _In_ FILETIME ftLastAccessTime, _In_ FILETIME ftLastWriteTime, _In_ DWORD nFileSizeHigh, _In_ DWORD nFileSizeLow, _In_ LPWSTR wszName)
|
||||
{
|
||||
WCHAR c;
|
||||
DWORD i = 0;
|
||||
PWIN32_FIND_DATAW pFindData;
|
||||
// 1: check if required to allocate more FileList items
|
||||
while(pFileList->cFiles == VFSLIST_CONFIG_FILELIST_ITEMS) {
|
||||
if(pFileList->FLink) {
|
||||
pFileList = pFileList->FLink;
|
||||
continue;
|
||||
}
|
||||
pFileList->FLink = LocalAlloc(LMEM_ZEROINIT, sizeof(VFSLIST_DIRECTORY));
|
||||
if(!pFileList->FLink) { return; }
|
||||
pFileList = pFileList->FLink;
|
||||
}
|
||||
// 2: locate item to fill into
|
||||
pFindData = pFileList->pFiles + pFileList->cFiles;
|
||||
pFileList->cFiles++;
|
||||
// 3: fill
|
||||
pFindData->dwFileAttributes = dwFileAttributes;
|
||||
pFindData->ftCreationTime = ftCreationTime;
|
||||
pFindData->ftLastAccessTime = ftLastAccessTime;
|
||||
pFindData->ftLastWriteTime = ftLastWriteTime;
|
||||
pFindData->nFileSizeHigh = nFileSizeHigh;
|
||||
pFindData->nFileSizeLow = nFileSizeLow;
|
||||
while(i < MAX_PATH && (c = wszName[i])) {
|
||||
pFindData->cFileName[i++] = (c < 128) ? VFSLIST_ASCII[c] : c;
|
||||
}
|
||||
pFindData->cFileName[min(i, MAX_PATH - 1)] = 0;
|
||||
}
|
||||
|
||||
VOID VfsList_AddFile(_Inout_ HANDLE hFileList, _In_ LPWSTR wszName, _In_ QWORD cb, _In_opt_ PVMMDLL_VFS_FILELIST_EXINFO pExInfo)
|
||||
{
|
||||
PVFSLIST_DIRECTORY pFileList2 = (PVFSLIST_DIRECTORY)hFileList;
|
||||
BOOL fExInfo = pExInfo && (pExInfo->dwVersion == VMMDLL_VFS_FILELIST_EXINFO_VERSION);
|
||||
if(pFileList2 && (pFileList2->magic == VFSLIST_CONFIG_FILELIST_MAGIC)) {
|
||||
VfsList_AddDirectoryFileInternal(
|
||||
pFileList2,
|
||||
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | ((fExInfo && pExInfo->fCompressed) ? FILE_ATTRIBUTE_COMPRESSED : 0),
|
||||
(fExInfo && pExInfo->qwCreationTime) ? pExInfo->ftCreationTime : g_ctxVfsList.ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastAccessTime) ? pExInfo->ftLastAccessTime : g_ctxVfsList.ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastWriteTime) ? pExInfo->ftLastWriteTime : g_ctxVfsList.ftDefaultTime,
|
||||
(DWORD)(cb >> 32),
|
||||
(DWORD)cb,
|
||||
wszName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VOID VfsList_AddDirectory(_Inout_ HANDLE hFileList, _In_ LPWSTR wszName, _In_opt_ PVMMDLL_VFS_FILELIST_EXINFO pExInfo)
|
||||
{
|
||||
PVFSLIST_DIRECTORY pFileList2 = (PVFSLIST_DIRECTORY)hFileList;
|
||||
BOOL fExInfo = pExInfo && (pExInfo->dwVersion == VMMDLL_VFS_FILELIST_EXINFO_VERSION);
|
||||
if(pFileList2 && (pFileList2->magic == VFSLIST_CONFIG_FILELIST_MAGIC)) {
|
||||
VfsList_AddDirectoryFileInternal(
|
||||
pFileList2,
|
||||
FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | ((fExInfo && pExInfo->fCompressed) ? FILE_ATTRIBUTE_COMPRESSED : 0),
|
||||
(fExInfo && pExInfo->qwCreationTime) ? pExInfo->ftCreationTime : g_ctxVfsList.ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastAccessTime) ? pExInfo->ftLastAccessTime : g_ctxVfsList.ftDefaultTime,
|
||||
(fExInfo && pExInfo->qwLastWriteTime) ? pExInfo->ftLastWriteTime : g_ctxVfsList.ftDefaultTime,
|
||||
0,
|
||||
0,
|
||||
wszName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve a directory object given a path name.
|
||||
* CALLER DECREF: return
|
||||
* -- wszPath
|
||||
* -- return
|
||||
*/
|
||||
PVFSLISTOB_DIRECTORY VfsList_GetDirectory(_In_ LPWSTR wszPath)
|
||||
{
|
||||
QWORD qwHash;
|
||||
PVFSLISTOB_DIRECTORY pObDir;
|
||||
VMMDLL_VFS_FILELIST VfsFileList;
|
||||
// 1: try fetch from cache:
|
||||
qwHash = Util_HashPathW(wszPath);
|
||||
if((pObDir = ObCacheMap_GetByKey(g_ctxVfsList.pcm, qwHash))) {
|
||||
return pObDir;
|
||||
}
|
||||
// 2: create new:
|
||||
if(!(pObDir = Ob_Alloc('VFSD', LMEM_ZEROINIT, sizeof(VFSLISTOB_DIRECTORY), VfsList_CallbackCleanup_ObDirectory, NULL))) { return NULL; }
|
||||
pObDir->Dir.magic = VFSLIST_CONFIG_FILELIST_MAGIC;
|
||||
VfsFileList.dwVersion = VMMDLL_VFS_FILELIST_VERSION;
|
||||
VfsFileList.h = (HANDLE)&pObDir->Dir;
|
||||
VfsFileList.pfnAddFile = VfsList_AddFile;
|
||||
VfsFileList.pfnAddDirectory = VfsList_AddDirectory;
|
||||
if(ctxVfs->pVmmDll->VfsList(wszPath, &VfsFileList)) {
|
||||
pObDir->tc64 = GetTickCount64();
|
||||
pObDir->qwHash = qwHash;
|
||||
ObCacheMap_Push(g_ctxVfsList.pcm, qwHash, pObDir, 0);
|
||||
return pObDir;
|
||||
}
|
||||
Ob_DECREF(pObDir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* List a directory using a callback function
|
||||
* -- wszPath
|
||||
* -- ctx = optional context to pass along to callback function.
|
||||
* -- pfnListCallback = callback function called one time per directory entry.
|
||||
* -- return = TRUE if directory exists, otherwise FALSE.
|
||||
*/
|
||||
BOOL VfsList_ListDirectory(_In_ LPWSTR wszPath, _In_opt_ PVOID ctx, _In_opt_ PFN_VFSLIST_CALLBACK pfnListCallback)
|
||||
{
|
||||
DWORD i;
|
||||
PVFSLIST_DIRECTORY pDir;
|
||||
PVFSLISTOB_DIRECTORY pObDir;
|
||||
if(!(pObDir = VfsList_GetDirectory(wszPath))) { return FALSE; }
|
||||
if(pfnListCallback) {
|
||||
pDir = &pObDir->Dir;
|
||||
while(pDir) {
|
||||
for(i = 0; i < pDir->cFiles; i++) {
|
||||
pfnListCallback(pDir->pFiles + i, ctx);
|
||||
}
|
||||
pDir = pDir->FLink;
|
||||
}
|
||||
}
|
||||
Ob_DECREF(pObDir);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve information about a single entry inside a directory.
|
||||
* -- wszPath
|
||||
* -- wszFile
|
||||
* -- pFindData
|
||||
* -- pfPathValid = receives if wszPath is valid or not.
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL VfsList_GetSingle(_In_ LPWSTR wszPath, _In_ LPWSTR wszFile, _Out_ PWIN32_FIND_DATAW pFindData, _Out_ PBOOL pfPathValid)
|
||||
{
|
||||
DWORD i;
|
||||
PVFSLIST_DIRECTORY pDir;
|
||||
PVFSLISTOB_DIRECTORY pObDir;
|
||||
*pfPathValid = FALSE;
|
||||
if((pObDir = VfsList_GetDirectory(wszPath))) {
|
||||
*pfPathValid = TRUE;
|
||||
pDir = &pObDir->Dir;
|
||||
while(pDir) {
|
||||
for(i = 0; i < pDir->cFiles; i++) {
|
||||
if(!wcscmp(wszFile, pDir->pFiles[i].cFileName)) {
|
||||
memcpy(pFindData, pDir->pFiles + i, sizeof(WIN32_FIND_DATAW));
|
||||
Ob_DECREF(pObDir);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
pDir = pDir->FLink;
|
||||
}
|
||||
Ob_DECREF(pObDir);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate whether a given cachemap entry is still valid time wise.
|
||||
* -- qwContext
|
||||
* -- qwKey
|
||||
* -- pvObject
|
||||
* -- return
|
||||
*/
|
||||
BOOL VfsList_ValidEntry(_Inout_ PQWORD qwContext, _In_ QWORD qwKey, _In_ PVFSLISTOB_DIRECTORY pvObject)
|
||||
{
|
||||
return pvObject->tc64 + g_ctxVfsList.qwCacheValidMs > GetTickCount64();
|
||||
}
|
||||
|
||||
/*
|
||||
* Close and clean up the vfs list functionality.
|
||||
*/
|
||||
void VfsList_Close()
|
||||
{
|
||||
Ob_DECREF(g_ctxVfsList.pcm);
|
||||
ZeroMemory(&g_ctxVfsList, sizeof(VFSLIST_CONTEXT));
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the vfs list functionality.
|
||||
* -- hModuleVmm
|
||||
* -- dwCacheValidMs
|
||||
* -- cCacheMaxEntries
|
||||
* -- return
|
||||
*/
|
||||
_Success_(return)
|
||||
BOOL VfsList_Initialize(_In_ HMODULE hModuleVmm, _In_ DWORD dwCacheValidMs, _In_ DWORD cCacheMaxEntries)
|
||||
{
|
||||
SYSTEMTIME SystemTimeNow;
|
||||
if(!(g_ctxVfsList.pcm = ObCacheMap_New(cCacheMaxEntries, VfsList_ValidEntry, OB_CACHEMAP_FLAGS_OBJECT_OB))) { return FALSE; }
|
||||
g_ctxVfsList.pfnVfsList = (BOOL(*)(LPCWSTR, PVMMDLL_VFS_FILELIST))GetProcAddress(hModuleVmm, "VMMDLL_VfsList");
|
||||
g_ctxVfsList.qwCacheValidMs = dwCacheValidMs;
|
||||
GetSystemTime(&SystemTimeNow);
|
||||
SystemTimeToFileTime(&SystemTimeNow, &g_ctxVfsList.ftDefaultTime);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -108,8 +108,8 @@ Please find some ideas for possible future expansions of the memory process file
|
||||
- Forensic mode JSON file generation.
|
||||
|
||||
License:
|
||||
======
|
||||
The project source code is released under GPLv3. Some bundled Microsoft redistributable binaries are released under separate licenses.
|
||||
========
|
||||
The project source code is released under: GNU Affero General Public License v3.0. Some bundled dependencies and plugins are released under GPLv3. Some bundled Microsoft redistributable binaries are released under separate licenses. Alternative licensing may be possible.
|
||||
|
||||
Contributing:
|
||||
=============
|
||||
@@ -227,3 +227,7 @@ v1.1-v2.10
|
||||
* Windows Kernel Object Manager Objects.
|
||||
* Additional kernel driver information.
|
||||
* Detailed Object and Object Header Info.
|
||||
|
||||
Latest:
|
||||
* License Change to: GNU Affero General Public License v3.0.
|
||||
* MemProcFS.exe updates.
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
|
||||
#define VERSION_MAJOR 3
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_REVISION 0
|
||||
#define VERSION_BUILD 23
|
||||
#define VERSION_REVISION 1
|
||||
#define VERSION_BUILD 24
|
||||
|
||||
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Plugin vmemd"
|
||||
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
|
||||
#define VERSION_MAJOR 3
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_REVISION 0
|
||||
#define VERSION_BUILD 23
|
||||
#define VERSION_REVISION 1
|
||||
#define VERSION_BUILD 24
|
||||
|
||||
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Core"
|
||||
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
|
||||
#define VERSION_MAJOR 3
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_REVISION 0
|
||||
#define VERSION_BUILD 23
|
||||
#define VERSION_REVISION 1
|
||||
#define VERSION_BUILD 24
|
||||
|
||||
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Python API"
|
||||
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#define VERSION_MAJOR 3
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_REVISION 0
|
||||
#define VERSION_BUILD 23
|
||||
#define VERSION_REVISION 1
|
||||
#define VERSION_BUILD 24
|
||||
|
||||
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Python Plugin Manager"
|
||||
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
|
||||
|
||||
Reference in New Issue
Block a user