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

#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <winternl.h>
#pragma warning(push)
#pragma warning(disable:4005)           /* macro redefinition */
#include <ntstatus.h>
#pragma warning(pop)

#define DeleteMaxTries                  30
#define DeleteSleepTimeout              300

static VOID WaitDeletePending(PCWSTR FileName);

NTSTATUS NTAPI ResilientNtCreateFile(
    PHANDLE PFileHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PIO_STATUS_BLOCK IoStatusBlock,
    PLARGE_INTEGER AllocationSize,
    ULONG FileAttributes,
    ULONG ShareAccess,
    ULONG CreateDisposition,
    ULONG CreateOptions,
    PVOID EaBuffer,
    ULONG EaLength)
{
    DWORD LastError = GetLastError(); /* preserve last error */
    NTSTATUS Result;

    Result = NtCreateFile(
        PFileHandle,
        DesiredAccess,
        ObjectAttributes,
        IoStatusBlock,
        AllocationSize,
        FileAttributes,
        ShareAccess,
        CreateDisposition,
        CreateOptions,
        EaBuffer,
        EaLength);

    if (NT_SUCCESS(Result) &&
        (FILE_DELETE_ON_CLOSE & CreateOptions))
    {
        /* HACK: remember FILE_DELETE_ON_CLOSE through HANDLE_FLAG_PROTECT_FROM_CLOSE */
        SetHandleInformation(*PFileHandle,
            HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
    }

    SetLastError(LastError);
    return Result;
}

NTSTATUS NTAPI ResilientNtClose(
    HANDLE Handle)
{
    DWORD LastError = GetLastError(); /* preserve last error */
    NTSTATUS Result;
    DWORD HandleFlags = 0, FileNameLen;
    WCHAR FileNameBuf[sizeof "\\\\?\\GLOBALROOT" - 1 + 1024] = L"\\\\?\\GLOBALROOT";

    if (GetHandleInformation(Handle, &HandleFlags) &&
        (HANDLE_FLAG_PROTECT_FROM_CLOSE & HandleFlags))
    {
        SetHandleInformation(Handle,
            HANDLE_FLAG_PROTECT_FROM_CLOSE, 0);
        FileNameLen = GetFinalPathNameByHandle(Handle,
            FileNameBuf + sizeof "\\\\?\\GLOBALROOT" - 1, 1023,
            FILE_NAME_OPENED | VOLUME_NAME_NT);
        if (0 == FileNameLen || FileNameLen >= 1024)
            HandleFlags = 0;
    }

    Result = NtClose(
        Handle);

    if (NT_SUCCESS(Result))
    {
        if (HANDLE_FLAG_PROTECT_FROM_CLOSE & HandleFlags)
            WaitDeletePending(FileNameBuf);
    }

    SetLastError(LastError);
    return Result;
}

HANDLE WINAPI ResilientCreateFileW(
    LPCWSTR lpFileName,
    DWORD dwDesiredAccess,
    DWORD dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition,
    DWORD dwFlagsAndAttributes,
    HANDLE hTemplateFile)
{
    HANDLE Handle;
    DWORD LastError;

    Handle = CreateFileW(
        lpFileName,
        dwDesiredAccess,
        dwShareMode,
        lpSecurityAttributes,
        dwCreationDisposition,
        dwFlagsAndAttributes,
        hTemplateFile);
    LastError = GetLastError();

    if (INVALID_HANDLE_VALUE != Handle &&
        (FILE_FLAG_DELETE_ON_CLOSE & dwFlagsAndAttributes))
    {
        /* HACK: remember FILE_FLAG_DELETE_ON_CLOSE through HANDLE_FLAG_PROTECT_FROM_CLOSE */
        SetHandleInformation(Handle,
            HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
    }

    SetLastError(LastError);
    return Handle;
}

BOOL WINAPI ResilientCloseHandle(
    HANDLE hObject)
{
    BOOL Success;
    DWORD LastError;
    DWORD HandleFlags = 0, FileNameLen;
    WCHAR FileNameBuf[sizeof "\\\\?\\GLOBALROOT" - 1 + 1024] = L"\\\\?\\GLOBALROOT";

    if (GetHandleInformation(hObject, &HandleFlags) &&
        (HANDLE_FLAG_PROTECT_FROM_CLOSE & HandleFlags))
    {
        SetHandleInformation(hObject,
            HANDLE_FLAG_PROTECT_FROM_CLOSE, 0);
        FileNameLen = GetFinalPathNameByHandle(hObject,
            FileNameBuf + sizeof "\\\\?\\GLOBALROOT" - 1, 1023,
            FILE_NAME_OPENED | VOLUME_NAME_NT);
        if (0 == FileNameLen || FileNameLen >= 1024)
            HandleFlags = 0;
    }

    Success = CloseHandle(
        hObject);
    LastError = GetLastError();

    if (Success)
    {
        if (HANDLE_FLAG_PROTECT_FROM_CLOSE & HandleFlags)
            WaitDeletePending(FileNameBuf);
    }

    SetLastError(LastError);
    return Success;
}

BOOL WINAPI ResilientDeleteFileW(
    LPCWSTR lpFileName)
{
    BOOL Success;
    DWORD LastError;

    Success = DeleteFileW(lpFileName);
    LastError = GetLastError();

    if (Success)
        WaitDeletePending(lpFileName);
    else
    {
        for (ULONG MaxTries = DeleteMaxTries;
            !Success && ERROR_SHARING_VIOLATION == GetLastError() && 0 != MaxTries;
            MaxTries--)
        {
            Sleep(DeleteSleepTimeout);
            Success = DeleteFileW(lpFileName);
        }
    }

    SetLastError(LastError);
    return Success;
}

BOOL WINAPI ResilientRemoveDirectoryW(
    LPCWSTR lpPathName)
{
    BOOL Success;
    DWORD LastError;

    Success = RemoveDirectoryW(lpPathName);
    LastError = GetLastError();

    if (Success)
        WaitDeletePending(lpPathName);
    else
    {
        for (ULONG MaxTries = DeleteMaxTries;
            !Success &&
            (ERROR_SHARING_VIOLATION == GetLastError() || ERROR_DIR_NOT_EMPTY == GetLastError()) &&
            0 != MaxTries;
            MaxTries--)
        {
            Sleep(DeleteSleepTimeout);
            Success = RemoveDirectoryW(lpPathName);
        }
    }

    SetLastError(LastError);
    return Success;
}

static VOID WaitDeletePending(PCWSTR FileName)
{
    for (ULONG MaxTries = DeleteMaxTries; 0 != MaxTries; MaxTries--)
    {
        HANDLE Handle = CreateFileW(FileName,
            FILE_READ_ATTRIBUTES, 0, 0, OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
        if (INVALID_HANDLE_VALUE != Handle)
            /* should never happen! */
            CloseHandle(Handle);
        else if (ERROR_ACCESS_DENIED == GetLastError())
            /* STATUS_DELETE_PENDING */
            Sleep(DeleteSleepTimeout);
        else
            break;
    }
}
