/**
 * @file ptfs-main.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 NTSTATUS EnablePrivileges(PWSTR PrivilegeName, ...)
{
    va_list ap;
    HANDLE Token = 0;
    TOKEN_PRIVILEGES Privileges;
    WCHAR WarnNames[1024], *WarnNameP = WarnNames;
    NTSTATUS Result;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token))
    {
        Result = FspNtStatusFromWin32(GetLastError());
        goto exit;
    }

    va_start(ap, PrivilegeName);
    for (PWSTR Name = PrivilegeName; 0 != Name; Name = va_arg(ap, PWSTR))
    {
        Privileges.PrivilegeCount = 1;
        Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

        if (!LookupPrivilegeValueW(0, Name, &Privileges.Privileges[0].Luid) ||
            !AdjustTokenPrivileges(Token, FALSE, &Privileges, 0, 0, 0))
        {
            Result = FspNtStatusFromWin32(GetLastError());
            goto exit;
        }

        if (ERROR_NOT_ALL_ASSIGNED == GetLastError())
        {
            *WarnNameP++ = ' ';
            size_t len = wcslen(Name);
            memcpy(WarnNameP, Name, len * sizeof(WCHAR));
            WarnNameP += len;
            *WarnNameP = '\0';
        }
    }
    va_end(ap);

    if (WarnNames != WarnNameP)
        warn(L"cannot enable privileges:%s", WarnNames);

    Result = STATUS_SUCCESS;

exit:
    if (0 != Token)
        CloseHandle(Token);

    return Result;
}

static ULONG wcstol_deflt(wchar_t *w, ULONG deflt)
{
    wchar_t *endp;
    ULONG ul = wcstol(w, &endp, 0);
    return L'\0' != w[0] && L'\0' == *endp ? ul : deflt;
}

static NTSTATUS SvcStart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv)
{
#define argtos(v)                       if (arge > ++argp) v = *argp; else goto usage
#define argtol(v)                       if (arge > ++argp) v = wcstol_deflt(*argp, v); else goto usage

    wchar_t **argp, **arge;
    PWSTR RootPath = 0;
    ULONG FileInfoTimeout = INFINITE;
    ULONG FsAttributeMask = 0;
    PWSTR VolumePrefix = 0;
    PWSTR MountPoint = 0;
    PWSTR OptionString = 0;
    PWSTR DebugLogFile = 0;
    ULONG DebugFlags = 0;
    HANDLE DebugLogHandle = INVALID_HANDLE_VALUE;
    WCHAR RootPathBuf[MAX_PATH];
    PTFS *Ptfs = 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':
            argtol(DebugFlags);
            break;
        case L'D':
            argtos(DebugLogFile);
            break;
        case L'm':
            argtos(MountPoint);
            break;
        case L'o':
            argtos(OptionString);
            if (0 == _wcsicmp(L"ExtraFeatures", OptionString))
                FsAttributeMask |=
                    PtfsReparsePoints |
                    PtfsNamedStreams |
                    PtfsExtendedAttributes |
                    PtfsWslFeatures;
            else if (0 == _wcsicmp(L"ReparsePoints", OptionString))
                FsAttributeMask |= PtfsReparsePoints;
            else if (0 == _wcsicmp(L"NamedStreams", OptionString))
                FsAttributeMask |= PtfsNamedStreams;
            else if (0 == _wcsicmp(L"ExtendedAttributes", OptionString))
                FsAttributeMask |= PtfsExtendedAttributes;
            else if (0 == _wcsicmp(L"WslFeatures", OptionString))
                FsAttributeMask |= PtfsWslFeatures;
            else if (0 == _wcsicmp(L"FlushAndPurgeOnCleanup", OptionString))
                FsAttributeMask |= PtfsFlushAndPurgeOnCleanup;
            else if (0 == _wcsicmp(L"SetAllocationSizeOnCleanup", OptionString))
                FsAttributeMask |= PtfsSetAllocationSizeOnCleanup;
            else
                goto usage;
            break;
        case L'p':
            argtos(RootPath);
            break;
        case L't':
            argtol(FileInfoTimeout);
            break;
        case L'u':
            argtos(VolumePrefix);
            break;
        default:
            goto usage;
        }
    }

    if (arge > argp)
        goto usage;

    if (0 == RootPath && 0 != VolumePrefix)
    {
        PWSTR P;

        P = wcschr(VolumePrefix, L'\\');
        if (0 != P && L'\\' != P[1])
        {
            P = wcschr(P + 1, L'\\');
            if (0 != P &&
                (
                (L'A' <= P[1] && P[1] <= L'Z') ||
                (L'a' <= P[1] && P[1] <= L'z')
                ) &&
                L'$' == P[2])
            {
                StringCbPrintf(RootPathBuf, sizeof RootPathBuf, L"%c:%s", P[1], P + 3);
                RootPath = RootPathBuf;
            }
        }
    }

    if (0 == RootPath || 0 == MountPoint)
        goto usage;

    if (0 != DebugLogFile)
    {
        if (0 == 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);
    }

    EnablePrivileges(SE_SECURITY_NAME, SE_BACKUP_NAME, SE_RESTORE_NAME, SE_CREATE_SYMBOLIC_LINK_NAME, 0);

    Result = PtfsCreate(
        RootPath,
        FileInfoTimeout,
        FsAttributeMask,
        VolumePrefix,
        MountPoint,
        DebugFlags,
        &Ptfs);
    if (!NT_SUCCESS(Result))
    {
        fail(L"cannot create file system");
        goto exit;
    }

    Result = FspFileSystemStartDispatcher(Ptfs->FileSystem, 0);
    if (!NT_SUCCESS(Result))
    {
        fail(L"cannot start file system");
        goto exit;
    }

    MountPoint = FspFileSystemMountPoint(Ptfs->FileSystem);

    info(L"%s -t %ld%s%s -p %s -m %s",
        L"" PROGNAME,
        FileInfoTimeout,
        0 != VolumePrefix && L'\0' != VolumePrefix[0] ? L" -u " : L"",
            0 != VolumePrefix && L'\0' != VolumePrefix[0] ? VolumePrefix : L"",
        RootPath,
        MountPoint);

    Service->UserContext = Ptfs;
    Result = STATUS_SUCCESS;

exit:
    if (!NT_SUCCESS(Result) && 0 != Ptfs)
        PtfsDelete(Ptfs);

    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"
        "    -t FileInfoTimeout  [millis]\n"
        "    -o ExtraFeatures    [extra Windows file system features]\n"
        "        -o ReparsePoints\n"
        "        -o NamedStreams\n"
        "        -o ExtendedAttributes\n"
        "        -o WslFeatures\n"
        "    -u \\Server\\Share    [UNC prefix (single backslash)]\n"
        "    -p Directory        [directory to expose as pass through file system]\n"
        "    -m MountPoint       [X:|*|directory]\n";

    fail(usage, L"" PROGNAME);

    return STATUS_UNSUCCESSFUL;

#undef argtos
#undef argtol
}

static NTSTATUS SvcStop(FSP_SERVICE *Service)
{
    PTFS *Ptfs = Service->UserContext;

    FspFileSystemStopDispatcher(Ptfs->FileSystem);
    PtfsDelete(Ptfs);

    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);
}
