/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    custerr.cxx

Abstract:

    Custom Error Utility

Author:

    Keith Moore (keithmo)        04-Sep-1997

Revision History:

--*/


#include "precomp.hxx"
#pragma hdrstop


//
// Private constants.
//

#define TEST_HRESULT(api,hr,fatal)                                          \
            if( FAILED(hr) ) {                                              \
                                                                            \
                wprintf(                                                    \
                    L"%S:%lu failed, error %lx %S\n",                       \
                    (api),                                                  \
                    __LINE__,                                               \
                    (result),                                               \
                    (fatal)                                                 \
                        ? "ABORTING"                                        \
                        : "CONTINUING"                                      \
                    );                                                      \
                                                                            \
                if( fatal ) {                                               \
                                                                            \
                    goto cleanup;                                           \
                                                                            \
                }                                                           \
                                                                            \
            } else

#define ALLOC( cb ) (PVOID)LocalAlloc( LPTR, (cb) )
#define FREE( ptr ) (VOID)LocalFree( (HLOCAL)(ptr) )


//
// Private types.
//


//
// Private globals.
//


//
// Private prototypes.
//

VOID
Usage(
    VOID
    );

VOID
SetCustomError(
    IMSAdminBase * AdmCom,
    LPWSTR MetaPath,
    LPWSTR FileName
    );

VOID
DumpCustomError(
    IMSAdminBase * AdmCom,
    LPWSTR MetaPath
    );


//
// Public functions.
//


INT
__cdecl
wmain(
    INT argc,
    LPWSTR argv[]
    )
{

    HRESULT result;
    IMSAdminBase * admCom;
    LPWSTR metaPath;
    LPWSTR fileName;
    LPWSTR arg;
    INT i;

    //
    // Setup locals so we know how to cleanup on exit.
    //

    admCom = NULL;

    //
    // Establish defaults.
    //

    metaPath = L"w3svc";
    fileName = NULL;

    //
    // Validate the command line arguments.
    //

    for( i = 1 ; i < argc ; i++ ) {
        arg = argv[i];

        if( arg[0] != L'-' ||
            arg[1] == L'\0' ||
            arg[2] != L':' ||
            arg[3] == L'\0' ) {
            Usage();
            return 1;
        }

        switch( arg[1] ) {
        case L'h' :
        case L'H' :
        case L'?' :
            Usage();
            return 1;

        case L'p' :
        case L'P' :
            metaPath = arg + 3;
            break;

        case L'f' :
        case L'F' :
            fileName = arg + 3;
            break;

        default :
            Usage();
            return 1;
        }

    }

    //
    // Initialize COM.
    //

    result = CoInitializeEx(
                 NULL,
                 COINIT_MULTITHREADED
                 );

    TEST_HRESULT( "CoInitializeEx()", result, TRUE );

    //
    // Get the admin object.
    //

    result = MdGetAdminObject( &admCom );

    TEST_HRESULT( "MdGetAdminObject()", result, TRUE );

    //
    // Do it.
    //

    if( fileName == NULL ) {
        DumpCustomError( admCom, metaPath );
    } else {
        SetCustomError( admCom, metaPath, fileName );
    }

cleanup:

    //
    // Release the admin object.
    //

    if( admCom != NULL ) {

        result = MdReleaseAdminObject( admCom );
        TEST_HRESULT( "MdReleaseAdminObject()", result, FALSE );

    }

    //
    // Shutdown COM.
    //

    CoUninitialize();
    return 0;

}   // main


//
// Private functions.
//

VOID
Usage(
    VOID
    )
{

    wprintf(
        L"use: custerr [options]\n"
        L"\n"
        L"valid options are:\n"
        L"\n"
        L"    -p:meta_path\n"
        L"    -f:error_file\n"
        L"\n"
        );

}   // Usage

VOID
SetCustomError(
    IMSAdminBase * AdmCom,
    LPWSTR MetaPath,
    LPWSTR FileName
    )
{

    HANDLE fileHandle;
    DWORD length;
    DWORD lengthHigh;
    DWORD bytesRemaining;
    HANDLE mappingHandle;
    PCHAR view;
    PCHAR viewScan;
    PWCHAR multiSz;
    PWCHAR multiSzScan;
    HRESULT result;
    BOOL gotOne;
    METADATA_HANDLE metaHandle;
    METADATA_RECORD metaRecord;
    DWORD err;
    CHAR ansiFileName[MAX_PATH];
    WCHAR fullMetaPath[MAX_PATH];

    //
    // Setup locals so we know how to cleanup on exit.
    //

    fileHandle = INVALID_HANDLE_VALUE;
    mappingHandle = NULL;
    view = NULL;
    multiSz = NULL;
    metaHandle = 0;

    //
    // Open the file, get its length.
    //

    sprintf(
        ansiFileName,
        "%S",
        FileName
        );

    fileHandle = CreateFileA(
                     ansiFileName,
                     GENERIC_READ,
                     FILE_SHARE_READ,
                     NULL,
                     OPEN_EXISTING,
                     FILE_ATTRIBUTE_NORMAL,
                     NULL
                     );

    if( fileHandle == INVALID_HANDLE_VALUE ) {
        err = GetLastError();
        wprintf(
            L"custerr: cannot open %s, error %lu\n",
            FileName,
            err
            );
        goto cleanup;
    }

    length = GetFileSize( fileHandle, &lengthHigh );

    if( length == 0xFFFFFFFF || lengthHigh > 0 ) {
        err = GetLastError();
        wprintf(
            L"custerr: cannot read %s, error %lu\n",
            FileName,
            err
            );
        goto cleanup;
    }

    //
    // Map it in.
    //

    mappingHandle = CreateFileMapping(
                        fileHandle,
                        NULL,
                        PAGE_READONLY,
                        0,
                        0,
                        NULL
                        );

    if( mappingHandle == NULL ) {
        err = GetLastError();
        wprintf(
            L"custerr: cannot read %s, error %lu\n",
            FileName,
            err
            );
        goto cleanup;
    }

    view = (PCHAR)MapViewOfFile(
                      mappingHandle,
                      FILE_MAP_READ,
                      0,
                      0,
                      0
                      );

    if( view == NULL ) {
        err = GetLastError();
        wprintf(
            L"custerr: cannot read %s, error %lu\n",
            FileName,
            err
            );
        goto cleanup;
    }

    //
    // Allocate the multisz buffer. Assume it will be roughly the same
    // size as the file.
    //

    multiSz = (PWCHAR) ALLOC( length * sizeof(WCHAR) );

    if( multiSz == NULL ) {
        wprintf(
            L"custerr: not enough memory\n"
            );
        goto cleanup;
    }

    //
    // Build the multisz.
    //

    viewScan = view;
    multiSzScan = multiSz;
    bytesRemaining = length;

    while( bytesRemaining > 0 ) {

        //
        // Skip leading whitespace.
        //

        while( bytesRemaining > 0 &&
               ( *viewScan == ' ' || *viewScan == '\t' ||
                 *viewScan == '\r' || *viewScan == '\n' ) ) {
            bytesRemaining--;
            viewScan++;
        }

        //
        // Copy it over, collapse embedded whitespace, perform
        // cheesy ANSI-to-UNICODE conversion.
        //

        gotOne = FALSE;

        while( bytesRemaining > 0 &&
               ( *viewScan != '\r' && *viewScan != '\n' ) ) {
            bytesRemaining--;
            if( *viewScan != ' ' && *viewScan != '\t' ) {
                *multiSzScan++ = (WCHAR)*viewScan;
                gotOne = TRUE;
            }
            viewScan++;
        }

        if( gotOne ) {
            *multiSzScan++ = L'\0';
        }

    }

    *multiSzScan++ = L'\0';

    //
    // Open the metabase.
    //

    swprintf(
        fullMetaPath,
        L"/%S/%s",
        IIS_MD_LOCAL_MACHINE_PATH,
        MetaPath
        );

    result = AdmCom->OpenKey(
                 METADATA_MASTER_ROOT_HANDLE,
                 fullMetaPath,
                 METADATA_PERMISSION_WRITE,
                 METABASE_OPEN_TIMEOUT,
                 &metaHandle
                 );

    TEST_HRESULT( "AdmCom->OpenKey()", result, TRUE );

    //
    // Write the new custom error value.
    //

    length = ( multiSzScan - multiSz ) * sizeof(WCHAR);

    INITIALIZE_METADATA_RECORD(
        &metaRecord,
        MD_CUSTOM_ERROR,
        METADATA_INHERIT,
        IIS_MD_UT_SERVER,
        MULTISZ_METADATA,
        length,
        multiSz
        );

    result = AdmCom->SetData(
                 metaHandle,
                 L"",
                 &metaRecord
                 );

    TEST_HRESULT( "AdmCom->SetData()", result, TRUE );

cleanup:

    if( metaHandle != 0 ) {
        AdmCom->CloseKey( metaHandle );
    }

    if( multiSz != NULL ) {
        FREE( multiSz );
    }

    if( view != NULL ) {
        UnmapViewOfFile( view );
    }

    if( mappingHandle != NULL ) {
        CloseHandle( mappingHandle );
    }

    if( fileHandle != INVALID_HANDLE_VALUE ) {
        CloseHandle( fileHandle );
    }

}   // SetCustomError

VOID
DumpCustomError(
    IMSAdminBase * AdmCom,
    LPWSTR MetaPath
    )
{


    HRESULT result;
    METADATA_HANDLE metaHandle;
    METADATA_RECORD metaRecord;
    DWORD bytesRequired;
    PWCHAR buffer;
    PWCHAR bufferScan;
    WCHAR fullMetaPath[MAX_PATH];

    //
    // Setup locals so we know how to cleanup on exit.
    //

    metaHandle = 0;
    buffer = NULL;

    //
    // Open the metabase.
    //

    swprintf(
        fullMetaPath,
        L"/%S/%s",
        IIS_MD_LOCAL_MACHINE_PATH,
        MetaPath
        );

    result = AdmCom->OpenKey(
                 METADATA_MASTER_ROOT_HANDLE,
                 fullMetaPath,
                 METADATA_PERMISSION_READ,
                 METABASE_OPEN_TIMEOUT,
                 &metaHandle
                 );

    TEST_HRESULT( "AdmCom->OpenKey()", result, TRUE );

    //
    // Get the data size.
    //

    INITIALIZE_METADATA_RECORD(
        &metaRecord,
        MD_CUSTOM_ERROR,
        METADATA_INHERIT,
        IIS_MD_UT_SERVER,
        MULTISZ_METADATA,
        2,
        L""
        );

    result = AdmCom->GetData(
                 metaHandle,
                 L"",
                 &metaRecord,
                 &bytesRequired
                 );

    if( result != RETURNCODETOHRESULT( ERROR_INSUFFICIENT_BUFFER ) ) {
        TEST_HRESULT( "AdmCom->GetData()", result, TRUE );
    }

    //
    // Allocate our data buffer.
    //

    buffer = (PWCHAR)ALLOC( bytesRequired );

    if( buffer == NULL ) {
        wprintf(
            L"custerr: not enough memory\n"
            );
        goto cleanup;
    }

    //
    // Now actually read it.
    //

    INITIALIZE_METADATA_RECORD(
        &metaRecord,
        MD_CUSTOM_ERROR,
        METADATA_INHERIT,
        IIS_MD_UT_SERVER,
        MULTISZ_METADATA,
        bytesRequired,
        buffer
        );

    result = AdmCom->GetData(
                 metaHandle,
                 L"",
                 &metaRecord,
                 &bytesRequired
                 );

    TEST_HRESULT( "AdmCom->GetData()", result, TRUE );

    //
    // Print it.
    //

    bufferScan = buffer;

    while( *bufferScan != L'\0' ) {
        wprintf( L"%s\n", bufferScan );
        bufferScan += wcslen( bufferScan ) + 1;
    }

cleanup:

    if( buffer != NULL ) {
        FREE( buffer );
    }

    if( metaHandle != 0 ) {
        AdmCom->CloseKey( metaHandle );
    }

}   // DumpCustomError