/** * @file memfs.cpp * * @copyright 2015-2016 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 file in * accordance with the commercial license agreement provided with the * software. */ #undef _DEBUG #include "memfs.h" #include #include #include #include #include /* * 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 DEBUG_BUFFER_CHECK macro on Windows 8 or above. This includes * a check for the Write buffer to ensure that it is read-only. */ #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 MemfsCompareString(PWSTR a, int alen, PWSTR b, int blen, BOOLEAN CaseInsensitive) { int len, res; if (-1 == alen) alen = (int)wcslen(a); if (-1 == blen) blen = (int)wcslen(b); len = alen < blen ? alen : blen; /* we should still be in the C locale */ if (CaseInsensitive) res = _wcsnicmp(a, b, len); else res = wcsncmp(a, b, len); if (0 == res) res = alen - blen; return res; } static inline int MemfsFileNameCompare(PWSTR a, PWSTR b, BOOLEAN CaseInsensitive) { return MemfsCompareString(a, -1, b, -1, CaseInsensitive); } static inline BOOLEAN MemfsFileNameHasPrefix(PWSTR a, PWSTR b, BOOLEAN CaseInsensitive) { int alen = (int)wcslen(a); int blen = (int)wcslen(b); return alen >= blen && 0 == MemfsCompareString(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 } typedef struct _MEMFS_FILE_NODE { WCHAR FileName[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 ULONG RefCount; #if defined(MEMFS_NAMED_STREAMS) struct _MEMFS_FILE_NODE *MainFileNode; #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, b, CaseInsensitive); } BOOLEAN CaseInsensitive; }; typedef std::map MEMFS_FILE_NODE_MAP; typedef struct _MEMFS { FSP_FILE_SYSTEM *FileSystem; MEMFS_FILE_NODE_MAP *FileNodeMap; ULONG MaxFileNodes; ULONG MaxFileSize; 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; if (MAX_PATH <= wcslen(FileName)) return STATUS_OBJECT_NAME_INVALID; 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_REPARSE_POINTS) free(FileNode->ReparseData); #endif LargeHeapFree(FileNode->FileData); free(FileNode->FileSecurity); free(FileNode); } static inline VOID MemfsFileNodeReference(MEMFS_FILE_NODE *FileNode) { FileNode->RefCount++; } static inline VOID MemfsFileNodeDereference(MEMFS_FILE_NODE *FileNode) { if (0 == --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 } 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[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[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 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); 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)) 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, FileNode->FileName, 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[MAX_PATH]; size_t Length0 = wcslen(FileNode->FileName); size_t Length1 = 1 != Length0 || L'\\' != FileNode->FileName[0]; size_t Length2 = wcslen(PrevFileName0); if (MAX_PATH <= Length0 + Length1 + Length2) /* fall back to linear scan! */ goto fallback; 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->find(PrevFileName); if (FileNodeMap->end() == iter) /* fall back to linear scan! */ goto fallback; } else fallback: 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, FileNode->FileName, 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); } typedef std::unordered_map MEMFS_DIR_DESC; static inline NTSTATUS MemfsDirDescCreate(MEMFS_DIR_DESC **PDirDesc) { *PDirDesc = 0; try { *PDirDesc = new MEMFS_DIR_DESC; return STATUS_SUCCESS; } catch (...) { return STATUS_INSUFFICIENT_RESOURCES; } } static inline VOID MemfsDirDescDelete(MEMFS_DIR_DESC *DirDesc) { delete DirDesc; } static inline VOID MemfsDirDescReset(MEMFS_DIR_DESC *DirDesc) { DirDesc->clear(); } static inline PWSTR MemfsDirDescGetFileName(MEMFS_DIR_DESC *DirDesc, UINT64 Offset) { MEMFS_DIR_DESC::iterator iter = DirDesc->find(Offset); if (iter == DirDesc->end()) return 0; return const_cast(iter->second.c_str()); } static inline NTSTATUS MemfsDirDescInsertFileName(MEMFS_DIR_DESC *DirDesc, UINT64 Offset, PWSTR FileName) { try { DirDesc->insert(MEMFS_DIR_DESC::value_type(Offset, FileName)); return STATUS_SUCCESS; } catch (...) { return STATUS_INSUFFICIENT_RESOURCES; } } /* * 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 SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo); 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, PVOID *PFileNode, FSP_FSCTL_FILE_INFO *FileInfo) { MEMFS *Memfs = (MEMFS *)FileSystem->UserContext; #if defined(MEMFS_NAME_NORMALIZATION) WCHAR FileNameBuf[MAX_PATH]; #endif MEMFS_FILE_NODE *FileNode; MEMFS_FILE_NODE *ParentNode; MEMFS_DIR_DESC *DirDesc = 0; NTSTATUS Result; BOOLEAN Inserted; 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 == MemfsCompareString(Remain, -1, ParentNode->FileName, -1, TRUE)); FspPathCombine(FileName, Suffix); RemainLength = wcslen(ParentNode->FileName); BSlashLength = 1 < RemainLength; SuffixLength = wcslen(Suffix); if (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 if (CreateOptions & FILE_DIRECTORY_FILE) { Result = MemfsDirDescCreate(&DirDesc); if (!NT_SUCCESS(Result)) return Result; } Result = MemfsFileNodeCreate(FileName, &FileNode); if (!NT_SUCCESS(Result)) { if (0 != DirDesc) MemfsDirDescDelete(DirDesc); 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); if (0 != DirDesc) MemfsDirDescDelete(DirDesc); return STATUS_INSUFFICIENT_RESOURCES; } memcpy(FileNode->FileSecurity, SecurityDescriptor, FileNode->FileSecuritySize); } FileNode->FileInfo.AllocationSize = AllocationSize; if (0 != FileNode->FileInfo.AllocationSize) { FileNode->FileData = LargeHeapAlloc((size_t)FileNode->FileInfo.AllocationSize); if (0 == FileNode->FileData) { MemfsFileNodeDelete(FileNode); if (0 != DirDesc) MemfsDirDescDelete(DirDesc); return STATUS_INSUFFICIENT_RESOURCES; } } Result = MemfsFileNodeMapInsert(Memfs->FileNodeMap, FileNode, &Inserted); if (!NT_SUCCESS(Result) || !Inserted) { MemfsFileNodeDelete(FileNode); if (0 != DirDesc) MemfsDirDescDelete(DirDesc); 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 /* since we now use dir descriptors to quickly retrieve dir contents, place it in UserContext2 */ FspFileSystemGetOperationContext()->Response->Rsp.Create.Opened.UserContext2 = (UINT_PTR)DirDesc; 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; MEMFS_DIR_DESC *DirDesc = 0; NTSTATUS Result; FileNode = MemfsFileNodeMapGet(Memfs->FileNodeMap, FileName); if (0 == FileNode) { Result = STATUS_OBJECT_NAME_NOT_FOUND; MemfsFileNodeMapGetParent(Memfs->FileNodeMap, FileName, &Result); return Result; } if (0 != (FileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { Result = MemfsDirDescCreate(&DirDesc); if (!NT_SUCCESS(Result)) return Result; } /* * NTFS and FastFat do this at Cleanup time, but we are going to cheat. * * To properly implement this we should maintain some state of whether * we modified the file or not. Alternatively we could have the driver * report to us at Cleanup time whether the file was modified. [The * FSD does maintain the FO_FILE_MODIFIED bit, but does not send it * to us.] * * TBD. */ if (0 == (FileNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (GrantedAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA))) FileNode->FileInfo.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE; 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 /* since we now use dir descriptors to quickly retrieve dir contents, place it in UserContext2 */ FspFileSystemGetOperationContext()->Response->Rsp.Create.Opened.UserContext2 = (UINT_PTR)DirDesc; return STATUS_SUCCESS; } static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, 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 = { FALSE }; ULONG Index; MemfsFileNodeMapEnumerateNamedStreams(Memfs->FileNodeMap, FileNode, MemfsFileNodeMapEnumerateFn, &Context); for (Index = 0; Context.Count > Index; Index++) if (1 >= Context.FileNodes[Index]->RefCount) MemfsFileNodeMapRemove(Memfs->FileNodeMap, Context.FileNodes[Index]); MemfsFileNodeMapEnumerateFree(&Context); #endif Result = SetFileSize(FileSystem, FileNode, AllocationSize, TRUE, FileInfo); 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.LastWriteTime = FileNode->FileInfo.LastAccessTime = MemfsGetSystemTime(); MemfsFileNodeGetFileInfo(FileNode, FileInfo); return STATUS_SUCCESS; } static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, PWSTR FileName, BOOLEAN Delete) { MEMFS *Memfs = (MEMFS *)FileSystem->UserContext; MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0; assert(Delete); /* the new FSP_FSCTL_VOLUME_PARAMS::PostCleanupOnDeleteOnly ensures this */ if (Delete && !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; MEMFS_DIR_DESC *DirDesc = (MEMFS_DIR_DESC *)(UINT_PTR) FspFileSystemGetOperationContext()->Request->Req.Close.UserContext2; MemfsFileNodeDereference(FileNode); if (0 != DirDesc) MemfsDirDescDelete(DirDesc); } 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; 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 = SetFileSize(FileSystem, FileNode, EndOffset, FALSE, FileInfo); if (!NT_SUCCESS(Result)) return Result; } } 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 FileNode) { /* nothing to do, since we do not cache anything */ 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, 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; MemfsFileNodeGetFileInfo(FileNode, FileInfo); return STATUS_SUCCESS; } static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo) { 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 = SetFileSize(FileSystem, FileNode, AllocationSize, TRUE, FileInfo); 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; } } 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 (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; UINT64 Offset; ULONG Length; PULONG PBytesTransferred; BOOLEAN OffsetFound; MEMFS_DIR_DESC *DirDesc; } MEMFS_READ_DIRECTORY_CONTEXT; static BOOLEAN AddDirInfo(MEMFS_DIR_DESC *DirDesc, 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); MemfsDirDescInsertFileName(DirDesc, FileNode->FileInfo.IndexNumber, FileName); } memset(DirInfo->Padding, 0, sizeof DirInfo->Padding); DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + wcslen(FileName) * sizeof(WCHAR)); DirInfo->FileInfo = FileNode->FileInfo; DirInfo->NextOffset = FileNode->FileInfo.IndexNumber; 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; if (0 != Context->Offset && !Context->OffsetFound) { Context->OffsetFound = FileNode->FileInfo.IndexNumber == Context->Offset; return TRUE; } return AddDirInfo(Context->DirDesc, FileNode, 0, Context->Buffer, Context->Length, Context->PBytesTransferred); } static NTSTATUS ReadDirectory(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, PVOID Buffer, UINT64 Offset, ULONG Length, PWSTR Pattern, PULONG PBytesTransferred) { MEMFS *Memfs = (MEMFS *)FileSystem->UserContext; MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0; MEMFS_DIR_DESC *DirDesc = (MEMFS_DIR_DESC *)(UINT_PTR) FspFileSystemGetOperationContext()->Request->Req.QueryDirectory.UserContext2; MEMFS_FILE_NODE *ParentNode; MEMFS_READ_DIRECTORY_CONTEXT Context; NTSTATUS Result; Context.Buffer = Buffer; Context.Offset = Offset; Context.Length = Length; Context.PBytesTransferred = PBytesTransferred; Context.OffsetFound = FALSE; Context.DirDesc = DirDesc; if (0 == Offset) MemfsDirDescReset(DirDesc); 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 == Offset) if (!AddDirInfo(DirDesc, FileNode, L".", Buffer, Length, PBytesTransferred)) return STATUS_SUCCESS; if (0 == Offset || FileNode->FileInfo.IndexNumber == Offset) { Context.OffsetFound = FileNode->FileInfo.IndexNumber == Context.Offset; if (!AddDirInfo(DirDesc, ParentNode, L"..", Buffer, Length, PBytesTransferred)) return STATUS_SUCCESS; } } if (MemfsFileNodeMapEnumerateChildren(Memfs->FileNodeMap, FileNode, MemfsDirDescGetFileName(DirDesc, Offset), ReadDirectoryEnumFn, &Context)) FspFileSystemAddDirInfo(0, Buffer, Length, PBytesTransferred); return STATUS_SUCCESS; } #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 static FSP_FILE_SYSTEM_INTERFACE MemfsInterface = { GetVolumeInfo, SetVolumeLabel, GetSecurityByName, Create, Open, Overwrite, 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 }; /* * Public API */ NTSTATUS MemfsCreate( ULONG Flags, ULONG FileInfoTimeout, ULONG MaxFileNodes, ULONG MaxFileSize, PWSTR VolumePrefix, PWSTR RootSddl, MEMFS **PMemfs) { NTSTATUS Result; FSP_FSCTL_VOLUME_PARAMS VolumeParams; BOOLEAN CaseInsensitive = !!(Flags & MemfsCaseInsensitive); PWSTR DevicePath = (Flags & MemfsNet) ? 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); Result = MemfsFileNodeMapCreate(CaseInsensitive, &Memfs->FileNodeMap); if (!NT_SUCCESS(Result)) { free(Memfs); LocalFree(RootSecurity); return Result; } memset(&VolumeParams, 0, sizeof VolumeParams); 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.PostCleanupOnDeleteOnly = 1; if (0 != VolumePrefix) wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix); wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR), 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) { return FspFileSystemStartDispatcher(Memfs->FileSystem, 0); } VOID MemfsStop(MEMFS *Memfs) { FspFileSystemStopDispatcher(Memfs->FileSystem); } 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; }