From d8686a7726176f839796e341b6dafa5f5a9ebe48 Mon Sep 17 00:00:00 2001 From: Bill Zissimopoulos Date: Wed, 13 Mar 2019 21:44:10 -0700 Subject: [PATCH] tst: memfs: extended attributes support --- inc/winfsp/winfsp.h | 66 ++++++++++- src/dll/fsop.c | 62 +++++++++- tst/memfs/memfs.cpp | 267 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 384 insertions(+), 11 deletions(-) diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h index b02cd46e..2d08893f 100644 --- a/inc/winfsp/winfsp.h +++ b/inc/winfsp/winfsp.h @@ -88,6 +88,9 @@ typedef struct _REPARSE_DATA_BUFFER /* * The FILE_FULL_EA_INFORMATION definitions are missing from the user mode headers. */ +#if !defined(FILE_NEED_EA) +#define FILE_NEED_EA 0x00000080 +#endif typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; @@ -953,7 +956,7 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE NTSTATUS (*CreateEx)(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize, - PFILE_FULL_EA_INFORMATION Ea, SIZE_T EaLength, + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo); /** * Overwrite a file. @@ -985,7 +988,7 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE */ NTSTATUS (*OverwriteEx)(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, - PFILE_FULL_EA_INFORMATION Ea, SIZE_T EaLength, + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, FSP_FSCTL_FILE_INFO *FileInfo); /** * Get extended attributes. @@ -996,16 +999,19 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE * The file context of the file to get extended attributes for. * @param Ea * Extended attributes buffer. - * @param EaLength [in,out] + * @param EaLength * Extended attributes buffer length. + * @param PBytesTransferred [out] + * Pointer to a memory location that will receive the actual number of bytes transferred. * @return * STATUS_SUCCESS or error code. * @see * SetEa + * FspFileSystemAddEa */ NTSTATUS (*GetEa)(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, - PFILE_FULL_EA_INFORMATION Ea, PSIZE_T PEaLength); + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred); /** * Set extended attributes. * @@ -1024,7 +1030,7 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE */ NTSTATUS (*SetEa)(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, - PFILE_FULL_EA_INFORMATION Ea, SIZE_T EaLength); + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength); /* * This ensures that this interface will always contain 64 function pointers. @@ -1568,6 +1574,56 @@ FSP_API NTSTATUS FspFileSystemCanReplaceReparsePoint( */ FSP_API BOOLEAN FspFileSystemAddStreamInfo(FSP_FSCTL_STREAM_INFO *StreamInfo, PVOID Buffer, ULONG Length, PULONG PBytesTransferred); +/** + * Enumerate extended attributes in a buffer. + * + * This is a helper for implementing the CreateEx and SetEa operations in file systems + * that support extended attributes. + * + * @param FileSystem + * The file system object. + * @param EnumerateEa + * Pointer to function that receives a single extended attribute. The function + * should return STATUS_SUCCESS or an error code if unsuccessful. + * @param Context + * User context to supply to EnumEa. + * @param Ea + * Extended attributes buffer. + * @param EaLength + * Extended attributes buffer length. + * @return + * STATUS_SUCCESS or error code from EnumerateEa. + */ +FSP_API NTSTATUS FspFileSystemEnumerateEa(FSP_FILE_SYSTEM *FileSystem, + NTSTATUS (*EnumerateEa)( + FSP_FILE_SYSTEM *FileSystem, PVOID Context, + PFILE_FULL_EA_INFORMATION SingleEa), + PVOID Context, + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength); +/** + * Add extended attribute to a buffer. + * + * This is a helper for implementing the GetEa operation. + * + * @param SingleEa + * The extended attribute to add. A value of NULL acts as an EOF marker for a GetEa + * operation. + * @param Ea + * Pointer to a buffer that will receive the extended attribute. This should contain + * the same value passed to the GetEa Ea parameter. + * @param EaLength + * Length of buffer. This should contain the same value passed to the GetEa + * EaLength parameter. + * @param PBytesTransferred [out] + * Pointer to a memory location that will receive the actual number of bytes stored. This should + * contain the same value passed to the GetEa PBytesTransferred parameter. + * @return + * TRUE if the extended attribute was added, FALSE if there was not enough space to add it. + * @see + * GetEa + */ +FSP_API BOOLEAN FspFileSystemAddEa(PFILE_FULL_EA_INFORMATION SingleEa, + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred); /* * Directory buffering diff --git a/src/dll/fsop.c b/src/dll/fsop.c index b8c1aca5..54b25822 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -1187,21 +1187,21 @@ FSP_API NTSTATUS FspFileSystemOpQueryEa(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { NTSTATUS Result; - SIZE_T EaSize; + ULONG BytesTransferred; if (0 == FileSystem->Interface->GetEa) return STATUS_INVALID_DEVICE_REQUEST; - EaSize = FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX; + BytesTransferred = 0; Result = FileSystem->Interface->GetEa(FileSystem, (PVOID)ValOfFileContext(Request->Req.QueryEa), - (PVOID)Response->Buffer, &EaSize); + (PVOID)Response->Buffer, FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX, &BytesTransferred); if (!NT_SUCCESS(Result)) return STATUS_BUFFER_OVERFLOW != Result ? Result : STATUS_EA_LIST_INCONSISTENT; - Response->Size = (UINT16)(sizeof *Response + EaSize); + Response->Size = (UINT16)(sizeof *Response + BytesTransferred); Response->Rsp.QueryEa.Ea.Offset = 0; - Response->Rsp.QueryEa.Ea.Size = (UINT16)EaSize; + Response->Rsp.QueryEa.Ea.Size = (UINT16)BytesTransferred; return STATUS_SUCCESS; } @@ -1797,3 +1797,55 @@ FSP_API BOOLEAN FspFileSystemAddStreamInfo(FSP_FSCTL_STREAM_INFO *StreamInfo, { return FspFileSystemAddXxxInfo(StreamInfo, Buffer, Length, PBytesTransferred); } + +FSP_API NTSTATUS FspFileSystemEnumerateEa(FSP_FILE_SYSTEM *FileSystem, + NTSTATUS (*EnumerateEa)( + FSP_FILE_SYSTEM *FileSystem, PVOID Context, + PFILE_FULL_EA_INFORMATION SingleEa), + PVOID Context, + PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength) +{ + PFILE_FULL_EA_INFORMATION EaEnd = (PVOID)((PUINT8)Ea + EaLength); + NTSTATUS Result; + + Result = STATUS_SUCCESS; + for (; + EaEnd > Ea && 0 != Ea->NextEntryOffset; + Ea = (PVOID)((PUINT8)Ea + Ea->NextEntryOffset)) + { + Result = EnumerateEa(FileSystem, Context, Ea); + if (!NT_SUCCESS(Result)) + break; + } + + return Result; +} + +FSP_API BOOLEAN FspFileSystemAddEa(PFILE_FULL_EA_INFORMATION SingleEa, + PFILE_FULL_EA_INFORMATION Ea, ULONG Length, PULONG PBytesTransferred) +{ + if (0 != SingleEa) + { + PUINT8 EaEnd = (PUINT8)Ea + Length; + ULONG EaLength = sizeof *SingleEa + SingleEa->EaNameLength + SingleEa->EaValueLength; + + Ea = (PVOID)((PUINT8)Ea + *PBytesTransferred); + if ((PUINT8)Ea + EaLength > EaEnd) + return FALSE; + + memcpy(Ea, SingleEa, EaLength); + Ea->NextEntryOffset = FSP_FSCTL_ALIGN_UP(EaLength, sizeof(ULONG)); + *PBytesTransferred += EaLength; + } + else if (sizeof *SingleEa <= *PBytesTransferred) + { + PUINT8 EaEnd = (PUINT8)Ea + *PBytesTransferred; + + while (EaEnd > (PUINT8)Ea + Ea->NextEntryOffset) + Ea = (PVOID)((PUINT8)Ea + Ea->NextEntryOffset); + + Ea->NextEntryOffset = 0; + } + + return TRUE; +} diff --git a/tst/memfs/memfs.cpp b/tst/memfs/memfs.cpp index f361b048..bc0dbecb 100644 --- a/tst/memfs/memfs.cpp +++ b/tst/memfs/memfs.cpp @@ -64,6 +64,11 @@ FSP_FSCTL_STATIC_ASSERT(MEMFS_MAX_PATH > MAX_PATH, */ #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. @@ -237,6 +242,36 @@ BOOLEAN MemfsFileNameHasPrefix(PWSTR a, PWSTR b, BOOLEAN CaseInsensitive) #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 MEMFS_FILE_NODE_EA_MAP; +#endif + typedef struct _MEMFS_FILE_NODE { WCHAR FileName[MEMFS_MAX_PATH]; @@ -252,6 +287,9 @@ typedef struct _MEMFS_FILE_NODE #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 @@ -311,6 +349,15 @@ NTSTATUS MemfsFileNodeCreate(PWSTR FileName, MEMFS_FILE_NODE **PFileNode) 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 @@ -351,6 +398,120 @@ VOID MemfsFileNodeGetFileInfo(MEMFS_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *Fi #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) { @@ -869,6 +1030,9 @@ static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM *FileSystem, 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; @@ -948,6 +1112,18 @@ static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem, 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) { @@ -1005,6 +1181,22 @@ static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem, 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); @@ -1025,6 +1217,9 @@ static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem, 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; @@ -1047,6 +1242,15 @@ static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem, 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; @@ -1881,7 +2085,7 @@ static NTSTATUS GetStreamInfo(FSP_FILE_SYSTEM *FileSystem, #if defined(MEMFS_CONTROL) static NTSTATUS Control(FSP_FILE_SYSTEM *FileSystem, - PVOID FileContext, UINT32 ControlCode, + PVOID FileNode, UINT32 ControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred) { @@ -1911,14 +2115,63 @@ static NTSTATUS Control(FSP_FILE_SYSTEM *FileSystem, } #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, @@ -1957,6 +2210,18 @@ static FSP_FILE_SYSTEM_INTERFACE MemfsInterface = Control, #else 0, +#endif + 0, +#if defined(MEMFS_EA) + Create, + Overwrite, + GetEa, + SetEa +#else + 0, + 0, + 0, + 0, #endif };