mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 16:33:02 -05:00
2423 lines
71 KiB
C++
2423 lines
71 KiB
C++
/**
|
|
* @file memfs.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.
|
|
*/
|
|
|
|
#undef _DEBUG
|
|
#include "memfs.h"
|
|
#include <sddl.h>
|
|
#include <VersionHelpers.h>
|
|
#include <cassert>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
|
|
/* SLOWIO */
|
|
#include <thread>
|
|
|
|
#define MEMFS_MAX_PATH 512
|
|
FSP_FSCTL_STATIC_ASSERT(MEMFS_MAX_PATH > MAX_PATH,
|
|
"MEMFS_MAX_PATH must be greater than MAX_PATH.");
|
|
|
|
/*
|
|
* Define the MEMFS_NAME_NORMALIZATION macro to include name normalization support.
|
|
*/
|
|
#define MEMFS_NAME_NORMALIZATION
|
|
|
|
/*
|
|
* Define the MEMFS_REPARSE_POINTS macro to include reparse points support.
|
|
*/
|
|
#define MEMFS_REPARSE_POINTS
|
|
|
|
/*
|
|
* Define the MEMFS_NAMED_STREAMS macro to include named streams support.
|
|
*/
|
|
#define MEMFS_NAMED_STREAMS
|
|
|
|
/*
|
|
* Define the MEMFS_DIRINFO_BY_NAME macro to include GetDirInfoByName.
|
|
*/
|
|
#define MEMFS_DIRINFO_BY_NAME
|
|
|
|
/*
|
|
* Define the MEMFS_SLOWIO macro to include delayed I/O response support.
|
|
*/
|
|
#define MEMFS_SLOWIO
|
|
|
|
/*
|
|
* Define the MEMFS_CONTROL macro to include DeviceControl support.
|
|
*/
|
|
#define MEMFS_CONTROL
|
|
|
|
/*
|
|
* Define the MEMFS_EA macro to include extended attributes support.
|
|
*/
|
|
#define MEMFS_EA
|
|
|
|
/*
|
|
* Define the DEBUG_BUFFER_CHECK macro on Windows 8 or above. This includes
|
|
* a check for the Write buffer to ensure that it is read-only.
|
|
*
|
|
* Since ProcessBuffer support in the FSD, this is no longer a guarantee.
|
|
*/
|
|
#if !defined(NDEBUG)
|
|
//#define DEBUG_BUFFER_CHECK
|
|
#endif
|
|
|
|
#define MEMFS_SECTOR_SIZE 512
|
|
#define MEMFS_SECTORS_PER_ALLOCATION_UNIT 1
|
|
|
|
/*
|
|
* Large Heap Support
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
DWORD Options;
|
|
SIZE_T InitialSize;
|
|
SIZE_T MaximumSize;
|
|
SIZE_T Alignment;
|
|
} LARGE_HEAP_INITIALIZE_PARAMS;
|
|
static INIT_ONCE LargeHeapInitOnce = INIT_ONCE_STATIC_INIT;
|
|
static HANDLE LargeHeap;
|
|
static SIZE_T LargeHeapAlignment;
|
|
static BOOL WINAPI LargeHeapInitOnceF(
|
|
PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context)
|
|
{
|
|
LARGE_HEAP_INITIALIZE_PARAMS *Params = (LARGE_HEAP_INITIALIZE_PARAMS *)Parameter;
|
|
LargeHeap = HeapCreate(Params->Options, Params->InitialSize, Params->MaximumSize);
|
|
LargeHeapAlignment = 0 != Params->Alignment ?
|
|
FSP_FSCTL_ALIGN_UP(Params->Alignment, 4096) :
|
|
16 * 4096;
|
|
return TRUE;
|
|
}
|
|
static inline
|
|
BOOLEAN LargeHeapInitialize(
|
|
DWORD Options,
|
|
SIZE_T InitialSize,
|
|
SIZE_T MaximumSize,
|
|
SIZE_T Alignment)
|
|
{
|
|
LARGE_HEAP_INITIALIZE_PARAMS Params;
|
|
Params.Options = Options;
|
|
Params.InitialSize = InitialSize;
|
|
Params.MaximumSize = MaximumSize;
|
|
Params.Alignment = Alignment;
|
|
InitOnceExecuteOnce(&LargeHeapInitOnce, LargeHeapInitOnceF, &Params, 0);
|
|
return 0 != LargeHeap;
|
|
}
|
|
static inline
|
|
PVOID LargeHeapAlloc(SIZE_T Size)
|
|
{
|
|
return HeapAlloc(LargeHeap, 0, FSP_FSCTL_ALIGN_UP(Size, LargeHeapAlignment));
|
|
}
|
|
static inline
|
|
PVOID LargeHeapRealloc(PVOID Pointer, SIZE_T Size)
|
|
{
|
|
if (0 != Pointer)
|
|
{
|
|
if (0 != Size)
|
|
return HeapReAlloc(LargeHeap, 0, Pointer, FSP_FSCTL_ALIGN_UP(Size, LargeHeapAlignment));
|
|
else
|
|
return HeapFree(LargeHeap, 0, Pointer), 0;
|
|
}
|
|
else
|
|
{
|
|
if (0 != Size)
|
|
return HeapAlloc(LargeHeap, 0, FSP_FSCTL_ALIGN_UP(Size, LargeHeapAlignment));
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
static inline
|
|
VOID LargeHeapFree(PVOID Pointer)
|
|
{
|
|
if (0 != Pointer)
|
|
HeapFree(LargeHeap, 0, Pointer);
|
|
}
|
|
|
|
/*
|
|
* MEMFS
|
|
*/
|
|
|
|
static inline
|
|
UINT64 MemfsGetSystemTime(VOID)
|
|
{
|
|
FILETIME FileTime;
|
|
GetSystemTimeAsFileTime(&FileTime);
|
|
return ((PLARGE_INTEGER)&FileTime)->QuadPart;
|
|
}
|
|
|
|
static inline
|
|
int MemfsFileNameCompare(PWSTR a, int alen, PWSTR b, int blen, BOOLEAN CaseInsensitive)
|
|
{
|
|
PWSTR p, endp, partp, q, endq, partq;
|
|
WCHAR c, d;
|
|
int plen, qlen, len, res;
|
|
|
|
if (-1 == alen)
|
|
alen = lstrlenW(a);
|
|
if (-1 == blen)
|
|
blen = lstrlenW(b);
|
|
|
|
for (p = a, endp = p + alen, q = b, endq = q + blen; endp > p && endq > q;)
|
|
{
|
|
c = d = 0;
|
|
for (; endp > p && (L':' == *p || L'\\' == *p); p++)
|
|
c = *p;
|
|
for (; endq > q && (L':' == *q || L'\\' == *q); q++)
|
|
d = *q;
|
|
|
|
if (L':' == c)
|
|
c = 1;
|
|
else if (L'\\' == c)
|
|
c = 2;
|
|
if (L':' == d)
|
|
d = 1;
|
|
else if (L'\\' == d)
|
|
d = 2;
|
|
|
|
res = c - d;
|
|
if (0 != res)
|
|
return res;
|
|
|
|
for (partp = p; endp > p && L':' != *p && L'\\' != *p; p++)
|
|
;
|
|
for (partq = q; endq > q && L':' != *q && L'\\' != *q; q++)
|
|
;
|
|
|
|
plen = (int)(p - partp);
|
|
qlen = (int)(q - partq);
|
|
|
|
len = plen < qlen ? plen : qlen;
|
|
|
|
if (CaseInsensitive)
|
|
{
|
|
res = CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, partp, plen, partq, qlen);
|
|
if (0 != res)
|
|
res -= 2;
|
|
else
|
|
res = _wcsnicmp(partp, partq, len);
|
|
}
|
|
else
|
|
res = wcsncmp(partp, partq, len);
|
|
|
|
if (0 == res)
|
|
res = plen - qlen;
|
|
|
|
if (0 != res)
|
|
return res;
|
|
}
|
|
|
|
return -(endp <= p) + (endq <= q);
|
|
}
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNameHasPrefix(PWSTR a, PWSTR b, BOOLEAN CaseInsensitive)
|
|
{
|
|
int alen = (int)wcslen(a);
|
|
int blen = (int)wcslen(b);
|
|
|
|
return alen >= blen && 0 == MemfsFileNameCompare(a, blen, b, blen, CaseInsensitive) &&
|
|
(alen == blen || (1 == blen && L'\\' == b[0]) ||
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
(L'\\' == a[blen] || L':' == a[blen]));
|
|
#else
|
|
(L'\\' == a[blen]));
|
|
#endif
|
|
}
|
|
|
|
#if defined(MEMFS_EA)
|
|
static inline
|
|
int MemfsEaNameCompare(PSTR a, PSTR b)
|
|
{
|
|
/* EA names are always case-insensitive in MEMFS (to be inline with NTFS) */
|
|
|
|
int res;
|
|
|
|
res = CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, a, -1, b, -1);
|
|
if (0 != res)
|
|
res -= 2;
|
|
else
|
|
res = _stricmp(a, b);
|
|
|
|
return res;
|
|
}
|
|
|
|
struct MEMFS_FILE_NODE_EA_LESS
|
|
{
|
|
MEMFS_FILE_NODE_EA_LESS()
|
|
{
|
|
}
|
|
bool operator()(PSTR a, PSTR b) const
|
|
{
|
|
return 0 > MemfsEaNameCompare(a, b);
|
|
}
|
|
};
|
|
typedef std::map<PSTR, FILE_FULL_EA_INFORMATION *, MEMFS_FILE_NODE_EA_LESS> MEMFS_FILE_NODE_EA_MAP;
|
|
#endif
|
|
|
|
typedef struct _MEMFS_FILE_NODE
|
|
{
|
|
WCHAR FileName[MEMFS_MAX_PATH];
|
|
FSP_FSCTL_FILE_INFO FileInfo;
|
|
SIZE_T FileSecuritySize;
|
|
PVOID FileSecurity;
|
|
PVOID FileData;
|
|
#if defined(MEMFS_REPARSE_POINTS)
|
|
SIZE_T ReparseDataSize;
|
|
PVOID ReparseData;
|
|
#endif
|
|
volatile LONG RefCount;
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
struct _MEMFS_FILE_NODE *MainFileNode;
|
|
#endif
|
|
#if defined(MEMFS_EA)
|
|
MEMFS_FILE_NODE_EA_MAP *EaMap;
|
|
#endif
|
|
} MEMFS_FILE_NODE;
|
|
|
|
struct MEMFS_FILE_NODE_LESS
|
|
{
|
|
MEMFS_FILE_NODE_LESS(BOOLEAN CaseInsensitive) : CaseInsensitive(CaseInsensitive)
|
|
{
|
|
}
|
|
bool operator()(PWSTR a, PWSTR b) const
|
|
{
|
|
return 0 > MemfsFileNameCompare(a, -1, b, -1, CaseInsensitive);
|
|
}
|
|
BOOLEAN CaseInsensitive;
|
|
};
|
|
typedef std::map<PWSTR, MEMFS_FILE_NODE *, MEMFS_FILE_NODE_LESS> MEMFS_FILE_NODE_MAP;
|
|
|
|
typedef struct _MEMFS
|
|
{
|
|
FSP_FILE_SYSTEM *FileSystem;
|
|
MEMFS_FILE_NODE_MAP *FileNodeMap;
|
|
ULONG MaxFileNodes;
|
|
ULONG MaxFileSize;
|
|
#ifdef MEMFS_SLOWIO
|
|
ULONG SlowioMaxDelay;
|
|
ULONG SlowioPercentDelay;
|
|
ULONG SlowioRarefyDelay;
|
|
volatile LONG SlowioThreadsRunning;
|
|
#endif
|
|
UINT16 VolumeLabelLength;
|
|
WCHAR VolumeLabel[32];
|
|
} MEMFS;
|
|
|
|
static inline
|
|
NTSTATUS MemfsFileNodeCreate(PWSTR FileName, MEMFS_FILE_NODE **PFileNode)
|
|
{
|
|
static UINT64 IndexNumber = 1;
|
|
MEMFS_FILE_NODE *FileNode;
|
|
|
|
*PFileNode = 0;
|
|
|
|
FileNode = (MEMFS_FILE_NODE *)malloc(sizeof *FileNode);
|
|
if (0 == FileNode)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
memset(FileNode, 0, sizeof *FileNode);
|
|
wcscpy_s(FileNode->FileName, sizeof FileNode->FileName / sizeof(WCHAR), FileName);
|
|
FileNode->FileInfo.CreationTime =
|
|
FileNode->FileInfo.LastAccessTime =
|
|
FileNode->FileInfo.LastWriteTime =
|
|
FileNode->FileInfo.ChangeTime = MemfsGetSystemTime();
|
|
FileNode->FileInfo.IndexNumber = IndexNumber++;
|
|
|
|
*PFileNode = FileNode;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeDelete(MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
#if defined(MEMFS_EA)
|
|
if (0 != FileNode->EaMap)
|
|
{
|
|
for (MEMFS_FILE_NODE_EA_MAP::iterator p = FileNode->EaMap->begin(), q = FileNode->EaMap->end();
|
|
p != q; ++p)
|
|
free(p->second);
|
|
delete FileNode->EaMap;
|
|
}
|
|
#endif
|
|
#if defined(MEMFS_REPARSE_POINTS)
|
|
free(FileNode->ReparseData);
|
|
#endif
|
|
LargeHeapFree(FileNode->FileData);
|
|
free(FileNode->FileSecurity);
|
|
free(FileNode);
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeReference(MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
InterlockedIncrement(&FileNode->RefCount);
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeDereference(MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
if (0 == InterlockedDecrement(&FileNode->RefCount))
|
|
MemfsFileNodeDelete(FileNode);
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeGetFileInfo(MEMFS_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 == FileNode->MainFileNode)
|
|
*FileInfo = FileNode->FileInfo;
|
|
else
|
|
{
|
|
*FileInfo = FileNode->MainFileNode->FileInfo;
|
|
FileInfo->FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
|
|
/* named streams cannot be directories */
|
|
FileInfo->AllocationSize = FileNode->FileInfo.AllocationSize;
|
|
FileInfo->FileSize = FileNode->FileInfo.FileSize;
|
|
}
|
|
#else
|
|
*FileInfo = FileNode->FileInfo;
|
|
#endif
|
|
}
|
|
|
|
#if defined(MEMFS_EA)
|
|
static inline
|
|
NTSTATUS MemfsFileNodeGetEaMap(MEMFS_FILE_NODE *FileNode, MEMFS_FILE_NODE_EA_MAP **PEaMap)
|
|
{
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
*PEaMap = FileNode->EaMap;
|
|
if (0 != *PEaMap)
|
|
return STATUS_SUCCESS;
|
|
|
|
try
|
|
{
|
|
*PEaMap = FileNode->EaMap = new MEMFS_FILE_NODE_EA_MAP(MEMFS_FILE_NODE_EA_LESS());
|
|
return STATUS_SUCCESS;
|
|
}
|
|
catch (...)
|
|
{
|
|
*PEaMap = 0;
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
static inline
|
|
NTSTATUS MemfsFileNodeSetEa(
|
|
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
|
|
PFILE_FULL_EA_INFORMATION Ea)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)Context;
|
|
MEMFS_FILE_NODE_EA_MAP *EaMap;
|
|
FILE_FULL_EA_INFORMATION *FileNodeEa = 0;
|
|
MEMFS_FILE_NODE_EA_MAP::iterator p;
|
|
NTSTATUS Result;
|
|
|
|
Result = MemfsFileNodeGetEaMap(FileNode, &EaMap);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
if (0 != Ea->EaValueLength)
|
|
{
|
|
FileNodeEa = (FILE_FULL_EA_INFORMATION *)malloc(
|
|
sizeof *FileNodeEa + Ea->EaNameLength + Ea->EaValueLength);
|
|
if (0 == FileNodeEa)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
memcpy(FileNodeEa, Ea, sizeof *FileNodeEa + Ea->EaNameLength + Ea->EaValueLength);
|
|
FileNodeEa->NextEntryOffset = 0;
|
|
}
|
|
|
|
p = EaMap->find(Ea->EaName);
|
|
if (p != EaMap->end())
|
|
{
|
|
free(p->second);
|
|
EaMap->erase(p);
|
|
}
|
|
|
|
if (0 != Ea->EaValueLength)
|
|
{
|
|
try
|
|
{
|
|
EaMap->insert(MEMFS_FILE_NODE_EA_MAP::value_type(FileNodeEa->EaName, FileNodeEa));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
catch (...)
|
|
{
|
|
free(FileNodeEa);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeNeedEa(MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (0 != FileNode->EaMap)
|
|
{
|
|
for (MEMFS_FILE_NODE_EA_MAP::iterator p = FileNode->EaMap->begin(), q = FileNode->EaMap->end();
|
|
p != q; ++p)
|
|
if (0 != (p->second->Flags & FILE_NEED_EA))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeEnumerateEa(MEMFS_FILE_NODE *FileNode,
|
|
BOOLEAN (*EnumFn)(PFILE_FULL_EA_INFORMATION Ea, PVOID), PVOID Context)
|
|
{
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (0 != FileNode->EaMap)
|
|
{
|
|
for (MEMFS_FILE_NODE_EA_MAP::iterator p = FileNode->EaMap->begin(), q = FileNode->EaMap->end();
|
|
p != q; ++p)
|
|
if (!EnumFn(p->second, Context))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static inline
|
|
VOID MemfsFileNodeMapDump(MEMFS_FILE_NODE_MAP *FileNodeMap)
|
|
{
|
|
for (MEMFS_FILE_NODE_MAP::iterator p = FileNodeMap->begin(), q = FileNodeMap->end(); p != q; ++p)
|
|
FspDebugLog("%c %04lx %6lu %S\n",
|
|
FILE_ATTRIBUTE_DIRECTORY & p->second->FileInfo.FileAttributes ? 'd' : 'f',
|
|
(ULONG)p->second->FileInfo.FileAttributes,
|
|
(ULONG)p->second->FileInfo.FileSize,
|
|
p->second->FileName);
|
|
}
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeMapIsCaseInsensitive(MEMFS_FILE_NODE_MAP *FileNodeMap)
|
|
{
|
|
return FileNodeMap->key_comp().CaseInsensitive;
|
|
}
|
|
|
|
static inline
|
|
NTSTATUS MemfsFileNodeMapCreate(BOOLEAN CaseInsensitive, MEMFS_FILE_NODE_MAP **PFileNodeMap)
|
|
{
|
|
*PFileNodeMap = 0;
|
|
try
|
|
{
|
|
*PFileNodeMap = new MEMFS_FILE_NODE_MAP(MEMFS_FILE_NODE_LESS(CaseInsensitive));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
catch (...)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeMapDelete(MEMFS_FILE_NODE_MAP *FileNodeMap)
|
|
{
|
|
for (MEMFS_FILE_NODE_MAP::iterator p = FileNodeMap->begin(), q = FileNodeMap->end(); p != q; ++p)
|
|
MemfsFileNodeDelete(p->second);
|
|
|
|
delete FileNodeMap;
|
|
}
|
|
|
|
static inline
|
|
SIZE_T MemfsFileNodeMapCount(MEMFS_FILE_NODE_MAP *FileNodeMap)
|
|
{
|
|
return FileNodeMap->size();
|
|
}
|
|
|
|
static inline
|
|
MEMFS_FILE_NODE *MemfsFileNodeMapGet(MEMFS_FILE_NODE_MAP *FileNodeMap, PWSTR FileName)
|
|
{
|
|
MEMFS_FILE_NODE_MAP::iterator iter = FileNodeMap->find(FileName);
|
|
if (iter == FileNodeMap->end())
|
|
return 0;
|
|
return iter->second;
|
|
}
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
static inline
|
|
MEMFS_FILE_NODE *MemfsFileNodeMapGetMain(MEMFS_FILE_NODE_MAP *FileNodeMap, PWSTR FileName0)
|
|
{
|
|
WCHAR FileName[MEMFS_MAX_PATH];
|
|
wcscpy_s(FileName, sizeof FileName / sizeof(WCHAR), FileName0);
|
|
PWSTR StreamName = wcschr(FileName, L':');
|
|
if (0 == StreamName)
|
|
return 0;
|
|
StreamName[0] = L'\0';
|
|
MEMFS_FILE_NODE_MAP::iterator iter = FileNodeMap->find(FileName);
|
|
if (iter == FileNodeMap->end())
|
|
return 0;
|
|
return iter->second;
|
|
}
|
|
#endif
|
|
|
|
static inline
|
|
MEMFS_FILE_NODE *MemfsFileNodeMapGetParent(MEMFS_FILE_NODE_MAP *FileNodeMap, PWSTR FileName0,
|
|
PNTSTATUS PResult)
|
|
{
|
|
WCHAR Root[2] = L"\\";
|
|
PWSTR Remain, Suffix;
|
|
WCHAR FileName[MEMFS_MAX_PATH];
|
|
wcscpy_s(FileName, sizeof FileName / sizeof(WCHAR), FileName0);
|
|
FspPathSuffix(FileName, &Remain, &Suffix, Root);
|
|
MEMFS_FILE_NODE_MAP::iterator iter = FileNodeMap->find(Remain);
|
|
FspPathCombine(FileName, Suffix);
|
|
if (iter == FileNodeMap->end())
|
|
{
|
|
*PResult = STATUS_OBJECT_PATH_NOT_FOUND;
|
|
return 0;
|
|
}
|
|
if (0 == (iter->second->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
*PResult = STATUS_NOT_A_DIRECTORY;
|
|
return 0;
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeMapTouchParent(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
NTSTATUS Result;
|
|
MEMFS_FILE_NODE *Parent;
|
|
if (L'\\' == FileNode->FileName[0] && L'\0' == FileNode->FileName[1])
|
|
return;
|
|
Parent = MemfsFileNodeMapGetParent(FileNodeMap, FileNode->FileName, &Result);
|
|
if (0 == Parent)
|
|
return;
|
|
Parent->FileInfo.LastAccessTime =
|
|
Parent->FileInfo.LastWriteTime =
|
|
Parent->FileInfo.ChangeTime = MemfsGetSystemTime();
|
|
}
|
|
|
|
static inline
|
|
NTSTATUS MemfsFileNodeMapInsert(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode,
|
|
PBOOLEAN PInserted)
|
|
{
|
|
*PInserted = 0;
|
|
try
|
|
{
|
|
*PInserted = FileNodeMap->insert(MEMFS_FILE_NODE_MAP::value_type(FileNode->FileName, FileNode)).second;
|
|
if (*PInserted)
|
|
{
|
|
MemfsFileNodeReference(FileNode);
|
|
MemfsFileNodeMapTouchParent(FileNodeMap, FileNode);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
catch (...)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeMapRemove(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
if (FileNodeMap->erase(FileNode->FileName))
|
|
{
|
|
MemfsFileNodeMapTouchParent(FileNodeMap, FileNode);
|
|
MemfsFileNodeDereference(FileNode);
|
|
}
|
|
}
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeMapHasChild(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode)
|
|
{
|
|
BOOLEAN Result = FALSE;
|
|
WCHAR Root[2] = L"\\";
|
|
PWSTR Remain, Suffix;
|
|
MEMFS_FILE_NODE_MAP::iterator iter = FileNodeMap->upper_bound(FileNode->FileName);
|
|
for (; FileNodeMap->end() != iter; ++iter)
|
|
{
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != wcschr(iter->second->FileName, L':'))
|
|
continue;
|
|
#endif
|
|
FspPathSuffix(iter->second->FileName, &Remain, &Suffix, Root);
|
|
Result = 0 == MemfsFileNameCompare(Remain, -1, FileNode->FileName, -1,
|
|
MemfsFileNodeMapIsCaseInsensitive(FileNodeMap));
|
|
FspPathCombine(iter->second->FileName, Suffix);
|
|
break;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeMapEnumerateChildren(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode,
|
|
PWSTR PrevFileName0, BOOLEAN (*EnumFn)(MEMFS_FILE_NODE *, PVOID), PVOID Context)
|
|
{
|
|
WCHAR Root[2] = L"\\";
|
|
PWSTR Remain, Suffix;
|
|
MEMFS_FILE_NODE_MAP::iterator iter;
|
|
BOOLEAN IsDirectoryChild;
|
|
if (0 != PrevFileName0)
|
|
{
|
|
WCHAR PrevFileName[MEMFS_MAX_PATH + 256];
|
|
size_t Length0 = wcslen(FileNode->FileName);
|
|
size_t Length1 = 1 != Length0 || L'\\' != FileNode->FileName[0];
|
|
size_t Length2 = wcslen(PrevFileName0);
|
|
assert(MEMFS_MAX_PATH + 256 > Length0 + Length1 + Length2);
|
|
memcpy(PrevFileName, FileNode->FileName, Length0 * sizeof(WCHAR));
|
|
memcpy(PrevFileName + Length0, L"\\", Length1 * sizeof(WCHAR));
|
|
memcpy(PrevFileName + Length0 + Length1, PrevFileName0, Length2 * sizeof(WCHAR));
|
|
PrevFileName[Length0 + Length1 + Length2] = L'\0';
|
|
iter = FileNodeMap->upper_bound(PrevFileName);
|
|
}
|
|
else
|
|
iter = FileNodeMap->upper_bound(FileNode->FileName);
|
|
for (; FileNodeMap->end() != iter; ++iter)
|
|
{
|
|
if (!MemfsFileNameHasPrefix(iter->second->FileName, FileNode->FileName,
|
|
MemfsFileNodeMapIsCaseInsensitive(FileNodeMap)))
|
|
break;
|
|
FspPathSuffix(iter->second->FileName, &Remain, &Suffix, Root);
|
|
IsDirectoryChild = 0 == MemfsFileNameCompare(Remain, -1, FileNode->FileName, -1,
|
|
MemfsFileNodeMapIsCaseInsensitive(FileNodeMap));
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
IsDirectoryChild = IsDirectoryChild && 0 == wcschr(Suffix, L':');
|
|
#endif
|
|
FspPathCombine(iter->second->FileName, Suffix);
|
|
if (IsDirectoryChild)
|
|
{
|
|
if (!EnumFn(iter->second, Context))
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
static inline
|
|
BOOLEAN MemfsFileNodeMapEnumerateNamedStreams(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode,
|
|
BOOLEAN (*EnumFn)(MEMFS_FILE_NODE *, PVOID), PVOID Context)
|
|
{
|
|
MEMFS_FILE_NODE_MAP::iterator iter = FileNodeMap->upper_bound(FileNode->FileName);
|
|
for (; FileNodeMap->end() != iter; ++iter)
|
|
{
|
|
if (!MemfsFileNameHasPrefix(iter->second->FileName, FileNode->FileName,
|
|
MemfsFileNodeMapIsCaseInsensitive(FileNodeMap)))
|
|
break;
|
|
if (L':' != iter->second->FileName[wcslen(FileNode->FileName)])
|
|
break;
|
|
if (!EnumFn(iter->second, Context))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeMapEnumerateDescendants(MEMFS_FILE_NODE_MAP *FileNodeMap, MEMFS_FILE_NODE *FileNode,
|
|
BOOLEAN (*EnumFn)(MEMFS_FILE_NODE *, PVOID), PVOID Context)
|
|
{
|
|
WCHAR Root[2] = L"\\";
|
|
MEMFS_FILE_NODE_MAP::iterator iter = FileNodeMap->lower_bound(FileNode->FileName);
|
|
for (; FileNodeMap->end() != iter; ++iter)
|
|
{
|
|
if (!MemfsFileNameHasPrefix(iter->second->FileName, FileNode->FileName,
|
|
MemfsFileNodeMapIsCaseInsensitive(FileNodeMap)))
|
|
break;
|
|
if (!EnumFn(iter->second, Context))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct _MEMFS_FILE_NODE_MAP_ENUM_CONTEXT
|
|
{
|
|
BOOLEAN Reference;
|
|
MEMFS_FILE_NODE **FileNodes;
|
|
ULONG Capacity, Count;
|
|
} MEMFS_FILE_NODE_MAP_ENUM_CONTEXT;
|
|
|
|
static inline
|
|
BOOLEAN MemfsFileNodeMapEnumerateFn(MEMFS_FILE_NODE *FileNode, PVOID Context0)
|
|
{
|
|
MEMFS_FILE_NODE_MAP_ENUM_CONTEXT *Context = (MEMFS_FILE_NODE_MAP_ENUM_CONTEXT *)Context0;
|
|
|
|
if (Context->Capacity <= Context->Count)
|
|
{
|
|
ULONG Capacity = 0 != Context->Capacity ? Context->Capacity * 2 : 16;
|
|
PVOID P = realloc(Context->FileNodes, Capacity * sizeof Context->FileNodes[0]);
|
|
if (0 == P)
|
|
{
|
|
FspDebugLog(__FUNCTION__ ": cannot allocate memory; aborting\n");
|
|
abort();
|
|
}
|
|
|
|
Context->FileNodes = (MEMFS_FILE_NODE **)P;
|
|
Context->Capacity = Capacity;
|
|
}
|
|
|
|
Context->FileNodes[Context->Count++] = FileNode;
|
|
if (Context->Reference)
|
|
MemfsFileNodeReference(FileNode);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline
|
|
VOID MemfsFileNodeMapEnumerateFree(MEMFS_FILE_NODE_MAP_ENUM_CONTEXT *Context)
|
|
{
|
|
if (Context->Reference)
|
|
{
|
|
for (ULONG Index = 0; Context->Count > Index; Index++)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = Context->FileNodes[Index];
|
|
MemfsFileNodeDereference(FileNode);
|
|
}
|
|
}
|
|
free(Context->FileNodes);
|
|
}
|
|
|
|
#ifdef MEMFS_SLOWIO
|
|
/*
|
|
* SLOWIO
|
|
*
|
|
* This is included for two uses:
|
|
*
|
|
* 1) For testing winfsp, by allowing memfs to act more like a non-ram file system,
|
|
* with some IO taking many milliseconds, and some IO completion delayed.
|
|
*
|
|
* 2) As sample code for how to use winfsp's STATUS_PENDING capabilities.
|
|
*
|
|
*/
|
|
|
|
static inline UINT64 Hash(UINT64 x)
|
|
{
|
|
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull;
|
|
x = (x ^ (x >> 27)) * 0x94d049bb133111ebull;
|
|
x = x ^ (x >> 31);
|
|
return x;
|
|
}
|
|
|
|
static inline ULONG PseudoRandom(ULONG to)
|
|
{
|
|
/* John Oberschelp's PRNG */
|
|
static UINT64 spin = 0;
|
|
InterlockedIncrement(&spin);
|
|
return Hash(spin) % to;
|
|
}
|
|
|
|
static inline BOOLEAN SlowioReturnPending(FSP_FILE_SYSTEM *FileSystem)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
if (0 == Memfs->SlowioMaxDelay)
|
|
return FALSE;
|
|
return PseudoRandom(100) < Memfs->SlowioPercentDelay;
|
|
}
|
|
|
|
static inline VOID SlowioSnooze(FSP_FILE_SYSTEM *FileSystem)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
if (0 == Memfs->SlowioMaxDelay)
|
|
return;
|
|
ULONG millis = PseudoRandom(Memfs->SlowioMaxDelay + 1) >> PseudoRandom(Memfs->SlowioRarefyDelay + 1);
|
|
Sleep(millis);
|
|
}
|
|
|
|
void SlowioReadThread(
|
|
FSP_FILE_SYSTEM *FileSystem,
|
|
MEMFS_FILE_NODE *FileNode,
|
|
PVOID Buffer,
|
|
UINT64 Offset,
|
|
UINT64 EndOffset,
|
|
UINT64 RequestHint)
|
|
{
|
|
SlowioSnooze(FileSystem);
|
|
|
|
memcpy(Buffer, (PUINT8)FileNode->FileData + Offset, (size_t)(EndOffset - Offset));
|
|
UINT32 BytesTransferred = (ULONG)(EndOffset - Offset);
|
|
|
|
FSP_FSCTL_TRANSACT_RSP ResponseBuf;
|
|
memset(&ResponseBuf, 0, sizeof ResponseBuf);
|
|
ResponseBuf.Size = sizeof ResponseBuf;
|
|
ResponseBuf.Kind = FspFsctlTransactReadKind;
|
|
ResponseBuf.Hint = RequestHint; // IRP that is being completed
|
|
ResponseBuf.IoStatus.Status = STATUS_SUCCESS;
|
|
ResponseBuf.IoStatus.Information = BytesTransferred; // bytes read
|
|
FspFileSystemSendResponse(FileSystem, &ResponseBuf);
|
|
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
InterlockedDecrement(&Memfs->SlowioThreadsRunning);
|
|
}
|
|
|
|
void SlowioWriteThread(
|
|
FSP_FILE_SYSTEM *FileSystem,
|
|
MEMFS_FILE_NODE *FileNode,
|
|
PVOID Buffer,
|
|
UINT64 Offset,
|
|
UINT64 EndOffset,
|
|
UINT64 RequestHint)
|
|
{
|
|
SlowioSnooze(FileSystem);
|
|
|
|
memcpy((PUINT8)FileNode->FileData + Offset, Buffer, (size_t)(EndOffset - Offset));
|
|
UINT32 BytesTransferred = (ULONG)(EndOffset - Offset);
|
|
|
|
FSP_FSCTL_TRANSACT_RSP ResponseBuf;
|
|
memset(&ResponseBuf, 0, sizeof ResponseBuf);
|
|
ResponseBuf.Size = sizeof ResponseBuf;
|
|
ResponseBuf.Kind = FspFsctlTransactWriteKind;
|
|
ResponseBuf.Hint = RequestHint; // IRP that is being completed
|
|
ResponseBuf.IoStatus.Status = STATUS_SUCCESS;
|
|
ResponseBuf.IoStatus.Information = BytesTransferred; // bytes written
|
|
MemfsFileNodeGetFileInfo(FileNode, &ResponseBuf.Rsp.Write.FileInfo);
|
|
FspFileSystemSendResponse(FileSystem, &ResponseBuf);
|
|
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
InterlockedDecrement(&Memfs->SlowioThreadsRunning);
|
|
}
|
|
|
|
void SlowioReadDirectoryThread(
|
|
FSP_FILE_SYSTEM *FileSystem,
|
|
ULONG BytesTransferred,
|
|
UINT64 RequestHint)
|
|
{
|
|
SlowioSnooze(FileSystem);
|
|
|
|
FSP_FSCTL_TRANSACT_RSP ResponseBuf;
|
|
memset(&ResponseBuf, 0, sizeof ResponseBuf);
|
|
ResponseBuf.Size = sizeof ResponseBuf;
|
|
ResponseBuf.Kind = FspFsctlTransactQueryDirectoryKind;
|
|
ResponseBuf.Hint = RequestHint; // IRP that is being completed
|
|
ResponseBuf.IoStatus.Status = STATUS_SUCCESS;
|
|
ResponseBuf.IoStatus.Information = BytesTransferred; // bytes of directory info read
|
|
FspFileSystemSendResponse(FileSystem, &ResponseBuf);
|
|
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
InterlockedDecrement(&Memfs->SlowioThreadsRunning);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* FSP_FILE_SYSTEM_INTERFACE
|
|
*/
|
|
|
|
#if defined(MEMFS_REPARSE_POINTS)
|
|
static NTSTATUS GetReparsePointByName(
|
|
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
|
|
PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize);
|
|
#endif
|
|
|
|
static NTSTATUS SetFileSizeInternal(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, UINT64 NewSize, BOOLEAN SetAllocationSize);
|
|
|
|
static NTSTATUS GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem,
|
|
FSP_FSCTL_VOLUME_INFO *VolumeInfo)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
|
|
VolumeInfo->TotalSize = Memfs->MaxFileNodes * (UINT64)Memfs->MaxFileSize;
|
|
VolumeInfo->FreeSize = (Memfs->MaxFileNodes - MemfsFileNodeMapCount(Memfs->FileNodeMap)) *
|
|
(UINT64)Memfs->MaxFileSize;
|
|
VolumeInfo->VolumeLabelLength = Memfs->VolumeLabelLength;
|
|
memcpy(VolumeInfo->VolumeLabel, Memfs->VolumeLabel, Memfs->VolumeLabelLength);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetVolumeLabel(FSP_FILE_SYSTEM *FileSystem,
|
|
PWSTR VolumeLabel,
|
|
FSP_FSCTL_VOLUME_INFO *VolumeInfo)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
|
|
Memfs->VolumeLabelLength = (UINT16)(wcslen(VolumeLabel) * sizeof(WCHAR));
|
|
if (Memfs->VolumeLabelLength > sizeof Memfs->VolumeLabel)
|
|
Memfs->VolumeLabelLength = sizeof Memfs->VolumeLabel;
|
|
memcpy(Memfs->VolumeLabel, VolumeLabel, Memfs->VolumeLabelLength);
|
|
|
|
VolumeInfo->TotalSize = Memfs->MaxFileNodes * Memfs->MaxFileSize;
|
|
VolumeInfo->FreeSize =
|
|
(Memfs->MaxFileNodes - MemfsFileNodeMapCount(Memfs->FileNodeMap)) * Memfs->MaxFileSize;
|
|
VolumeInfo->VolumeLabelLength = Memfs->VolumeLabelLength;
|
|
memcpy(VolumeInfo->VolumeLabel, Memfs->VolumeLabel, Memfs->VolumeLabelLength);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM *FileSystem,
|
|
PWSTR FileName, PUINT32 PFileAttributes,
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode;
|
|
NTSTATUS Result;
|
|
|
|
FileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, FileName);
|
|
if (0 == FileNode)
|
|
{
|
|
Result = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
#if defined(MEMFS_REPARSE_POINTS)
|
|
if (FspFileSystemFindReparsePoint(FileSystem, GetReparsePointByName, 0,
|
|
FileName, PFileAttributes))
|
|
Result = STATUS_REPARSE;
|
|
else
|
|
#endif
|
|
MemfsFileNodeMapGetParent(Memfs->FileNodeMap, FileName, &Result);
|
|
|
|
return Result;
|
|
}
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
UINT32 FileAttributesMask = ~(UINT32)0;
|
|
if (0 != FileNode->MainFileNode)
|
|
{
|
|
FileAttributesMask = ~(UINT32)FILE_ATTRIBUTE_DIRECTORY;
|
|
FileNode = FileNode->MainFileNode;
|
|
}
|
|
|
|
if (0 != PFileAttributes)
|
|
*PFileAttributes = FileNode->FileInfo.FileAttributes & FileAttributesMask;
|
|
#else
|
|
if (0 != PFileAttributes)
|
|
*PFileAttributes = FileNode->FileInfo.FileAttributes;
|
|
#endif
|
|
|
|
if (0 != PSecurityDescriptorSize)
|
|
{
|
|
if (FileNode->FileSecuritySize > *PSecurityDescriptorSize)
|
|
{
|
|
*PSecurityDescriptorSize = FileNode->FileSecuritySize;
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
*PSecurityDescriptorSize = FileNode->FileSecuritySize;
|
|
if (0 != SecurityDescriptor)
|
|
memcpy(SecurityDescriptor, FileNode->FileSecurity, FileNode->FileSecuritySize);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem,
|
|
PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess,
|
|
UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize,
|
|
#if defined(MEMFS_EA)
|
|
PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
|
|
#endif
|
|
PVOID *PFileNode, FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
#if defined(MEMFS_NAME_NORMALIZATION)
|
|
WCHAR FileNameBuf[MEMFS_MAX_PATH];
|
|
#endif
|
|
MEMFS_FILE_NODE *FileNode;
|
|
MEMFS_FILE_NODE *ParentNode;
|
|
NTSTATUS Result;
|
|
BOOLEAN Inserted;
|
|
|
|
if (MEMFS_MAX_PATH <= wcslen(FileName))
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
|
|
if (CreateOptions & FILE_DIRECTORY_FILE)
|
|
AllocationSize = 0;
|
|
|
|
FileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, FileName);
|
|
if (0 != FileNode)
|
|
return STATUS_OBJECT_NAME_COLLISION;
|
|
|
|
ParentNode = MemfsFileNodeMapGetParent(Memfs->FileNodeMap, FileName, &Result);
|
|
if (0 == ParentNode)
|
|
return Result;
|
|
|
|
if (MemfsFileNodeMapCount(Memfs->FileNodeMap) >= Memfs->MaxFileNodes)
|
|
return STATUS_CANNOT_MAKE;
|
|
|
|
if (AllocationSize > Memfs->MaxFileSize)
|
|
return STATUS_DISK_FULL;
|
|
|
|
#if defined(MEMFS_NAME_NORMALIZATION)
|
|
if (MemfsFileNodeMapIsCaseInsensitive(Memfs->FileNodeMap))
|
|
{
|
|
WCHAR Root[2] = L"\\";
|
|
PWSTR Remain, Suffix;
|
|
size_t RemainLength, BSlashLength, SuffixLength;
|
|
|
|
FspPathSuffix(FileName, &Remain, &Suffix, Root);
|
|
assert(0 == MemfsFileNameCompare(Remain, -1, ParentNode->FileName, -1, TRUE));
|
|
FspPathCombine(FileName, Suffix);
|
|
|
|
RemainLength = wcslen(ParentNode->FileName);
|
|
BSlashLength = 1 < RemainLength;
|
|
SuffixLength = wcslen(Suffix);
|
|
if (MEMFS_MAX_PATH <= RemainLength + BSlashLength + SuffixLength)
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
|
|
memcpy(FileNameBuf, ParentNode->FileName, RemainLength * sizeof(WCHAR));
|
|
memcpy(FileNameBuf + RemainLength, L"\\", BSlashLength * sizeof(WCHAR));
|
|
memcpy(FileNameBuf + RemainLength + BSlashLength, Suffix, (SuffixLength + 1) * sizeof(WCHAR));
|
|
|
|
FileName = FileNameBuf;
|
|
}
|
|
#endif
|
|
|
|
Result = MemfsFileNodeCreate(FileName, &FileNode);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
FileNode->MainFileNode = MemfsFileNodeMapGetMain(Memfs->FileNodeMap, FileName);
|
|
#endif
|
|
|
|
FileNode->FileInfo.FileAttributes = (FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
|
|
FileAttributes : FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
|
|
|
|
if (0 != SecurityDescriptor)
|
|
{
|
|
FileNode->FileSecuritySize = GetSecurityDescriptorLength(SecurityDescriptor);
|
|
FileNode->FileSecurity = (PSECURITY_DESCRIPTOR)malloc(FileNode->FileSecuritySize);
|
|
if (0 == FileNode->FileSecurity)
|
|
{
|
|
MemfsFileNodeDelete(FileNode);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
memcpy(FileNode->FileSecurity, SecurityDescriptor, FileNode->FileSecuritySize);
|
|
}
|
|
|
|
#if defined(MEMFS_EA)
|
|
if (0 != Ea)
|
|
{
|
|
Result = FspFileSystemEnumerateEa(FileSystem, MemfsFileNodeSetEa, FileNode, Ea, EaLength);
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
MemfsFileNodeDelete(FileNode);
|
|
return Result;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FileNode->FileInfo.AllocationSize = AllocationSize;
|
|
if (0 != FileNode->FileInfo.AllocationSize)
|
|
{
|
|
FileNode->FileData = LargeHeapAlloc((size_t)FileNode->FileInfo.AllocationSize);
|
|
if (0 == FileNode->FileData)
|
|
{
|
|
MemfsFileNodeDelete(FileNode);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
Result = MemfsFileNodeMapInsert(Memfs->FileNodeMap, FileNode, &Inserted);
|
|
if (!NT_SUCCESS(Result) || !Inserted)
|
|
{
|
|
MemfsFileNodeDelete(FileNode);
|
|
if (NT_SUCCESS(Result))
|
|
Result = STATUS_OBJECT_NAME_COLLISION; /* should not happen! */
|
|
return Result;
|
|
}
|
|
|
|
MemfsFileNodeReference(FileNode);
|
|
*PFileNode = FileNode;
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
#if defined(MEMFS_NAME_NORMALIZATION)
|
|
if (MemfsFileNodeMapIsCaseInsensitive(Memfs->FileNodeMap))
|
|
{
|
|
FSP_FSCTL_OPEN_FILE_INFO *OpenFileInfo = FspFileSystemGetOpenFileInfo(FileInfo);
|
|
|
|
wcscpy_s(OpenFileInfo->NormalizedName, OpenFileInfo->NormalizedNameSize / sizeof(WCHAR),
|
|
FileNode->FileName);
|
|
OpenFileInfo->NormalizedNameSize = (UINT16)(wcslen(FileNode->FileName) * sizeof(WCHAR));
|
|
}
|
|
#endif
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem,
|
|
PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess,
|
|
PVOID *PFileNode, FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode;
|
|
NTSTATUS Result;
|
|
|
|
if (MEMFS_MAX_PATH <= wcslen(FileName))
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
|
|
FileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, FileName);
|
|
if (0 == FileNode)
|
|
{
|
|
Result = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
MemfsFileNodeMapGetParent(Memfs->FileNodeMap, FileName, &Result);
|
|
return Result;
|
|
}
|
|
|
|
#if defined(MEMFS_EA)
|
|
/* if the OP specified no EA's check the need EA count, but only if accessing main stream */
|
|
if (0 != (CreateOptions & FILE_NO_EA_KNOWLEDGE)
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
&& (0 == FileNode->MainFileNode)
|
|
#endif
|
|
)
|
|
{
|
|
if (MemfsFileNodeNeedEa(FileNode))
|
|
{
|
|
Result = STATUS_ACCESS_DENIED;
|
|
return Result;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
MemfsFileNodeReference(FileNode);
|
|
*PFileNode = FileNode;
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
#if defined(MEMFS_NAME_NORMALIZATION)
|
|
if (MemfsFileNodeMapIsCaseInsensitive(Memfs->FileNodeMap))
|
|
{
|
|
FSP_FSCTL_OPEN_FILE_INFO *OpenFileInfo = FspFileSystemGetOpenFileInfo(FileInfo);
|
|
|
|
wcscpy_s(OpenFileInfo->NormalizedName, OpenFileInfo->NormalizedNameSize / sizeof(WCHAR),
|
|
FileNode->FileName);
|
|
OpenFileInfo->NormalizedNameSize = (UINT16)(wcslen(FileNode->FileName) * sizeof(WCHAR));
|
|
}
|
|
#endif
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize,
|
|
#if defined(MEMFS_EA)
|
|
PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
|
|
#endif
|
|
FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
NTSTATUS Result;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
MEMFS_FILE_NODE_MAP_ENUM_CONTEXT Context = { TRUE };
|
|
ULONG Index;
|
|
|
|
MemfsFileNodeMapEnumerateNamedStreams(Memfs->FileNodeMap, FileNode,
|
|
MemfsFileNodeMapEnumerateFn, &Context);
|
|
for (Index = 0; Context.Count > Index; Index++)
|
|
{
|
|
LONG RefCount = Context.FileNodes[Index]->RefCount;
|
|
MemoryBarrier();
|
|
if (2 >= RefCount)
|
|
MemfsFileNodeMapRemove(Memfs->FileNodeMap, Context.FileNodes[Index]);
|
|
}
|
|
MemfsFileNodeMapEnumerateFree(&Context);
|
|
#endif
|
|
|
|
#if defined(MEMFS_EA)
|
|
if (0 != Ea)
|
|
{
|
|
Result = FspFileSystemEnumerateEa(FileSystem, MemfsFileNodeSetEa, FileNode, Ea, EaLength);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
}
|
|
#endif
|
|
|
|
Result = SetFileSizeInternal(FileSystem, FileNode, AllocationSize, TRUE);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
if (ReplaceFileAttributes)
|
|
FileNode->FileInfo.FileAttributes = FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
|
|
else
|
|
FileNode->FileInfo.FileAttributes |= FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
|
|
|
|
FileNode->FileInfo.FileSize = 0;
|
|
FileNode->FileInfo.LastAccessTime =
|
|
FileNode->FileInfo.LastWriteTime =
|
|
FileNode->FileInfo.ChangeTime = MemfsGetSystemTime();
|
|
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, PWSTR FileName, ULONG Flags)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
MEMFS_FILE_NODE *MainFileNode = 0 != FileNode->MainFileNode ?
|
|
FileNode->MainFileNode : FileNode;
|
|
#else
|
|
MEMFS_FILE_NODE *MainFileNode = FileNode;
|
|
#endif
|
|
|
|
assert(0 != Flags); /* FSP_FSCTL_VOLUME_PARAMS::PostCleanupWhenModifiedOnly ensures this */
|
|
|
|
if (Flags & FspCleanupSetArchiveBit)
|
|
{
|
|
if (0 == (MainFileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
MainFileNode->FileInfo.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
|
|
}
|
|
|
|
if (Flags & (FspCleanupSetLastAccessTime | FspCleanupSetLastWriteTime | FspCleanupSetChangeTime))
|
|
{
|
|
UINT64 SystemTime = MemfsGetSystemTime();
|
|
|
|
if (Flags & FspCleanupSetLastAccessTime)
|
|
MainFileNode->FileInfo.LastAccessTime = SystemTime;
|
|
if (Flags & FspCleanupSetLastWriteTime)
|
|
MainFileNode->FileInfo.LastWriteTime = SystemTime;
|
|
if (Flags & FspCleanupSetChangeTime)
|
|
MainFileNode->FileInfo.ChangeTime = SystemTime;
|
|
}
|
|
|
|
if (Flags & FspCleanupSetAllocationSize)
|
|
{
|
|
UINT64 AllocationUnit = MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT;
|
|
UINT64 AllocationSize = (FileNode->FileInfo.FileSize + AllocationUnit - 1) /
|
|
AllocationUnit * AllocationUnit;
|
|
|
|
SetFileSizeInternal(FileSystem, FileNode, AllocationSize, TRUE);
|
|
}
|
|
|
|
if ((Flags & FspCleanupDelete) && !MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode))
|
|
{
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
MEMFS_FILE_NODE_MAP_ENUM_CONTEXT Context = { FALSE };
|
|
ULONG Index;
|
|
|
|
MemfsFileNodeMapEnumerateNamedStreams(Memfs->FileNodeMap, FileNode,
|
|
MemfsFileNodeMapEnumerateFn, &Context);
|
|
for (Index = 0; Context.Count > Index; Index++)
|
|
MemfsFileNodeMapRemove(Memfs->FileNodeMap, Context.FileNodes[Index]);
|
|
MemfsFileNodeMapEnumerateFree(&Context);
|
|
#endif
|
|
|
|
MemfsFileNodeMapRemove(Memfs->FileNodeMap, FileNode);
|
|
}
|
|
}
|
|
|
|
static VOID Close(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
MemfsFileNodeDereference(FileNode);
|
|
}
|
|
|
|
static NTSTATUS Read(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, PVOID Buffer, UINT64 Offset, ULONG Length,
|
|
PULONG PBytesTransferred)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
UINT64 EndOffset;
|
|
|
|
if (Offset >= FileNode->FileInfo.FileSize)
|
|
return STATUS_END_OF_FILE;
|
|
|
|
EndOffset = Offset + Length;
|
|
if (EndOffset > FileNode->FileInfo.FileSize)
|
|
EndOffset = FileNode->FileInfo.FileSize;
|
|
|
|
#ifdef MEMFS_SLOWIO
|
|
if (SlowioReturnPending(FileSystem))
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
try
|
|
{
|
|
InterlockedIncrement(&Memfs->SlowioThreadsRunning);
|
|
std::thread(SlowioReadThread,
|
|
FileSystem, FileNode, Buffer, Offset, EndOffset,
|
|
FspFileSystemGetOperationContext()->Request->Hint).
|
|
detach();
|
|
return STATUS_PENDING;
|
|
}
|
|
catch (...)
|
|
{
|
|
InterlockedDecrement(&Memfs->SlowioThreadsRunning);
|
|
}
|
|
}
|
|
SlowioSnooze(FileSystem);
|
|
#endif
|
|
|
|
memcpy(Buffer, (PUINT8)FileNode->FileData + Offset, (size_t)(EndOffset - Offset));
|
|
|
|
*PBytesTransferred = (ULONG)(EndOffset - Offset);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS Write(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, PVOID Buffer, UINT64 Offset, ULONG Length,
|
|
BOOLEAN WriteToEndOfFile, BOOLEAN ConstrainedIo,
|
|
PULONG PBytesTransferred, FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
#if defined(DEBUG_BUFFER_CHECK)
|
|
SYSTEM_INFO SystemInfo;
|
|
GetSystemInfo(&SystemInfo);
|
|
for (PUINT8 P = (PUINT8)Buffer, EndP = P + Length; EndP > P; P += SystemInfo.dwPageSize)
|
|
__try
|
|
{
|
|
*P = *P | 0;
|
|
assert(!IsWindows8OrGreater());
|
|
/* only on Windows 8 we can make the buffer read-only! */
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* ignore! */
|
|
}
|
|
#endif
|
|
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
UINT64 EndOffset;
|
|
NTSTATUS Result;
|
|
|
|
if (ConstrainedIo)
|
|
{
|
|
if (Offset >= FileNode->FileInfo.FileSize)
|
|
return STATUS_SUCCESS;
|
|
EndOffset = Offset + Length;
|
|
if (EndOffset > FileNode->FileInfo.FileSize)
|
|
EndOffset = FileNode->FileInfo.FileSize;
|
|
}
|
|
else
|
|
{
|
|
if (WriteToEndOfFile)
|
|
Offset = FileNode->FileInfo.FileSize;
|
|
EndOffset = Offset + Length;
|
|
if (EndOffset > FileNode->FileInfo.FileSize)
|
|
{
|
|
Result = SetFileSizeInternal(FileSystem, FileNode, EndOffset, FALSE);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
#ifdef MEMFS_SLOWIO
|
|
if (SlowioReturnPending(FileSystem))
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
try
|
|
{
|
|
InterlockedIncrement(&Memfs->SlowioThreadsRunning);
|
|
std::thread(SlowioWriteThread,
|
|
FileSystem, FileNode, Buffer, Offset, EndOffset,
|
|
FspFileSystemGetOperationContext()->Request->Hint).
|
|
detach();
|
|
return STATUS_PENDING;
|
|
}
|
|
catch (...)
|
|
{
|
|
InterlockedDecrement(&Memfs->SlowioThreadsRunning);
|
|
}
|
|
}
|
|
SlowioSnooze(FileSystem);
|
|
#endif
|
|
|
|
memcpy((PUINT8)FileNode->FileData + Offset, Buffer, (size_t)(EndOffset - Offset));
|
|
|
|
*PBytesTransferred = (ULONG)(EndOffset - Offset);
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS Flush(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
/* nothing to flush, since we do not cache anything */
|
|
|
|
if (0 != FileNode)
|
|
{
|
|
#if 0
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode->MainFileNode->FileInfo.LastAccessTime =
|
|
FileNode->MainFileNode->FileInfo.LastWriteTime =
|
|
FileNode->MainFileNode->FileInfo.ChangeTime = MemfsGetSystemTime();
|
|
else
|
|
#endif
|
|
FileNode->FileInfo.LastAccessTime =
|
|
FileNode->FileInfo.LastWriteTime =
|
|
FileNode->FileInfo.ChangeTime = MemfsGetSystemTime();
|
|
#endif
|
|
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS GetFileInfo(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, UINT32 FileAttributes,
|
|
UINT64 CreationTime, UINT64 LastAccessTime, UINT64 LastWriteTime, UINT64 ChangeTime,
|
|
FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (INVALID_FILE_ATTRIBUTES != FileAttributes)
|
|
FileNode->FileInfo.FileAttributes = FileAttributes;
|
|
if (0 != CreationTime)
|
|
FileNode->FileInfo.CreationTime = CreationTime;
|
|
if (0 != LastAccessTime)
|
|
FileNode->FileInfo.LastAccessTime = LastAccessTime;
|
|
if (0 != LastWriteTime)
|
|
FileNode->FileInfo.LastWriteTime = LastWriteTime;
|
|
if (0 != ChangeTime)
|
|
FileNode->FileInfo.ChangeTime = ChangeTime;
|
|
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetFileSizeInternal(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, UINT64 NewSize, BOOLEAN SetAllocationSize)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
if (SetAllocationSize)
|
|
{
|
|
if (FileNode->FileInfo.AllocationSize != NewSize)
|
|
{
|
|
if (NewSize > Memfs->MaxFileSize)
|
|
return STATUS_DISK_FULL;
|
|
|
|
PVOID FileData = LargeHeapRealloc(FileNode->FileData, (size_t)NewSize);
|
|
if (0 == FileData && 0 != NewSize)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
FileNode->FileData = FileData;
|
|
|
|
FileNode->FileInfo.AllocationSize = NewSize;
|
|
if (FileNode->FileInfo.FileSize > NewSize)
|
|
FileNode->FileInfo.FileSize = NewSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FileNode->FileInfo.FileSize != NewSize)
|
|
{
|
|
if (FileNode->FileInfo.AllocationSize < NewSize)
|
|
{
|
|
UINT64 AllocationUnit = MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT;
|
|
UINT64 AllocationSize = (NewSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit;
|
|
|
|
NTSTATUS Result = SetFileSizeInternal(FileSystem, FileNode, AllocationSize, TRUE);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
}
|
|
|
|
if (FileNode->FileInfo.FileSize < NewSize)
|
|
memset((PUINT8)FileNode->FileData + FileNode->FileInfo.FileSize, 0,
|
|
(size_t)(NewSize - FileNode->FileInfo.FileSize));
|
|
FileNode->FileInfo.FileSize = NewSize;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, UINT64 NewSize, BOOLEAN SetAllocationSize,
|
|
FSP_FSCTL_FILE_INFO *FileInfo)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
NTSTATUS Result;
|
|
|
|
Result = SetFileSizeInternal(FileSystem, FileNode0, NewSize, SetAllocationSize);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS CanDelete(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, PWSTR FileName)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
if (MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode))
|
|
return STATUS_DIRECTORY_NOT_EMPTY;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS Rename(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
PWSTR FileName, PWSTR NewFileName, BOOLEAN ReplaceIfExists)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
MEMFS_FILE_NODE *NewFileNode, *DescendantFileNode;
|
|
MEMFS_FILE_NODE_MAP_ENUM_CONTEXT Context = { TRUE };
|
|
ULONG Index, FileNameLen, NewFileNameLen;
|
|
BOOLEAN Inserted;
|
|
NTSTATUS Result;
|
|
|
|
NewFileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, NewFileName);
|
|
if (0 != NewFileNode && FileNode != NewFileNode)
|
|
{
|
|
if (!ReplaceIfExists)
|
|
{
|
|
Result = STATUS_OBJECT_NAME_COLLISION;
|
|
goto exit;
|
|
}
|
|
|
|
if (NewFileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
Result = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
MemfsFileNodeMapEnumerateDescendants(Memfs->FileNodeMap, FileNode,
|
|
MemfsFileNodeMapEnumerateFn, &Context);
|
|
|
|
FileNameLen = (ULONG)wcslen(FileNode->FileName);
|
|
NewFileNameLen = (ULONG)wcslen(NewFileName);
|
|
for (Index = 0; Context.Count > Index; Index++)
|
|
{
|
|
DescendantFileNode = Context.FileNodes[Index];
|
|
if (MEMFS_MAX_PATH <= wcslen(DescendantFileNode->FileName) - FileNameLen + NewFileNameLen)
|
|
{
|
|
Result = STATUS_OBJECT_NAME_INVALID;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
if (0 != NewFileNode)
|
|
{
|
|
MemfsFileNodeReference(NewFileNode);
|
|
MemfsFileNodeMapRemove(Memfs->FileNodeMap, NewFileNode);
|
|
MemfsFileNodeDereference(NewFileNode);
|
|
}
|
|
|
|
for (Index = 0; Context.Count > Index; Index++)
|
|
{
|
|
DescendantFileNode = Context.FileNodes[Index];
|
|
MemfsFileNodeMapRemove(Memfs->FileNodeMap, DescendantFileNode);
|
|
memmove(DescendantFileNode->FileName + NewFileNameLen,
|
|
DescendantFileNode->FileName + FileNameLen,
|
|
(wcslen(DescendantFileNode->FileName) + 1 - FileNameLen) * sizeof(WCHAR));
|
|
memcpy(DescendantFileNode->FileName, NewFileName, NewFileNameLen * sizeof(WCHAR));
|
|
Result = MemfsFileNodeMapInsert(Memfs->FileNodeMap, DescendantFileNode, &Inserted);
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
FspDebugLog(__FUNCTION__ ": cannot insert into FileNodeMap; aborting\n");
|
|
abort();
|
|
}
|
|
assert(Inserted);
|
|
}
|
|
|
|
Result = STATUS_SUCCESS;
|
|
|
|
exit:
|
|
MemfsFileNodeMapEnumerateFree(&Context);
|
|
|
|
return Result;
|
|
}
|
|
|
|
static NTSTATUS GetSecurity(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (FileNode->FileSecuritySize > *PSecurityDescriptorSize)
|
|
{
|
|
*PSecurityDescriptorSize = FileNode->FileSecuritySize;
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
*PSecurityDescriptorSize = FileNode->FileSecuritySize;
|
|
if (0 != SecurityDescriptor)
|
|
memcpy(SecurityDescriptor, FileNode->FileSecurity, FileNode->FileSecuritySize);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetSecurity(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR ModificationDescriptor)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
PSECURITY_DESCRIPTOR NewSecurityDescriptor, FileSecurity;
|
|
SIZE_T FileSecuritySize;
|
|
NTSTATUS Result;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
Result = FspSetSecurityDescriptor(
|
|
FileNode->FileSecurity,
|
|
SecurityInformation,
|
|
ModificationDescriptor,
|
|
&NewSecurityDescriptor);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
FileSecuritySize = GetSecurityDescriptorLength(NewSecurityDescriptor);
|
|
FileSecurity = (PSECURITY_DESCRIPTOR)malloc(FileSecuritySize);
|
|
if (0 == FileSecurity)
|
|
{
|
|
FspDeleteSecurityDescriptor(NewSecurityDescriptor, (NTSTATUS (*)())FspSetSecurityDescriptor);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
memcpy(FileSecurity, NewSecurityDescriptor, FileSecuritySize);
|
|
FspDeleteSecurityDescriptor(NewSecurityDescriptor, (NTSTATUS (*)())FspSetSecurityDescriptor);
|
|
|
|
free(FileNode->FileSecurity);
|
|
FileNode->FileSecuritySize = FileSecuritySize;
|
|
FileNode->FileSecurity = FileSecurity;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
typedef struct _MEMFS_READ_DIRECTORY_CONTEXT
|
|
{
|
|
PVOID Buffer;
|
|
ULONG Length;
|
|
PULONG PBytesTransferred;
|
|
} MEMFS_READ_DIRECTORY_CONTEXT;
|
|
|
|
static BOOLEAN AddDirInfo(MEMFS_FILE_NODE *FileNode, PWSTR FileName,
|
|
PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
|
|
{
|
|
UINT8 DirInfoBuf[sizeof(FSP_FSCTL_DIR_INFO) + sizeof FileNode->FileName];
|
|
FSP_FSCTL_DIR_INFO *DirInfo = (FSP_FSCTL_DIR_INFO *)DirInfoBuf;
|
|
WCHAR Root[2] = L"\\";
|
|
PWSTR Remain, Suffix;
|
|
|
|
if (0 == FileName)
|
|
{
|
|
FspPathSuffix(FileNode->FileName, &Remain, &Suffix, Root);
|
|
FileName = Suffix;
|
|
FspPathCombine(FileNode->FileName, Suffix);
|
|
}
|
|
|
|
memset(DirInfo->Padding, 0, sizeof DirInfo->Padding);
|
|
DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + wcslen(FileName) * sizeof(WCHAR));
|
|
DirInfo->FileInfo = FileNode->FileInfo;
|
|
memcpy(DirInfo->FileNameBuf, FileName, DirInfo->Size - sizeof(FSP_FSCTL_DIR_INFO));
|
|
|
|
return FspFileSystemAddDirInfo(DirInfo, Buffer, Length, PBytesTransferred);
|
|
}
|
|
|
|
static BOOLEAN ReadDirectoryEnumFn(MEMFS_FILE_NODE *FileNode, PVOID Context0)
|
|
{
|
|
MEMFS_READ_DIRECTORY_CONTEXT *Context = (MEMFS_READ_DIRECTORY_CONTEXT *)Context0;
|
|
|
|
return AddDirInfo(FileNode, 0,
|
|
Context->Buffer, Context->Length, Context->PBytesTransferred);
|
|
}
|
|
|
|
static NTSTATUS ReadDirectory(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, PWSTR Pattern, PWSTR Marker,
|
|
PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
|
|
{
|
|
assert(0 == Pattern);
|
|
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
MEMFS_FILE_NODE *ParentNode;
|
|
MEMFS_READ_DIRECTORY_CONTEXT Context;
|
|
NTSTATUS Result;
|
|
|
|
Context.Buffer = Buffer;
|
|
Context.Length = Length;
|
|
Context.PBytesTransferred = PBytesTransferred;
|
|
|
|
if (L'\0' != FileNode->FileName[1])
|
|
{
|
|
/* if this is not the root directory add the dot entries */
|
|
|
|
ParentNode = MemfsFileNodeMapGetParent(Memfs->FileNodeMap, FileNode->FileName, &Result);
|
|
if (0 == ParentNode)
|
|
return Result;
|
|
|
|
if (0 == Marker)
|
|
{
|
|
if (!AddDirInfo(FileNode, L".", Buffer, Length, PBytesTransferred))
|
|
return STATUS_SUCCESS;
|
|
}
|
|
if (0 == Marker || (L'.' == Marker[0] && L'\0' == Marker[1]))
|
|
{
|
|
if (!AddDirInfo(ParentNode, L"..", Buffer, Length, PBytesTransferred))
|
|
return STATUS_SUCCESS;
|
|
Marker = 0;
|
|
}
|
|
}
|
|
|
|
if (MemfsFileNodeMapEnumerateChildren(Memfs->FileNodeMap, FileNode, Marker,
|
|
ReadDirectoryEnumFn, &Context))
|
|
FspFileSystemAddDirInfo(0, Buffer, Length, PBytesTransferred);
|
|
|
|
#ifdef MEMFS_SLOWIO
|
|
if (SlowioReturnPending(FileSystem))
|
|
{
|
|
try
|
|
{
|
|
InterlockedIncrement(&Memfs->SlowioThreadsRunning);
|
|
std::thread(SlowioReadDirectoryThread,
|
|
FileSystem, *PBytesTransferred,
|
|
FspFileSystemGetOperationContext()->Request->Hint).
|
|
detach();
|
|
return STATUS_PENDING;
|
|
}
|
|
catch (...)
|
|
{
|
|
InterlockedDecrement(&Memfs->SlowioThreadsRunning);
|
|
}
|
|
}
|
|
SlowioSnooze(FileSystem);
|
|
#endif
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
#if defined(MEMFS_DIRINFO_BY_NAME)
|
|
static NTSTATUS GetDirInfoByName(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID ParentNode0, PWSTR FileName,
|
|
FSP_FSCTL_DIR_INFO *DirInfo)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *ParentNode = (MEMFS_FILE_NODE *)ParentNode0;
|
|
MEMFS_FILE_NODE *FileNode;
|
|
WCHAR FileNameBuf[MEMFS_MAX_PATH];
|
|
size_t ParentLength, BSlashLength, FileNameLength;
|
|
WCHAR Root[2] = L"\\";
|
|
PWSTR Remain, Suffix;
|
|
|
|
ParentLength = wcslen(ParentNode->FileName);
|
|
BSlashLength = 1 < ParentLength;
|
|
FileNameLength = wcslen(FileName);
|
|
if (MEMFS_MAX_PATH <= ParentLength + BSlashLength + FileNameLength)
|
|
return STATUS_OBJECT_NAME_NOT_FOUND; //STATUS_OBJECT_NAME_INVALID?
|
|
|
|
memcpy(FileNameBuf, ParentNode->FileName, ParentLength * sizeof(WCHAR));
|
|
memcpy(FileNameBuf + ParentLength, L"\\", BSlashLength * sizeof(WCHAR));
|
|
memcpy(FileNameBuf + ParentLength + BSlashLength, FileName, (FileNameLength + 1) * sizeof(WCHAR));
|
|
|
|
FileName = FileNameBuf;
|
|
|
|
FileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, FileName);
|
|
if (0 == FileNode)
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
FspPathSuffix(FileNode->FileName, &Remain, &Suffix, Root);
|
|
FileName = Suffix;
|
|
FspPathCombine(FileNode->FileName, Suffix);
|
|
|
|
//memset(DirInfo->Padding, 0, sizeof DirInfo->Padding);
|
|
DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + wcslen(FileName) * sizeof(WCHAR));
|
|
DirInfo->FileInfo = FileNode->FileInfo;
|
|
memcpy(DirInfo->FileNameBuf, FileName, DirInfo->Size - sizeof(FSP_FSCTL_DIR_INFO));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#if defined(MEMFS_REPARSE_POINTS)
|
|
static NTSTATUS ResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem,
|
|
PWSTR FileName, UINT32 ReparsePointIndex, BOOLEAN ResolveLastPathComponent,
|
|
PIO_STATUS_BLOCK PIoStatus, PVOID Buffer, PSIZE_T PSize)
|
|
{
|
|
return FspFileSystemResolveReparsePoints(FileSystem, GetReparsePointByName, 0,
|
|
FileName, ReparsePointIndex, ResolveLastPathComponent,
|
|
PIoStatus, Buffer, PSize);
|
|
}
|
|
|
|
static NTSTATUS GetReparsePointByName(
|
|
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
|
|
PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
/* GetReparsePointByName will never receive a named stream */
|
|
assert(0 == wcschr(FileName, L':'));
|
|
#endif
|
|
|
|
FileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, FileName);
|
|
if (0 == FileNode)
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
if (0 == (FileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
return STATUS_NOT_A_REPARSE_POINT;
|
|
|
|
if (0 != Buffer)
|
|
{
|
|
if (FileNode->ReparseDataSize > *PSize)
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
*PSize = FileNode->ReparseDataSize;
|
|
memcpy(Buffer, FileNode->ReparseData, FileNode->ReparseDataSize);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS GetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
PWSTR FileName, PVOID Buffer, PSIZE_T PSize)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (0 == (FileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
return STATUS_NOT_A_REPARSE_POINT;
|
|
|
|
if (FileNode->ReparseDataSize > *PSize)
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
*PSize = FileNode->ReparseDataSize;
|
|
memcpy(Buffer, FileNode->ReparseData, FileNode->ReparseDataSize);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
PWSTR FileName, PVOID Buffer, SIZE_T Size)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
PVOID ReparseData;
|
|
NTSTATUS Result;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode))
|
|
return STATUS_DIRECTORY_NOT_EMPTY;
|
|
|
|
if (0 != FileNode->ReparseData)
|
|
{
|
|
Result = FspFileSystemCanReplaceReparsePoint(
|
|
FileNode->ReparseData, FileNode->ReparseDataSize,
|
|
Buffer, Size);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
}
|
|
|
|
ReparseData = realloc(FileNode->ReparseData, Size);
|
|
if (0 == ReparseData && 0 != Size)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
FileNode->FileInfo.FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
FileNode->FileInfo.ReparseTag = *(PULONG)Buffer;
|
|
/* the first field in a reparse buffer is the reparse tag */
|
|
FileNode->ReparseDataSize = Size;
|
|
FileNode->ReparseData = ReparseData;
|
|
memcpy(FileNode->ReparseData, Buffer, Size);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS DeleteReparsePoint(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
PWSTR FileName, PVOID Buffer, SIZE_T Size)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
NTSTATUS Result;
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
#endif
|
|
|
|
if (0 != FileNode->ReparseData)
|
|
{
|
|
Result = FspFileSystemCanReplaceReparsePoint(
|
|
FileNode->ReparseData, FileNode->ReparseDataSize,
|
|
Buffer, Size);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
}
|
|
else
|
|
return STATUS_NOT_A_REPARSE_POINT;
|
|
|
|
free(FileNode->ReparseData);
|
|
|
|
FileNode->FileInfo.FileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT;
|
|
FileNode->FileInfo.ReparseTag = 0;
|
|
FileNode->ReparseDataSize = 0;
|
|
FileNode->ReparseData = 0;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
typedef struct _MEMFS_GET_STREAM_INFO_CONTEXT
|
|
{
|
|
PVOID Buffer;
|
|
ULONG Length;
|
|
PULONG PBytesTransferred;
|
|
} MEMFS_GET_STREAM_INFO_CONTEXT;
|
|
|
|
static BOOLEAN AddStreamInfo(MEMFS_FILE_NODE *FileNode,
|
|
PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
|
|
{
|
|
UINT8 StreamInfoBuf[sizeof(FSP_FSCTL_STREAM_INFO) + sizeof FileNode->FileName];
|
|
FSP_FSCTL_STREAM_INFO *StreamInfo = (FSP_FSCTL_STREAM_INFO *)StreamInfoBuf;
|
|
PWSTR StreamName;
|
|
|
|
StreamName = wcschr(FileNode->FileName, L':');
|
|
if (0 != StreamName)
|
|
StreamName++;
|
|
else
|
|
StreamName = L"";
|
|
|
|
StreamInfo->Size = (UINT16)(sizeof(FSP_FSCTL_STREAM_INFO) + wcslen(StreamName) * sizeof(WCHAR));
|
|
StreamInfo->StreamSize = FileNode->FileInfo.FileSize;
|
|
StreamInfo->StreamAllocationSize = FileNode->FileInfo.AllocationSize;
|
|
memcpy(StreamInfo->StreamNameBuf, StreamName, StreamInfo->Size - sizeof(FSP_FSCTL_STREAM_INFO));
|
|
|
|
return FspFileSystemAddStreamInfo(StreamInfo, Buffer, Length, PBytesTransferred);
|
|
}
|
|
|
|
static BOOLEAN GetStreamInfoEnumFn(MEMFS_FILE_NODE *FileNode, PVOID Context0)
|
|
{
|
|
MEMFS_GET_STREAM_INFO_CONTEXT *Context = (MEMFS_GET_STREAM_INFO_CONTEXT *)Context0;
|
|
|
|
return AddStreamInfo(FileNode,
|
|
Context->Buffer, Context->Length, Context->PBytesTransferred);
|
|
}
|
|
|
|
static NTSTATUS GetStreamInfo(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0, PVOID Buffer, ULONG Length,
|
|
PULONG PBytesTransferred)
|
|
{
|
|
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
MEMFS_GET_STREAM_INFO_CONTEXT Context;
|
|
|
|
if (0 != FileNode->MainFileNode)
|
|
FileNode = FileNode->MainFileNode;
|
|
|
|
Context.Buffer = Buffer;
|
|
Context.Length = Length;
|
|
Context.PBytesTransferred = PBytesTransferred;
|
|
|
|
if (0 == (FileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
!AddStreamInfo(FileNode, Buffer, Length, PBytesTransferred))
|
|
return STATUS_SUCCESS;
|
|
|
|
if (MemfsFileNodeMapEnumerateNamedStreams(Memfs->FileNodeMap, FileNode, GetStreamInfoEnumFn, &Context))
|
|
FspFileSystemAddStreamInfo(0, Buffer, Length, PBytesTransferred);
|
|
|
|
/* ???: how to handle out of response buffer condition? */
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#if defined(MEMFS_CONTROL)
|
|
static NTSTATUS Control(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode, UINT32 ControlCode,
|
|
PVOID InputBuffer, ULONG InputBufferLength,
|
|
PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred)
|
|
{
|
|
/* MEMFS also supports encryption! See below :) */
|
|
if (CTL_CODE(0x8000 + 'M', 'R', METHOD_BUFFERED, FILE_ANY_ACCESS) == ControlCode)
|
|
{
|
|
if (OutputBufferLength != InputBufferLength)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
for (PUINT8 P = (PUINT8)InputBuffer, Q = (PUINT8)OutputBuffer, EndP = P + InputBufferLength;
|
|
EndP > P; P++, Q++)
|
|
{
|
|
if (('A' <= *P && *P <= 'M') || ('a' <= *P && *P <= 'm'))
|
|
*Q = *P + 13;
|
|
else
|
|
if (('N' <= *P && *P <= 'Z') || ('n' <= *P && *P <= 'z'))
|
|
*Q = *P - 13;
|
|
else
|
|
*Q = *P;
|
|
}
|
|
|
|
*PBytesTransferred = InputBufferLength;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
#endif
|
|
|
|
#if defined(MEMFS_EA)
|
|
typedef struct _MEMFS_GET_EA_CONTEXT
|
|
{
|
|
PFILE_FULL_EA_INFORMATION Ea;
|
|
ULONG EaLength;
|
|
PULONG PBytesTransferred;
|
|
} MEMFS_GET_EA_CONTEXT;
|
|
|
|
static BOOLEAN GetEaEnumFn(PFILE_FULL_EA_INFORMATION Ea, PVOID Context0)
|
|
{
|
|
MEMFS_GET_EA_CONTEXT *Context = (MEMFS_GET_EA_CONTEXT *)Context0;
|
|
|
|
return FspFileSystemAddEa(Ea,
|
|
Context->Ea, Context->EaLength, Context->PBytesTransferred);
|
|
}
|
|
|
|
static NTSTATUS GetEa(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode0,
|
|
PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred)
|
|
{
|
|
MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
|
|
MEMFS_GET_EA_CONTEXT Context;
|
|
|
|
Context.Ea = Ea;
|
|
Context.EaLength = EaLength;
|
|
Context.PBytesTransferred = PBytesTransferred;
|
|
|
|
if (MemfsFileNodeEnumerateEa(FileNode, GetEaEnumFn, &Context))
|
|
FspFileSystemAddEa(0, Ea, EaLength, PBytesTransferred);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS SetEa(FSP_FILE_SYSTEM *FileSystem,
|
|
PVOID FileNode,
|
|
PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength)
|
|
{
|
|
return FspFileSystemEnumerateEa(FileSystem, MemfsFileNodeSetEa, FileNode, Ea, EaLength);
|
|
}
|
|
#endif
|
|
|
|
static FSP_FILE_SYSTEM_INTERFACE MemfsInterface =
|
|
{
|
|
GetVolumeInfo,
|
|
SetVolumeLabel,
|
|
GetSecurityByName,
|
|
#if defined(MEMFS_EA)
|
|
0,
|
|
#else
|
|
Create,
|
|
#endif
|
|
Open,
|
|
#if defined(MEMFS_EA)
|
|
0,
|
|
#else
|
|
Overwrite,
|
|
#endif
|
|
Cleanup,
|
|
Close,
|
|
Read,
|
|
Write,
|
|
Flush,
|
|
GetFileInfo,
|
|
SetBasicInfo,
|
|
SetFileSize,
|
|
CanDelete,
|
|
Rename,
|
|
GetSecurity,
|
|
SetSecurity,
|
|
ReadDirectory,
|
|
#if defined(MEMFS_REPARSE_POINTS)
|
|
ResolveReparsePoints,
|
|
GetReparsePoint,
|
|
SetReparsePoint,
|
|
DeleteReparsePoint,
|
|
#else
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
#endif
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
GetStreamInfo,
|
|
#else
|
|
0,
|
|
#endif
|
|
#if defined(MEMFS_DIRINFO_BY_NAME)
|
|
GetDirInfoByName,
|
|
#else
|
|
0,
|
|
#endif
|
|
#if defined(MEMFS_CONTROL)
|
|
Control,
|
|
#else
|
|
0,
|
|
#endif
|
|
0,
|
|
#if defined(MEMFS_EA)
|
|
Create,
|
|
Overwrite,
|
|
GetEa,
|
|
SetEa
|
|
#else
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* Public API
|
|
*/
|
|
|
|
NTSTATUS MemfsCreateFunnel(
|
|
ULONG Flags,
|
|
ULONG FileInfoTimeout,
|
|
ULONG MaxFileNodes,
|
|
ULONG MaxFileSize,
|
|
ULONG SlowioMaxDelay,
|
|
ULONG SlowioPercentDelay,
|
|
ULONG SlowioRarefyDelay,
|
|
PWSTR FileSystemName,
|
|
PWSTR VolumePrefix,
|
|
PWSTR RootSddl,
|
|
MEMFS **PMemfs)
|
|
{
|
|
NTSTATUS Result;
|
|
FSP_FSCTL_VOLUME_PARAMS VolumeParams;
|
|
BOOLEAN CaseInsensitive = !!(Flags & MemfsCaseInsensitive);
|
|
BOOLEAN FlushAndPurgeOnCleanup = !!(Flags & MemfsFlushAndPurgeOnCleanup);
|
|
PWSTR DevicePath = MemfsNet == (Flags & MemfsDeviceMask) ?
|
|
L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME;
|
|
UINT64 AllocationUnit;
|
|
MEMFS *Memfs;
|
|
MEMFS_FILE_NODE *RootNode;
|
|
PSECURITY_DESCRIPTOR RootSecurity;
|
|
ULONG RootSecuritySize;
|
|
BOOLEAN Inserted;
|
|
|
|
*PMemfs = 0;
|
|
|
|
Result = MemfsHeapConfigure(0, 0, 0);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
if (0 == RootSddl)
|
|
RootSddl = L"O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)";
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(RootSddl, SDDL_REVISION_1,
|
|
&RootSecurity, &RootSecuritySize))
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
|
|
Memfs = (MEMFS *)malloc(sizeof *Memfs);
|
|
if (0 == Memfs)
|
|
{
|
|
LocalFree(RootSecurity);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
memset(Memfs, 0, sizeof *Memfs);
|
|
Memfs->MaxFileNodes = MaxFileNodes;
|
|
AllocationUnit = MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT;
|
|
Memfs->MaxFileSize = (ULONG)((MaxFileSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit);
|
|
|
|
#ifdef MEMFS_SLOWIO
|
|
Memfs->SlowioMaxDelay = SlowioMaxDelay;
|
|
Memfs->SlowioPercentDelay = SlowioPercentDelay;
|
|
Memfs->SlowioRarefyDelay = SlowioRarefyDelay;
|
|
#endif
|
|
|
|
Result = MemfsFileNodeMapCreate(CaseInsensitive, &Memfs->FileNodeMap);
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
free(Memfs);
|
|
LocalFree(RootSecurity);
|
|
return Result;
|
|
}
|
|
|
|
memset(&VolumeParams, 0, sizeof VolumeParams);
|
|
VolumeParams.Version = sizeof FSP_FSCTL_VOLUME_PARAMS;
|
|
VolumeParams.SectorSize = MEMFS_SECTOR_SIZE;
|
|
VolumeParams.SectorsPerAllocationUnit = MEMFS_SECTORS_PER_ALLOCATION_UNIT;
|
|
VolumeParams.VolumeCreationTime = MemfsGetSystemTime();
|
|
VolumeParams.VolumeSerialNumber = (UINT32)(MemfsGetSystemTime() / (10000 * 1000));
|
|
VolumeParams.FileInfoTimeout = FileInfoTimeout;
|
|
VolumeParams.CaseSensitiveSearch = !CaseInsensitive;
|
|
VolumeParams.CasePreservedNames = 1;
|
|
VolumeParams.UnicodeOnDisk = 1;
|
|
VolumeParams.PersistentAcls = 1;
|
|
VolumeParams.ReparsePoints = 1;
|
|
VolumeParams.ReparsePointsAccessCheck = 0;
|
|
#if defined(MEMFS_NAMED_STREAMS)
|
|
VolumeParams.NamedStreams = 1;
|
|
#endif
|
|
VolumeParams.PostCleanupWhenModifiedOnly = 1;
|
|
#if defined(MEMFS_DIRINFO_BY_NAME)
|
|
VolumeParams.PassQueryDirectoryFileName = 1;
|
|
#endif
|
|
VolumeParams.FlushAndPurgeOnCleanup = FlushAndPurgeOnCleanup;
|
|
#if defined(MEMFS_CONTROL)
|
|
VolumeParams.DeviceControl = 1;
|
|
#endif
|
|
VolumeParams.AllowOpenInKernelMode = 1;
|
|
if (0 != VolumePrefix)
|
|
wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix);
|
|
wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR),
|
|
0 != FileSystemName ? FileSystemName : L"-MEMFS");
|
|
|
|
Result = FspFileSystemCreate(DevicePath, &VolumeParams, &MemfsInterface, &Memfs->FileSystem);
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
MemfsFileNodeMapDelete(Memfs->FileNodeMap);
|
|
free(Memfs);
|
|
LocalFree(RootSecurity);
|
|
return Result;
|
|
}
|
|
|
|
Memfs->FileSystem->UserContext = Memfs;
|
|
Memfs->VolumeLabelLength = sizeof L"MEMFS" - sizeof(WCHAR);
|
|
memcpy(Memfs->VolumeLabel, L"MEMFS", Memfs->VolumeLabelLength);
|
|
|
|
#if 0
|
|
FspFileSystemSetOperationGuardStrategy(Memfs->FileSystem,
|
|
FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_COARSE);
|
|
#endif
|
|
|
|
/*
|
|
* Create root directory.
|
|
*/
|
|
|
|
Result = MemfsFileNodeCreate(L"\\", &RootNode);
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
MemfsDelete(Memfs);
|
|
LocalFree(RootSecurity);
|
|
return Result;
|
|
}
|
|
|
|
RootNode->FileInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
RootNode->FileSecurity = malloc(RootSecuritySize);
|
|
if (0 == RootNode->FileSecurity)
|
|
{
|
|
MemfsFileNodeDelete(RootNode);
|
|
MemfsDelete(Memfs);
|
|
LocalFree(RootSecurity);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
RootNode->FileSecuritySize = RootSecuritySize;
|
|
memcpy(RootNode->FileSecurity, RootSecurity, RootSecuritySize);
|
|
|
|
Result = MemfsFileNodeMapInsert(Memfs->FileNodeMap, RootNode, &Inserted);
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
MemfsFileNodeDelete(RootNode);
|
|
MemfsDelete(Memfs);
|
|
LocalFree(RootSecurity);
|
|
return Result;
|
|
}
|
|
|
|
LocalFree(RootSecurity);
|
|
|
|
*PMemfs = Memfs;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID MemfsDelete(MEMFS *Memfs)
|
|
{
|
|
FspFileSystemDelete(Memfs->FileSystem);
|
|
|
|
MemfsFileNodeMapDelete(Memfs->FileNodeMap);
|
|
|
|
free(Memfs);
|
|
}
|
|
|
|
NTSTATUS MemfsStart(MEMFS *Memfs)
|
|
{
|
|
#ifdef MEMFS_SLOWIO
|
|
Memfs->SlowioThreadsRunning = 0;
|
|
#endif
|
|
|
|
return FspFileSystemStartDispatcher(Memfs->FileSystem, 0);
|
|
}
|
|
|
|
VOID MemfsStop(MEMFS *Memfs)
|
|
{
|
|
FspFileSystemStopDispatcher(Memfs->FileSystem);
|
|
|
|
#ifdef MEMFS_SLOWIO
|
|
while (Memfs->SlowioThreadsRunning)
|
|
Sleep(1);
|
|
#endif
|
|
}
|
|
|
|
FSP_FILE_SYSTEM *MemfsFileSystem(MEMFS *Memfs)
|
|
{
|
|
return Memfs->FileSystem;
|
|
}
|
|
|
|
NTSTATUS MemfsHeapConfigure(SIZE_T InitialSize, SIZE_T MaximumSize, SIZE_T Alignment)
|
|
{
|
|
return LargeHeapInitialize(0, InitialSize, MaximumSize, LargeHeapAlignment) ?
|
|
STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|