/*
 *  ReactOS kernel
 *  Copyright (C) 2002, 2004 ReactOS Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS kernel
 * FILE:             drivers/filesystems/cdfs/misc.c
 * PURPOSE:          CDROM (ISO 9660) filesystem driver
 * PROGRAMMER:       Eric Kohl
 * UPDATE HISTORY:
 */

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

#include "cdfs.h"

#define NDEBUG
#include <debug.h>

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

/*
 * FUNCTION: Used with IRP to set them to TopLevelIrp field
 * ARGUMENTS:
 *           Irp = The IRP to set
 * RETURNS: TRUE if top level was null, else FALSE
 */
BOOLEAN
CdfsIsIrpTopLevel(
    PIRP Irp)
{
    BOOLEAN ReturnCode = FALSE;

    DPRINT("CdfsIsIrpTopLevel()\n");

    if (IoGetTopLevelIrp() == NULL)
    {
        IoSetTopLevelIrp(Irp);
        ReturnCode = TRUE;
    }

    return ReturnCode;
}


/*
 * FUNCTION: Allocate and fill a CDFS_IRP_CONTEXT struct in order to use it for IRP
 * ARGUMENTS:
 *           DeviceObject = Used to fill in struct 
 *           Irp = The IRP that need IRP_CONTEXT struct
 * RETURNS: NULL or PCDFS_IRP_CONTEXT
 */
PCDFS_IRP_CONTEXT
CdfsAllocateIrpContext(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PCDFS_IRP_CONTEXT IrpContext;

    DPRINT("CdfsAllocateIrpContext()\n");

    IrpContext = (PCDFS_IRP_CONTEXT)ExAllocateFromNPagedLookasideList(&CdfsGlobalData->IrpContextLookasideList);
    if (IrpContext == NULL)
        return NULL;

    RtlZeroMemory(IrpContext, sizeof(CDFS_IRP_CONTEXT));

//    IrpContext->Identifier.Type = NTFS_TYPE_IRP_CONTEST;
//    IrpContext->Identifier.Size = sizeof(NTFS_IRP_CONTEXT);
    IrpContext->Irp = Irp;
    IrpContext->DeviceObject = DeviceObject;
    IrpContext->Stack = IoGetCurrentIrpStackLocation(Irp);
    IrpContext->MajorFunction = IrpContext->Stack->MajorFunction;
    IrpContext->MinorFunction = IrpContext->Stack->MinorFunction;
    IrpContext->FileObject = IrpContext->Stack->FileObject;
    IrpContext->IsTopLevel = (IoGetTopLevelIrp() == Irp);
    IrpContext->PriorityBoost = IO_NO_INCREMENT;
    IrpContext->Flags = IRPCONTEXT_COMPLETE;

    if (IrpContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL ||
        IrpContext->MajorFunction == IRP_MJ_DEVICE_CONTROL ||
        IrpContext->MajorFunction == IRP_MJ_SHUTDOWN ||
        (IrpContext->MajorFunction != IRP_MJ_CLEANUP &&
         IrpContext->MajorFunction != IRP_MJ_CLOSE &&
         IoIsOperationSynchronous(Irp)))
    {
        IrpContext->Flags |= IRPCONTEXT_CANWAIT;
    }

    return IrpContext;
}


VOID
CdfsSwapString(PWCHAR Out,
               PUCHAR In,
               ULONG Count)
{
    PUCHAR t = (PUCHAR)Out;
    ULONG i;

    for (i = 0; i < Count; i += 2)
    {
        t[i] = In[i+1];
        t[i+1] = In[i];
        if (t[i+1] == 0 && t[i] == ';')
            break;
    }
    if ((i>2)&&(t[i-2] == '.'))
    {
        t[i-2] = 0;
        t[i-1] = 0;
    }
    t[i] = 0;
    t[i+1] = 0;
}


VOID
CdfsDateTimeToSystemTime(PFCB Fcb,
                         PLARGE_INTEGER SystemTime)
{
    TIME_FIELDS TimeFields;
    LARGE_INTEGER LocalTime;

    TimeFields.Milliseconds = 0;
    TimeFields.Second = Fcb->Entry.Second;
    TimeFields.Minute = Fcb->Entry.Minute;
    TimeFields.Hour = Fcb->Entry.Hour;

    TimeFields.Day = Fcb->Entry.Day;
    TimeFields.Month = Fcb->Entry.Month;
    TimeFields.Year = Fcb->Entry.Year + 1900;

    RtlTimeFieldsToTime(&TimeFields,
        &LocalTime);
    ExLocalTimeToSystemTime(&LocalTime, SystemTime);
}


VOID
CdfsFileFlagsToAttributes(PFCB Fcb,
                          PULONG FileAttributes)
{
    /* FIXME: Fix attributes */

    *FileAttributes = // FILE_ATTRIBUTE_READONLY |
        ((Fcb->Entry.FileFlags & FILE_FLAG_HIDDEN) ? FILE_ATTRIBUTE_HIDDEN : 0) |
        ((Fcb->Entry.FileFlags & FILE_FLAG_DIRECTORY) ? FILE_ATTRIBUTE_DIRECTORY : 0) |
        ((Fcb->Entry.FileFlags & FILE_FLAG_SYSTEM) ? FILE_ATTRIBUTE_SYSTEM : 0) |
        ((Fcb->Entry.FileFlags & FILE_FLAG_READONLY) ? FILE_ATTRIBUTE_READONLY : 0);
}

BOOLEAN
CdfsIsNameLegalDOS8Dot3(IN UNICODE_STRING FileName
    )
{
    ULONG i;
    STRING DbcsName;
    CHAR DbcsNameBuffer[12];

    /* 8dot3 filename is max 12 length */
    if (FileName.Length / sizeof(WCHAR) > 12)
    {
        return FALSE;
    }

    ASSERT(FileName.Length >= sizeof(WCHAR));
    for (i = 0; i < FileName.Length / sizeof(WCHAR) ; i++)
    {
        /* Don't allow spaces in FileName */
        if (FileName.Buffer[i] == L' ')
            return FALSE;
    }

    /* If FileName is finishing with a dot, remove it */
    if (FileName.Buffer[FileName.Length / sizeof(WCHAR) - 1] == '.')
    {
        FileName.Length -= sizeof(WCHAR);
    }

    /* Finally, convert the string to call the FsRtl function */
    RtlInitEmptyAnsiString(&DbcsName, DbcsNameBuffer, sizeof(DbcsNameBuffer));
    if (!NT_SUCCESS(RtlUnicodeStringToCountedOemString(&DbcsName,
                                                       &FileName,
                                                       FALSE)))
    {

        return FALSE;
    }
    return FsRtlIsFatDbcsLegal(DbcsName, FALSE, FALSE, FALSE);
}

BOOLEAN
CdfsIsRecordValid(IN PDEVICE_EXTENSION DeviceExt,
                  IN PDIR_RECORD Record)
{
    if (Record->RecordLength < Record->FileIdLength + FIELD_OFFSET(DIR_RECORD, FileId))
    {
        DPRINT1("Found corrupted entry! %u - %u\n", Record->RecordLength, Record->FileIdLength + FIELD_OFFSET(DIR_RECORD, FileId));
        return FALSE;
    }

    if (Record->FileIdLength == 0)
    {
        DPRINT1("Found corrupted entry (null size)!\n");
        return FALSE;
    }

    if (DeviceExt->CdInfo.JolietLevel == 0)
    {
        if (Record->FileId[0] == ANSI_NULL && Record->FileIdLength != 1)
        {
            DPRINT1("Found corrupted entry!\n");
            return FALSE;
        }
    }
    else
    {
        if (Record->FileIdLength & 1 && Record->FileIdLength != 1)
        {
            DPRINT1("Found corrupted entry! %u\n", Record->FileIdLength);
            return FALSE;
        }

        if (Record->FileIdLength == 1 && Record->FileId[0] != 0 &&  Record->FileId[0] != 1)
        {
            DPRINT1("Found corrupted entry! %c\n", Record->FileId[0]);
            DPRINT1("%wc\n", ((PWSTR)Record->FileId)[0]);
            return FALSE;
        }
    }

    return TRUE;
}

VOID
CdfsShortNameCacheGet
(PFCB DirectoryFcb, 
 PLARGE_INTEGER StreamOffset, 
 PUNICODE_STRING LongName, 
 PUNICODE_STRING ShortName)
{
    PLIST_ENTRY Entry;
    PCDFS_SHORT_NAME ShortNameEntry;
    GENERATE_NAME_CONTEXT Context = { 0 };

    DPRINT("CdfsShortNameCacheGet(%I64d,%wZ)\n", StreamOffset->QuadPart, LongName);

    /* Get the name list resource */
    ExAcquireResourceExclusiveLite(&DirectoryFcb->NameListResource, TRUE);

    /* Try to find the name in our cache */
    for (Entry = DirectoryFcb->ShortNameList.Flink; 
        Entry != &DirectoryFcb->ShortNameList;
        Entry = Entry->Flink)
    {
        ShortNameEntry = CONTAINING_RECORD(Entry, CDFS_SHORT_NAME, Entry);
        if (ShortNameEntry->StreamOffset.QuadPart == StreamOffset->QuadPart)
        {
            /* Cache hit */
            RtlCopyUnicodeString(ShortName, &ShortNameEntry->Name);
            ExReleaseResourceLite(&DirectoryFcb->NameListResource);
            DPRINT("Yield short name %wZ from cache\n", ShortName);
            return;
        }
    }

    /* Cache miss */
    if (!CdfsIsNameLegalDOS8Dot3(*LongName))
    {
        RtlGenerate8dot3Name(LongName, FALSE, &Context, ShortName);
    }
    else
    {
        /* copy short name */
        RtlUpcaseUnicodeString
            (ShortName,
            LongName,
            FALSE);
    }

    DPRINT("Initial Guess %wZ\n", ShortName);

    /* Make it unique by scanning the cache and bumping */
    /* Note that incrementing the ambiguous name is enough, since we add new
    * entries at the tail.  We'll scan over all collisions. */
    /* XXX could perform better. */
    for (Entry = DirectoryFcb->ShortNameList.Flink; 
        Entry != &DirectoryFcb->ShortNameList;
        Entry = Entry->Flink)
    {
        ShortNameEntry = CONTAINING_RECORD(Entry, CDFS_SHORT_NAME, Entry);
        if (RtlCompareUnicodeString
            (ShortName,
            &ShortNameEntry->Name,
            TRUE) == 0) /* Match */
        {
            RtlGenerate8dot3Name(LongName, FALSE, &Context, ShortName);
            DPRINT("Collide; try %wZ\n", ShortName);
        }
    }

    /* We've scanned over all entries and now have a unique one.  Cache it. */
    ShortNameEntry = ExAllocatePoolWithTag(PagedPool,
                                           sizeof(CDFS_SHORT_NAME),
                                           CDFS_SHORT_NAME_TAG);
    if (!ShortNameEntry) 
    {
        /* We couldn't cache it, but we can return it.  We run the risk of
        * generating a non-unique name later. */
        ExReleaseResourceLite(&DirectoryFcb->NameListResource);
        DPRINT1("Couldn't cache potentially clashing 8.3 name %wZ\n", ShortName);
        return;
    }

    ShortNameEntry->StreamOffset = *StreamOffset;
    RtlInitEmptyUnicodeString(&ShortNameEntry->Name,
                              ShortNameEntry->NameBuffer,
                              sizeof(ShortNameEntry->NameBuffer));
    RtlCopyUnicodeString(&ShortNameEntry->Name, ShortName);
    InsertTailList(&DirectoryFcb->ShortNameList, &ShortNameEntry->Entry);
    ExReleaseResourceLite(&DirectoryFcb->NameListResource);

    DPRINT("Returning short name %wZ for long name %wZ\n", ShortName, LongName);
}

VOID
CdfsGetDirEntryName(PDEVICE_EXTENSION DeviceExt,
                    PDIR_RECORD Record,
                    PWSTR Name)
                    /*
                    * FUNCTION: Retrieves the file name from a directory record.
                    */
{
    if (Record->FileIdLength == 1 && Record->FileId[0] == 0)
    {
        wcscpy(Name, L".");
    }
    else if (Record->FileIdLength == 1 && Record->FileId[0] == 1)
    {
        wcscpy(Name, L"..");
    }
    else
    {
        if (DeviceExt->CdInfo.JolietLevel == 0)
        {
            ULONG i;

            for (i = 0; i < Record->FileIdLength && Record->FileId[i] != ';'; i++)
                Name[i] = (WCHAR)Record->FileId[i];
            Name[i] = 0;
        }
        else
        {
            CdfsSwapString(Name,
                Record->FileId,
                Record->FileIdLength);
        }
    }

    DPRINT("Name '%S'\n", Name);
}

/* EOF */