/**
 * @file airfs.cpp
 *
 * @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.
 */
/*
 * Airfs is based on Memfs with changes contributed by John Oberschelp.
 * The contributed changes are under joint copyright by Bill Zissimopoulos
 * and John Oberschelp per the Contributor Agreement found at the
 * root of this project.
 */

#include "common.h"

enum
{
    AirfsDisk                   = 0x00000000,
    AirfsNet                    = 0x00000001,
    AirfsDeviceMask             = 0x0000000f,
    AirfsCaseInsensitive        = 0x80000000,
    AirfsFlushAndPurgeOnCleanup = 0x40000000,
};

//////////////////////////////////////////////////////////////////////

NTSTATUS CreateNode(AIRFS_ Airfs, PWSTR Name, NODE_ *PNode)
{
    static UINT64 IndexNumber = 1;

    NODE_ Node = (NODE_) StorageAllocate(Airfs, sizeof NODE);

    if (!Node)
    {
        *PNode = 0;
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    memset(Node, 0, sizeof NODE);

    size_t NameNumBytes = (wcslen(Name)+1) * sizeof WCHAR;
    WCHAR* NameAllocation = (WCHAR*) StorageAllocate(Airfs, NameNumBytes);
    if (!NameAllocation)
    {
        StorageFree(Airfs, Node);
        *PNode = 0;
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    memcpy(NameAllocation, Name, NameNumBytes);
    Node->Name = NameAllocation;

    Node->FileInfo.CreationTime =
    Node->FileInfo.LastAccessTime =
    Node->FileInfo.LastWriteTime =
    Node->FileInfo.ChangeTime = SystemTime();
    Node->FileInfo.IndexNumber = IndexNumber++;

    *PNode = Node;
    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

void DeleteNode(AIRFS_ Airfs, NODE_ Node)
{
    NODE_ Block;
    while (Block = Node->FileBlocks)
    {
        Detach(Node->FileBlocks, Block);
        StorageFree(Airfs, Block);
    }

    StorageFree(Airfs, Node->Name);
    StorageFree(Airfs, Node->ReparseData);
    StorageFree(Airfs, Node->SecurityDescriptor);
    StorageFree(Airfs, Node);
}

//////////////////////////////////////////////////////////////////////

inline void ReferenceNode(NODE_ Node)
{
    InterlockedIncrement(&Node->RefCount);
}

//////////////////////////////////////////////////////////////////////

inline void DereferenceNode(AIRFS_ Airfs, NODE_ Node)
{
    if (InterlockedDecrement(&Node->RefCount) == 0)
        DeleteNode(Airfs, Node);
}

//////////////////////////////////////////////////////////////////////

void GetFileInfo(NODE_ Node, FSP_FSCTL_FILE_INFO *FileInfo)
{
    if (Node->IsAStream)
    {
        *FileInfo = Node->Parent->FileInfo;
        FileInfo->FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
        //  Named streams cannot be directories.
        FileInfo->AllocationSize = Node->FileInfo.AllocationSize;
        FileInfo->FileSize = Node->FileInfo.FileSize;
    }
    else
        *FileInfo = Node->FileInfo;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS FindNode(AIRFS_ Airfs, PWSTR Name, PWSTR *BaseName, NODE_ *PParent, NODE_ *PNode)
{
    CompareFunction* NodeCmp = Airfs->CaseInsensitive ? CaselessNameCmp : ExactNameCmp;

    //  Special case root.
    if (Name[0] == 0 || Name[1] == 0)
    {
        if (BaseName) *BaseName = Name;
        if (PParent) *PParent = Airfs->Root;
        *PNode = Airfs->Root;
        return 0;
    }

    WCHAR ParsedName[AIRFS_MAX_PATH];
    wcscpy_s(ParsedName, sizeof ParsedName / sizeof WCHAR, Name);

    //  From root, for each ancestor...
    NODE_ Ancestor = Airfs->Root;
    WCHAR* fm;
    WCHAR* to = ParsedName;
    WCHAR* Colon = 0;
    for (;;)
    {
        //  Isolate the next base name.
        for (fm = to+1; *fm == L'\\'; fm++) {}
        for (to = fm; *to != L'\0' && *to != L'\\'; to++)
            if (*to == ':') {Colon = to; break;}
        if (*to == 0) break;
        *to = 0;

        //  Find this name.
        NODE_ Child = Find(Ancestor->Children, fm, NodeCmp);
        if (!Child)
        {
            if (PParent) *PParent = 0;
            *PNode = 0;
            if (Colon) return STATUS_OBJECT_NAME_NOT_FOUND;
            return STATUS_OBJECT_PATH_NOT_FOUND;
        }

        Ancestor = Child;

        if (Colon)
        {
            fm = to+1;
            break;
        }

        if (!(Ancestor->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {
            if (PParent) *PParent = 0;
            *PNode = 0;
            return STATUS_NOT_A_DIRECTORY;
        }
    }

    if (BaseName)
        *BaseName = Name + (  (  (UINT_PTR)fm - (UINT_PTR)ParsedName  )  / sizeof WCHAR);

    if (PParent)
        *PParent = Ancestor;

    if (Colon)
    {
        //  Find the stream, if it exists.
        if (!Ancestor->Streams)
        {
            *PNode = 0;
            return STATUS_OBJECT_NAME_NOT_FOUND;
        }
        NODE_ Stream = Find(Ancestor->Streams, fm, NodeCmp);
        if (!Stream)
        {
            *PNode = 0;
            return STATUS_OBJECT_NAME_NOT_FOUND;
        }
        *PNode = Stream;
        return 0;
    }

    //  Find the directory entry, if it exists.
    NODE_ Found = Find(Ancestor->Children, fm, NodeCmp);
    if (!Found)
    {
        *PNode = 0;
        return STATUS_OBJECT_NAME_NOT_FOUND;
    }
    *PNode = Found;
    return 0;
}

//////////////////////////////////////////////////////////////////////

BOOLEAN AddStreamInfo(NODE_ Node, PWSTR StreamName, PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
{
    UINT8 StreamInfoBuf[sizeof FSP_FSCTL_STREAM_INFO + AIRFS_MAX_PATH * sizeof WCHAR];
    FSP_FSCTL_STREAM_INFO *StreamInfo = (FSP_FSCTL_STREAM_INFO *)StreamInfoBuf;

    StreamInfo->Size = (UINT16)(sizeof FSP_FSCTL_STREAM_INFO + wcslen(StreamName) * sizeof WCHAR);
    StreamInfo->StreamSize = Node->FileInfo.FileSize;
    StreamInfo->StreamAllocationSize = Node->FileInfo.AllocationSize;
    memcpy(StreamInfo->StreamNameBuf, StreamName, StreamInfo->Size - sizeof FSP_FSCTL_STREAM_INFO);

    return FspFileSystemAddStreamInfo(StreamInfo, Buffer, Length, PBytesTransferred);
}

//////////////////////////////////////////////////////////////////////

inline void TouchNode(NODE_ Node)
{
    Node->FileInfo.LastAccessTime =
    Node->FileInfo.LastWriteTime =
    Node->FileInfo.ChangeTime = SystemTime();
}

//////////////////////////////////////////////////////////////////////

void InsertNode(AIRFS_ Airfs, NODE_ Parent, NODE_ Node)
{
    CompareFunction* NodeCmp = Airfs->CaseInsensitive ? CaselessNameCmp : ExactNameCmp;

    WCHAR* key = Node->Name;
    if (Node->IsAStream) Attach(Parent->Streams  , Node, NodeCmp, key);
    else                 Attach(Parent->Children , Node, NodeCmp, key);

    Node->Parent = Parent;
    ReferenceNode(Node);
    TouchNode(Parent);
}

//////////////////////////////////////////////////////////////////////

void RemoveNode(AIRFS_ Airfs, NODE_ Node)
{
    NODE_ Parent = Node->Parent;
    TouchNode(Parent);

    if (Node->IsAStream)
    {
        if (Parent->Streams) Detach(Parent->Streams, Node);
    }
    else
        Detach(Parent->Children, Node);

    DereferenceNode(Airfs, Node);
}

//////////////////////////////////////////////////////////////////////

NTSTATUS GetReparsePointByName(FSP_FILE_SYSTEM *FileSystem, PVOID Context,
    PWSTR Name, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node;

    NTSTATUS Result = FindNode(Airfs, Name, 0, 0, &Node);

    if (!Node) return STATUS_OBJECT_NAME_NOT_FOUND;
    if (!(Node->FileInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) return STATUS_NOT_A_REPARSE_POINT;

    if (Buffer)
    {
        if (Node->ReparseDataSize > *PSize) return STATUS_BUFFER_TOO_SMALL;
        *PSize = Node->ReparseDataSize;
        memcpy(Buffer, Node->ReparseData.Address(), Node->ReparseDataSize);
    }

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS SetAllocSize(AIRFS_ Airfs, NODE_ Node, UINT64 RequestedAllocSize)
{
    NTSTATUS Result = StorageSetFileCapacity(Airfs, Node, RequestedAllocSize);
    if (!Result)
    {
        Node->FileInfo.AllocationSize = RequestedAllocSize;
        if (Node->FileInfo.FileSize > Node->FileInfo.AllocationSize) 
            Node->FileInfo.FileSize = Node->FileInfo.AllocationSize;

    }
    return Result;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS SetFileSize(AIRFS_ Airfs, NODE_ Node, UINT64 RequestedFileSize)
{
    if (Node->FileInfo.FileSize != RequestedFileSize)
    {
        if (Node->FileInfo.AllocationSize < RequestedFileSize)
        {
            NTSTATUS Result = SetAllocSize(Airfs, Node, RequestedFileSize);
            if (!NT_SUCCESS(Result))
                return Result;
        }

        if (Node->FileInfo.FileSize < RequestedFileSize)
            StorageAccessFile(ZERO, Node, Node->FileInfo.FileSize, RequestedFileSize - Node->FileInfo.FileSize, 0);

        Node->FileInfo.FileSize = RequestedFileSize;
    }

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

BOOLEAN AddDirInfo(NODE_ Node, PWSTR Name, PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
{
    size_t NameBytes = wcslen(Name) * sizeof WCHAR;
    UINT8 DirInfoBuf[sizeof FSP_FSCTL_DIR_INFO + AIRFS_MAX_PATH * sizeof WCHAR];
    FSP_FSCTL_DIR_INFO *DirInfo = (FSP_FSCTL_DIR_INFO *)DirInfoBuf;
    DirInfo->Size = (UINT16)(sizeof FSP_FSCTL_DIR_INFO + NameBytes);
    DirInfo->FileInfo = Node->FileInfo;
    memset(DirInfo->Padding, 0, sizeof DirInfo->Padding);
    memcpy(DirInfo->FileNameBuf, Name, NameBytes + sizeof WCHAR);

    return FspFileSystemAddDirInfo(DirInfo, Buffer, Length, PBytesTransferred);
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetVolumeInfo(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_VOLUME_INFO *VolumeInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;

    VolumeInfo->TotalSize = Airfs->VolumeSize;
    VolumeInfo->FreeSize = Airfs->FreeSize;
    VolumeInfo->VolumeLabelLength = Airfs->VolumeLabelLength;
    memcpy(VolumeInfo->VolumeLabel, Airfs->VolumeLabel, Airfs->VolumeLabelLength);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiSetVolumeLabel(FSP_FILE_SYSTEM *FileSystem, PWSTR VolumeLabel,
    FSP_FSCTL_VOLUME_INFO *VolumeInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;

    Airfs->VolumeLabelLength = (UINT16)(wcslen(VolumeLabel) * sizeof WCHAR);
    if (Airfs->VolumeLabelLength > sizeof Airfs->VolumeLabel)
        Airfs->VolumeLabelLength = sizeof Airfs->VolumeLabel;
    memcpy(Airfs->VolumeLabel, VolumeLabel, Airfs->VolumeLabelLength);

    VolumeInfo->TotalSize = Airfs->VolumeSize;
    VolumeInfo->FreeSize = Airfs->FreeSize;
    VolumeInfo->VolumeLabelLength = Airfs->VolumeLabelLength;
    memcpy(VolumeInfo->VolumeLabel, Airfs->VolumeLabel, Airfs->VolumeLabelLength);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetSecurityByName(FSP_FILE_SYSTEM *FileSystem, PWSTR Name, PUINT32 PFileAttributes,
    PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node;
    NTSTATUS Result = FindNode(Airfs, Name, 0, 0, &Node);
    if (!Node)
    {
        if (FspFileSystemFindReparsePoint(FileSystem, GetReparsePointByName, 0,
            Name, PFileAttributes))
        {
            return STATUS_REPARSE;
        }
        return Result;
    }

    UINT32 FileAttributesMask = ~(UINT32)0;
    if (Node->IsAStream)
    {
        FileAttributesMask = ~(UINT32)FILE_ATTRIBUTE_DIRECTORY;
        Node = Node->Parent;
    }

    if (PFileAttributes)
        *PFileAttributes = Node->FileInfo.FileAttributes & FileAttributesMask;

    if (PSecurityDescriptorSize)
    {
        if (Node->SecurityDescriptorSize > *PSecurityDescriptorSize)
        {
            *PSecurityDescriptorSize = Node->SecurityDescriptorSize;
            return STATUS_BUFFER_OVERFLOW;
        }

        *PSecurityDescriptorSize = Node->SecurityDescriptorSize;
        if (SecurityDescriptor)
            memcpy(SecurityDescriptor, Node->SecurityDescriptor.Address(), Node->SecurityDescriptorSize);
    }

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiCreate(FSP_FILE_SYSTEM *FileSystem, PWSTR Name, UINT32 CreateOptions,
    UINT32 GrantedAccess, UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor,
    UINT64 AllocationSize, PVOID *PNode, FSP_FSCTL_FILE_INFO *FileInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node;
    NODE_ Parent;
    NTSTATUS Result;
    PWSTR BaseName;

    if (AIRFS_MAX_PATH <= wcslen(Name))
        return STATUS_OBJECT_NAME_INVALID;

    if (CreateOptions & FILE_DIRECTORY_FILE)
        AllocationSize = 0;

    Result = FindNode(Airfs, Name, &BaseName, &Parent, &Node);
    if (Node) return STATUS_OBJECT_NAME_COLLISION;
    if (!Parent) return Result;

    Result = CreateNode(Airfs, BaseName, &Node);
    if (!NT_SUCCESS(Result)) return Result;

    Node->IsAStream = BaseName[-1] == L':';

    Node->FileInfo.FileAttributes = (FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
        FileAttributes : FileAttributes | FILE_ATTRIBUTE_ARCHIVE;

    if (SecurityDescriptor)
    {
        Node->SecurityDescriptorSize = GetSecurityDescriptorLength(SecurityDescriptor);
        Node->SecurityDescriptor = StorageAllocate(Airfs, (int)Node->SecurityDescriptorSize);
        if (!Node->SecurityDescriptor)
        {
            DeleteNode(Airfs, Node);
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        memcpy(Node->SecurityDescriptor.Address(), SecurityDescriptor, Node->SecurityDescriptorSize);
    }

    if (AllocationSize)
    {
        NTSTATUS Result = SetAllocSize(Airfs, Node, AllocationSize);
        if (Result)
        {
            DeleteNode(Airfs, Node);
            return Result;
        }
    }

    InsertNode(Airfs, Parent, Node);
    ReferenceNode(Node);
    *PNode = Node;
    GetFileInfo(Node, FileInfo);

    if (Airfs->CaseInsensitive)
    {
        int ParentPathNumBytes = (int)(  (char*)BaseName - (char*)Name  );
        int NodeNameNumBytes = (int)(  wcslen(Node->Name) * sizeof WCHAR  );

        FSP_FSCTL_OPEN_FILE_INFO *OpenFileInfo = FspFileSystemGetOpenFileInfo(FileInfo);
        memcpy(OpenFileInfo->NormalizedName, Name, ParentPathNumBytes);
        memcpy(OpenFileInfo->NormalizedName+ParentPathNumBytes / sizeof WCHAR, Node->Name, NodeNameNumBytes);
        OpenFileInfo->NormalizedNameSize = ParentPathNumBytes + NodeNameNumBytes;
    }

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiOpen(FSP_FILE_SYSTEM *FileSystem, PWSTR Name, UINT32 CreateOptions,
    UINT32 GrantedAccess, PVOID *PNode, FSP_FSCTL_FILE_INFO *FileInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node;
    NTSTATUS Result;

    if (AIRFS_MAX_PATH <= wcslen(Name))
        return STATUS_OBJECT_NAME_INVALID;

    PWSTR baseName;
    Result = FindNode(Airfs, Name, &baseName, 0, &Node);
    if (Result) return Result;
    if (!Node)
    {
        return STATUS_OBJECT_NAME_NOT_FOUND;
    }

    ReferenceNode(Node);
    *PNode = Node;
    GetFileInfo(Node, FileInfo);

    if (Airfs->CaseInsensitive)
    {
        int ParentPathNumBytes = (int)(  (char*)baseName - (char*)Name  );
        int NodeNameNumBytes = (int)(  wcslen(Node->Name) * sizeof WCHAR  );

        FSP_FSCTL_OPEN_FILE_INFO *OpenFileInfo = FspFileSystemGetOpenFileInfo(FileInfo);
        memcpy(OpenFileInfo->NormalizedName, Name, ParentPathNumBytes);
        memcpy(OpenFileInfo->NormalizedName+ParentPathNumBytes / sizeof WCHAR, Node->Name, NodeNameNumBytes);
        OpenFileInfo->NormalizedNameSize = ParentPathNumBytes + NodeNameNumBytes;
    }

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiOverwrite(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, UINT32 FileAttributes,
    BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, FSP_FSCTL_FILE_INFO *FileInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;

    for (NODE_ Stream = First(Node->Streams); Stream; )
    {
        NODE_ NextStream = Next(Stream);
        LONG RefCount = Stream->RefCount;
        MemoryBarrier();
        if (RefCount <= 1)
        {
            RemoveNode(Airfs, Stream);
        }
        Stream = NextStream;
    }

    NTSTATUS Result = SetAllocSize(Airfs, Node, AllocationSize);
    if (!NT_SUCCESS(Result))
        return Result;

    if (ReplaceFileAttributes) Node->FileInfo.FileAttributes  = FileAttributes | FILE_ATTRIBUTE_ARCHIVE;
    else                       Node->FileInfo.FileAttributes |= FileAttributes | FILE_ATTRIBUTE_ARCHIVE;

    Node->FileInfo.FileSize = 0;
    Node->FileInfo.LastAccessTime =
    Node->FileInfo.LastWriteTime =
    Node->FileInfo.ChangeTime = SystemTime();

    GetFileInfo(Node, FileInfo);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

void ApiCleanup(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, PWSTR Name, ULONG Flags)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;

    NODE_ MainNode = Node->IsAStream ? Node->Parent : Node;

    if (Flags & FspCleanupSetArchiveBit)
    {
        if (!(MainNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
              MainNode->FileInfo.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
    }

    if (Flags & (FspCleanupSetLastAccessTime | FspCleanupSetLastWriteTime | FspCleanupSetChangeTime))
    {
        UINT64 Time = SystemTime();
        if (Flags & FspCleanupSetLastAccessTime ) MainNode->FileInfo.LastAccessTime = Time;
        if (Flags & FspCleanupSetLastWriteTime  ) MainNode->FileInfo.LastWriteTime  = Time;
        if (Flags & FspCleanupSetChangeTime     ) MainNode->FileInfo.ChangeTime     = Time;
    }

    if (Flags & FspCleanupSetAllocationSize)
    {
        SetAllocSize(Airfs, Node, Node->FileInfo.FileSize);
    }

    if ((Flags & FspCleanupDelete) && !Node->Children)
    {
        NODE_ Stream;
        while (Stream = Node->Streams)
        {
            Detach(Node->Streams, Stream);
            DeleteNode(Airfs, Stream);
        }
        RemoveNode(Airfs, Node);
    }
}

//////////////////////////////////////////////////////////////////////

void ApiClose(FSP_FILE_SYSTEM *FileSystem, PVOID Node0)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    DereferenceNode(Airfs, Node);
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiRead(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, PVOID Buffer,
    UINT64 BegOffset, ULONG Length, PULONG PBytesTransferred)
{
    NODE_ Node = (NODE_) Node0;
    UINT64 EndOffset;

    if (BegOffset >= Node->FileInfo.FileSize)
        return STATUS_END_OF_FILE;

    EndOffset = BegOffset + Length;
    if (EndOffset > Node->FileInfo.FileSize)
        EndOffset = Node->FileInfo.FileSize;

    StorageAccessFile(READ, Node, BegOffset, EndOffset - BegOffset, (char*)Buffer);

    *PBytesTransferred = (ULONG)(EndOffset - BegOffset);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiWrite(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, PVOID Buffer,
    UINT64 BegOffset, ULONG Length, BOOLEAN WriteToEndOfFile,
    BOOLEAN ConstrainedIo, PULONG PBytesTransferred, FSP_FSCTL_FILE_INFO *FileInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    UINT64 EndOffset;
    NTSTATUS Result;

    if (ConstrainedIo)
    {
        if (BegOffset >= Node->FileInfo.FileSize)
            return STATUS_SUCCESS;
        EndOffset = BegOffset + Length;
        if (EndOffset > Node->FileInfo.FileSize)
            EndOffset = Node->FileInfo.FileSize;
    }
    else
    {
        if (WriteToEndOfFile)
            BegOffset = Node->FileInfo.FileSize;
        EndOffset = BegOffset + Length;
        if (EndOffset > Node->FileInfo.FileSize)
        {
            Result = SetFileSize(Airfs, Node, EndOffset);
            if (!NT_SUCCESS(Result))
                return Result;
        }
    }

    StorageAccessFile(WRITE, Node, BegOffset, EndOffset - BegOffset, (char*)Buffer);

    *PBytesTransferred = (ULONG)(EndOffset - BegOffset);
    GetFileInfo(Node, FileInfo);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiFlush(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, FSP_FSCTL_FILE_INFO *FileInfo)
{
    NODE_ Node = (NODE_) Node0;

    if (Node) GetFileInfo(Node, FileInfo);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetFileInfo(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    FSP_FSCTL_FILE_INFO *FileInfo)
{
    NODE_ Node = (NODE_) Node0;

    GetFileInfo(Node, FileInfo);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiSetBasicInfo(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, UINT32 FileAttributes,
    UINT64 CreationTime, UINT64 LastAccessTime, UINT64 LastWriteTime, UINT64 ChangeTime,
    FSP_FSCTL_FILE_INFO *FileInfo)
{
    NODE_ Node = (NODE_) Node0;

    if (Node->IsAStream) Node = Node->Parent;

    if (INVALID_FILE_ATTRIBUTES != FileAttributes)
                         Node->FileInfo.FileAttributes = FileAttributes;
    if (CreationTime   ) Node->FileInfo.CreationTime   = CreationTime;
    if (LastAccessTime ) Node->FileInfo.LastAccessTime = LastAccessTime;
    if (LastWriteTime  ) Node->FileInfo.LastWriteTime  = LastWriteTime;
    if (ChangeTime     ) Node->FileInfo.ChangeTime     = ChangeTime;

    GetFileInfo(Node, FileInfo);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiSetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    NTSTATUS Result = SetAllocationSize
        ? SetAllocSize (Airfs, Node, NewSize)
        : SetFileSize  (Airfs, Node, NewSize);
    if (!NT_SUCCESS(Result))
        return Result;

    GetFileInfo(Node, FileInfo);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiCanDelete(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, PWSTR Name)
{
    NODE_ Node = (NODE_) Node0;

    if (Node->Children)
        return STATUS_DIRECTORY_NOT_EMPTY;

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiRename(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, PWSTR Name,
    PWSTR NewFileName, BOOLEAN ReplaceIfExists)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    NODE_ NewNode;
    NTSTATUS Result;
    PWSTR NewBaseName;
    NODE_ NewParent;
    Result = FindNode(Airfs, NewFileName, &NewBaseName, &NewParent, &NewNode);

    //  Create the replacement name.
    size_t NewBaseNameNumBytes = (wcslen(NewBaseName)+1) * sizeof WCHAR;
    WCHAR* NewBaseNameAlloc = (WCHAR*) StorageAllocate(Airfs, NewBaseNameNumBytes);
    if (!NewBaseNameAlloc)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    memcpy(NewBaseNameAlloc, NewBaseName, NewBaseNameNumBytes);

    if (NewNode && Node != NewNode)
    {
        if (!ReplaceIfExists)
        {
            StorageFree(Airfs, NewBaseNameAlloc);
            return STATUS_OBJECT_NAME_COLLISION;
        }

        if (NewNode->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            StorageFree(Airfs, NewBaseNameAlloc);
            return STATUS_ACCESS_DENIED;
        }

        ReferenceNode(NewNode);
        RemoveNode(Airfs, NewNode);
        DereferenceNode(Airfs, NewNode);
    }

    ReferenceNode(Node);
    RemoveNode(Airfs, Node);

    StorageFree(Airfs, Node->Name);
    Node->Name = NewBaseNameAlloc;

    InsertNode(Airfs, NewParent, Node);
    DereferenceNode(Airfs, Node);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize)
{
    NODE_ Node = (NODE_) Node0;

    if (Node->IsAStream) Node = Node->Parent;

    if (Node->SecurityDescriptorSize > *PSecurityDescriptorSize)
    {
        *PSecurityDescriptorSize = Node->SecurityDescriptorSize;
        return STATUS_BUFFER_OVERFLOW;
    }

    *PSecurityDescriptorSize = Node->SecurityDescriptorSize;
    if (SecurityDescriptor)
        memcpy(SecurityDescriptor, Node->SecurityDescriptor.Address(), Node->SecurityDescriptorSize);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiSetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR ModificationDescriptor)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    PSECURITY_DESCRIPTOR NewSecurityDescriptor, MallocSecurityDescriptor;

    if (Node->IsAStream) Node = Node->Parent;

    NTSTATUS Result = FspSetSecurityDescriptor(
        Node->SecurityDescriptor.Address(),
        SecurityInformation,
        ModificationDescriptor,
        &NewSecurityDescriptor);
    if (!NT_SUCCESS(Result))
        return Result;

    uint64_t SecurityDescriptorSize = GetSecurityDescriptorLength(NewSecurityDescriptor);
    MallocSecurityDescriptor = (PSECURITY_DESCRIPTOR) malloc(SecurityDescriptorSize);
    if (!MallocSecurityDescriptor)
    {
        FspDeleteSecurityDescriptor(NewSecurityDescriptor, (NTSTATUS (*)())FspSetSecurityDescriptor);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    memcpy(MallocSecurityDescriptor, NewSecurityDescriptor, SecurityDescriptorSize);
    FspDeleteSecurityDescriptor(NewSecurityDescriptor, (NTSTATUS (*)())FspSetSecurityDescriptor);

    NODE_ NewHunk = (NODE_) StorageReallocate(Airfs, Node->SecurityDescriptor, SecurityDescriptorSize);
    if (!NewHunk && SecurityDescriptorSize)
    {
        free(MallocSecurityDescriptor);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    Node->SecurityDescriptorSize = SecurityDescriptorSize;
    if (!SecurityDescriptorSize) Node->SecurityDescriptor = 0;
    else
    {
        Node->SecurityDescriptor = NewHunk;
        memcpy(Node->SecurityDescriptor.Address(), MallocSecurityDescriptor, SecurityDescriptorSize);
    }

    free(MallocSecurityDescriptor);
    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiReadDirectory(FSP_FILE_SYSTEM *FileSystem, PVOID Node0, PWSTR Pattern, 
    PWSTR Marker, PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;

    CompareFunction* NodeCmp = Airfs->CaseInsensitive ? CaselessNameCmp : ExactNameCmp;

    NODE_ Node = (NODE_) Node0;
    NODE_ Parent = Node->Parent;
    if (Parent)
    {
        //  If this is not the root directory, add the dot entries.
        if (!Marker)
        {
            if (!AddDirInfo(Node, L".", Buffer, Length, PBytesTransferred))
                return STATUS_SUCCESS;
        }
        if (!Marker || (L'.' == Marker[0] && L'\0' == Marker[1]))
        {
            if (!AddDirInfo(Parent, L"..", Buffer, Length, PBytesTransferred))
                return STATUS_SUCCESS;
            Marker = 0;
        }
    }

    //  Find the next Child, even if the Marker child no longer exists.
    NODE_ Child;
    if (!Marker) Child = First(Node->Children);
    else         Child = Near(Node->Children, Marker, NodeCmp, GT);

    for (; Child; Child = Next(Child))
    {
        if (!AddDirInfo(Child, Child->Name, Buffer, Length, PBytesTransferred))
            goto bufferReady;
    }
    FspFileSystemAddDirInfo(0, Buffer, Length, PBytesTransferred);

  bufferReady:

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetDirInfoByName(FSP_FILE_SYSTEM *FileSystem, PVOID ParentNode0,
    PWSTR Name, FSP_FSCTL_DIR_INFO *DirInfo)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;

    CompareFunction* NodeCmp = Airfs->CaseInsensitive ? CaselessNameCmp : ExactNameCmp;

    NODE_ Parent = (NODE_) ParentNode0;
    NODE_ Node = Find(Parent->Children, Name, NodeCmp);
    if (!Node)
        return STATUS_OBJECT_NAME_NOT_FOUND;

    DirInfo->Size = (UINT16)(sizeof FSP_FSCTL_DIR_INFO + wcslen(Node->Name) * sizeof WCHAR);
    DirInfo->FileInfo = Node->FileInfo;
    memcpy(DirInfo->FileNameBuf, Node->Name, DirInfo->Size - sizeof FSP_FSCTL_DIR_INFO);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem, PWSTR Name,
    UINT32 ReparsePointIndex, BOOLEAN ResolveLastPathComponent,
    PIO_STATUS_BLOCK PIoStatus, PVOID Buffer, PSIZE_T PSize)
{
    return FspFileSystemResolveReparsePoints(FileSystem, GetReparsePointByName, 0,
        Name, ReparsePointIndex, ResolveLastPathComponent,
        PIoStatus, Buffer, PSize);
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    PWSTR Name, PVOID Buffer, PSIZE_T PSize)
{
    NODE_ Node = (NODE_) Node0;

    if (Node->IsAStream) Node = Node->Parent;

    if (!(Node->FileInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
        return STATUS_NOT_A_REPARSE_POINT;

    if (Node->ReparseDataSize > *PSize)
        return STATUS_BUFFER_TOO_SMALL;

    *PSize = Node->ReparseDataSize;
    memcpy(Buffer, Node->ReparseData.Address(), Node->ReparseDataSize);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiSetReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    PWSTR Name, PVOID Buffer, SIZE_T Size)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    NTSTATUS Result;

    if (Node->IsAStream) Node = Node->Parent;

    if (Node->Children)
        return STATUS_DIRECTORY_NOT_EMPTY;

    if (Node->ReparseData)
    {
        Result = FspFileSystemCanReplaceReparsePoint(
            Node->ReparseData.Address(), Node->ReparseDataSize,
            Buffer, Size);
        if (!NT_SUCCESS(Result))
            return Result;
    }

    NODE_ ReparseData = (NODE_) StorageReallocate(Airfs, Node->ReparseData, Size);
    if (!ReparseData && Size)
        return STATUS_INSUFFICIENT_RESOURCES;

    Node->FileInfo.FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
    Node->FileInfo.ReparseTag = *(PULONG)Buffer;
    //  The first field in a reparse buffer is the reparse tag.
    Node->ReparseDataSize = Size;
    Node->ReparseData = ReparseData;
    memcpy(Node->ReparseData.Address(), Buffer, Size);

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiDeleteReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    PWSTR Name, PVOID Buffer, SIZE_T Size)
{
    AIRFS_ Airfs = (AIRFS_) FileSystem->UserContext;
    NODE_ Node = (NODE_) Node0;
    NTSTATUS Result;

    if (Node->IsAStream) Node = Node->Parent;

    if (Node->ReparseData)
    {
        Result = FspFileSystemCanReplaceReparsePoint(
            Node->ReparseData.Address(), Node->ReparseDataSize,
            Buffer, Size);
        if (!NT_SUCCESS(Result))
            return Result;
    }
    else
        return STATUS_NOT_A_REPARSE_POINT;

    StorageFree(Airfs, Node->ReparseData);

    Node->FileInfo.FileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT;
    Node->FileInfo.ReparseTag = 0;
    Node->ReparseDataSize = 0;
    Node->ReparseData = 0;

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiGetStreamInfo(FSP_FILE_SYSTEM *FileSystem, PVOID Node0,
    PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
{
    NODE_ Node = (NODE_) Node0;

    if (Node->IsAStream) Node = Node->Parent;

    if (!(Node->FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        if (!AddStreamInfo(Node, L"", Buffer, Length, PBytesTransferred))
            return STATUS_SUCCESS;

    for (NODE_ Stream = First(Node->Streams); Stream; Stream = Next(Stream))
    {
        BOOLEAN added = AddStreamInfo(Stream, Stream->Name, Buffer, Length, PBytesTransferred);
        if (!added) goto done;
    }

    FspFileSystemAddStreamInfo(0, Buffer, Length, PBytesTransferred);

  done:

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS ApiControl(FSP_FILE_SYSTEM *FileSystem,
    PVOID FileContext, UINT32 ControlCode,
    PVOID InputBuffer, ULONG InputBufferLength,
    PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred)
{
    //  Trivial example: Perform a ROT13 translation on alphas.
    if (CTL_CODE(0x8000 + 'M', 'R', METHOD_BUFFERED, FILE_ANY_ACCESS) == ControlCode)
    {
        if (OutputBufferLength != InputBufferLength) return STATUS_INVALID_PARAMETER;
        for (ULONG i = 0; i < InputBufferLength; i++)
        {
            char c = ((char*)InputBuffer)[i];
            if (('A' <= c && c <= 'M') || ('a' <= c && c <= 'm')) c += 13;
            else
            if (('N' <= c && c <= 'Z') || ('n' <= c && c <= 'z')) c -= 13;
            ((char*)OutputBuffer)[i] = c;
        }
        *PBytesTransferred = InputBufferLength;
        return STATUS_SUCCESS;
    }

    return STATUS_INVALID_DEVICE_REQUEST;
}

//////////////////////////////////////////////////////////////////////

FSP_FILE_SYSTEM_INTERFACE AirfsInterface =
{
    ApiGetVolumeInfo,
    ApiSetVolumeLabel,
    ApiGetSecurityByName,
    ApiCreate,
    ApiOpen,
    ApiOverwrite,
    ApiCleanup,
    ApiClose,
    ApiRead,
    ApiWrite,
    ApiFlush,
    ApiGetFileInfo,
    ApiSetBasicInfo,
    ApiSetFileSize,
    ApiCanDelete,
    ApiRename,
    ApiGetSecurity,
    ApiSetSecurity,
    ApiReadDirectory,
    ApiResolveReparsePoints,
    ApiGetReparsePoint,
    ApiSetReparsePoint,
    ApiDeleteReparsePoint,
    ApiGetStreamInfo,
    ApiGetDirInfoByName,
    ApiControl,
};

//////////////////////////////////////////////////////////////////////

void AirfsDelete(AIRFS_ Airfs)
{
    FspFileSystemDelete(Airfs->FileSystem);
    StorageShutdown(Airfs);
}

//////////////////////////////////////////////////////////////////////

NTSTATUS AirfsCreate(
    PWSTR StorageFileName,
    PWSTR MapName,
    ULONG Flags,
    ULONG FileInfoTimeout,
    UINT64 VolumeSize,
    PWSTR FileSystemName,
    PWSTR VolumePrefix,
    PWSTR RootSddl,
    AIRFS_ *PAirfs)
{
    NTSTATUS Result;
    BOOLEAN CaseInsensitive = !!(Flags & AirfsCaseInsensitive);
    BOOLEAN FlushAndPurgeOnCleanup = !!(Flags & AirfsFlushAndPurgeOnCleanup);
    PWSTR DevicePath = AirfsNet == (Flags & AirfsDeviceMask) ?
        L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME;
    AIRFS_ Airfs;
    PSECURITY_DESCRIPTOR RootSecurity = 0;
    ULONG RootSecuritySize;

    *PAirfs = 0;

    boolean StorageFileExists = *StorageFileName && (_waccess(StorageFileName, 0) != -1);

    Result = StorageStartup(Airfs, MapName, StorageFileName, VolumeSize);
    if (Result) return Result;

    boolean ShouldFormat = !StorageFileExists || memcmp(Airfs->Signature, "Airfs\0\0\0", 8);

    if (ShouldFormat)
    {
        memcpy(Airfs->Signature,"Airfs\0\0\0"  "\1\0\0\0"  "\0\0\0\0", 16);

        if (!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 GetLastErrorAsStatus();

        Airfs->VolumeSize = ROUND_DOWN(VolumeSize, ALLOCATION_UNIT);
        Airfs->CaseInsensitive = CaseInsensitive;
        Airfs->VolumeLabelLength = sizeof L"AIRFS" - sizeof WCHAR;
        memcpy(Airfs->VolumeLabel, L"AIRFS", Airfs->VolumeLabelLength);

        FSP_FSCTL_VOLUME_PARAMS V;
        memset(&V, 0, sizeof V);
        V.Version = sizeof FSP_FSCTL_VOLUME_PARAMS;
        V.SectorSize = SECTOR_SIZE;
        V.SectorsPerAllocationUnit = SECTORS_PER_ALLOCATION_UNIT;
        V.VolumeCreationTime = SystemTime();
        V.VolumeSerialNumber = (UINT32)(SystemTime() / (10000 * 1000));
        V.FileInfoTimeout = FileInfoTimeout;
        V.CaseSensitiveSearch = !CaseInsensitive;
        V.CasePreservedNames = 1;
        V.UnicodeOnDisk = 1;
        V.PersistentAcls = 1;
        V.ReparsePoints = 1;
        V.ReparsePointsAccessCheck = 0;
        V.NamedStreams = 1;
        V.PostCleanupWhenModifiedOnly = 1;
        V.PassQueryDirectoryFileName = 1;
        V.FlushAndPurgeOnCleanup = FlushAndPurgeOnCleanup;
        V.DeviceControl = 1;
        wcscpy_s(V.Prefix, sizeof V.Prefix / sizeof WCHAR, VolumePrefix);
        wcscpy_s(V.FileSystemName, sizeof V.FileSystemName / sizeof WCHAR,
            FileSystemName ? FileSystemName : L"-AIRFS");
        Airfs->VolumeParams = V;

        //  Set up the available storage in chunks.
        Airfs->FreeSize = 0;
        Airfs->Available = 0;
        uint64_t to = 4096;
        for (;;)
        {
            uint64_t fm = to + sizeof int32_t;
            to =  fm + MAXIMUM_ALLOCSIZE;
            if (to > Airfs->VolumeSize - MINIMUM_ALLOCSIZE) to = Airfs->VolumeSize;
            int32_t Size = (int32_t)( to - fm );
            char* Addr = (char*)Airfs + fm;// + sizeof int32_t;
            NODE_ NewItem = (NODE_) Addr;
            ((int32_t*)NewItem)[-1] = Size;
            Attach(Airfs->Available, NewItem, SizeCmp, &Size);
            Airfs->FreeSize += Size;
            if (to == Airfs->VolumeSize) break;
        }

        //  Create the root directory.
        Airfs->Root = 0;
        NODE_ RootNode;
        Result = CreateNode(Airfs, L"", &RootNode);
        if (!NT_SUCCESS(Result))
        {
            AirfsDelete(Airfs);
            LocalFree(RootSecurity);
            return Result;
        }
        RootNode->P = RootNode->L = RootNode->R = RootNode->E = RootNode->Parent = 0;
        RootNode->FileInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        RootNode->SecurityDescriptor = StorageAllocate(Airfs, RootSecuritySize);
        if (!RootNode->SecurityDescriptor)
        {
            DeleteNode(Airfs, RootNode);
            AirfsDelete(Airfs);
            LocalFree(RootSecurity);
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        RootNode->SecurityDescriptorSize = RootSecuritySize;
        memcpy(RootNode->SecurityDescriptor.Address(), RootSecurity, RootSecuritySize);
        Airfs->Root = RootNode;
        ReferenceNode(RootNode);
        LocalFree(RootSecurity);
    }

    Result = FspFileSystemCreate(DevicePath, &Airfs->VolumeParams, &AirfsInterface, &Airfs->FileSystem);
    if (!NT_SUCCESS(Result))
    {
        LocalFree(RootSecurity);
        return Result;
    }

    Airfs->FileSystem->UserContext = Airfs;

    *PAirfs = Airfs;

    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS SvcStart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv)
{
    wchar_t **argp, **arge;
    ULONG DebugFlags = 0;
    PWSTR DebugLogFile = 0;
    ULONG Flags = AirfsDisk;
    ULONG OtherFlags = 0;
    ULONG FileInfoTimeout = INFINITE;
    PWSTR StorageFileName = L"";
    PWSTR MapName = L"";
    UINT64 VolumeSize = 16LL * 1024 * 1024;
    PWSTR FileSystemName = 0;
    PWSTR MountPoint = 0;
    PWSTR VolumePrefix = L"";
    PWSTR RootSddl = 0;
    HANDLE DebugLogHandle = INVALID_HANDLE_VALUE;
    AIRFS_ Airfs = 0;
    NTSTATUS Result;

    for (argp = argv + 1, arge = argv + argc; arge > argp; argp++)
    {
        if (L'-' != argp[0][0])
            break;
        switch (argp[0][1])
        {
        case L'?': goto usage;
        case L'd': ARG_TO_4(DebugFlags); break;
        case L'D': ARG_TO_S(DebugLogFile); break;
        case L'f': OtherFlags = AirfsFlushAndPurgeOnCleanup; break;
        case L'F': ARG_TO_S(FileSystemName); break;
        case L'i': OtherFlags = AirfsCaseInsensitive; break;
        case L'm': ARG_TO_S(MountPoint); break;
        case L'N': ARG_TO_S(StorageFileName);  break;
        case L'n': ARG_TO_S(MapName);  break;
        case L'S': ARG_TO_S(RootSddl); break;
        case L's': ARG_TO_8(VolumeSize); break;
        case L't': ARG_TO_4(FileInfoTimeout); break;
        case L'u':
            ARG_TO_S(VolumePrefix);
            if (*VolumePrefix) Flags = AirfsNet;
            break;
        default:
            goto usage;
        }
    }

    if (arge > argp)
        goto usage;

    if (AirfsDisk == Flags && !MountPoint)
        goto usage;

    if (DebugLogFile)
    {
        if (!wcscmp(L"-", DebugLogFile))
            DebugLogHandle = GetStdHandle(STD_ERROR_HANDLE);
        else
            DebugLogHandle = CreateFileW(
                DebugLogFile,
                FILE_APPEND_DATA,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                0,
                OPEN_ALWAYS,
                FILE_ATTRIBUTE_NORMAL,
                0);
        if (INVALID_HANDLE_VALUE == DebugLogHandle)
        {
            FAIL(L"cannot open debug log file");
            goto usage;
        }

        FspDebugLogSetHandle(DebugLogHandle);
    }

    Result = AirfsCreate(
        StorageFileName,
        MapName,
        Flags | OtherFlags,
        FileInfoTimeout,
        VolumeSize,
        FileSystemName,
        VolumePrefix,
        RootSddl,
        &Airfs);
    
    if (!NT_SUCCESS(Result))
    {
        FAIL(L"cannot create AIRFS");
        goto exit;
    }

    FspFileSystemSetDebugLog(Airfs->FileSystem, DebugFlags);

    if (MountPoint && L'\0' != MountPoint[0])
    {
        Result = FspFileSystemSetMountPoint(Airfs->FileSystem,
            L'*' == MountPoint[0] && L'\0' == MountPoint[1] ? 0 : MountPoint);
        if (!NT_SUCCESS(Result))
        {
            FAIL(L"cannot mount AIRFS");
            goto exit;
        }
    }

    Result = FspFileSystemStartDispatcher(Airfs->FileSystem, 0);
    if (!NT_SUCCESS(Result))
    {
        FAIL(L"cannot start AIRFS");
        goto exit;
    }

    MountPoint = FspFileSystemMountPoint(Airfs->FileSystem);

    WCHAR buffer[1024];
    _snwprintf_s(buffer, 1024, L"%S%S%s%S%s -t %ld -s %lld%S%s%S%s%S%s",
        PROGNAME, 
        *StorageFileName ? " -N " : "", *StorageFileName ? StorageFileName : L"", 
        *MapName         ? " -n " : "", *MapName         ? MapName         : L"",
        FileInfoTimeout, VolumeSize,
        RootSddl         ? " -S " : "", RootSddl         ? RootSddl        : L"",
        *VolumePrefix    ? " -u " : "", *VolumePrefix    ? VolumePrefix    : L"",
        MountPoint       ? " -m " : "", MountPoint       ? MountPoint      : L"");
    INFO(buffer);

    Service->UserContext = Airfs;
    Result = STATUS_SUCCESS;

  exit:

    if (!NT_SUCCESS(Result) && Airfs)
        AirfsDelete(Airfs);
    return Result;

  usage:
    
    static wchar_t usage[] = L""
        "usage: %s OPTIONS\n"
        "\n"
        "options:\n"
        "    -d DebugFlags       [-1: enable all debug logs]\n"
        "    -D DebugLogFile     [file path; use - for stderr]\n"
        "    -f                  [flush and purge cache on cleanup]\n"
        "    -F FileSystemName\n"
        "    -i                  [case insensitive file system]\n"
        "    -m MountPoint       [X:|* (required if no UNC prefix)]\n"
        "    -n MapName          [(ex) \"Local\\Airfs\"]\n"
        "    -N StorageFileName  [\"\": in memory only]\n"
        "    -s VolumeSize       [bytes]\n"
        "    -S RootSddl         [file rights: FA, etc; NO generic rights: GA, etc.]\n"
        "    -t FileInfoTimeout  [millis]\n"
        "    -u \\Server\\Share    [UNC prefix (single backslash)]\n";

    FAIL(usage, L"" PROGNAME);

    return STATUS_UNSUCCESSFUL;
}

//////////////////////////////////////////////////////////////////////

NTSTATUS SvcStop(FSP_SERVICE *Service)
{
    AIRFS_ Airfs = (AIRFS_) Service->UserContext;
    FspFileSystemStopDispatcher(Airfs->FileSystem);
    AirfsDelete(Airfs);
    return STATUS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////

int wmain(int argc, wchar_t **argv)
{
    if (!NT_SUCCESS(FspLoad(0)))
        return ERROR_DELAY_LOAD_FAILED;

    return FspServiceRun(L"" PROGNAME, SvcStart, SvcStop, 0);
}

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
