#include "lfn.h"
#pragma hdrstop



PMYTEXTFILE
ReadRenameFile(
    IN PCWSTR DriveRootPath
    );

BOOLEAN
FindSections(
    IN PMYTEXTFILE TextFile
    );

BOOLEAN
GetLine(
    IN  PWCHAR  StartOfLine,
    OUT PWSTR   LineBuffer,
    IN  ULONG   BufferSizeChars,
    OUT PWCHAR *StartOfNextLine
    );

int
_CRTAPI2
ComparePaths(
    const void *p1,
    const void *p2
    );


PMYTEXTFILE
LoadRenameFile(
    IN PCWSTR DriveRootPath
    )
{
    PMYTEXTFILE TextFile;
    BOOLEAN b;

    //
    // Read in the file.
    //
    if(TextFile = ReadRenameFile(DriveRootPath)) {

        if(b = FindSections(TextFile)) {

            return(TextFile);
        }

        UnloadRenameFile(&TextFile);
    }

    return(FALSE);
}


VOID
UnloadRenameFile(
    IN OUT PMYTEXTFILE *TextFile
    )
{
    PMYTEXTFILE textFile;
    ULONG u;

    textFile = *TextFile;
    *TextFile = NULL;

    if(textFile->Sections) {

        for(u=0; u<textFile->SectionCount; u++) {
            FREE(textFile->Sections[u].Name);
        }

        FREE(textFile->Sections);
    }

    FREE(textFile);
}


PMYTEXTFILE
ReadRenameFile(
    IN PCWSTR DriveRootPath
    )
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING UnicodeString;
    IO_STATUS_BLOCK IoStatusBlock;
    WCHAR FullPath[NTMAXPATH];
    HANDLE Handle;
    FILE_STANDARD_INFORMATION FileInfo;
    PVOID Buffer;
    PWCHAR UnicodeBuffer;
    ULONG u;
    PMYTEXTFILE p;
    ULONG CharCount;

    wcscpy(FullPath,DriveRootPath);
    ConcatenatePaths(FullPath,WINNT_OEM_LFNLIST_W,NTMAXPATH);

    //
    // Open the file.
    //
    INIT_OBJA(&ObjectAttributes,&UnicodeString,FullPath);
    Status = NtCreateFile(
                &Handle,
                FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                &ObjectAttributes,
                &IoStatusBlock,
                NULL,
                0,
                FILE_SHARE_READ,
                FILE_OPEN,
                FILE_SYNCHRONOUS_IO_ALERT | FILE_NON_DIRECTORY_FILE,
                NULL,
                0
                );

    if(!NT_SUCCESS(Status)) {
        KdPrint(("LFN: Unable to open %ws (%x)\n",FullPath,Status));
        goto c0;
    }

    //
    // Determine the size of the file.
    //
    Status = NtQueryInformationFile(
                Handle,
                &IoStatusBlock,
                &FileInfo,
                sizeof(FileInfo),
                FileStandardInformation
                );

    if(!NT_SUCCESS(Status)) {
        KdPrint(("LFN: Unable to determine size of %ws (%x)\n",FullPath,Status));
        goto c1;
    }

    //
    // Allocate a chunk of memory and read the file in.
    //
    Buffer = MALLOC(FileInfo.EndOfFile.LowPart);
    if(!Buffer) {
        KdPrint(("LFN: malloc failed\n"));
        goto c1;
    }

    Status = NtReadFile(
                Handle,
                NULL,
                NULL,
                NULL,
                &IoStatusBlock,
                Buffer,
                FileInfo.EndOfFile.LowPart,
                NULL,
                NULL
                );

    if(!NT_SUCCESS(Status)) {
        KdPrint((
            "LFN: Unable to read %u bytes from file %ws (%x)\n",
            FileInfo.EndOfFile.LowPart,
            FullPath,
            Status
            ));
        goto c2;
    }

    //
    // Allocate a buffer for unicode conversion.
    // Leave room for a terminating NUL.
    //
    UnicodeBuffer = MALLOC((FileInfo.EndOfFile.LowPart+1)*sizeof(WCHAR));
    if(!UnicodeBuffer) {
        KdPrint(("LFN: malloc failed\n"));
        goto c2;
    }

    //
    // Convert to unicode.
    //
    Status = RtlMultiByteToUnicodeN(
                UnicodeBuffer,
                FileInfo.EndOfFile.LowPart*sizeof(WCHAR),
                &CharCount,
                Buffer,
                FileInfo.EndOfFile.LowPart
                );

    if(!NT_SUCCESS(Status)) {
        KdPrint(("LFN: Unable to convert file data to unicode (%x)\n",Status));
        goto c3;
    }

    CharCount /= sizeof(WCHAR);

    //
    // Nul-terminate the buffer and change instances of CR and control-z
    // to spaces. Also make sure there are no 0 chars in the buffer.
    //
    for(u=0; u<CharCount; u++) {
        if((UnicodeBuffer[u] == 26) || (UnicodeBuffer[u] == L'\r') || !UnicodeBuffer[u]) {
            UnicodeBuffer[u] = L' ';
        }
    }

    //
    // Allocate a text file structure.
    //
    p = MALLOC(sizeof(MYTEXTFILE));
    if(!p) {
        KdPrint(("LFN: malloc failed\n"));
        goto c3;
    }

    RtlZeroMemory(p,sizeof(MYTEXTFILE));
    p->Text = UnicodeBuffer;
    UnicodeBuffer[CharCount] = 0;

c3:
    if(!NT_SUCCESS(Status)) {
        FREE(UnicodeBuffer);
    }
c2:
    FREE(Buffer);
c1:
    NtClose(Handle);
c0:
    return(NT_SUCCESS(Status) ? p : NULL);
}


BOOLEAN
FindSections(
    IN PMYTEXTFILE TextFile
    )
{
    PWCHAR p,n;
    WCHAR Line[2*NTMAXPATH];
    PWCHAR e;
    PVOID NewArray;
    PWCHAR s;
    PWSTR SectionName;

    for(p=TextFile->Text; GetLine(p,Line,sizeof(Line)/sizeof(WCHAR),&n); p=n) {

        //
        // If this is a section save it away in a table of section names.
        //
        if(Line[0] == L'[') {

            s = Line+1;
            while((*s == L' ') || (*s == L'\t')) {
                s++;
            }
            if(*s == L'\\') {
                s++;
            }

            //
            // Find the end, which is either the ] or a nul.
            // Strip off trailing spaces.
            //
            if(e = wcschr(s,L']')) {
                *e = 0;
            } else {
                e = wcschr(s,0);
            }
            while((*(e-1) == L' ') || (*(e-1) == L'\t')) {
                e--;
                *e = 0;
            }

            if(SectionName = MALLOC((wcslen(s)+1)*sizeof(WCHAR))) {

                wcscpy(SectionName,s);

                if(TextFile->SectionCount == TextFile->SectionArraySize) {

                    if(TextFile->SectionCount) {
                        NewArray = REALLOC(TextFile->Sections,(TextFile->SectionCount+10)*sizeof(MYSECTION));
                    } else {
                        NewArray = MALLOC(10*sizeof(MYSECTION));
                    }

                    if(NewArray) {
                        TextFile->Sections = NewArray;
                        TextFile->SectionArraySize += 10;
                    } else {
                        FREE(SectionName);
                        KdPrint(("LFN: malloc failed\n"));
                        return(FALSE);
                    }
                }

                TextFile->Sections[TextFile->SectionCount].Name = SectionName;
                TextFile->Sections[TextFile->SectionCount].Data = n;

                TextFile->SectionCount++;

            } else {
                KdPrint(("LFN: malloc failed\n"));
                return(FALSE);
            }
        }
    }

    //
    // Now sort the sections by name.
    //
    qsort(TextFile->Sections,TextFile->SectionCount,sizeof(MYSECTION),ComparePaths);

    return(TRUE);
}


BOOLEAN
GetLineInSection(
    IN  PWCHAR  StartOfLine,
    OUT PWSTR   LineBuffer,
    IN  ULONG   BufferSizeChars,
    OUT PWCHAR *StartOfNextLine
    )
{
    //
    // Get the line and check if we've reached the end of the section.
    //
    if(!GetLine(StartOfLine,LineBuffer,BufferSizeChars,StartOfNextLine)
    || (LineBuffer[0] == L'[')) {

        return(FALSE);
    }

    return(TRUE);
}


BOOLEAN
GetLine(
    IN  PWCHAR  StartOfLine,
    OUT PWSTR   LineBuffer,
    IN  ULONG   BufferSizeChars,
    OUT PWCHAR *StartOfNextLine
    )
{
    PWCHAR LineEnd;
    ULONG Count;

    while(1) {
        //
        // Skip space chars.
        //
        while(*StartOfLine && ((*StartOfLine == L' ') || (*StartOfLine == L'\t'))) {
            StartOfLine++;
        }
        if(*StartOfLine == 0) {
            //
            // Nothing left.
            //
            return(FALSE);
        }

        //
        // Find the end of the line, which is either the newline or nul.
        //
        if(LineEnd = wcschr(StartOfLine,L'\n')) {
            *StartOfNextLine = LineEnd+1;
        } else {
            LineEnd = wcschr(StartOfLine,0);
            *StartOfNextLine = LineEnd;
        }

        //
        // Ignore this line if it's a comment or empty.
        // Otherwise return it.
        //
        if((*StartOfLine != L';') && (*StartOfLine != L' ')) {
            Count = LineEnd - StartOfLine;
            if(Count >= BufferSizeChars) {
                Count = BufferSizeChars-1;
            }

            RtlCopyMemory(LineBuffer,StartOfLine,Count*sizeof(WCHAR));
            LineBuffer[Count] = 0;
            return(TRUE);
        }

        StartOfLine = *StartOfNextLine;
    }
}


int
_CRTAPI2
ComparePaths(
    const void *p1,
    const void *p2
    )
{
    unsigned u1,u2;
    PWCHAR s1,s2;

    //
    // Count \'s in each. The one with fewer is 'greater'.
    //
    s1 = ((PMYSECTION)p1)->Name;
    s2 = ((PMYSECTION)p2)->Name;

    u1 = 0;
    u2 = 0;

    while(*s1) {
        if(*s1 == L'\\') {
            u1++;
        }
        s1++;
    }

    while(*s2) {
        if(*s2 == L'\\') {
            u2++;
        }
        s2++;
    }

    if(u1 == u2) {
        return(0);
    }

    return((u1 < u2) ? 1 : -1);
}


BOOLEAN
ParseLine(
    IN OUT PWSTR  Line,
       OUT PWSTR *LHS,
       OUT PWSTR *RHS
    )
{
    PWCHAR p,q;

    //
    // We rely on the routines abive to have stripped out
    // leading spaces.
    //
    *LHS = Line;

    //
    // Find the equals. The LHS isn't allowed to be quoted.
    // Strip trailing space off the LHS.
    //
    p = wcschr(Line,L'=');
    if(!p || (p == Line)) {
        return(FALSE);
    }
    q = p+1;
    *p-- = 0;

    while((*p == L' ') || (*p == L'\t')) {
        *p-- = 0;
    }

    while(*q && ((*q == L' ') || (*q == L'\t'))) {
        q++;
    }
    if(*q == 0) {
        return(FALSE);
    }
    if(*q == L'\"') {
        q++;
    }

    *RHS = q;
    p = q + wcslen(q);
    p--;
    while((*p == L' ') || (*p == L'\t')) {
        *p-- = 0;
    }
    if(*p == L'\"') {
        *p = 0;
    }
    return(TRUE);
}