/* Copyright (c) 2010, 2011
 * The Regents of the University of Michigan
 * All Rights Reserved
 * 
 * Olga Kornievskaia <aglo@umich.edu>
 * Casey Bodley <cbodley@umich.edu>
 *
 * Permission is granted to use, copy and redistribute this software
 * for noncommercial education and research purposes, so long as no
 * fee is charged, and so long as the name of the University of Michigan
 * is not used in any advertising or publicity pertaining to the use
 * or distribution of this software without specific, written prior
 * authorization.  Permission to modify or otherwise create derivative
 * works of this software is not granted.
 *
 * This software is provided as is, without representation or warranty
 * of any kind either express or implied, including without limitation
 * the implied warranties of merchantability, fitness for a particular
 * purpose, or noninfringement.  The Regents of the University of
 * Michigan shall not be liable for any damages, including special,
 * indirect, incidental, or consequential damages, with respect to any
 * claim arising out of or in connection with the use of the software,
 * even if it has been or is hereafter advised of the possibility of
 * such damages.
 */

#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <stdio.h>

#include "options.h"


DWORD InitializeMountOptions(
    IN OUT PMOUNT_OPTION_LIST Options,
    IN ULONG BufferSize)
{
    Options->Current = NULL;
    Options->Remaining = BufferSize;
    Options->Buffer = LocalAlloc(LMEM_ZEROINIT, BufferSize);
    if (Options->Buffer == NULL)
        return ERROR_OUTOFMEMORY;

    Options->Buffer->Secret = MOUNT_OPTION_BUFFER_SECRET;
    return NO_ERROR;
}

void FreeMountOptions(
    IN OUT PMOUNT_OPTION_LIST Options)
{
    Options->Current = NULL;
    Options->Remaining = 0;
    if (Options->Buffer)
    {
        LocalFree(Options->Buffer);
        Options->Buffer = NULL;
    }
}

static BOOL FindOptionByName(
    IN LPCTSTR Name,
    IN PMOUNT_OPTION_LIST Options,
    OUT PFILE_FULL_EA_INFORMATION* ppOption)
{
    PFILE_FULL_EA_INFORMATION Current =
        (PFILE_FULL_EA_INFORMATION)Options->Buffer->Buffer;
    ULONG NameLength = (ULONG)_tcslen(Name) * sizeof(TCHAR);

    for (;;)
    {
        if (Current->EaNameLength == NameLength &&
            _tcscmp((LPTSTR)Current->EaName, Name) == 0)
        {
            *ppOption = Current;
            return TRUE;
        }
        if (Current->NextEntryOffset == 0)
            break;
        Current = (PFILE_FULL_EA_INFORMATION)
            ((PBYTE)Current + Current->NextEntryOffset);
    }
    return FALSE;
}

static FORCEINLINE ULONG EaBufferSize(
    IN UCHAR NameSize,
    IN USHORT ValueSize)
{
    ULONG Size = sizeof(ULONG) + 2 * sizeof(UCHAR) + sizeof(USHORT)
        + NameSize + ValueSize + sizeof(TCHAR);
    /* extended attributes require ULONG alignment;
     * see documentation for IoCheckEaBufferValidity() */
    return ( (Size + 3) / sizeof(ULONG) ) * sizeof(ULONG);
}

static FORCEINLINE ULONG EaBufferNextOffset(
    IN PFILE_FULL_EA_INFORMATION EaBuffer)
{
    return EaBufferSize(
        EaBuffer->EaNameLength,
        EaBuffer->EaValueLength);
}

BOOL InsertOption(
    IN LPCTSTR Name,
    IN LPCTSTR Value,
    IN OUT PMOUNT_OPTION_LIST Options)
{
    PFILE_FULL_EA_INFORMATION Current;
    UCHAR NameLen = (UCHAR)_tcslen(Name) * sizeof(TCHAR);
    USHORT ValueLen = (USHORT)_tcslen(Value) * sizeof(TCHAR);
    ULONG SpaceRequired = EaBufferSize(NameLen, ValueLen);

    /* don't allow duplicate options */
    if (FindOptionByName(Name, Options, &Current)) {
        _ftprintf(stderr, TEXT("Found a duplicate option ")
            TEXT("'%s%s%s' while parsing '%s%s%s'.\n"),
            (PTCH)Current->EaName,
            Current->EaValueLength ? TEXT("=") : TEXT(""),
            (PTCH)(Current->EaName + Current->EaNameLength + sizeof(TCHAR)),
            Name, ValueLen ? TEXT("=") : Value, Value);
        return FALSE;
    }

    /* fail if we're out of space */
    if (SpaceRequired > Options->Remaining) {
        _ftprintf(stderr, TEXT("Out of space for options!\n"));
        return FALSE;
    }

    if (Options->Current == NULL)
        Current = Options->Current = (PFILE_FULL_EA_INFORMATION)
            Options->Buffer->Buffer;
    else
        Current = Options->Current = (PFILE_FULL_EA_INFORMATION)
            ((PBYTE)Options->Current + Options->Current->NextEntryOffset);

    Current->EaNameLength = NameLen;
    if (NameLen) /* copy attribute name */
        StringCbCopy((LPTSTR)Current->EaName,
            NameLen + sizeof(TCHAR), Name);

    Current->EaValueLength = ValueLen;
    if (ValueLen) /* copy attribute value */
        StringCbCopy((LPTSTR)(Current->EaName + NameLen + sizeof(TCHAR)),
            ValueLen + sizeof(TCHAR), Value);

    Current->Flags = 0;
    Current->NextEntryOffset = EaBufferNextOffset(Options->Current);

    Options->Buffer->Length = (ULONG)(
        (Current->EaName + NameLen + ValueLen + 2 * sizeof(TCHAR))
            - Options->Buffer->Buffer );
    Options->Remaining -= SpaceRequired;
    return TRUE;
}

static void RecursivePrintEaInformation(
    IN PFILE_FULL_EA_INFORMATION EA)
{
    _tprintf(
        TEXT("----------------------\n")
        TEXT("Alignment:           %5lu\n")
        TEXT("NextEntryOffset:     %5lu\n")
        TEXT("Flags:               %5u\n")
        TEXT("EaNameLength:        %5u\n")
        TEXT("EaValueLength:       %5u\n")
        TEXT("EaName:   %16ls\n")
        TEXT("EaValue:  %16ls\n\n"),
        (ULONG_PTR)EA % sizeof(ULONG),
        EA->NextEntryOffset,
        EA->Flags,
        EA->EaNameLength,
        EA->EaValueLength,
        (LPTSTR)EA->EaName,
        (LPTSTR)(EA->EaName + EA->EaNameLength + sizeof(TCHAR)));

    if (EA->NextEntryOffset)
        RecursivePrintEaInformation((PFILE_FULL_EA_INFORMATION)
            ((PBYTE)EA + EA->NextEntryOffset));
}

static const TCHAR COMMA_T = TEXT(',');
static const TCHAR EQUAL_T = TEXT('=');

BOOL ParseMountOptions(
    IN LPTSTR Arg,
    IN OUT PMOUNT_OPTION_LIST Options)
{
    PTCH pos, comma, equals;

    pos = Arg;
    for (;;)
    {
        comma = _tcschr(pos, COMMA_T);
        if (comma)
        {
            if (comma == pos)
                goto out_empty_option;
            *comma = 0;
        }
        else if (_tcslen(pos) == 0)
            goto out_empty_option;

        /* accept 'option=value' or 'option' */
        equals = _tcschr(pos, EQUAL_T);
        if (equals)
        {
            if (equals == pos)
                goto out_empty_option;
            *equals = 0;
            if (!InsertOption(pos, equals + 1, Options))
                return FALSE;
        }
        else if (!InsertOption(pos, TEXT(""), Options))
            return FALSE;

        if (comma == NULL)
            break;

        pos = comma + 1;
    }

/*  RecursivePrintEaInformation(
        (PFILE_FULL_EA_INFORMATION)Options->Buffer->Buffer); */
    return TRUE;

out_empty_option:
    _ftprintf(stderr, TEXT("Found an empty option while ")
        TEXT("reading mount options at '%s'.\n"), pos);
    return FALSE;
}
