/**
 * @file lfs.c
 *
 * @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.
 */

#include "ptfs.h"

static inline HANDLE LfsThreadEvent(VOID)
{
    static __declspec(thread) HANDLE Event;

    if (0 == Event)
        Event = CreateEventW(0, TRUE, FALSE, 0);
    return Event;
}

NTSTATUS LfsCreateFile(
    PHANDLE PHandle,
    ACCESS_MASK DesiredAccess,
    HANDLE RootHandle,
    PWSTR FileName,
    PSECURITY_DESCRIPTOR SecurityDescriptor,
    PLARGE_INTEGER AllocationSize,
    ULONG FileAttributes,
    ULONG CreateDisposition,
    ULONG CreateOptions,
    PVOID EaBuffer,
    ULONG EaLength)
{
    UNICODE_STRING Ufnm;
    OBJECT_ATTRIBUTES Obja;
    IO_STATUS_BLOCK Iosb;
    NTSTATUS Result;

    RtlInitUnicodeString(&Ufnm, FileName + 1);
    InitializeObjectAttributes(&Obja, &Ufnm, 0, RootHandle, SecurityDescriptor);

    Result = NtCreateFile(
        PHandle,
        FILE_READ_ATTRIBUTES | DesiredAccess,
        &Obja,
        &Iosb,
        AllocationSize,
        FileAttributes,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        CreateDisposition,
        CreateOptions,
        EaBuffer,
        EaLength);
#if 0
    if (STATUS_DELETE_PENDING == Result && IsDebuggerPresent())
        DebugBreak();
#endif
    return Result;
}

NTSTATUS LfsOpenFile(
    PHANDLE PHandle,
    ACCESS_MASK DesiredAccess,
    HANDLE RootHandle,
    PWSTR FileName,
    ULONG OpenOptions)
{
    UNICODE_STRING Ufnm;
    OBJECT_ATTRIBUTES Obja;
    IO_STATUS_BLOCK Iosb;
    NTSTATUS Result;

    RtlInitUnicodeString(&Ufnm, FileName + 1);
    InitializeObjectAttributes(&Obja, &Ufnm, 0, RootHandle, 0);

    Result = NtOpenFile(
        PHandle,
        FILE_READ_ATTRIBUTES | DesiredAccess,
        &Obja,
        &Iosb,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        OpenOptions);
#if 0
    if (STATUS_DELETE_PENDING == Result && IsDebuggerPresent())
        DebugBreak();
#endif
    return Result;
}

NTSTATUS LfsGetFileInfo(
    HANDLE Handle,
    ULONG RootPrefixLength,
    FSP_FSCTL_FILE_INFO *FileInfo)
{
    FSP_FSCTL_OPEN_FILE_INFO *OpenFileInfo = -1 != RootPrefixLength ?
        FspFileSystemGetOpenFileInfo(FileInfo) : 0;
    IO_STATUS_BLOCK Iosb;
    union
    {
        FILE_ALL_INFORMATION V;
        UINT8 B[FIELD_OFFSET(FILE_ALL_INFORMATION, NameInformation.FileName) + FSP_FSCTL_TRANSACT_PATH_SIZEMAX];
    } FileAllInfo;
    FILE_ATTRIBUTE_TAG_INFORMATION FileAttrInfo;
    NTSTATUS Result;

    Result = NtQueryInformationFile(
        Handle,
        &Iosb,
        &FileAllInfo,
        sizeof FileAllInfo,
        18/*FileAllInformation*/);
    if (STATUS_BUFFER_OVERFLOW == Result)
        OpenFileInfo = 0;
    else if (!NT_SUCCESS(Result))
        return Result;
    if (0 != (FILE_ATTRIBUTE_REPARSE_POINT & FileAllInfo.V.BasicInformation.FileAttributes))
    {
        Result = NtQueryInformationFile(
            Handle,
            &Iosb,
            &FileAttrInfo,
            sizeof FileAttrInfo,
            35/*FileAttributeTagInformation*/);
        if (!NT_SUCCESS(Result))
            return Result;
    }

    Result = STATUS_SUCCESS;

    FileInfo->FileAttributes = FileAllInfo.V.BasicInformation.FileAttributes;
    FileInfo->ReparseTag = 0 != (FILE_ATTRIBUTE_REPARSE_POINT & FileAllInfo.V.BasicInformation.FileAttributes) ?
        FileAttrInfo.ReparseTag : 0;
    FileInfo->AllocationSize = FileAllInfo.V.StandardInformation.AllocationSize.QuadPart;
    FileInfo->FileSize = FileAllInfo.V.StandardInformation.EndOfFile.QuadPart;
    FileInfo->CreationTime = FileAllInfo.V.BasicInformation.CreationTime.QuadPart;
    FileInfo->LastAccessTime = FileAllInfo.V.BasicInformation.LastAccessTime.QuadPart;
    FileInfo->LastWriteTime = FileAllInfo.V.BasicInformation.LastWriteTime.QuadPart;
    FileInfo->ChangeTime = FileAllInfo.V.BasicInformation.ChangeTime.QuadPart;
    FileInfo->IndexNumber = FileAllInfo.V.InternalInformation.IndexNumber.QuadPart;
    FileInfo->HardLinks = 0;
    FileInfo->EaSize = LfsGetEaSize(FileAllInfo.V.EaInformation.EaSize);

    if (0 != OpenFileInfo &&
        OpenFileInfo->NormalizedNameSize > sizeof(WCHAR) + FileAllInfo.V.NameInformation.FileNameLength &&
        RootPrefixLength <= FileAllInfo.V.NameInformation.FileNameLength)
    {
        PWSTR P = (PVOID)((PUINT8)FileAllInfo.V.NameInformation.FileName + RootPrefixLength);
        ULONG L = FileAllInfo.V.NameInformation.FileNameLength - RootPrefixLength;

        if (L'\\' == *P)
        {
            memcpy(OpenFileInfo->NormalizedName, P, L);
            OpenFileInfo->NormalizedNameSize = (UINT16)L;
        }
        else
        {
            *OpenFileInfo->NormalizedName = L'\\';
            memcpy(OpenFileInfo->NormalizedName + 1, P, L);
            OpenFileInfo->NormalizedNameSize = (UINT16)(L + sizeof(WCHAR));
        }
    }

    return Result;
}

NTSTATUS LfsReadFile(
    HANDLE Handle,
    PVOID Buffer,
    UINT64 Offset,
    ULONG Length,
    PULONG PBytesTransferred)
{
    HANDLE Event;
    IO_STATUS_BLOCK Iosb;
    NTSTATUS Result;

    Event = LfsThreadEvent();
    if (0 == Event)
        return STATUS_INSUFFICIENT_RESOURCES;

    Result = NtReadFile(
        Handle,
        Event,
        0,
        0,
        &Iosb,
        Buffer,
        Length,
        (PLARGE_INTEGER)&Offset,
        0);
    if (STATUS_PENDING == Result)
    {
        WaitForSingleObject(Event, INFINITE);
        Result = Iosb.Status;
    }

    *PBytesTransferred = (ULONG)Iosb.Information;

    return Result;
}

NTSTATUS LfsWriteFile(
    HANDLE Handle,
    PVOID Buffer,
    UINT64 Offset,
    ULONG Length,
    PULONG PBytesTransferred)
{
    HANDLE Event;
    IO_STATUS_BLOCK Iosb;
    NTSTATUS Result;

    Event = LfsThreadEvent();
    if (0 == Event)
        return STATUS_INSUFFICIENT_RESOURCES;

    Result = NtWriteFile(
        Handle,
        Event,
        0,
        0,
        &Iosb,
        Buffer,
        Length,
        (PLARGE_INTEGER)&Offset,
        0);
    if (STATUS_PENDING == Result)
    {
        WaitForSingleObject(Event, INFINITE);
        Result = Iosb.Status;
    }

    *PBytesTransferred = (ULONG)Iosb.Information;

    return Result;
}

NTSTATUS LfsQueryDirectoryFile(
    HANDLE Handle,
    PVOID Buffer,
    ULONG Length,
    FILE_INFORMATION_CLASS FileInformationClass,
    BOOLEAN ReturnSingleEntry,
    PWSTR FileName,
    BOOLEAN RestartScan,
    PULONG PBytesTransferred)
{
    HANDLE Event;
    UNICODE_STRING Ufnm;
    IO_STATUS_BLOCK Iosb;
    NTSTATUS Result;

    Event = LfsThreadEvent();
    if (0 == Event)
        return STATUS_INSUFFICIENT_RESOURCES;

    if (0 != FileName)
        RtlInitUnicodeString(&Ufnm, FileName);

    Result = NtQueryDirectoryFile(
        Handle,
        Event,
        0,
        0,
        &Iosb,
        Buffer,
        Length,
        FileInformationClass,
        ReturnSingleEntry,
        0 != FileName ? &Ufnm : 0,
        RestartScan);
    if (STATUS_PENDING == Result)
    {
        WaitForSingleObject(Event, INFINITE);
        Result = Iosb.Status;
    }

    *PBytesTransferred = (ULONG)Iosb.Information;

    return Result;
}

NTSTATUS LfsFsControlFile(
    HANDLE Handle,
    ULONG FsControlCode,
    PVOID InputBuffer,
    ULONG InputBufferLength,
    PVOID OutputBuffer,
    ULONG OutputBufferLength,
    PULONG PBytesTransferred)
{
    HANDLE Event;
    IO_STATUS_BLOCK Iosb;
    NTSTATUS Result;

    Event = LfsThreadEvent();
    if (0 == Event)
        return STATUS_INSUFFICIENT_RESOURCES;

    Result = NtFsControlFile(
        Handle,
        Event,
        0,
        0,
        &Iosb,
        FsControlCode,
        InputBuffer,
        InputBufferLength,
        OutputBuffer,
        OutputBufferLength);
    if (STATUS_PENDING == Result)
    {
        WaitForSingleObject(Event, INFINITE);
        Result = Iosb.Status;
    }

    *PBytesTransferred = (ULONG)Iosb.Information;

    return Result;
}
