mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -05:00
Merge pull request #241 from JohnOberschelp/master
Add persistence to Airfs
This commit is contained in:
commit
51b33f02aa
@ -15,6 +15,13 @@ init:
|
|||||||
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- ps: |
|
||||||
|
# Hack to make WDK 1903 work on VS2015.
|
||||||
|
# See https://github.com/appveyor-tests/WDK-10.0.14393.0/blob/31cf12217fe0c92b218c70d7027dfe145be4f4cb/appveyor.yml#L7
|
||||||
|
[xml]$targets = get-content "C:\Program Files (x86)\Windows Kits\10\build\WindowsDriver.Common.targets"
|
||||||
|
$usingTask = $targets.ChildNodes[1].UsingTask | ? {$_.TaskName -eq "ValidateNTTargetVersion"}
|
||||||
|
$usingTask.AssemblyFile = '$(WDKContentRoot)build\bin\Microsoft.DriverKit.Build.Tasks.16.0.dll'
|
||||||
|
$targets.Save("C:\Program Files (x86)\Windows Kits\10\build\WindowsDriver.Common.targets")
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
- appveyor AddMessage "Change boot configuration and reboot" -Category Information
|
- appveyor AddMessage "Change boot configuration and reboot" -Category Information
|
||||||
- bcdedit /set testsigning on
|
- bcdedit /set testsigning on
|
||||||
|
@ -391,6 +391,12 @@
|
|||||||
<Component Id="C.airfs.cpp">
|
<Component Id="C.airfs.cpp">
|
||||||
<File Name="airfs.cpp" KeyPath="yes" />
|
<File Name="airfs.cpp" KeyPath="yes" />
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component Id="C.persistence.cpp">
|
||||||
|
<File Name="persistence.cpp" KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
|
<Component Id="C.common.h">
|
||||||
|
<File Name="common.h" KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
<Component Id="C.airfs.sln">
|
<Component Id="C.airfs.sln">
|
||||||
<File Name="airfs.sln" KeyPath="yes" />
|
<File Name="airfs.sln" KeyPath="yes" />
|
||||||
</Component>
|
</Component>
|
||||||
@ -587,6 +593,8 @@
|
|||||||
<ComponentRef Id="C.memfs.cpp" />
|
<ComponentRef Id="C.memfs.cpp" />
|
||||||
<ComponentRef Id="C.memfs_main.c" />
|
<ComponentRef Id="C.memfs_main.c" />
|
||||||
<ComponentRef Id="C.airfs.cpp" />
|
<ComponentRef Id="C.airfs.cpp" />
|
||||||
|
<ComponentRef Id="C.persistence.cpp" />
|
||||||
|
<ComponentRef Id="C.common.h" />
|
||||||
<ComponentRef Id="C.airfs.sln" />
|
<ComponentRef Id="C.airfs.sln" />
|
||||||
<ComponentRef Id="C.airfs.vcxproj" />
|
<ComponentRef Id="C.airfs.vcxproj" />
|
||||||
<ComponentRef Id="C.airfs.vcxproj.filters" />
|
<ComponentRef Id="C.airfs.vcxproj.filters" />
|
||||||
@ -750,4 +758,4 @@
|
|||||||
</ScheduleReboot>
|
</ScheduleReboot>
|
||||||
</InstallExecuteSequence>
|
</InstallExecuteSequence>
|
||||||
</Product>
|
</Product>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
1342
tst/airfs/airfs.cpp
1342
tst/airfs/airfs.cpp
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
<ProjectConfiguration Include="Debug|Win32">
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
@ -171,8 +171,12 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="airfs.cpp" />
|
<ClCompile Include="airfs.cpp" />
|
||||||
|
<ClCompile Include="persistence.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="common.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="Source">
|
<Filter Include="Source">
|
||||||
@ -10,5 +10,13 @@
|
|||||||
<ClCompile Include="airfs.cpp">
|
<ClCompile Include="airfs.cpp">
|
||||||
<Filter>Source</Filter>
|
<Filter>Source</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="persistence.cpp">
|
||||||
|
<Filter>Source</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
<ItemGroup>
|
||||||
|
<ClInclude Include="common.h">
|
||||||
|
<Filter>Source</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
212
tst/airfs/common.h
Normal file
212
tst/airfs/common.h
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* @file common.h
|
||||||
|
*
|
||||||
|
* @copyright 2015-2019 Bill Zissimopoulos
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* This file is part of WinFsp.
|
||||||
|
*
|
||||||
|
* You can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License version 3 as published by the Free Software
|
||||||
|
* Foundation.
|
||||||
|
*
|
||||||
|
* Licensees holding a valid commercial license may use this software
|
||||||
|
* in accordance with the commercial license agreement provided in
|
||||||
|
* conjunction with the software. The terms and conditions of any such
|
||||||
|
* commercial license agreement shall govern, supersede, and render
|
||||||
|
* ineffective any application of the GPLv3 license to this software,
|
||||||
|
* notwithstanding of any reference thereto in the software or
|
||||||
|
* associated repository.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Airfs is based on Memfs with changes contributed by John Oberschelp.
|
||||||
|
* The contributed changes are under joint copyright by Bill Zissimopoulos
|
||||||
|
* and John Oberschelp per the Contributor Agreement found at the
|
||||||
|
* root of this project.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <winfsp/winfsp.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <sddl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define PROGNAME "airfs"
|
||||||
|
#define ROUND_UP( bytes, units ) (((bytes) + (units) - 1) / (units) * (units))
|
||||||
|
#define ROUND_DOWN( bytes, units ) (((bytes) ) / (units) * (units))
|
||||||
|
#define MINIMUM_ALLOCSIZE 196
|
||||||
|
#define MAXIMUM_ALLOCSIZE ROUND_DOWN(10*1024*1024, MINIMUM_ALLOCSIZE)
|
||||||
|
#define SECTOR_SIZE 512
|
||||||
|
#define SECTORS_PER_ALLOCATION_UNIT 1
|
||||||
|
#define ALLOCATION_UNIT ( SECTOR_SIZE * SECTORS_PER_ALLOCATION_UNIT )
|
||||||
|
#define INFO(format, ...) FspServiceLog(EVENTLOG_INFORMATION_TYPE , format, __VA_ARGS__)
|
||||||
|
#define WARN(format, ...) FspServiceLog(EVENTLOG_WARNING_TYPE , format, __VA_ARGS__)
|
||||||
|
#define FAIL(format, ...) FspServiceLog(EVENTLOG_ERROR_TYPE , format, __VA_ARGS__)
|
||||||
|
#define AIRFS_MAX_PATH 512
|
||||||
|
#define FILEBLOCK_OVERHEAD 40 // size of ( P + E + L + R + FileOffset ) = 8 * 5 = 40
|
||||||
|
#define ARG_TO_S(v) if (arge > ++argp) v = *argp; else goto usage
|
||||||
|
#define ARG_TO_4(v) if (arge > ++argp) v = (int32_t) wcstoll_default(*argp, v); else goto usage
|
||||||
|
#define ARG_TO_8(v) if (arge > ++argp) v = wcstoll_default(*argp, v); else goto usage
|
||||||
|
|
||||||
|
enum StorageFileAccessType {ZERO=0,READ,WRITE};
|
||||||
|
enum Neighbor {LT=-2,LE=-1,EQ=0,GE=1,GT=2};
|
||||||
|
|
||||||
|
struct NODE;
|
||||||
|
typedef NODE* NODE_;
|
||||||
|
|
||||||
|
typedef int CompareFunction (void* key, NODE_);
|
||||||
|
|
||||||
|
inline NTSTATUS GetLastErrorAsStatus()
|
||||||
|
{
|
||||||
|
return FspNtStatusFromWin32(GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline UINT64 SystemTime()
|
||||||
|
{
|
||||||
|
FILETIME FileTime;
|
||||||
|
GetSystemTimeAsFileTime(&FileTime);
|
||||||
|
return ((PLARGE_INTEGER)&FileTime)->QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t wcstoll_default(wchar_t *w, int64_t deflt)
|
||||||
|
{
|
||||||
|
wchar_t *endp;
|
||||||
|
int64_t i = wcstoll(w, &endp, 0);
|
||||||
|
return L'\0' != w[0] && L'\0' == *endp ? i : deflt;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Where<T> Class: This class manages an offset within our memory-mapped
|
||||||
|
// volume to another location within our memory-mapped volume. Because it is
|
||||||
|
// a self-relative offset, this delta is constant regardless of where in
|
||||||
|
// memory the file system is mapped, so we can always reoptain its address.
|
||||||
|
// A delta of 0 is the special case for "null".
|
||||||
|
//
|
||||||
|
|
||||||
|
template <class T> class Where
|
||||||
|
{
|
||||||
|
int64_t delta;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Where() = default;
|
||||||
|
~Where() = default;
|
||||||
|
Where(T t) : delta( t ?( (char*)t -(char*)this ):0) {}
|
||||||
|
Where(Where& w) : delta( w.delta?( ((char*)&w+w.delta)-(char*)this ):0) {}
|
||||||
|
|
||||||
|
operator bool () { return delta != 0; }
|
||||||
|
operator T () { return (T) ( delta?( (char*)this+delta ):0); }
|
||||||
|
T operator -> () { return (T) ( delta?( (char*)this+delta ):0); }
|
||||||
|
operator void* () { return (void*)( delta?( (char*)this+delta ):0); }
|
||||||
|
|
||||||
|
bool operator == (Where& rhs) { return (char*)this+delta == (char*)&rhs+rhs.delta; }
|
||||||
|
bool operator != (Where& rhs) { return (char*)this+delta != (char*)&rhs+rhs.delta; }
|
||||||
|
bool operator == (T rhs) { return (char*)this+delta == (char*)rhs; }
|
||||||
|
bool operator != (T rhs) { return (char*)this+delta != (char*)rhs; }
|
||||||
|
|
||||||
|
Where& operator = (Where& rhs) { delta = rhs.delta?( ((char*)&rhs+rhs.delta) - ((char*)this) ):0; return *this; }
|
||||||
|
Where& operator = (void* rhs) { delta = rhs ?( (char*)rhs - ((char*)this) ):0; return *this; }
|
||||||
|
|
||||||
|
char* Address () { return (char*)this+delta; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// The header for an Airfs volume
|
||||||
|
//
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char Signature[8]; // Airfs\0\0\0
|
||||||
|
char MapFormatVersion[4]; // Major.Minor.Patch.Build
|
||||||
|
char filler[4];
|
||||||
|
Where<NODE_> Root;
|
||||||
|
Where<NODE_> Available;
|
||||||
|
UINT64 VolumeSize;
|
||||||
|
UINT64 FreeSize;
|
||||||
|
WCHAR VolumeLabel[32];
|
||||||
|
UINT16 VolumeLabelLength;
|
||||||
|
UINT16 filler1,filler2,filler3;
|
||||||
|
UINT32 CaseInsensitive;
|
||||||
|
UINT32 filler4;
|
||||||
|
WCHAR MapName[256];
|
||||||
|
WCHAR VolumeName[256]; // Use "" for a memory-only page file.
|
||||||
|
int64_t VolumeLength;
|
||||||
|
FSP_FSCTL_VOLUME_PARAMS VolumeParams;
|
||||||
|
FSP_FILE_SYSTEM *FileSystem;
|
||||||
|
HANDLE MapFileHandle;
|
||||||
|
HANDLE MapHandle;
|
||||||
|
} AIRFS, *AIRFS_;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Information per file or directory
|
||||||
|
//
|
||||||
|
struct NODE
|
||||||
|
{
|
||||||
|
Where<NODE_> P,L,R,E; // Sorted sibling tree: Parent, Left, Right, and Equal
|
||||||
|
union
|
||||||
|
{
|
||||||
|
Where<WCHAR*> Name;
|
||||||
|
int64_t FileOffset;
|
||||||
|
};
|
||||||
|
Where<NODE_> Parent;
|
||||||
|
Where<NODE_> Children;
|
||||||
|
FSP_FSCTL_FILE_INFO FileInfo;
|
||||||
|
uint64_t SecurityDescriptorSize;
|
||||||
|
Where<char*> SecurityDescriptor;
|
||||||
|
Where<NODE_> FileBlocks;
|
||||||
|
uint64_t ReparseDataSize;
|
||||||
|
Where<char*> ReparseData;
|
||||||
|
volatile LONG RefCount;
|
||||||
|
Where<NODE_> Streams;
|
||||||
|
BOOLEAN IsAStream;
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class SpinLock
|
||||||
|
{
|
||||||
|
LONG C; // Counter
|
||||||
|
HANDLE S; // Semaphore
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
SpinLock() { C = 0; S = CreateSemaphore(NULL, 0, 1, NULL); }
|
||||||
|
~SpinLock() { CloseHandle(S); }
|
||||||
|
|
||||||
|
void Acquire() { if (_InterlockedIncrement(&C) > 1) WaitForSingleObject(S, INFINITE); }
|
||||||
|
void Release() { if (_InterlockedDecrement(&C) > 0) ReleaseSemaphore(S, 1, NULL); }
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void Airprint (const char * format, ...);
|
||||||
|
|
||||||
|
int SizeCmp (void* key, NODE_);
|
||||||
|
int ExactNameCmp (void* key, NODE_);
|
||||||
|
int CaselessNameCmp (void* key, NODE_);
|
||||||
|
|
||||||
|
NODE_ Find (Where<NODE_> &root, void* key, CompareFunction);
|
||||||
|
NODE_ Near (Where<NODE_> &root, void* key, CompareFunction, Neighbor);
|
||||||
|
void Attach (Where<NODE_> &root, NODE_ attach, CompareFunction, void* key);
|
||||||
|
void Detach (Where<NODE_> &root, NODE_ detach);
|
||||||
|
NODE_ First (NODE_ start);
|
||||||
|
NODE_ Last (NODE_ start);
|
||||||
|
NODE_ Next (NODE_);
|
||||||
|
NODE_ Prev (NODE_);
|
||||||
|
|
||||||
|
NTSTATUS StorageStartup (AIRFS_ &, WCHAR* MapName, WCHAR* VolumeName, int64_t Length);
|
||||||
|
NTSTATUS StorageShutdown (AIRFS_);
|
||||||
|
void* StorageAllocate (AIRFS_ Airfs, int64_t RequestedSize);
|
||||||
|
void* StorageReallocate (AIRFS_ Airfs, void* Reallocate, int64_t RequestedSize);
|
||||||
|
void StorageFree (AIRFS_ Airfs, void* Release);
|
||||||
|
NTSTATUS StorageSetFileCapacity (AIRFS_, NODE_, int64_t MinimumRequiredCapacity);
|
||||||
|
void StorageAccessFile (StorageFileAccessType, NODE_, int64_t Offset, int64_t NumBytes, char* Address);
|
||||||
|
|
||||||
|
static_assert(AIRFS_MAX_PATH > MAX_PATH, "AIRFS_MAX_PATH must be greater than MAX_PATH.");
|
||||||
|
static_assert(sizeof NODE + sizeof int32_t == MINIMUM_ALLOCSIZE, "MINIMUM_ALLOCSIZE should be 196.");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
597
tst/airfs/persistence.cpp
Normal file
597
tst/airfs/persistence.cpp
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
/**
|
||||||
|
* @file persistence.cpp
|
||||||
|
*
|
||||||
|
* @copyright 2015-2019 Bill Zissimopoulos
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* This file is part of WinFsp.
|
||||||
|
*
|
||||||
|
* You can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License version 3 as published by the Free Software
|
||||||
|
* Foundation.
|
||||||
|
*
|
||||||
|
* Licensees holding a valid commercial license may use this software
|
||||||
|
* in accordance with the commercial license agreement provided in
|
||||||
|
* conjunction with the software. The terms and conditions of any such
|
||||||
|
* commercial license agreement shall govern, supersede, and render
|
||||||
|
* ineffective any application of the GPLv3 license to this software,
|
||||||
|
* notwithstanding of any reference thereto in the software or
|
||||||
|
* associated repository.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Airfs is based on Memfs with changes contributed by John Oberschelp.
|
||||||
|
* The contributed changes are under joint copyright by Bill Zissimopoulos
|
||||||
|
* and John Oberschelp per the Contributor Agreement found at the
|
||||||
|
* root of this project.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Airfs uses a memory-mapped file per volume to achieve persistence.
|
||||||
|
* The primary advantage of this is that volume loads and saves are automatic.
|
||||||
|
* The two primary disadvantages, and our workarounds are:
|
||||||
|
* 1. We can't use standard containers or memory management,
|
||||||
|
* so the below Rubbertree and Storage functions are used instead.
|
||||||
|
* 2. Each process will map the volume to an arbitrary address,
|
||||||
|
* so Where<T> offsets are used in place of pointers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
SpinLock StorageLock, AirprintLock, SetLock;
|
||||||
|
|
||||||
|
int SizeCmp ( void* key, NODE_ x)
|
||||||
|
{
|
||||||
|
return *(int32_t*)key - ((int32_t*)x)[-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
int BlockCmp ( void* key, NODE_ x)
|
||||||
|
{
|
||||||
|
int64_t left = *(int64_t*)key;
|
||||||
|
int64_t right = x->FileOffset;
|
||||||
|
return left == right ? 0 : (left < right ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CaselessNameCmp ( void* key, NODE_ x)
|
||||||
|
{
|
||||||
|
WCHAR* c1 = (WCHAR*) key;
|
||||||
|
WCHAR* c2 = x->Name;
|
||||||
|
return _wcsicmp(c1,c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExactNameCmp ( void* key, NODE_ x)
|
||||||
|
{
|
||||||
|
WCHAR* c1 = (WCHAR*) key;
|
||||||
|
WCHAR* c2 = x->Name;
|
||||||
|
return wcscmp(c1,c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void Airprint (const char * format, ...)
|
||||||
|
{
|
||||||
|
AirprintLock.Acquire();
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
char szBuffer[512];
|
||||||
|
sprintf_s(szBuffer, 511, "Airfs %5.5f ----", SystemTime() / 10'000'000.0);
|
||||||
|
vsnprintf(szBuffer+25, 511-25, format, args);
|
||||||
|
OutputDebugStringA(szBuffer);
|
||||||
|
va_end(args);
|
||||||
|
AirprintLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Rubbertree (because it is flexible!)
|
||||||
|
// Implements a sorted set of elements, using a binary tree.
|
||||||
|
// Has a function, Near, that finds nodes at or adjacent to a key.
|
||||||
|
// Has an equal branch for trees that require handling equivalent
|
||||||
|
// nodes, like Airfs' memory heap manager.
|
||||||
|
// Attach, Find, and Near use splay to improve random access times.
|
||||||
|
// First, Last, Next, and Prev do not, to improve sequential access times.
|
||||||
|
// Replacing Where<NODE_> with NODE_ would make it a pointer-based tree.
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline void rotateL(Where<NODE_> &root, NODE_ x)
|
||||||
|
{
|
||||||
|
NODE_ y = x->R, p = x->P;
|
||||||
|
if (x->R = y->L) y->L->P = x;
|
||||||
|
if (!(y->P = p))
|
||||||
|
root = y;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (x == p->L) p->L = y;
|
||||||
|
else p->R = y;
|
||||||
|
}
|
||||||
|
(y->L = x)->P = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline void rotateR(Where<NODE_> &root, NODE_ y)
|
||||||
|
{
|
||||||
|
NODE_ x = y->L, p = y->P;
|
||||||
|
if (y->L = x->R) x->R->P = y;
|
||||||
|
if (!(x->P = p))
|
||||||
|
root = x;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (y == p->L) p->L = x;
|
||||||
|
else p->R = x;
|
||||||
|
}
|
||||||
|
(x->R = y)->P = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void splay(Where<NODE_> &root, NODE_ x)
|
||||||
|
{
|
||||||
|
while (NODE_ p = x->P)
|
||||||
|
{
|
||||||
|
if (!p->P)
|
||||||
|
{
|
||||||
|
if (p->L == x) rotateR(root, p);
|
||||||
|
else rotateL(root, p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (p == p->P->R)
|
||||||
|
{
|
||||||
|
if (p->R == x) rotateL(root, p->P);
|
||||||
|
else rotateR(root, p);
|
||||||
|
rotateL(root, x->P);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (p->L == x) rotateR(root, p->P);
|
||||||
|
else rotateL(root, p);
|
||||||
|
rotateR(root, x->P);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline int seek(Where<NODE_> &root, NODE_ &x, void* key, CompareFunction CMP)
|
||||||
|
{
|
||||||
|
x = root;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int diff = CMP(key, x);
|
||||||
|
if (diff < 0)
|
||||||
|
{
|
||||||
|
if (!x->L) return -1;
|
||||||
|
x = x->L;
|
||||||
|
}
|
||||||
|
else if (diff > 0)
|
||||||
|
{
|
||||||
|
if (!x->R) return 1;
|
||||||
|
x = x->R;
|
||||||
|
}
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline NODE_ next(NODE_ x)
|
||||||
|
{
|
||||||
|
if (x->R) { x = x->R; while (x->L) x = x->L; return x; }
|
||||||
|
NODE_ p = x->P;
|
||||||
|
while (p && x == p->R) { x = p; p = p->P; }
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline NODE_ prev(NODE_ x)
|
||||||
|
{
|
||||||
|
if (x->L) { x = x->L; while (x->R) x = x->R; return x; }
|
||||||
|
NODE_ p = x->P;
|
||||||
|
while (p && x == p->L) { x = p; p = p->P; }
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NODE_ First(NODE_ x)
|
||||||
|
{
|
||||||
|
SetLock.Acquire();
|
||||||
|
if (x) while (x->L) x = x->L;
|
||||||
|
SetLock.Release();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NODE_ Last(NODE_ x)
|
||||||
|
{
|
||||||
|
SetLock.Acquire();
|
||||||
|
if (x) while (x->R) x = x->R;
|
||||||
|
SetLock.Release();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NODE_ Next(NODE_ x)
|
||||||
|
{
|
||||||
|
SetLock.Acquire();
|
||||||
|
x = next(x);
|
||||||
|
SetLock.Release();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NODE_ Prev(NODE_ x)
|
||||||
|
{
|
||||||
|
SetLock.Acquire();
|
||||||
|
x = prev(x);
|
||||||
|
SetLock.Release();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NODE_ Near(Where<NODE_> &root, void* key, CompareFunction CMP, Neighbor want)
|
||||||
|
{
|
||||||
|
// Return a node relative to (just <, <=, ==, >=, or >) a key.
|
||||||
|
if (!root) return 0;
|
||||||
|
SetLock.Acquire();
|
||||||
|
NODE_ x;
|
||||||
|
int dir = seek(root, x, key, CMP);
|
||||||
|
if ((dir == 0 && want == GT) || (dir > 0 && want >= GE)) x = next(x);
|
||||||
|
else
|
||||||
|
if ((dir == 0 && want == LT) || (dir < 0 && want <= LE)) x = prev(x);
|
||||||
|
else
|
||||||
|
if (dir != 0 && want == EQ) x = 0;
|
||||||
|
if (x) splay(root, x);
|
||||||
|
SetLock.Release();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NODE_ Find(Where<NODE_> &root, void* key, CompareFunction CMP)
|
||||||
|
{
|
||||||
|
if (!root) return 0;
|
||||||
|
SetLock.Acquire();
|
||||||
|
NODE_ x;
|
||||||
|
int direction = seek(root, x, key, CMP);
|
||||||
|
splay(root, x);
|
||||||
|
SetLock.Release();
|
||||||
|
return direction?0:x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void Attach(Where<NODE_> &root, NODE_ x, CompareFunction CMP, void* key)
|
||||||
|
{
|
||||||
|
SetLock.Acquire();
|
||||||
|
if (!root)
|
||||||
|
{
|
||||||
|
root = x;
|
||||||
|
x->P = x->L = x->R = x->E = 0;
|
||||||
|
SetLock.Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NODE_ f;
|
||||||
|
int diff = seek(root, f, key, CMP);
|
||||||
|
if (!diff)
|
||||||
|
{
|
||||||
|
if (x->L = f->L) x->L->P = x;
|
||||||
|
if (x->R = f->R) x->R->P = x;
|
||||||
|
NODE_ p = f->P;
|
||||||
|
if (x->P = p) { if (p->L == f) p->L = x; else p->R = x; }
|
||||||
|
else root = x;
|
||||||
|
(x->E = f)->P = x;
|
||||||
|
f->L = f->R = 0;
|
||||||
|
splay(root, x);
|
||||||
|
SetLock.Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (diff < 0) f->L = x; else f->R = x;
|
||||||
|
x->P = f;
|
||||||
|
x->L = x->R = x->E = 0;
|
||||||
|
splay(root, x);
|
||||||
|
SetLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void Detach(Where<NODE_> &root, NODE_ x)
|
||||||
|
{
|
||||||
|
SetLock.Acquire();
|
||||||
|
NODE_ e = x->E, p = x->P;
|
||||||
|
if (p && p->E == x) { if (p->E = e) e->P = p; }
|
||||||
|
else if (e)
|
||||||
|
{
|
||||||
|
if (e->L = x->L) e->L->P = e;
|
||||||
|
if (e->R = x->R) e->R->P = e;
|
||||||
|
if (e->P = p) { if (p->L == x) p->L = e; else p->R = e; }
|
||||||
|
else root = e;
|
||||||
|
}
|
||||||
|
else if (!x->L)
|
||||||
|
{
|
||||||
|
if (p) { if ( p->L == x) p->L = x->R; else p->R = x->R; }
|
||||||
|
else root = x->R;
|
||||||
|
if (x->R) x->R->P = p;
|
||||||
|
}
|
||||||
|
else if (!x->R)
|
||||||
|
{
|
||||||
|
if (p) { if ( p->L == x) p->L = x->L; else p->R = x->L; }
|
||||||
|
else root = x->L;
|
||||||
|
if (x->L) x->L->P = p;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
e = x->L;
|
||||||
|
if (e->R)
|
||||||
|
{
|
||||||
|
do { e = e->R; } while (e->R);
|
||||||
|
if (e->P->R = e->L) e->L->P = e->P;
|
||||||
|
(e->L = x->L)->P = e;
|
||||||
|
}
|
||||||
|
(e->R = x->R)->P = e;
|
||||||
|
if (e->P = x->P) { if (e->P->L == x) e->P->L = e; else e->P->R = e; }
|
||||||
|
else root = e;
|
||||||
|
}
|
||||||
|
SetLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Storage Functions for our memory-mapped file-based persistent volumes
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void* StorageAllocate(AIRFS_ Airfs, int64_t RequestedSize)
|
||||||
|
{
|
||||||
|
if (!RequestedSize) return 0;
|
||||||
|
if (RequestedSize + sizeof int32_t > MAXIMUM_ALLOCSIZE) return 0;
|
||||||
|
|
||||||
|
StorageLock.Acquire();
|
||||||
|
int32_t RoundedSize = (int32_t) ROUND_UP(RequestedSize, MINIMUM_ALLOCSIZE - sizeof int32_t);
|
||||||
|
int32_t SplitableSize = RoundedSize + MINIMUM_ALLOCSIZE;
|
||||||
|
|
||||||
|
// See if we have a freed node of the size we requested.
|
||||||
|
NODE_ NewItem = Near(Airfs->Available, &RoundedSize, SizeCmp, GE);
|
||||||
|
if (NewItem)
|
||||||
|
{
|
||||||
|
int32_t FoundSize = ((int32_t*)NewItem)[-1];
|
||||||
|
if (FoundSize < SplitableSize)
|
||||||
|
{
|
||||||
|
Detach(Airfs->Available, NewItem);
|
||||||
|
Airfs->FreeSize -= FoundSize;
|
||||||
|
StorageLock.Release();
|
||||||
|
return NewItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, see if we can downsize a larger freed element.
|
||||||
|
NewItem = Near(Airfs->Available, &SplitableSize, SizeCmp, GE);
|
||||||
|
if (NewItem)
|
||||||
|
{
|
||||||
|
int32_t FoundSize = ((int32_t*)NewItem)[-1];
|
||||||
|
Detach(Airfs->Available, NewItem);
|
||||||
|
Airfs->FreeSize -= FoundSize;
|
||||||
|
char* Addr = (char*)NewItem + RoundedSize + sizeof int32_t;
|
||||||
|
NODE_ Remainder = (NODE_) Addr;
|
||||||
|
int32_t RemainderSize = FoundSize - (RoundedSize + sizeof int32_t);
|
||||||
|
((int32_t*)Remainder)[-1] = RemainderSize;
|
||||||
|
Attach(Airfs->Available, Remainder, SizeCmp, &RemainderSize);
|
||||||
|
Airfs->FreeSize += RemainderSize;
|
||||||
|
((int32_t*)NewItem)[-1] = RoundedSize;
|
||||||
|
StorageLock.Release();
|
||||||
|
return NewItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, give up.
|
||||||
|
StorageLock.Release();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void* StorageReallocate(AIRFS_ Airfs, void* OldAlloc, int64_t RequestedSize)
|
||||||
|
{
|
||||||
|
if (!OldAlloc)
|
||||||
|
{
|
||||||
|
return StorageAllocate(Airfs, RequestedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RequestedSize)
|
||||||
|
{
|
||||||
|
StorageFree(Airfs, OldAlloc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t OldSize = ((int32_t*)OldAlloc)[-1];
|
||||||
|
void* NewAlloc = StorageAllocate(Airfs, RequestedSize);
|
||||||
|
if (!NewAlloc) return 0;
|
||||||
|
memcpy(NewAlloc, OldAlloc, min(RequestedSize, OldSize));
|
||||||
|
StorageFree(Airfs, OldAlloc);
|
||||||
|
return NewAlloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StorageFree(AIRFS_ Airfs, void* r)
|
||||||
|
{
|
||||||
|
if (!r) return;
|
||||||
|
StorageLock.Acquire();
|
||||||
|
NODE_ release = (NODE_) r;
|
||||||
|
int32_t Size = ((int32_t*)r)[-1];
|
||||||
|
Attach(Airfs->Available, release, SizeCmp, &Size);
|
||||||
|
Airfs->FreeSize += Size;
|
||||||
|
StorageLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StorageAccessFile(StorageFileAccessType Type, NODE_ Node, int64_t AccessOffset, int64_t NumBytes, char* MemoryAddress)
|
||||||
|
{
|
||||||
|
StorageLock.Acquire();
|
||||||
|
|
||||||
|
NODE_ Block = Near(Node->FileBlocks, &AccessOffset, BlockCmp, LE);
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int32_t BlockSize = ((int32_t*)Block)[-1];
|
||||||
|
int64_t BlockOffset = Block->FileOffset;
|
||||||
|
int64_t BlockIndex = AccessOffset - BlockOffset + FILEBLOCK_OVERHEAD;
|
||||||
|
int64_t BlockNum = min(BlockSize-BlockIndex, NumBytes);
|
||||||
|
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case ZERO : { memset((char*)Block + BlockIndex, 0, BlockNum); break; }
|
||||||
|
case READ : { memcpy(MemoryAddress, (char*)Block + BlockIndex, BlockNum); break; }
|
||||||
|
case WRITE : { memcpy((char*)Block + BlockIndex, MemoryAddress, BlockNum); break; }
|
||||||
|
}
|
||||||
|
NumBytes -= BlockNum;
|
||||||
|
if (!NumBytes) break;
|
||||||
|
MemoryAddress += BlockNum;
|
||||||
|
AccessOffset += BlockNum;
|
||||||
|
Block = Next(Block);
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NTSTATUS StorageSetFileCapacity(AIRFS_ Airfs, NODE_ Node, int64_t minimumRequiredCapacity)
|
||||||
|
{
|
||||||
|
StorageLock.Acquire();
|
||||||
|
|
||||||
|
int64_t TargetCapacity = ROUND_UP(minimumRequiredCapacity, ALLOCATION_UNIT);
|
||||||
|
NODE_ Block = Last(Node->FileBlocks);
|
||||||
|
int32_t BlockSize = Block ? ((int32_t*)Block)[-1] : 0;
|
||||||
|
int64_t CurrentCapacity = Block ? Block->FileOffset + BlockSize - FILEBLOCK_OVERHEAD: 0;
|
||||||
|
int64_t Add = TargetCapacity - CurrentCapacity;
|
||||||
|
|
||||||
|
while (Add > 0)
|
||||||
|
{
|
||||||
|
// Add a block if we can, preferably as large or larger than we need.
|
||||||
|
Add += FILEBLOCK_OVERHEAD;
|
||||||
|
Block = Near(Airfs->Available, &Add, SizeCmp, GE);
|
||||||
|
if (!Block) Block = Near(Airfs->Available, &Add, SizeCmp, LT);
|
||||||
|
Add -= FILEBLOCK_OVERHEAD;
|
||||||
|
if (Block)
|
||||||
|
{
|
||||||
|
Detach(Airfs->Available, Block);
|
||||||
|
BlockSize = ((int32_t*)Block)[-1];
|
||||||
|
Airfs->FreeSize -= BlockSize;
|
||||||
|
Block->FileOffset = CurrentCapacity;
|
||||||
|
Attach(Node->FileBlocks, Block, BlockCmp, &CurrentCapacity);
|
||||||
|
CurrentCapacity += BlockSize - FILEBLOCK_OVERHEAD;
|
||||||
|
Add -= BlockSize - FILEBLOCK_OVERHEAD;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageLock.Release();
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw away any trailing blocks that are no longer needed.
|
||||||
|
while (Add < 0)
|
||||||
|
{
|
||||||
|
Block = Last(Node->FileBlocks);
|
||||||
|
BlockSize = ((int32_t*)Block)[-1];
|
||||||
|
if (BlockSize - FILEBLOCK_OVERHEAD > -Add) break;
|
||||||
|
Add += BlockSize - FILEBLOCK_OVERHEAD;
|
||||||
|
Detach(Node->FileBlocks, Block);
|
||||||
|
Attach(Airfs->Available, Block, SizeCmp, &BlockSize);
|
||||||
|
Airfs->FreeSize += BlockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Possibly downsize the last block.
|
||||||
|
if (Add < 0)
|
||||||
|
{
|
||||||
|
Block = Last(Node->FileBlocks);
|
||||||
|
int32_t OldBlockSize = ((int32_t*)Block)[-1];
|
||||||
|
int32_t NewBlockSize = OldBlockSize - (int32_t) ROUND_DOWN(-Add, MINIMUM_ALLOCSIZE);
|
||||||
|
if (NewBlockSize < MINIMUM_ALLOCSIZE) NewBlockSize = MINIMUM_ALLOCSIZE;
|
||||||
|
int32_t RemainderBlockSize = OldBlockSize - NewBlockSize - sizeof int32_t;
|
||||||
|
if (RemainderBlockSize >= MINIMUM_ALLOCSIZE) // i.e. if not too near the end
|
||||||
|
{
|
||||||
|
char* Addr = (char*)Block + NewBlockSize + sizeof int32_t;
|
||||||
|
NODE_ Remainder = (NODE_) Addr;
|
||||||
|
((int32_t*)Remainder)[-1] = RemainderBlockSize;
|
||||||
|
Attach(Airfs->Available, Remainder, SizeCmp, &RemainderBlockSize);
|
||||||
|
Airfs->FreeSize += RemainderBlockSize;
|
||||||
|
((int32_t*)Block)[-1] = NewBlockSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageLock.Release();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NTSTATUS StorageStartup(AIRFS_ &Airfs, WCHAR* MapName, WCHAR* VolumeName, int64_t VolumeLength)
|
||||||
|
{
|
||||||
|
HANDLE MapFileHandle = INVALID_HANDLE_VALUE;
|
||||||
|
Airfs = 0;
|
||||||
|
|
||||||
|
// Open.
|
||||||
|
if (*VolumeName)
|
||||||
|
{
|
||||||
|
MapFileHandle = CreateFileW(VolumeName, GENERIC_READ|GENERIC_WRITE|GENERIC_EXECUTE, 0, NULL, OPEN_ALWAYS, NULL, NULL);
|
||||||
|
if (MapFileHandle == INVALID_HANDLE_VALUE) return GetLastErrorAsStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map.
|
||||||
|
HANDLE MapHandle = CreateFileMappingW(MapFileHandle, NULL, PAGE_EXECUTE_READWRITE, VolumeLength>>32, VolumeLength & 0xFFFFFFFF, MapName);
|
||||||
|
if (!MapHandle) return GetLastErrorAsStatus();
|
||||||
|
|
||||||
|
// Point.
|
||||||
|
char* MappedAddress = (char*) MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, VolumeLength);
|
||||||
|
if (!MappedAddress) return GetLastErrorAsStatus();
|
||||||
|
|
||||||
|
// Keep.
|
||||||
|
Airfs = (AIRFS_) MappedAddress;
|
||||||
|
Airfs->MapFileHandle = MapFileHandle;
|
||||||
|
Airfs->MapHandle = MapHandle;
|
||||||
|
wcscpy_s(Airfs->VolumeName, 256, VolumeName); // Use "" for a memory-only page file.
|
||||||
|
wcscpy_s(Airfs->MapName, 256, MapName);
|
||||||
|
Airfs->VolumeLength = VolumeLength;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
NTSTATUS StorageShutdown(AIRFS_ Airfs)
|
||||||
|
{
|
||||||
|
BOOL Ok;
|
||||||
|
NTSTATUS Result = 0;
|
||||||
|
HANDLE M = Airfs->MapHandle;
|
||||||
|
HANDLE F = Airfs->MapFileHandle;
|
||||||
|
|
||||||
|
if (Airfs)
|
||||||
|
{
|
||||||
|
Ok = FlushViewOfFile(Airfs, 0); if (!Ok && !Result) Result = GetLastErrorAsStatus();
|
||||||
|
if (F != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
Ok = FlushFileBuffers(F); if (!Ok && !Result) Result = GetLastErrorAsStatus();
|
||||||
|
}
|
||||||
|
Ok = UnmapViewOfFile(Airfs); if (!Ok && !Result) Result = GetLastErrorAsStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (M)
|
||||||
|
{
|
||||||
|
Ok = CloseHandle(M); if (!Ok && !Result) Result = GetLastErrorAsStatus();
|
||||||
|
}
|
||||||
|
if (F != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
Ok = CloseHandle(F); if (!Ok && !Result) Result = GetLastErrorAsStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
Loading…
x
Reference in New Issue
Block a user