/*
 * PROJECT:     ReactOS API Tests
 * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
 * PURPOSE:     Test for NtQueryObject
 * COPYRIGHT:   Copyright 2024 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
 */

#include "precomp.h"

/* Flags combination allowing all the read, write and delete share modes.
 * Currently similar to FILE_SHARE_VALID_FLAGS. */
#define FILE_SHARE_ALL \
    (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)

/* Adapted from kmtests/ntos_ob/ObQuery.c!ObjectNameInformationTests().
 * Please sync both tests in case you add or remove new features. */
START_TEST(NtQueryObject)
{
    ULONG g_OsVersion =
        SharedUserData->NtMajorVersion << 8 | SharedUserData->NtMinorVersion;

    NTSTATUS Status;
    HANDLE DeviceHandle;
    UNICODE_STRING DeviceName;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;

    ULONG BufferSize1, BufferSize2, BufferSize3;
    struct { OBJECT_NAME_INFORMATION; WCHAR Buffer[MAX_PATH]; } ObjectNameBuffer;
    PUNICODE_STRING ObjectName = &ObjectNameBuffer.Name;

    /* Test the drive containing SystemRoot */
    WCHAR NtDeviceName[] = L"\\DosDevices\\?:";
    NtDeviceName[sizeof("\\DosDevices\\")-1] = SharedUserData->NtSystemRoot[0];

    /* Open a handle to the device */
    RtlInitUnicodeString(&DeviceName, NtDeviceName);
    InitializeObjectAttributes(&ObjectAttributes,
                               &DeviceName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = NtOpenFile(&DeviceHandle,
                        FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                        &ObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_ALL,
                        FILE_SYNCHRONOUS_IO_NONALERT);
    ok_ntstatus(Status, STATUS_SUCCESS);
    if (!NT_SUCCESS(Status))
    {
        skip("Device '%S': Opening failed\n", NtDeviceName);
        return;
    }

    /* Invoke ObjectNameInformation that retrieves the canonical device name */
    Status = NtQueryObject(DeviceHandle,
                           ObjectNameInformation,
                           &ObjectNameBuffer,
                           0,
                           &BufferSize1);
    ok_ntstatus(Status, STATUS_INFO_LENGTH_MISMATCH);

    Status = NtQueryObject(DeviceHandle,
                           ObjectNameInformation,
                           &ObjectNameBuffer,
                           sizeof(OBJECT_NAME_INFORMATION),
                           &BufferSize2);
    ok_ntstatus(Status, STATUS_BUFFER_OVERFLOW);

    Status = NtQueryObject(DeviceHandle,
                           ObjectNameInformation,
                           &ObjectNameBuffer,
                           sizeof(ObjectNameBuffer),
                           &BufferSize3);
    ok_ntstatus(Status, STATUS_SUCCESS);

    NtClose(DeviceHandle);

    /* Compare the returned buffer sizes */

    /* The returned size behaviour changed (when NtQueryObject()'s
     * input Length is zero) between Windows <= 2003 and Vista+ */
    if (g_OsVersion < _WIN32_WINNT_VISTA)
        ok_eq_ulong(BufferSize1, (ULONG)sizeof(OBJECT_NAME_INFORMATION));
    else
        ok_eq_ulong(BufferSize1, (ULONG)sizeof(OBJECT_NAME_INFORMATION) + ObjectName->MaximumLength);

    ok_eq_ulong(BufferSize2, BufferSize3);
    ok_eq_ulong(BufferSize3, (ULONG)sizeof(OBJECT_NAME_INFORMATION) + ObjectName->MaximumLength);

    /* Test the name buffer */
    ok(ObjectName->Length > 0, "ObjectName->Length == %hu, expected > 0\n", ObjectName->Length);
    ok_eq_uint(ObjectName->MaximumLength, ObjectName->Length + sizeof(WCHAR));
    ok(ObjectName->Buffer[ObjectName->Length / sizeof(WCHAR)] == UNICODE_NULL,
       "UNICODE_NULL not found at end of ObjectName->Buffer\n");
    if (ObjectName->Buffer[ObjectName->Length / sizeof(WCHAR)] != UNICODE_NULL)
    {
        skip("ObjectName->Buffer string length check skipped\n");
        return;
    }

    /* Verify that ObjectName->Length doesn't count extra NUL-terminators */
    SIZE_T strLen = wcslen(ObjectName->Buffer) * sizeof(WCHAR);
    ok_eq_size(strLen, (SIZE_T)ObjectName->Length);

    /* Get the full path name of the current executable */
    WCHAR ExecutablePath[MAX_PATH];
    GetModuleFileNameW(NULL, ExecutablePath, MAX_PATH);

    /* Open the executable file */
    HANDLE FileHandle;
    FileHandle = CreateFileW(ExecutablePath,
                             GENERIC_READ,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             NULL,
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL,
                             NULL);
    ok(FileHandle != INVALID_HANDLE_VALUE,
       "File '%S': Opening failed\n", ExecutablePath);
    if (FileHandle == INVALID_HANDLE_VALUE)
    {
        skip("File '%S': Opening failed\n", ExecutablePath);
        return;
    }

    /* Query the name of the file */
    Status = NtQueryObject(FileHandle,
                           ObjectNameInformation,
                           &ObjectNameBuffer,
                           sizeof(ObjectNameBuffer),
                           &BufferSize3);
    ok_ntstatus(Status, STATUS_SUCCESS);

    /* Validate that the name starts with "\\Device" */
    ok(wcsncmp(ObjectName->Buffer, L"\\Device", 7) == 0,
       "ObjectName->Buffer: '%S' does not start with '\\Device'\n",
       ObjectName->Buffer);
}