/*
 * PROJECT:     ReactOS Setup Library
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     ARC path to-and-from NT path resolver.
 * COPYRIGHT:   Copyright 2017-2018 Hermes Belusca-Maito
 */
/*
 * References:
 *
 * - ARC Specification v1.2: http://netbsd.org./docs/Hardware/Machines/ARC/riscspec.pdf
 * - "Setup and Startup", MSDN article: https://technet.microsoft.com/en-us/library/cc977184.aspx
 * - Answer for "How do I determine the ARC path for a particular drive letter in Windows?": https://serverfault.com/a/5929
 * - ARC - LinuxMIPS: https://www.linux-mips.org/wiki/ARC
 * - ARCLoad - LinuxMIPS: https://www.linux-mips.org/wiki/ARCLoad
 * - Inside Windows 2000 Server: https://books.google.fr/books?id=kYT7gKnwUQ8C&pg=PA71&lpg=PA71&dq=nt+arc+path&source=bl&ots=K8I1F_KQ_u&sig=EJq5t-v2qQk-QB7gNSREFj7pTVo&hl=en&sa=X&redir_esc=y#v=onepage&q=nt%20arc%20path&f=false
 * - Inside Windows Server 2003: https://books.google.fr/books?id=zayrcM9ZYdAC&pg=PA61&lpg=PA61&dq=arc+path+to+nt+path&source=bl&ots=x2JSWfp2MA&sig=g9mufN6TCOrPejDov6Rjp0Jrldo&hl=en&sa=X&redir_esc=y#v=onepage&q=arc%20path%20to%20nt%20path&f=false
 *
 * Stuff to read: http://www.adminxp.com/windows2000/index.php?aid=46 and http://www.trcb.com/Computers-and-Technology/Windows-XP/Windows-XP-ARC-Naming-Conventions-1432.htm
 * concerning which values of disk() or rdisk() are valid when either scsi() or multi() adapters are specified.
 */

/* INCLUDES *****************************************************************/

#include "precomp.h"

#include "filesup.h"
#include "partlist.h"
#include "arcname.h"

#define NDEBUG
#include <debug.h>


/* Supported adapter types */
typedef enum _ADAPTER_TYPE
{
    EisaAdapter,
    ScsiAdapter,
    MultiAdapter,
    NetAdapter,
    RamdiskAdapter,
    AdapterTypeMax
} ADAPTER_TYPE, *PADAPTER_TYPE;
const PCSTR AdapterTypes_A[] =
{
    "eisa",
    "scsi",
    "multi",
    "net",
    "ramdisk",
    NULL
};
const PCWSTR AdapterTypes_U[] =
{
    L"eisa",
    L"scsi",
    L"multi",
    L"net",
    L"ramdisk",
    NULL
};

/* Supported controller types */
typedef enum _CONTROLLER_TYPE
{
    DiskController,
    CdRomController,
    ControllerTypeMax
} CONTROLLER_TYPE, *PCONTROLLER_TYPE;
const PCSTR ControllerTypes_A[] =
{
    "disk",
    "cdrom",
    NULL
};
const PCWSTR ControllerTypes_U[] =
{
    L"disk",
    L"cdrom",
    NULL
};

/* Supported peripheral types */
typedef enum _PERIPHERAL_TYPE
{
//  VDiskPeripheral,
    RDiskPeripheral,
    FDiskPeripheral,
    CdRomPeripheral,
    PeripheralTypeMax
} PERIPHERAL_TYPE, *PPERIPHERAL_TYPE;
const PCSTR PeripheralTypes_A[] =
{
//  "vdisk", // Enable this when we'll support boot from virtual disks!
    "rdisk",
    "fdisk",
    "cdrom",
    NULL
};
const PCWSTR PeripheralTypes_U[] =
{
//  L"vdisk", // Enable this when we'll support boot from virtual disks!
    L"rdisk",
    L"fdisk",
    L"cdrom",
    NULL
};


/* FUNCTIONS ****************************************************************/

PCSTR
ArcGetNextTokenA(
    IN  PCSTR ArcPath,
    OUT PANSI_STRING TokenSpecifier,
    OUT PULONG Key)
{
    NTSTATUS Status;
    PCSTR p = ArcPath;
    ULONG SpecifierLength;
    ULONG KeyValue;

    /*
     * We must have a valid "specifier(key)" string, where 'specifier'
     * cannot be the empty string, and is followed by '('.
     */
    p = strchr(p, '(');
    if (p == NULL)
        return NULL; /* No '(' found */
    if (p == ArcPath)
        return NULL; /* Path starts with '(' and is thus invalid */

    SpecifierLength = (p - ArcPath) * sizeof(CHAR);

    /*
     * The strtoul function skips any leading whitespace.
     *
     * Note that if the token is "specifier()" then strtoul won't perform
     * any conversion and return 0, therefore effectively making the token
     * equivalent to "specifier(0)", as it should be.
     */
    // KeyValue = atoi(p);
    KeyValue = strtoul(p, (PSTR*)&p, 10);

    /* Skip any trailing whitespace */
    while (isspace(*p)) ++p;

    /* The token must terminate with ')' */
    if (*p != ')')
        return NULL;
#if 0
    p = strchr(p, ')');
    if (p == NULL)
        return NULL;
#endif

    /* We should have succeeded, copy the token specifier in the buffer */
    Status = RtlStringCbCopyNA(TokenSpecifier->Buffer,
                               TokenSpecifier->MaximumLength,
                               ArcPath, SpecifierLength);
    if (!NT_SUCCESS(Status))
        return NULL;

    TokenSpecifier->Length = strlen(TokenSpecifier->Buffer) * sizeof(CHAR);

    /* We succeeded, return the token key value */
    *Key = KeyValue;

    /* Next token starts just after */
    return ++p;
}

PCWSTR
ArcGetNextTokenU(
    IN  PCWSTR ArcPath,
    OUT PUNICODE_STRING TokenSpecifier,
    OUT PULONG Key)
{
    NTSTATUS Status;
    PCWSTR p = ArcPath;
    ULONG SpecifierLength;
    ULONG KeyValue;

    /*
     * We must have a valid "specifier(key)" string, where 'specifier'
     * cannot be the empty string, and is followed by '('.
     */
    p = wcschr(p, L'(');
    if (p == NULL)
        return NULL; /* No '(' found */
    if (p == ArcPath)
        return NULL; /* Path starts with '(' and is thus invalid */

    SpecifierLength = (p - ArcPath) * sizeof(WCHAR);

    ++p;

    /*
     * The strtoul function skips any leading whitespace.
     *
     * Note that if the token is "specifier()" then strtoul won't perform
     * any conversion and return 0, therefore effectively making the token
     * equivalent to "specifier(0)", as it should be.
     */
    // KeyValue = _wtoi(p);
    KeyValue = wcstoul(p, (PWSTR*)&p, 10);

    /* Skip any trailing whitespace */
    while (iswspace(*p)) ++p;

    /* The token must terminate with ')' */
    if (*p != L')')
        return NULL;
#if 0
    p = wcschr(p, L')');
    if (p == NULL)
        return NULL;
#endif

    /* We should have succeeded, copy the token specifier in the buffer */
    Status = RtlStringCbCopyNW(TokenSpecifier->Buffer,
                               TokenSpecifier->MaximumLength,
                               ArcPath, SpecifierLength);
    if (!NT_SUCCESS(Status))
        return NULL;

    TokenSpecifier->Length = wcslen(TokenSpecifier->Buffer) * sizeof(WCHAR);

    /* We succeeded, return the token key value */
    *Key = KeyValue;

    /* Next token starts just after */
    return ++p;
}


ULONG
ArcMatchTokenA(
    IN PCSTR CandidateToken,
    IN const PCSTR* TokenTable)
{
    ULONG Index = 0;

    while (TokenTable[Index] && _stricmp(CandidateToken, TokenTable[Index]) != 0)
    {
        ++Index;
    }

    return Index;
}

ULONG
ArcMatchTokenU(
    IN PCWSTR CandidateToken,
    IN const PCWSTR* TokenTable)
{
    ULONG Index = 0;

    while (TokenTable[Index] && _wcsicmp(CandidateToken, TokenTable[Index]) != 0)
    {
        ++Index;
    }

    return Index;
}

ULONG
ArcMatchToken_UStr(
    IN PCUNICODE_STRING CandidateToken,
    IN const PCWSTR* TokenTable)
{
    ULONG Index = 0;
#if 0
    SIZE_T Length;
#else
    UNICODE_STRING Token;
#endif

    while (TokenTable[Index])
    {
#if 0
        Length = wcslen(TokenTable[Index])*sizeof(WCHAR);
        if (RtlCompareMemory(CandidateToken->Buffer, TokenTable[Index], Length) == Length)
            break;
#else
        RtlInitUnicodeString(&Token, TokenTable[Index]);
        // if (RtlCompareUnicodeString(CandidateToken, &Token, TRUE) == 0)
        if (RtlEqualUnicodeString(CandidateToken, &Token, TRUE))
            break;
#endif

        ++Index;
    }

    return Index;
}


BOOLEAN
ArcPathNormalize(
    OUT PUNICODE_STRING NormalizedArcPath,
    IN  PCWSTR ArcPath)
{
    NTSTATUS Status;
    PCWSTR EndOfArcName;
    PCWSTR p;

    if (NormalizedArcPath->MaximumLength < sizeof(UNICODE_NULL))
        return FALSE;

    *NormalizedArcPath->Buffer = UNICODE_NULL;
    NormalizedArcPath->Length = 0;

    EndOfArcName = wcschr(ArcPath, OBJ_NAME_PATH_SEPARATOR);
    if (!EndOfArcName)
        EndOfArcName = ArcPath + wcslen(ArcPath);

    while ((p = wcsstr(ArcPath, L"()")) && (p < EndOfArcName))
    {
#if 0
        Status = RtlStringCbCopyNW(NormalizedArcPath->Buffer,
                                   NormalizedArcPath->MaximumLength,
                                   ArcPath, (p - ArcPath) * sizeof(WCHAR));
#else
        Status = RtlStringCbCatNW(NormalizedArcPath->Buffer,
                                  NormalizedArcPath->MaximumLength,
                                  ArcPath, (p - ArcPath) * sizeof(WCHAR));
#endif
        if (!NT_SUCCESS(Status))
            return FALSE;

        Status = RtlStringCbCatW(NormalizedArcPath->Buffer,
                                 NormalizedArcPath->MaximumLength,
                                 L"(0)");
        if (!NT_SUCCESS(Status))
            return FALSE;
#if 0
        NormalizedArcPath->Buffer += wcslen(NormalizedArcPath->Buffer);
#endif
        ArcPath = p + 2;
    }

    Status = RtlStringCbCatW(NormalizedArcPath->Buffer,
                             NormalizedArcPath->MaximumLength,
                             ArcPath);
    if (!NT_SUCCESS(Status))
        return FALSE;

    NormalizedArcPath->Length = wcslen(NormalizedArcPath->Buffer) * sizeof(WCHAR);
    return TRUE;
}


/*
 * ArcName:
 *      ARC name (counted string) to be resolved into a NT device name.
 *      The caller should have already delimited it from within an ARC path
 *      (usually by finding where the first path separator appears in the path).
 *
 * NtName:
 *      Receives the resolved NT name. The buffer is NULL-terminated.
 */
static NTSTATUS
ResolveArcNameNtSymLink(
    OUT PUNICODE_STRING NtName,
    IN  PUNICODE_STRING ArcName)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE DirectoryHandle, LinkHandle;
    UNICODE_STRING ArcNameDir;

    if (NtName->MaximumLength < sizeof(UNICODE_NULL))
        return STATUS_BUFFER_TOO_SMALL;

#if 0
    *NtName->Buffer = UNICODE_NULL;
    NtName->Length = 0;
#endif

    /* Open the \ArcName object directory */
    RtlInitUnicodeString(&ArcNameDir, L"\\ArcName");
    InitializeObjectAttributes(&ObjectAttributes,
                               &ArcNameDir,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = NtOpenDirectoryObject(&DirectoryHandle,
                                   DIRECTORY_ALL_ACCESS,
                                   &ObjectAttributes);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("NtOpenDirectoryObject(%wZ) failed, Status 0x%08lx\n", &ArcNameDir, Status);
        return Status;
    }

    /* Open the ARC name link */
    InitializeObjectAttributes(&ObjectAttributes,
                               ArcName,
                               OBJ_CASE_INSENSITIVE,
                               DirectoryHandle,
                               NULL);
    Status = NtOpenSymbolicLinkObject(&LinkHandle,
                                      SYMBOLIC_LINK_ALL_ACCESS,
                                      &ObjectAttributes);

    /* Close the \ArcName object directory handle */
    NtClose(DirectoryHandle);

    /* Check for success */
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("NtOpenSymbolicLinkObject(%wZ) failed, Status 0x%08lx\n", ArcName, Status);
        return Status;
    }

    /* Reserve one WCHAR for the NULL-termination */
    NtName->MaximumLength -= sizeof(UNICODE_NULL);

    /* Resolve the link */
    Status = NtQuerySymbolicLinkObject(LinkHandle, NtName, NULL);

    /* Restore the NULL-termination */
    NtName->MaximumLength += sizeof(UNICODE_NULL);

    /* Check for success */
    if (!NT_SUCCESS(Status))
    {
        /* We failed, don't touch NtName */
        DPRINT1("NtQuerySymbolicLinkObject(%wZ) failed, Status 0x%08lx\n", ArcName, Status);
    }
    else
    {
        /* We succeeded, NULL-terminate NtName */
        NtName->Buffer[NtName->Length / sizeof(WCHAR)] = UNICODE_NULL;
    }

    NtClose(LinkHandle);
    return Status;
}

/*
 * ArcNamePath:
 *      In input, pointer to an ARC path (NULL-terminated) starting by an ARC name
 *      to be resolved into a NT device name.
 *      In opposition to ResolveArcNameNtSymLink(), the caller does not have to
 *      delimit the ARC name from within an ARC path. The real ARC name is deduced
 *      after parsing the ARC path, and, in output, ArcNamePath points to the
 *      beginning of the path after the ARC name part.
 *
 * NtName:
 *      Receives the resolved NT name. The buffer is NULL-terminated.
 *
 * PartList:
 *      (Optional) partition list that helps in resolving the paths pointing
 *      to hard disks.
 */
static NTSTATUS
ResolveArcNameManually(
    OUT PUNICODE_STRING NtName,
    IN OUT PCWSTR* ArcNamePath,
    IN  PPARTLIST PartList OPTIONAL)
{
    NTSTATUS Status;
    WCHAR TokenBuffer[50];
    UNICODE_STRING Token;
    PCWSTR p, q;
    ULONG AdapterKey;
    ULONG ControllerKey;
    ULONG PeripheralKey;
    ULONG PartitionNumber;
    ADAPTER_TYPE AdapterType;
    CONTROLLER_TYPE ControllerType;
    PERIPHERAL_TYPE PeripheralType;
    BOOLEAN UseSignature = FALSE;

    PDISKENTRY DiskEntry;
    PPARTENTRY PartEntry = NULL;

    if (NtName->MaximumLength < sizeof(UNICODE_NULL))
        return STATUS_BUFFER_TOO_SMALL;

#if 0
    *NtName->Buffer = UNICODE_NULL;
    NtName->Length = 0;
#endif

    /*
     * The format of ArcName is:
     *    adapter(www)[controller(xxx)peripheral(yyy)[partition(zzz)][filepath]] ,
     * where the [filepath] part is not being parsed.
     */

    RtlInitEmptyUnicodeString(&Token, TokenBuffer, sizeof(TokenBuffer));

    p = *ArcNamePath;

    /* Retrieve the adapter */
    p = ArcGetNextTokenU(p, &Token, &AdapterKey);
    if (!p)
    {
        DPRINT1("No adapter specified!\n");
        return STATUS_OBJECT_PATH_SYNTAX_BAD;
    }

    /* Check for the 'signature()' pseudo-adapter, introduced in Windows 2000 */
    if (_wcsicmp(Token.Buffer, L"signature") == 0)
    {
        /*
         * We've got a signature! Remember this for later, and set the adapter type to SCSI.
         * We however check that the rest of the ARC path is valid by parsing the other tokens.
         * AdapterKey stores the disk signature value (that holds in a ULONG).
         */
        UseSignature = TRUE;
        AdapterType = ScsiAdapter;
    }
    else
    {
        /* Check for regular adapters */
        AdapterType = (ADAPTER_TYPE)/*ArcMatchTokenU*/ArcMatchToken_UStr(/*Token.Buffer*/&Token, AdapterTypes_U);
        if (AdapterType >= AdapterTypeMax)
        {
            DPRINT1("Invalid adapter type %wZ\n", &Token);
            return STATUS_OBJECT_NAME_INVALID;
        }

        /* Check for adapters that don't take any extra controller or peripheral nodes */
        if (AdapterType == NetAdapter || AdapterType == RamdiskAdapter)
        {
            // if (*p)
            //     return STATUS_OBJECT_PATH_SYNTAX_BAD;

            if (AdapterType == NetAdapter)
            {
                DPRINT1("%S(%lu) path is not supported!\n", AdapterTypes_U[AdapterType], AdapterKey);
                return STATUS_NOT_SUPPORTED;
            }

            Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
                                        L"\\Device\\Ramdisk%lu", AdapterKey);
            goto Quit;
        }
    }

    /* Here, we have either an 'eisa', a 'scsi/signature', or a 'multi' adapter */

    /* Check for a valid controller */
    p = ArcGetNextTokenU(p, &Token, &ControllerKey);
    if (!p)
    {
        DPRINT1("%S(%lu) adapter doesn't have a controller!\n", AdapterTypes_U[AdapterType], AdapterKey);
        return STATUS_OBJECT_PATH_SYNTAX_BAD;
    }
    ControllerType = (CONTROLLER_TYPE)/*ArcMatchTokenU*/ArcMatchToken_UStr(/*Token.Buffer*/&Token, ControllerTypes_U);
    if (ControllerType >= ControllerTypeMax)
    {
        DPRINT1("Invalid controller type %wZ\n", &Token);
        return STATUS_OBJECT_NAME_INVALID;
    }

    /* Here the controller can only be either a disk or a CDROM */

    /*
     * Ignore the controller in case we have a 'multi' adapter.
     * I guess a similar condition holds for the 'eisa' adapter too...
     *
     * For SignatureAdapter, as similar for ScsiAdapter, the controller key corresponds
     * to the disk target ID. Note that actually, the implementation just ignores the
     * target ID, as well as the LUN, and just loops over all the available disks and
     * searches for the one having the correct signature.
     */
    if ((AdapterType == MultiAdapter /* || AdapterType == EisaAdapter */) && ControllerKey != 0)
    {
        DPRINT1("%S(%lu) adapter with %S(%lu non-zero), ignored!\n",
               AdapterTypes_U[AdapterType], AdapterKey,
               ControllerTypes_U[ControllerType], ControllerKey);
        ControllerKey = 0;
    }

    /*
     * Only the 'scsi' adapter supports a direct 'cdrom' controller.
     * For the others, we need a 'disk' controller to which a 'cdrom' peripheral can talk to.
     */
    if ((AdapterType != ScsiAdapter) && (ControllerType == CdRomController))
    {
        DPRINT1("%S(%lu) adapter cannot have a CDROM controller!\n", AdapterTypes_U[AdapterType], AdapterKey);
        return STATUS_OBJECT_PATH_INVALID;
    }

    /* Check for a valid peripheral */
    p = ArcGetNextTokenU(p, &Token, &PeripheralKey);
    if (!p)
    {
        DPRINT1("%S(%lu)%S(%lu) adapter-controller doesn't have a peripheral!\n",
               AdapterTypes_U[AdapterType], AdapterKey,
               ControllerTypes_U[ControllerType], ControllerKey);
        return STATUS_OBJECT_PATH_SYNTAX_BAD;
    }
    PeripheralType = (PERIPHERAL_TYPE)/*ArcMatchTokenU*/ArcMatchToken_UStr(/*Token.Buffer*/&Token, PeripheralTypes_U);
    if (PeripheralType >= PeripheralTypeMax)
    {
        DPRINT1("Invalid peripheral type %wZ\n", &Token);
        return STATUS_OBJECT_NAME_INVALID;
    }

    /*
     * If we had a 'cdrom' controller already, the corresponding peripheral can only be 'fdisk'
     * (see for example the ARC syntax for SCSI CD-ROMs: scsi(x)cdrom(y)fdisk(z) where z == 0).
     */
    if ((ControllerType == CdRomController) && (PeripheralType != FDiskPeripheral))
    {
        DPRINT1("%S(%lu) controller cannot have a %S(%lu) peripheral! (note that we haven't check whether the adapter was SCSI or not)\n",
               ControllerTypes_U[ControllerType], ControllerKey,
               PeripheralTypes_U[PeripheralType], PeripheralKey);
        return STATUS_OBJECT_PATH_INVALID;
    }

    /* For a 'scsi' adapter, the possible peripherals are only 'rdisk' or 'fdisk' */
    if (AdapterType == ScsiAdapter && !(PeripheralType == RDiskPeripheral || PeripheralType == FDiskPeripheral))
    {
        DPRINT1("%S(%lu)%S(%lu) SCSI adapter-controller has an invalid peripheral %S(%lu) !\n",
               AdapterTypes_U[AdapterType], AdapterKey,
               ControllerTypes_U[ControllerType], ControllerKey,
               PeripheralTypes_U[PeripheralType], PeripheralKey);
        return STATUS_OBJECT_PATH_INVALID;
    }

#if 0
    if (AdapterType == SignatureAdapter && PeripheralKey != 0)
    {
        DPRINT1("%S(%lu) adapter with %S(%lu non-zero), ignored!\n",
               AdapterTypes_U[AdapterType], AdapterKey,
               PeripheralTypes_U[PeripheralType], PeripheralKey);
        PeripheralKey = 0;
    }
#endif

    /* Check for the optional 'partition' specifier */
    q = ArcGetNextTokenU(p, &Token, &PartitionNumber);
    if (q && _wcsicmp(Token.Buffer, L"partition") == 0)
    {
        /* We've got a partition! */
        p = q;
    }
    else
    {
        /*
         * Either no other ARC token was found, or we've got something else
         * (possibly invalid or not)...
         */
        PartitionNumber = 0;
    }


    // TODO: Check the partition number in case of fdisks and cdroms??


    if (ControllerType == CdRomController) // and so, AdapterType == ScsiAdapter and PeripheralType == FDiskPeripheral
    {
        Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
                                    L"\\Device\\Scsi\\CdRom%lu", ControllerKey);
    }
    else
    /* Now, ControllerType == DiskController */
    if (PeripheralType == CdRomPeripheral)
    {
        Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
                                    L"\\Device\\CdRom%lu", PeripheralKey);
    }
    else
    if (PeripheralType == FDiskPeripheral)
    {
        Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
                                    L"\\Device\\Floppy%lu", PeripheralKey);
    }
    else
    if (PeripheralType == RDiskPeripheral)
    {
        if (UseSignature)
        {
            /* The disk signature is stored in AdapterKey */
            DiskEntry = GetDiskBySignature(PartList, AdapterKey);
        }
        else
        {
            DiskEntry = GetDiskBySCSI(PartList, AdapterKey,
                                      ControllerKey, PeripheralKey);
        }
        if (!DiskEntry)
            return STATUS_OBJECT_PATH_NOT_FOUND; // STATUS_NOT_FOUND;

        if (PartitionNumber != 0)
        {
            PartEntry = GetPartition(DiskEntry, PartitionNumber);
            if (!PartEntry)
                return STATUS_OBJECT_PATH_NOT_FOUND; // STATUS_DEVICE_NOT_PARTITIONED;
            ASSERT(PartEntry->DiskEntry == DiskEntry);
        }

        Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
                                    L"\\Device\\Harddisk%lu\\Partition%lu",
                                    DiskEntry->DiskNumber, PartitionNumber);
    }
#if 0
    else
    if (PeripheralType == VDiskPeripheral)
    {
        // TODO: Check how Win 7+ deals with virtual disks.
        Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
                                    L"\\Device\\VirtualHarddisk%lu\\Partition%lu",
                                    PeripheralKey, PartitionNumber);
    }
#endif

Quit:
    if (!NT_SUCCESS(Status))
        return Status;

    *ArcNamePath = p;
    return STATUS_SUCCESS;
}


BOOLEAN
ArcPathToNtPath(
    OUT PUNICODE_STRING NtPath,
    IN  PCWSTR ArcPath,
    IN  PPARTLIST PartList OPTIONAL)
{
    NTSTATUS Status;
    PCWSTR BeginOfPath;
    UNICODE_STRING ArcName;

    /* TODO: We should "normalize" the path, i.e. expand all the xxx() into xxx(0) */

    if (NtPath->MaximumLength < sizeof(UNICODE_NULL))
        return FALSE;

    *NtPath->Buffer = UNICODE_NULL;
    NtPath->Length = 0;

    /*
     * - First, check whether the ARC path is already inside \\ArcName
     *   and if so, map it to the corresponding NT path.
     * - Only then, if we haven't found any ArcName, try to build a
     *   NT path by deconstructing the ARC path, using its disk and
     *   partition numbers. We may use here our disk/partition list.
     *
     * See also freeldr/arcname.c
     *
     * Note that it would be nice to maintain a cache of these mappings.
     */

    /*
     * Initialize the ARC name to resolve, by cutting the ARC path at the first
     * NT path separator. The ARC name therefore ends where the NT path part starts.
     */
    RtlInitUnicodeString(&ArcName, ArcPath);
    BeginOfPath = wcschr(ArcName.Buffer, OBJ_NAME_PATH_SEPARATOR);
    if (BeginOfPath)
        ArcName.Length = (ULONG_PTR)BeginOfPath - (ULONG_PTR)ArcName.Buffer;

    /* Resolve the ARC name via NT SymLinks. Note that NtPath is returned NULL-terminated. */
    Status = ResolveArcNameNtSymLink(NtPath, &ArcName);
    if (!NT_SUCCESS(Status))
    {
        /* We failed, attempt a manual resolution */
        DPRINT1("ResolveArcNameNtSymLink(ArcName = '%wZ') for ArcPath = '%S' failed, Status 0x%08lx\n", &ArcName, ArcPath, Status);

        /*
         * We failed at directly resolving the ARC path, and we cannot perform
         * a manual resolution because we don't have any disk/partition list,
         * we therefore fail here.
         */
        if (!PartList)
        {
            DPRINT1("PartList == NULL, cannot perform a manual resolution\n");
            return FALSE;
        }

        *NtPath->Buffer = UNICODE_NULL;
        NtPath->Length = 0;

        BeginOfPath = ArcPath;
        Status = ResolveArcNameManually(NtPath, &BeginOfPath, PartList);
        if (!NT_SUCCESS(Status))
        {
            /* We really failed this time, bail out */
            DPRINT1("ResolveArcNameManually(ArcPath = '%S') failed, Status 0x%08lx\n", ArcPath, Status);
            return FALSE;
        }
    }

    /*
     * We succeeded. Concatenate the rest of the system-specific path. We know the path is going
     * to be inside the NT namespace, therefore we can use the path string concatenation function
     * that uses '\\' as the path separator.
     */
    if (BeginOfPath && *BeginOfPath)
    {
        Status = ConcatPaths(NtPath->Buffer, NtPath->MaximumLength / sizeof(WCHAR), 1, BeginOfPath);
        if (!NT_SUCCESS(Status))
        {
            /* Buffer not large enough, or whatever...: just bail out */
            return FALSE;
        }
    }
    NtPath->Length = wcslen(NtPath->Buffer) * sizeof(WCHAR);
    return TRUE;
}

#if 0
PWSTR
NtPathToArcPath(
    IN PWSTR NtPath)
{
    /*
     * - First, check whether any of the ARC paths inside \\ArcName
     *   map to the corresponding NT path. If so, we are OK.
     * - Only then, if we haven't found any ArcName, try to build an
     *   ARC path by deconstructing the NT path, using its disk and
     *   partition numbers. We may use here our disk/partition list.
     *
     * See also freeldr/arcname.c
     *
     * Note that it would be nice to maintain a cache of these mappings.
     */
}
#endif

/* EOF */