/**
 * @file winfsp/winfsp.hpp
 * WinFsp C++ Layer.
 *
 * @copyright 2015-2026 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.
 */

#ifndef WINFSP_WINFSP_HPP_INCLUDED
#define WINFSP_WINFSP_HPP_INCLUDED

#ifndef __cplusplus
#error this header requires a C++ compiler
#endif

#include <winfsp/winfsp.h>

#define FSP_CPP_EXCEPTION_GUARD(...)\
    try { __VA_ARGS__ } catch (...) { return self->ExceptionHandler(); }
#define FSP_CPP_EXCEPTION_GUARD_VOID(...)\
    try { __VA_ARGS__ } catch (...) { self->ExceptionHandler(); return; }

namespace Fsp {

inline NTSTATUS Initialize()
{
    static NTSTATUS LoadResult = FspLoad(0);
    return LoadResult;
}

class FileSystemBase
{
public:
    typedef FSP_FSCTL_VOLUME_INFO VolumeInfo;
    typedef FSP_FSCTL_FILE_INFO FileInfo;
    typedef FSP_FSCTL_OPEN_FILE_INFO OpenFileInfo;
    typedef FSP_FSCTL_DIR_INFO DirInfo;
    typedef FSP_FSCTL_STREAM_INFO StreamInfo;
    enum CleanupFlags
    {
        CleanupDelete                   = FspCleanupDelete,
        CleanupSetAllocationSize        = FspCleanupSetAllocationSize,
        CleanupSetArchiveBit            = FspCleanupSetArchiveBit,
        CleanupSetLastAccessTime        = FspCleanupSetLastAccessTime,
        CleanupSetLastWriteTime         = FspCleanupSetLastWriteTime,
        CleanupSetChangeTime            = FspCleanupSetChangeTime,
    };

public:
    FileSystemBase()
    {
    }
    virtual ~FileSystemBase()
    {
    }

    /* operations */
    virtual NTSTATUS ExceptionHandler()
    {
        return STATUS_UNEXPECTED_IO_ERROR;
    }
    virtual NTSTATUS Init(PVOID Host)
    {
        return STATUS_SUCCESS;
    }
    virtual NTSTATUS Mounted(PVOID Host)
    {
        return STATUS_SUCCESS;
    }
    virtual VOID Unmounted(PVOID Host)
    {
    }
    virtual NTSTATUS GetVolumeInfo(
        VolumeInfo *VolumeInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS SetVolumeLabel_(
        PWSTR VolumeLabel,
        VolumeInfo *VolumeInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS GetSecurityByName(
        PWSTR FileName,
        PUINT32 PFileAttributes/* or ReparsePointIndex */,
        PSECURITY_DESCRIPTOR SecurityDescriptor,
        SIZE_T *PSecurityDescriptorSize)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS Create(
        PWSTR FileName,
        UINT32 CreateOptions,
        UINT32 GrantedAccess,
        UINT32 FileAttributes,
        PSECURITY_DESCRIPTOR SecurityDescriptor,
        UINT64 AllocationSize,
        PVOID *PFileNode,
        PVOID *PFileDesc,
        OpenFileInfo *OpenFileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS Open(
        PWSTR FileName,
        UINT32 CreateOptions,
        UINT32 GrantedAccess,
        PVOID *PFileNode,
        PVOID *PFileDesc,
        OpenFileInfo *OpenFileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS Overwrite(
        PVOID FileNode,
        PVOID FileDesc,
        UINT32 FileAttributes,
        BOOLEAN ReplaceFileAttributes,
        UINT64 AllocationSize,
        FileInfo *FileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual VOID Cleanup(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR FileName,
        ULONG Flags)
    {
    }
    virtual VOID Close(
        PVOID FileNode,
        PVOID FileDesc)
    {
    }
    virtual NTSTATUS Read(
        PVOID FileNode,
        PVOID FileDesc,
        PVOID Buffer,
        UINT64 Offset,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS Write(
        PVOID FileNode,
        PVOID FileDesc,
        PVOID Buffer,
        UINT64 Offset,
        ULONG Length,
        BOOLEAN WriteToEndOfFile,
        BOOLEAN ConstrainedIo,
        PULONG PBytesTransferred,
        FileInfo *FileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS Flush(
        PVOID FileNode,
        PVOID FileDesc,
        FileInfo *FileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS GetFileInfo(
        PVOID FileNode,
        PVOID FileDesc,
        FileInfo *FileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS SetBasicInfo(
        PVOID FileNode,
        PVOID FileDesc,
        UINT32 FileAttributes,
        UINT64 CreationTime,
        UINT64 LastAccessTime,
        UINT64 LastWriteTime,
        UINT64 ChangeTime,
        FileInfo *FileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS SetFileSize(
        PVOID FileNode,
        PVOID FileDesc,
        UINT64 NewSize,
        BOOLEAN SetAllocationSize,
        FileInfo *FileInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS CanDelete(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR FileName)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS Rename(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR FileName,
        PWSTR NewFileName,
        BOOLEAN ReplaceIfExists)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS GetSecurity(
        PVOID FileNode,
        PVOID FileDesc,
        PSECURITY_DESCRIPTOR SecurityDescriptor,
        SIZE_T *PSecurityDescriptorSize)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS SetSecurity(
        PVOID FileNode,
        PVOID FileDesc,
        SECURITY_INFORMATION SecurityInformation,
        PSECURITY_DESCRIPTOR ModificationDescriptor)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS ReadDirectory(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR Pattern,
        PWSTR Marker,
        PVOID Buffer,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        return SeekableReadDirectory(
            FileNode,
            FileDesc,
            Pattern,
            Marker,
            Buffer,
            Length,
            PBytesTransferred);
    }
    virtual NTSTATUS ReadDirectoryEntry(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR Pattern,
        PWSTR Marker,
        PVOID *PContext,
        DirInfo *DirInfo)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS ResolveReparsePoints(
        PWSTR FileName,
        UINT32 ReparsePointIndex,
        BOOLEAN ResolveLastPathComponent,
        PIO_STATUS_BLOCK PIoStatus,
        PVOID Buffer,
        PSIZE_T PSize)
    {
        return FspFileSystemResolveReparsePoints(
            0,
            GetReparsePointByName,
            this,
            FileName,
            ReparsePointIndex,
            ResolveLastPathComponent,
            PIoStatus,
            Buffer,
            PSize);
    }
    virtual NTSTATUS GetReparsePointByName(
        PWSTR FileName,
        BOOLEAN IsDirectory,
        PVOID Buffer,
        PSIZE_T PSize)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS GetReparsePoint(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR FileName,
        PVOID Buffer,
        PSIZE_T PSize)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS SetReparsePoint(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR FileName,
        PVOID Buffer,
        SIZE_T Size)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS DeleteReparsePoint(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR FileName,
        PVOID Buffer,
        SIZE_T Size)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    virtual NTSTATUS GetStreamInfo(
        PVOID FileNode,
        PVOID FileDesc,
        PVOID Buffer,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        return STATUS_INVALID_DEVICE_REQUEST;
    }

    /* helpers */
    static NTSTATUS NtStatusFromWin32(DWORD Error)
    {
        return FspNtStatusFromWin32(Error);
    }
    static DWORD Win32FromNtStatus(NTSTATUS Status)
    {
        return FspWin32FromNtStatus(Status);
    }
    static VOID DeleteDirectoryBuffer(PVOID *PDirBuffer)
    {
        FspFileSystemDeleteDirectoryBuffer(PDirBuffer);
    }
    NTSTATUS SeekableReadDirectory(
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR Pattern,
        PWSTR Marker,
        PVOID Buffer,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        PVOID Context = 0;
        union
        {
            UINT8 B[FIELD_OFFSET(FileSystemBase::DirInfo, FileNameBuf) + MAX_PATH * sizeof(WCHAR)];
            FileSystemBase::DirInfo D;
        } DirInfoBuf;
        FileSystemBase::DirInfo *DirInfo = &DirInfoBuf.D;
        NTSTATUS Result = STATUS_SUCCESS;
        *PBytesTransferred = 0;
        for (;;)
        {
            Result = ReadDirectoryEntry(FileNode, FileDesc, Pattern, Marker, &Context, DirInfo);
            if (STATUS_NO_MORE_FILES == Result)
            {
                Result = STATUS_SUCCESS;
                break;
            }
            if (!NT_SUCCESS(Result))
                break;
            if (!FspFileSystemAddDirInfo(DirInfo, Buffer, Length, PBytesTransferred))
                break;
        }
        if (!NT_SUCCESS(Result))
            return Result;
        return STATUS_SUCCESS;
    }
    NTSTATUS BufferedReadDirectory(
        PVOID *PDirBuffer,
        PVOID FileNode,
        PVOID FileDesc,
        PWSTR Pattern,
        PWSTR Marker,
        PVOID Buffer,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        PVOID Context = 0;
        union
        {
            UINT8 B[FIELD_OFFSET(FileSystemBase::DirInfo, FileNameBuf) + MAX_PATH * sizeof(WCHAR)];
            FileSystemBase::DirInfo D;
        } DirInfoBuf;
        FileSystemBase::DirInfo *DirInfo = &DirInfoBuf.D;
        NTSTATUS Result = STATUS_SUCCESS;
        *PBytesTransferred = 0;
        if (FspFileSystemAcquireDirectoryBuffer(PDirBuffer, 0 == Marker, &Result))
        {
            try
            {
                for (;;)
                {
                    Result = ReadDirectoryEntry(FileNode, FileDesc, Pattern, Marker, &Context, DirInfo);
                    if (STATUS_NO_MORE_FILES == Result)
                    {
                        Result = STATUS_SUCCESS;
                        break;
                    }
                    if (!NT_SUCCESS(Result))
                        break;
                    if (!FspFileSystemFillDirectoryBuffer(PDirBuffer, DirInfo, &Result))
                        break;
                }
            }
            catch (...)
            {
                FspFileSystemReleaseDirectoryBuffer(PDirBuffer);
                throw;
            }
            FspFileSystemReleaseDirectoryBuffer(PDirBuffer);
        }
        if (!NT_SUCCESS(Result))
            return Result;
        FspFileSystemReadDirectoryBuffer(PDirBuffer, Marker, Buffer, Length, PBytesTransferred);
        return STATUS_SUCCESS;
    }
    BOOLEAN FindReparsePoint(
        PWSTR FileName, PUINT32 PReparsePointIndex)
    {
        return FspFileSystemFindReparsePoint(
            0,
            GetReparsePointByName,
            this,
            FileName,
            PReparsePointIndex);
    }
    static NTSTATUS CanReplaceReparsePoint(
        PVOID CurrentReparseData, SIZE_T CurrentReparseDataSize,
        PVOID ReplaceReparseData, SIZE_T ReplaceReparseDataSize)
    {
        return FspFileSystemCanReplaceReparsePoint(
            CurrentReparseData, CurrentReparseDataSize,
            ReplaceReparseData, ReplaceReparseDataSize);
    }
    static BOOLEAN AddStreamInfo(StreamInfo *StreamInfo,
        PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
    {
        return FspFileSystemAddStreamInfo(StreamInfo, Buffer, Length, PBytesTransferred);
    }

private:
    static NTSTATUS GetReparsePointByName(FSP_FILE_SYSTEM *FileSystem,
        PVOID Context,
        PWSTR FileName,
        BOOLEAN IsDirectory,
        PVOID Buffer,
        PSIZE_T PSize)
    {
        FileSystemBase *self = (FileSystemBase *)Context;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetReparsePointByName(
                FileName,
                IsDirectory,
                Buffer,
                PSize);
        )
    }

private:
    /* disallow copy and assignment */
    FileSystemBase(const FileSystemBase &);
    FileSystemBase &operator=(const FileSystemBase &);
};

class FileSystemHost
{
public:
    /* ctor/dtor */
    FileSystemHost(FileSystemBase &FileSystem) :
        _VolumeParams(), _FileSystemPtr(0), _FileSystem(&FileSystem)
    {
        Initialize();
        _VolumeParams.UmFileContextIsFullContext = 1;
    }
    virtual ~FileSystemHost()
    {
        if (0 != _FileSystemPtr)
            FspFileSystemDelete(_FileSystemPtr);
    }

    /* properties */
    UINT16 SectorSize()
    {
        return _VolumeParams.SectorSize;
    }
    VOID SetSectorSize(UINT16 SectorSize)
    {
        _VolumeParams.SectorSize = SectorSize;
    }
    UINT16 SectorsPerAllocationUnit()
    {
        return _VolumeParams.SectorsPerAllocationUnit;
    }
    VOID SetSectorsPerAllocationUnit(UINT16 SectorsPerAllocationUnit)
    {
        _VolumeParams.SectorsPerAllocationUnit = SectorsPerAllocationUnit;
    }
    UINT16 MaxComponentLength()
    {
        return _VolumeParams.MaxComponentLength;
    }
    VOID SetMaxComponentLength(UINT16 MaxComponentLength)
    {
        _VolumeParams.MaxComponentLength = MaxComponentLength;
    }
    UINT64 VolumeCreationTime()
    {
        return _VolumeParams.VolumeCreationTime;
    }
    VOID SetVolumeCreationTime(UINT64 VolumeCreationTime)
    {
        _VolumeParams.VolumeCreationTime = VolumeCreationTime;
    }
    UINT32 VolumeSerialNumber()
    {
        return _VolumeParams.VolumeSerialNumber;
    }
    VOID SetVolumeSerialNumber(UINT32 VolumeSerialNumber)
    {
        _VolumeParams.VolumeSerialNumber = VolumeSerialNumber;
    }
    UINT32 FileInfoTimeout()
    {
        return _VolumeParams.FileInfoTimeout;
    }
    VOID SetFileInfoTimeout(UINT32 FileInfoTimeout)
    {
        _VolumeParams.FileInfoTimeout = FileInfoTimeout;
    }
    BOOLEAN CaseSensitiveSearch()
    {
        return _VolumeParams.CaseSensitiveSearch;
    }
    VOID SetCaseSensitiveSearch(BOOLEAN CaseSensitiveSearch)
    {
        _VolumeParams.CaseSensitiveSearch = !!CaseSensitiveSearch;
    }
    BOOLEAN CasePreservedNames()
    {
        return _VolumeParams.CasePreservedNames;
    }
    VOID SetCasePreservedNames(BOOLEAN CasePreservedNames)
    {
        _VolumeParams.CasePreservedNames = !!CasePreservedNames;
    }
    BOOLEAN UnicodeOnDisk()
    {
        return _VolumeParams.UnicodeOnDisk;
    }
    VOID SetUnicodeOnDisk(BOOLEAN UnicodeOnDisk)
    {
        _VolumeParams.UnicodeOnDisk = !!UnicodeOnDisk;
    }
    BOOLEAN PersistentAcls()
    {
        return _VolumeParams.PersistentAcls;
    }
    VOID SetPersistentAcls(BOOLEAN PersistentAcls)
    {
        _VolumeParams.PersistentAcls = !!PersistentAcls;
    }
    BOOLEAN ReparsePoints()
    {
        return _VolumeParams.ReparsePoints;
    }
    VOID SetReparsePoints(BOOLEAN ReparsePoints)
    {
        _VolumeParams.ReparsePoints = !!ReparsePoints;
    }
    BOOLEAN ReparsePointsAccessCheck()
    {
        return _VolumeParams.ReparsePointsAccessCheck;
    }
    VOID SetReparsePointsAccessCheck(BOOLEAN ReparsePointsAccessCheck)
    {
        _VolumeParams.ReparsePointsAccessCheck = !!ReparsePointsAccessCheck;
    }
    BOOLEAN NamedStreams()
    {
        return _VolumeParams.NamedStreams;
    }
    VOID SetNamedStreams(BOOLEAN NamedStreams)
    {
        _VolumeParams.NamedStreams = !!NamedStreams;
    }
    BOOLEAN PostCleanupWhenModifiedOnly()
    {
        return _VolumeParams.PostCleanupWhenModifiedOnly;
    }
    VOID SetPostCleanupWhenModifiedOnly(BOOLEAN PostCleanupWhenModifiedOnly)
    {
        _VolumeParams.PostCleanupWhenModifiedOnly = !!PostCleanupWhenModifiedOnly;
    }
    BOOLEAN PassQueryDirectoryPattern()
    {
        return _VolumeParams.PassQueryDirectoryPattern;
    }
    VOID SetPassQueryDirectoryPattern(BOOLEAN PassQueryDirectoryPattern)
    {
        _VolumeParams.PassQueryDirectoryPattern = !!PassQueryDirectoryPattern;
    }
    BOOLEAN FlushAndPurgeOnCleanup()
    {
        return _VolumeParams.FlushAndPurgeOnCleanup;
    }
    VOID SetFlushAndPurgeOnCleanup(BOOLEAN FlushAndPurgeOnCleanup)
    {
        _VolumeParams.FlushAndPurgeOnCleanup = !!FlushAndPurgeOnCleanup;
    }
    PWSTR Prefix()
    {
        return _VolumeParams.Prefix;
    }
    VOID SetPrefix(PWSTR Prefix)
    {
        int Size = lstrlenW(Prefix) * sizeof(WCHAR);
        if (Size > sizeof _VolumeParams.Prefix - sizeof(WCHAR))
            Size = sizeof _VolumeParams.Prefix - sizeof(WCHAR);
        RtlCopyMemory(_VolumeParams.Prefix, Prefix, Size);
        _VolumeParams.Prefix[Size / sizeof(WCHAR)] = L'\0';
    }
    PWSTR FileSystemName()
    {
        return _VolumeParams.FileSystemName;
    }
    VOID SetFileSystemName(PWSTR FileSystemName)
    {
        int Size = lstrlenW(FileSystemName) * sizeof(WCHAR);
        if (Size > sizeof _VolumeParams.FileSystemName - sizeof(WCHAR))
            Size = sizeof _VolumeParams.FileSystemName - sizeof(WCHAR);
        RtlCopyMemory(_VolumeParams.FileSystemName, FileSystemName, Size);
        _VolumeParams.FileSystemName[Size / sizeof(WCHAR)] = L'\0';
    }

    /* control */
    NTSTATUS Preflight(PWSTR MountPoint)
    {
        return FspFileSystemPreflight(
            _VolumeParams.Prefix[0] ? L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME,
            MountPoint);
    }
    NTSTATUS Mount(PWSTR MountPoint,
        PSECURITY_DESCRIPTOR SecurityDescriptor = 0,
        BOOLEAN Synchronized = FALSE,
        UINT32 DebugLog = 0)
    {
        NTSTATUS Result;
        try
        {
            Result = _FileSystem->Init(this);
        }
        catch (...)
        {
            Result = _FileSystem->ExceptionHandler();
        }
        if (!NT_SUCCESS(Result))
            return Result;
        Result = FspFileSystemCreate(
            _VolumeParams.Prefix[0] ? L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME,
            &_VolumeParams, Interface(), &_FileSystemPtr);
        if (!NT_SUCCESS(Result))
            return Result;
        _FileSystemPtr->UserContext = _FileSystem;
        FspFileSystemSetOperationGuardStrategy(_FileSystemPtr, Synchronized ?
            FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_COARSE :
            FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_FINE);
        FspFileSystemSetDebugLog(_FileSystemPtr, DebugLog);
        Result = FspFileSystemSetMountPointEx(_FileSystemPtr, MountPoint, SecurityDescriptor);
        if (NT_SUCCESS(Result))
        {
            try
            {
                Result = _FileSystem->Mounted(this);
            }
            catch (...)
            {
                Result = _FileSystem->ExceptionHandler();
            }
            if (NT_SUCCESS(Result))
            {
                Result = FspFileSystemStartDispatcher(_FileSystemPtr, 0);
                if (!NT_SUCCESS(Result))
                    try
                    {
                        _FileSystem->Unmounted(this);
                    }
                    catch (...)
                    {
                        _FileSystem->ExceptionHandler();
                    }
            }
        }
        if (!NT_SUCCESS(Result))
        {
            FspFileSystemDelete(_FileSystemPtr);
            _FileSystemPtr = 0;
        }
        return Result;
    }
    VOID Unmount()
    {
        FspFileSystemStopDispatcher(_FileSystemPtr);
        try
        {
            _FileSystem->Unmounted(this);
        }
        catch (...)
        {
            _FileSystem->ExceptionHandler();
        }
        _FileSystemPtr->UserContext = 0;
        FspFileSystemDelete(_FileSystemPtr);
        _FileSystemPtr = 0;
    }
    PWSTR MountPoint()
    {
        return 0 != _FileSystemPtr ? FspFileSystemMountPoint(_FileSystemPtr) : 0;
    }
    FSP_FILE_SYSTEM *FileSystemHandle()
    {
        return _FileSystemPtr;
    }
    FileSystemBase &FileSystem()
    {
        return *_FileSystem;
    }
    static NTSTATUS SetDebugLogFile(PWSTR FileName)
    {
        HANDLE Handle;
        if ('-' == FileName[0] && '\0' == FileName[1])
            Handle = GetStdHandle(STD_ERROR_HANDLE);
        else
            Handle = CreateFileW(
                FileName,
                FILE_APPEND_DATA,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                0,
                OPEN_ALWAYS,
                FILE_ATTRIBUTE_NORMAL,
                0);
        if (INVALID_HANDLE_VALUE == Handle)
            return FspNtStatusFromWin32(GetLastError());
        FspDebugLogSetHandle(Handle);
        return STATUS_SUCCESS;
    }

private:
    /* FSP_FILE_SYSTEM_INTERFACE */
    static NTSTATUS GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem0,
        FSP_FSCTL_VOLUME_INFO *VolumeInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetVolumeInfo(
                VolumeInfo);
        )
    }
    static NTSTATUS SetVolumeLabel_(FSP_FILE_SYSTEM *FileSystem0,
        PWSTR VolumeLabel,
        FSP_FSCTL_VOLUME_INFO *VolumeInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->SetVolumeLabel_(
                VolumeLabel,
                VolumeInfo);
        )
    }
    static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM *FileSystem0,
        PWSTR FileName,
        PUINT32 PFileAttributes/* or ReparsePointIndex */,
        PSECURITY_DESCRIPTOR SecurityDescriptor,
        SIZE_T *PSecurityDescriptorSize)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetSecurityByName(
                FileName,
                PFileAttributes,
                SecurityDescriptor,
                PSecurityDescriptorSize);
        )
    }
    static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem0,
        PWSTR FileName,
        UINT32 CreateOptions,
        UINT32 GrantedAccess,
        UINT32 FileAttributes,
        PSECURITY_DESCRIPTOR SecurityDescriptor,
        UINT64 AllocationSize,
        PVOID *FullContext,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        PVOID FileNode, FileDesc;
        NTSTATUS Result;
        FSP_CPP_EXCEPTION_GUARD(
            Result = self->Create(
                FileName,
                CreateOptions,
                GrantedAccess,
                FileAttributes,
                SecurityDescriptor,
                AllocationSize,
                &FileNode,
                &FileDesc,
                FspFileSystemGetOpenFileInfo(FileInfo));
        )
        ((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext = (UINT64)(UINT_PTR)FileNode;
        ((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2 = (UINT64)(UINT_PTR)FileDesc;
        return Result;
    }
    static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem0,
        PWSTR FileName,
        UINT32 CreateOptions,
        UINT32 GrantedAccess,
        PVOID *FullContext,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        PVOID FileNode, FileDesc;
        NTSTATUS Result;
        FSP_CPP_EXCEPTION_GUARD(
            Result = self->Open(
                FileName,
                CreateOptions,
                GrantedAccess,
                &FileNode,
                &FileDesc,
                FspFileSystemGetOpenFileInfo(FileInfo));
        )
        ((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext = (UINT64)(UINT_PTR)FileNode;
        ((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2 = (UINT64)(UINT_PTR)FileDesc;
        return Result;
    }
    static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        UINT32 FileAttributes,
        BOOLEAN ReplaceFileAttributes,
        UINT64 AllocationSize,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->Overwrite(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileAttributes,
                ReplaceFileAttributes,
                AllocationSize,
                FileInfo);
        )
    }
    static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR FileName,
        ULONG Flags)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD_VOID(
            return self->Cleanup(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileName,
                Flags);
        )
    }
    static VOID Close(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD_VOID(
            return self->Close(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2);
        )
    }
    static NTSTATUS Read(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PVOID Buffer,
        UINT64 Offset,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->Read(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                Buffer,
                Offset,
                Length,
                PBytesTransferred);
        )
    }
    static NTSTATUS Write(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PVOID Buffer,
        UINT64 Offset,
        ULONG Length,
        BOOLEAN WriteToEndOfFile,
        BOOLEAN ConstrainedIo,
        PULONG PBytesTransferred,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->Write(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                Buffer,
                Offset,
                Length,
                WriteToEndOfFile,
                ConstrainedIo,
                PBytesTransferred,
                FileInfo);
        )
    }
    static NTSTATUS Flush(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->Flush(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileInfo);
        )
    }
    static NTSTATUS GetFileInfo(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetFileInfo(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileInfo);
        )
    }
    static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        UINT32 FileAttributes,
        UINT64 CreationTime,
        UINT64 LastAccessTime,
        UINT64 LastWriteTime,
        UINT64 ChangeTime,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->SetBasicInfo(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileAttributes,
                CreationTime,
                LastAccessTime,
                LastWriteTime,
                ChangeTime,
                FileInfo);
        )
    }
    static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        UINT64 NewSize,
        BOOLEAN SetAllocationSize,
        FSP_FSCTL_FILE_INFO *FileInfo)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->SetFileSize(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                NewSize,
                SetAllocationSize,
                FileInfo);
        )
    }
    static NTSTATUS CanDelete(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR FileName)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->CanDelete(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileName);
        )
    }
    static NTSTATUS Rename(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR FileName,
        PWSTR NewFileName,
        BOOLEAN ReplaceIfExists)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->Rename(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileName,
                NewFileName,
                ReplaceIfExists);
        )
    }
    static NTSTATUS GetSecurity(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PSECURITY_DESCRIPTOR SecurityDescriptor,
        SIZE_T *PSecurityDescriptorSize)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetSecurity(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                SecurityDescriptor,
                PSecurityDescriptorSize);
        )
    }
    static NTSTATUS SetSecurity(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        SECURITY_INFORMATION SecurityInformation,
        PSECURITY_DESCRIPTOR ModificationDescriptor)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->SetSecurity(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                SecurityInformation,
                ModificationDescriptor);
        )
    }
    static NTSTATUS ReadDirectory(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR Pattern,
        PWSTR Marker,
        PVOID Buffer,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->ReadDirectory(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                Pattern,
                Marker,
                Buffer,
                Length,
                PBytesTransferred);
        )
    }
    static NTSTATUS ResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem0,
        PWSTR FileName,
        UINT32 ReparsePointIndex,
        BOOLEAN ResolveLastPathComponent,
        PIO_STATUS_BLOCK PIoStatus,
        PVOID Buffer,
        PSIZE_T PSize)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->ResolveReparsePoints(
                FileName,
                ReparsePointIndex,
                ResolveLastPathComponent,
                PIoStatus,
                Buffer,
                PSize);
        )
    }
    static NTSTATUS GetReparsePoint(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR FileName,
        PVOID Buffer,
        PSIZE_T PSize)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetReparsePoint(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileName,
                Buffer,
                PSize);
        )
    }
    static NTSTATUS SetReparsePoint(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR FileName,
        PVOID Buffer,
        SIZE_T Size)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->SetReparsePoint(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileName,
                Buffer,
                Size);
        )
    }
    static NTSTATUS DeleteReparsePoint(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PWSTR FileName,
        PVOID Buffer,
        SIZE_T Size)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->DeleteReparsePoint(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                FileName,
                Buffer,
                Size);
        )
    }
    static NTSTATUS GetStreamInfo(FSP_FILE_SYSTEM *FileSystem0,
        PVOID FullContext,
        PVOID Buffer,
        ULONG Length,
        PULONG PBytesTransferred)
    {
        FileSystemBase *self = (FileSystemBase *)FileSystem0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->GetStreamInfo(
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext,
                (PVOID)(UINT_PTR)((FSP_FSCTL_TRANSACT_FULL_CONTEXT *)FullContext)->UserContext2,
                Buffer,
                Length,
                PBytesTransferred);
        )
    }
    static FSP_FILE_SYSTEM_INTERFACE *Interface()
    {
        static FSP_FILE_SYSTEM_INTERFACE _Interface =
        {
            GetVolumeInfo,
            SetVolumeLabel_,
            GetSecurityByName,
            Create,
            Open,
            Overwrite,
            Cleanup,
            Close,
            Read,
            Write,
            Flush,
            GetFileInfo,
            SetBasicInfo,
            SetFileSize,
            CanDelete,
            Rename,
            GetSecurity,
            SetSecurity,
            ReadDirectory,
            ResolveReparsePoints,
            GetReparsePoint,
            SetReparsePoint,
            DeleteReparsePoint,
            GetStreamInfo,
        };
        return &_Interface;
    }

private:
    /* disallow copy and assignment */
    FileSystemHost(const FileSystemHost &);
    FileSystemHost &operator=(const FileSystemHost &);

private:
    FSP_FSCTL_VOLUME_PARAMS _VolumeParams;
    FSP_FILE_SYSTEM *_FileSystemPtr;
    FileSystemBase *_FileSystem;
};

class Service
{
public:
    /* ctor/dtor */
    Service(PWSTR ServiceName) : _Service(0)
    {
        Initialize();
        FspServiceCreate(ServiceName, OnStart, OnStop, 0, &_Service);
        if (0 != _Service)
            _Service->UserContext = this;
    }
    virtual ~Service()
    {
        if (0 != _Service)
            FspServiceDelete(_Service);
    }

    /* control */
    ULONG Run()
    {
        if (0 == _Service)
        {
            FspServiceLog(EVENTLOG_ERROR_TYPE,
                L"The service cannot be created (Status=%lx).",
                STATUS_INSUFFICIENT_RESOURCES);
            return FspWin32FromNtStatus(STATUS_INSUFFICIENT_RESOURCES);
        }
        FspServiceAllowConsoleMode(_Service);
        NTSTATUS Result = FspServiceLoop(_Service);
        ULONG ExitCode = FspServiceGetExitCode(_Service);
        if (!NT_SUCCESS(Result))
        {
            FspServiceLog(EVENTLOG_ERROR_TYPE,
                L"The service has failed to run (Status=%lx).",
                Result);
            return FspWin32FromNtStatus(Result);
        }
        return ExitCode;
    }
    VOID Stop()
    {
        if (0 == _Service)
            return;
        FspServiceStop(_Service);
    }
    VOID RequestTime(ULONG Time)
    {
        if (0 == _Service)
            return;
        FspServiceRequestTime(_Service, Time);
    }
    ULONG GetExitCode()
    {
        return 0 != _Service ? FspServiceGetExitCode(_Service) : ERROR_NO_SYSTEM_RESOURCES;
    }
    VOID SetExitCode(ULONG ExitCode)
    {
        if (0 == _Service)
            return;
        FspServiceSetExitCode(_Service, ExitCode);
    }
    FSP_SERVICE *ServiceHandle()
    {
        return _Service;
    }
    static VOID Log(ULONG Type, PWSTR Format, ...)
    {
        va_list ap;
        va_start(ap, Format);
        FspServiceLogV(Type, Format, ap);
        va_end(ap);
    }
    static VOID LogV(ULONG Type, PWSTR Format, va_list ap)
    {
        FspServiceLogV(Type, Format, ap);
    }

protected:
    /* start/stop */
    virtual NTSTATUS ExceptionHandler()
    {
        return 0xE06D7363/*STATUS_CPP_EH_EXCEPTION*/;
    }
    virtual NTSTATUS OnStart(ULONG Argc, PWSTR *Argv)
    {
        return STATUS_SUCCESS;
    }
    virtual NTSTATUS OnStop()
    {
        return STATUS_SUCCESS;
    }

private:
    /* callbacks */
    static NTSTATUS OnStart(FSP_SERVICE *Service0, ULONG Argc, PWSTR *Argv)
    {
        Service *self = (Service *)Service0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->OnStart(Argc, Argv);
        )
    }
    static NTSTATUS OnStop(FSP_SERVICE *Service0)
    {
        Service *self = (Service *)Service0->UserContext;
        FSP_CPP_EXCEPTION_GUARD(
            return self->OnStop();
        )
    }

private:
    /* disallow copy and assignment */
    Service(const Service &);
    Service &operator=(const Service &);

private:
    FSP_SERVICE *_Service;
};

}

#undef FSP_CPP_EXCEPTION_GUARD
#undef FSP_CPP_EXCEPTION_GUARD_VOID

#endif
