Version 3.8.1

This commit is contained in:
ufrisk
2021-02-03 18:56:00 +01:00
parent ef9b0d5d95
commit 145eb02063
19 changed files with 3321 additions and 659 deletions
+65 -78
View File
@@ -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>.
+7 -1
View File
@@ -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>
+25 -1
View File
@@ -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">
+4 -2
View File
@@ -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);
+841
View File
@@ -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__ */
+255
View File
@@ -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;
}
+146
View File
@@ -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;
}
+733
View File
@@ -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;
}
+574
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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__ */
+281
View File
@@ -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);
}
+320
View File
@@ -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;
}
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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