/*
 * kmp_i18n.c
 */


//===----------------------------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.txt for details.
//
//===----------------------------------------------------------------------===//



#include "kmp_i18n.h"

#include "kmp_os.h"
#include "kmp_debug.h"
#include "kmp.h"
#include "kmp_lock.h"
#include "kmp_io.h"          // __kmp_printf.

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <locale.h>
#include <stdarg.h>

#include "kmp_i18n_default.inc"
#include "kmp_str.h"
#include "kmp_environment.h"

#undef KMP_I18N_OK

#define get_section( id )  ( (id) >> 16 )
#define get_number( id )   ( (id) & 0xFFFF )

kmp_msg_t           __kmp_msg_empty = { kmp_mt_dummy, 0, "", 0  };
kmp_msg_t           __kmp_msg_null  = { kmp_mt_dummy, 0, NULL, 0 };
static char const * no_message_available = "(No message available)";

enum kmp_i18n_cat_status {
    KMP_I18N_CLOSED,    // Not yet opened or closed.
    KMP_I18N_OPENED,    // Opened successfully, ready to use.
    KMP_I18N_ABSENT     // Opening failed, message catalog should not be used.
}; // enum kmp_i18n_cat_status
typedef enum kmp_i18n_cat_status  kmp_i18n_cat_status_t;
static volatile kmp_i18n_cat_status_t  status = KMP_I18N_CLOSED;

/*
    Message catalog is opened at first usage, so we have to synchronize opening to avoid race and
    multiple openings.

    Closing does not require synchronization, because catalog is closed very late at library
    shutting down, when no other threads are alive.
*/

static void __kmp_i18n_do_catopen();
static kmp_bootstrap_lock_t  lock = KMP_BOOTSTRAP_LOCK_INITIALIZER( lock );
    // `lock' variable may be placed into __kmp_i18n_catopen function because it is used only by
    // that function. But we afraid a (buggy) compiler may treat it wrongly. So we put it outside of
    // function just in case.

void
__kmp_i18n_catopen(
) {
    if ( status == KMP_I18N_CLOSED ) {
        __kmp_acquire_bootstrap_lock( & lock );
        if ( status == KMP_I18N_CLOSED ) {
            __kmp_i18n_do_catopen();
        }; // if
        __kmp_release_bootstrap_lock( & lock );
    }; // if
} // func __kmp_i18n_catopen


/*
    ================================================================================================
    Linux* OS and OS X* part.
    ================================================================================================
*/

#if KMP_OS_UNIX
#define KMP_I18N_OK

#include <nl_types.h>

#define KMP_I18N_NULLCAT ((nl_catd)( -1 ))
static nl_catd       cat  = KMP_I18N_NULLCAT;    // !!! Shall it be volatile?
static char const *  name = ( KMP_VERSION_MAJOR == 4 ? "libguide.cat" : "libomp.cat" );

/*
    Useful links:
        http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html#tag_08_02
        http://www.opengroup.org/onlinepubs/000095399/functions/catopen.html
        http://www.opengroup.org/onlinepubs/000095399/functions/setlocale.html
*/

void
__kmp_i18n_do_catopen(
) {
    int    english = 0;
    char * lang    = __kmp_env_get( "LANG" );
    // TODO: What about LC_ALL or LC_MESSAGES?

    KMP_DEBUG_ASSERT( status == KMP_I18N_CLOSED );
    KMP_DEBUG_ASSERT( cat    == KMP_I18N_NULLCAT );

    english =
	lang == NULL                       ||  // In all these cases English language is used.
	strcmp( lang, "" )            == 0 ||
        strcmp( lang, " " )           == 0 ||
              // Workaround for Fortran RTL bug DPD200137873 "Fortran runtime resets LANG env var
              // to space if it is not set".
	strcmp( lang, "C" )           == 0 ||
	strcmp( lang, "POSIX" )       == 0;

    if ( ! english ) {  // English language is not yet detected, let us continue.
        // Format of LANG is: [language[_territory][.codeset][@modifier]]
        // Strip all parts except language.
        char * tail = NULL;
        __kmp_str_split( lang, '@', & lang, & tail );
        __kmp_str_split( lang, '.', & lang, & tail );
        __kmp_str_split( lang, '_', & lang, & tail );
        english = ( strcmp( lang, "en" ) == 0 );
    }; // if

    KMP_INTERNAL_FREE( lang );

    // Do not try to open English catalog because internal messages are
    // exact copy of messages in English catalog.
    if ( english ) {
	status = KMP_I18N_ABSENT;  // mark catalog as absent so it will not be re-opened.
	return;
    }

    cat = catopen( name, 0 );
    // TODO: Why do we pass 0 in flags?
    status = ( cat == KMP_I18N_NULLCAT ? KMP_I18N_ABSENT : KMP_I18N_OPENED );

    if ( status == KMP_I18N_ABSENT ) {
      if (__kmp_generate_warnings > kmp_warnings_low) { // AC: only issue warning in case explicitly asked to
        int    error   = errno; // Save errno immediately.
	char * nlspath = __kmp_env_get( "NLSPATH" );
        char * lang    = __kmp_env_get( "LANG" );

	// Infinite recursion will not occur -- status is KMP_I18N_ABSENT now, so
	// __kmp_i18n_catgets() will not try to open catalog, but will return default message.
	__kmp_msg(
	    kmp_ms_warning,
	    KMP_MSG( CantOpenMessageCatalog, name ),
	    KMP_ERR( error ),
	    KMP_HNT( CheckEnvVar, "NLSPATH", nlspath ),
            KMP_HNT( CheckEnvVar, "LANG", lang ),
	    __kmp_msg_null
	);
	KMP_INFORM( WillUseDefaultMessages );
        KMP_INTERNAL_FREE( nlspath );
        KMP_INTERNAL_FREE( lang );
      }
    } else { // status == KMP_I18N_OPENED

        int section = get_section( kmp_i18n_prp_Version );
        int number  = get_number( kmp_i18n_prp_Version );
        char const * expected = __kmp_i18n_default_table.sect[ section ].str[ number ];
            // Expected version of the catalog.
        kmp_str_buf_t version;   // Actual version of the catalog.
        __kmp_str_buf_init( & version );
        __kmp_str_buf_print( & version, "%s", catgets( cat, section, number, NULL ) );

            // String returned by catgets is invalid after closing the catalog, so copy it.
        if ( strcmp( version.str, expected ) != 0 ) {
            __kmp_i18n_catclose();     // Close bad catalog.
            status = KMP_I18N_ABSENT;  // And mark it as absent.
            if (__kmp_generate_warnings > kmp_warnings_low) { // AC: only issue warning in case explicitly asked to
                // And now print a warning using default messages.
                char const * name    = "NLSPATH";
                char const * nlspath = __kmp_env_get( name );
                __kmp_msg(
                    kmp_ms_warning,
                    KMP_MSG( WrongMessageCatalog, name, version.str, expected ),
                    KMP_HNT( CheckEnvVar, name, nlspath ),
                    __kmp_msg_null
                );
                KMP_INFORM( WillUseDefaultMessages );
                KMP_INTERNAL_FREE( (void *) nlspath );
            } // __kmp_generate_warnings
        }; // if
        __kmp_str_buf_free( & version );

    }; // if

} // func __kmp_i18n_do_catopen


void
__kmp_i18n_catclose(
) {
    if ( status == KMP_I18N_OPENED ) {
        KMP_DEBUG_ASSERT( cat != KMP_I18N_NULLCAT );
        catclose( cat );
        cat = KMP_I18N_NULLCAT;
    }; // if
    status = KMP_I18N_CLOSED;
} // func __kmp_i18n_catclose


char const *
__kmp_i18n_catgets(
    kmp_i18n_id_t  id
) {

    int section = get_section( id );
    int number  = get_number( id );
    char const * message = NULL;

    if ( 1 <= section && section <= __kmp_i18n_default_table.size ) {
        if ( 1 <= number && number <= __kmp_i18n_default_table.sect[ section ].size ) {
            if ( status == KMP_I18N_CLOSED ) {
                __kmp_i18n_catopen();
            }; // if
            if ( status == KMP_I18N_OPENED ) {
                message =
                    catgets(
                        cat,
                        section, number,
                        __kmp_i18n_default_table.sect[ section ].str[ number ]
                    );
            }; // if
            if ( message == NULL ) {
                message = __kmp_i18n_default_table.sect[ section ].str[ number ];
            }; // if
        }; // if
    }; // if
    if ( message == NULL ) {
        message = no_message_available;
    }; // if
    return message;

} // func __kmp_i18n_catgets


#endif // KMP_OS_UNIX

/*
    ================================================================================================
    Windows* OS part.
    ================================================================================================
*/

#if KMP_OS_WINDOWS
#define KMP_I18N_OK

#include "kmp_environment.h"
#include <windows.h>

#define KMP_I18N_NULLCAT  NULL
static HMODULE       cat  = KMP_I18N_NULLCAT;    // !!! Shall it be volatile?
static char const *  name = ( KMP_VERSION_MAJOR == 4 ? "libguide40ui.dll" : "libompui.dll" );

static kmp_i18n_table_t  table             = { 0, NULL };
    // Messages formatted by FormatMessage() should be freed, but catgets() interface assumes
    // user will not free messages. So we cache all the retrieved messages in the table, which
    // are freed at catclose().
static UINT const        default_code_page = CP_OEMCP;
static UINT              code_page         = default_code_page;

static char const * ___catgets( kmp_i18n_id_t  id );
static UINT         get_code_page();
static void         kmp_i18n_table_free( kmp_i18n_table_t * table );


static UINT
get_code_page(
) {

    UINT cp = default_code_page;
    char const * value = __kmp_env_get( "KMP_CODEPAGE" );
    if ( value != NULL ) {
        if ( _stricmp( value, "ANSI" ) == 0 ) {
            cp = CP_ACP;
        } else if ( _stricmp( value, "OEM" ) == 0 ) {
            cp = CP_OEMCP;
        } else if ( _stricmp( value, "UTF-8" ) == 0 || _stricmp( value, "UTF8" ) == 0 ) {
            cp = CP_UTF8;
        } else if ( _stricmp( value, "UTF-7" ) == 0 || _stricmp( value, "UTF7" ) == 0 ) {
            cp = CP_UTF7;
        } else {
            // !!! TODO: Issue a warning?
        }; // if
    }; // if
    KMP_INTERNAL_FREE( (void *) value );
    return cp;

} // func get_code_page


static void
kmp_i18n_table_free(
    kmp_i18n_table_t * table
) {
    int s;
    int m;
    for ( s = 0; s < table->size; ++ s ) {
        for ( m = 0; m < table->sect[ s ].size; ++ m ) {
            // Free message.
            KMP_INTERNAL_FREE( (void *) table->sect[ s ].str[ m ] );
            table->sect[ s ].str[ m ] = NULL;
        }; // for m
        table->sect[ s ].size = 0;
        // Free section itself.
        KMP_INTERNAL_FREE ( (void *) table->sect[ s ].str );
        table->sect[ s ].str = NULL;
    }; // for s
    table->size = 0;
    KMP_INTERNAL_FREE( (void *) table->sect );
    table->sect = NULL;
} // kmp_i8n_table_free


void
__kmp_i18n_do_catopen(
) {

    LCID          locale_id = GetThreadLocale();
    WORD 	  lang_id = LANGIDFROMLCID( locale_id );
    WORD          primary_lang_id = PRIMARYLANGID( lang_id );
    kmp_str_buf_t path;

    KMP_DEBUG_ASSERT( status == KMP_I18N_CLOSED );
    KMP_DEBUG_ASSERT( cat    == KMP_I18N_NULLCAT );

    __kmp_str_buf_init( & path );

    // Do not try to open English catalog because internal messages are
    // exact copy of messages in English catalog.
    if ( primary_lang_id == LANG_ENGLISH ) {
	status = KMP_I18N_ABSENT;  // mark catalog as absent so it will not be re-opened.
	goto end;
    }; // if

    // Construct resource DLL name.
    /*
        Simple
            LoadLibrary( name )
        is not suitable due to security issue (see
        http://www.microsoft.com/technet/security/advisory/2269637.mspx). We have to specify full
        path to the message catalog.
    */
    {

        // Get handle of our DLL first.
        HMODULE handle;
        BOOL brc =
            GetModuleHandleEx(
                GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                reinterpret_cast< LPCSTR >( & __kmp_i18n_do_catopen ),
                & handle
            );
        if ( ! brc ) {    // Error occurred.
            status = KMP_I18N_ABSENT;  // mark catalog as absent so it will not be re-opened.
            goto end;
            // TODO: Enable multiple messages (KMP_MSG) to be passed to __kmp_msg; and print
            // a proper warning.
        }; // if

        // Now get path to the our DLL.
        for ( ; ; ) {
            DWORD drc = GetModuleFileName( handle, path.str, path.size );
            if ( drc == 0 ) {    // Error occurred.
                status = KMP_I18N_ABSENT;
                goto end;
            }; // if
            if ( drc < path.size ) {
                path.used = drc;
                break;
            }; // if
            __kmp_str_buf_reserve( & path, path.size * 2 );
        }; // forever

        // Now construct the name of message catalog.
        kmp_str_fname fname;
        __kmp_str_fname_init( & fname, path.str );
        __kmp_str_buf_clear( & path );
        __kmp_str_buf_print( & path, "%s%lu/%s", fname.dir, (unsigned long)( locale_id ), name );
        __kmp_str_fname_free( & fname );

    }

    // For security reasons, use LoadLibraryEx() and load message catalog as a data file.
    cat = LoadLibraryEx( path.str, NULL, LOAD_LIBRARY_AS_DATAFILE );
    status = ( cat == KMP_I18N_NULLCAT ? KMP_I18N_ABSENT : KMP_I18N_OPENED );

    if ( status == KMP_I18N_ABSENT ) {
      if (__kmp_generate_warnings > kmp_warnings_low) { // AC: only issue warning in case explicitly asked to
	DWORD error = GetLastError();
	// Infinite recursion will not occur -- status is KMP_I18N_ABSENT now, so
	// __kmp_i18n_catgets() will not try to open catalog but will return default message.
        /*
         If message catalog for another architecture found (e.g. OpenMP RTL
	 for IA-32 architecture opens libompui.dll for Intel(R) 64)
	 Windows* OS returns error 193 (ERROR_BAD_EXE_FORMAT). However,
         FormatMessage fails to return a message for this error, so user
	 will see:

         OMP: Warning #2: Cannot open message catalog "1041\libompui.dll":
         OMP: System error #193: (No system error message available)
         OMP: Info #3: Default messages will be used.

         Issue a hint in this case to let cause of trouble more understandable.
        */
	__kmp_msg(
	    kmp_ms_warning,
	    KMP_MSG( CantOpenMessageCatalog, path.str ),
	    KMP_SYSERRCODE( error ),
            ( error == ERROR_BAD_EXE_FORMAT ? KMP_HNT( BadExeFormat, path.str, KMP_ARCH_STR ) : __kmp_msg_null ),
	    __kmp_msg_null
	);
	KMP_INFORM( WillUseDefaultMessages );
      }
    } else { // status == KMP_I18N_OPENED

        int section = get_section( kmp_i18n_prp_Version );
        int number  = get_number( kmp_i18n_prp_Version );
        char const * expected = __kmp_i18n_default_table.sect[ section ].str[ number ];
        kmp_str_buf_t version;   // Actual version of the catalog.
        __kmp_str_buf_init( & version );
        __kmp_str_buf_print( & version, "%s", ___catgets( kmp_i18n_prp_Version ) );
            // String returned by catgets is invalid after closing the catalog, so copy it.
        if ( strcmp( version.str, expected ) != 0 ) {
            // Close bad catalog.
            __kmp_i18n_catclose();
            status = KMP_I18N_ABSENT;  // And mark it as absent.
            if (__kmp_generate_warnings > kmp_warnings_low) {
                // And now print a warning using default messages.
                __kmp_msg(
                    kmp_ms_warning,
                    KMP_MSG( WrongMessageCatalog, path.str, version.str, expected ),
                    __kmp_msg_null
                );
                KMP_INFORM( WillUseDefaultMessages );
            } // __kmp_generate_warnings
        }; // if
        __kmp_str_buf_free( & version );

    }; // if
    code_page = get_code_page();

    end:
        __kmp_str_buf_free( & path );
        return;

} // func __kmp_i18n_do_catopen


void
__kmp_i18n_catclose(
) {
    if ( status == KMP_I18N_OPENED ) {
        KMP_DEBUG_ASSERT( cat != KMP_I18N_NULLCAT );
        kmp_i18n_table_free( & table );
        FreeLibrary( cat );
        cat = KMP_I18N_NULLCAT;
    }; // if
    code_page = default_code_page;
    status = KMP_I18N_CLOSED;
} // func __kmp_i18n_catclose

/*
    We use FormatMessage() to get strings from catalog, get system error messages, etc.
    FormatMessage() tends to return Windows* OS-style end-of-lines, "\r\n". When string is printed,
    printf() also replaces all the occurrences of "\n" with "\r\n" (again!), so sequences like
    "\r\r\r\n" appear in output. It is not too good.

    Additional mess comes from message catalog: Our catalog source en_US.mc file (generated by
    message-converter.pl) contains only "\n" characters, but en_US_msg_1033.bin file (produced by
    mc.exe) may contain "\r\n" or just "\n". This mess goes from en_US_msg_1033.bin file to
    message catalog, libompui.dll. For example, message

        Error

    (there is "\n" at the end) is compiled by mc.exe to "Error\r\n", while

        OMP: Error %1!d!: %2!s!\n

    (there is "\n" at the end as well) is compiled to "OMP: Error %1!d!: %2!s!\r\n\n".

    Thus, stripping all "\r" normalizes string and returns it to canonical form, so printf() will
    produce correct end-of-line sequences.

    ___strip_crs() serves for this purpose: it removes all the occurrences of "\r" in-place and
    returns new length of string.
*/
static
int
___strip_crs(
    char * str
) {
    int in  = 0;  // Input character index.
    int out = 0;  // Output character index.
    for ( ; ; ) {
        if ( str[ in ] != '\r' ) {
            str[ out ] = str[ in ];
            ++ out;
        }; // if
        if ( str[ in ] == 0 ) {
            break;
        }; // if
        ++ in;
    }; // forever
    return out - 1;
} // func __strip_crs


static
char const *
___catgets(
    kmp_i18n_id_t  id
) {

    char *    result = NULL;
    PVOID     addr   = NULL;
    wchar_t * wmsg   = NULL;
    DWORD     wlen   = 0;
    char *    msg    = NULL;
    int       len    = 0;
    int       rc;

    KMP_DEBUG_ASSERT( cat != KMP_I18N_NULLCAT );
    wlen =    // wlen does *not* include terminating null.
        FormatMessageW(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
                FORMAT_MESSAGE_IGNORE_INSERTS,
            cat,
            id,
            0,             // LangId
            (LPWSTR) & addr,
            0,             // Size in elements, not in bytes.
            NULL
        );
    if ( wlen <= 0 ) {
        goto end;
    }; // if
    wmsg = (wchar_t *) addr;  // Warning: wmsg may be not nul-terminated!

    // Calculate length of multibyte message.
    len =     // Since wlen does not include terminating null, len does not include it also.
        WideCharToMultiByte(
            code_page,
            0,                // Flags.
            wmsg, wlen,       // Wide buffer and size.
            NULL, 0,          // Buffer and size.
            NULL, NULL        // Default char and used default char.
        );
    if ( len <= 0 ) {
        goto end;
    }; // if

    // Allocate memory.
    msg = (char *) KMP_INTERNAL_MALLOC( len + 1 );

    // Convert wide message to multibyte one.
    rc =
        WideCharToMultiByte(
            code_page,
            0,                // Flags.
            wmsg, wlen,       // Wide buffer and size.
            msg, len,         // Buffer and size.
            NULL, NULL        // Default char and used default char.
        );
    if ( rc <= 0 || rc > len ) {
        goto end;
    }; // if
    KMP_DEBUG_ASSERT( rc == len );
    len = rc;
    msg[ len ] = 0;           // Put terminating null to the end.

    // Stripping all "\r" before stripping last end-of-line simplifies the task.
    len = ___strip_crs( msg );

    // Every message in catalog is terminated with "\n". Strip it.
    if ( len >= 1 && msg[ len - 1 ] == '\n' ) {
        -- len;
        msg[ len ] = 0;
    }; // if

    // Everything looks ok.
    result = msg;
    msg    = NULL;

    end:

    if ( msg != NULL ) {
        KMP_INTERNAL_FREE( msg );
    }; // if
    if ( wmsg != NULL ) {
        LocalFree( wmsg );
    }; // if

    return result;

} // ___catgets


char const *
__kmp_i18n_catgets(
    kmp_i18n_id_t  id
) {

    int section = get_section( id );
    int number  = get_number( id );
    char const * message = NULL;

    if ( 1 <= section && section <= __kmp_i18n_default_table.size ) {
        if ( 1 <= number && number <= __kmp_i18n_default_table.sect[ section ].size ) {
            if ( status == KMP_I18N_CLOSED ) {
                __kmp_i18n_catopen();
            }; // if
            if ( cat != KMP_I18N_NULLCAT ) {
                if ( table.size == 0 ) {
                    table.sect = (kmp_i18n_section_t *)
                        KMP_INTERNAL_CALLOC(
                            ( __kmp_i18n_default_table.size + 2 ),
                            sizeof( kmp_i18n_section_t )
                        );
                    table.size = __kmp_i18n_default_table.size;
                }; // if
                if ( table.sect[ section ].size == 0 ) {
                    table.sect[ section ].str = (const char **)
                        KMP_INTERNAL_CALLOC(
                            __kmp_i18n_default_table.sect[ section ].size + 2,
                            sizeof( char const * )
                        );
                    table.sect[ section ].size = __kmp_i18n_default_table.sect[ section ].size;
                }; // if
                if ( table.sect[ section ].str[ number ] == NULL ) {
                    table.sect[ section ].str[ number ] = ___catgets( id );
                }; // if
                message = table.sect[ section ].str[ number ];
            }; // if
            if ( message == NULL ) {
                // Catalog is not opened or message is not found, return default message.
                message = __kmp_i18n_default_table.sect[ section ].str[ number ];
            }; // if
        }; // if
    }; // if
    if ( message == NULL ) {
        message = no_message_available;
    }; // if
    return message;

} // func __kmp_i18n_catgets


#endif // KMP_OS_WINDOWS

// -------------------------------------------------------------------------------------------------

#ifndef KMP_I18N_OK
    #error I18n support is not implemented for this OS.
#endif // KMP_I18N_OK

// -------------------------------------------------------------------------------------------------

void
__kmp_i18n_dump_catalog(
    kmp_str_buf_t * buffer
) {

    struct kmp_i18n_id_range_t {
        kmp_i18n_id_t  first;
        kmp_i18n_id_t  last;
    }; // struct kmp_i18n_id_range_t

    static struct kmp_i18n_id_range_t ranges[] = {
        { kmp_i18n_prp_first, kmp_i18n_prp_last },
        { kmp_i18n_str_first, kmp_i18n_str_last },
        { kmp_i18n_fmt_first, kmp_i18n_fmt_last },
        { kmp_i18n_msg_first, kmp_i18n_msg_last },
        { kmp_i18n_hnt_first, kmp_i18n_hnt_last }
    }; // ranges

    int           num_of_ranges = sizeof( ranges ) / sizeof( struct kmp_i18n_id_range_t );
    int           range;
    kmp_i18n_id_t id;

    for ( range = 0; range < num_of_ranges; ++ range ) {
        __kmp_str_buf_print( buffer, "*** Set #%d ***\n", range + 1 );
        for ( id = (kmp_i18n_id_t)( ranges[ range ].first + 1 );
              id < ranges[ range ].last;
              id = (kmp_i18n_id_t)( id + 1 ) ) {
             __kmp_str_buf_print( buffer, "%d: <<%s>>\n", id, __kmp_i18n_catgets( id ) );
        }; // for id
    }; // for range

    __kmp_printf( "%s", buffer->str );

} // __kmp_i18n_dump_catalog

// -------------------------------------------------------------------------------------------------

kmp_msg_t
__kmp_msg_format(
    kmp_i18n_id_t id,
    ...
) {

    kmp_msg_t      msg;
    va_list        args;
    kmp_str_buf_t  buffer;
    __kmp_str_buf_init( & buffer );

    va_start( args, id );
    #if KMP_OS_UNIX
        // On Linux* OS and OS X*, printf() family functions process parameter numbers, for example:
        // "%2$s %1$s".
        __kmp_str_buf_vprint( & buffer, __kmp_i18n_catgets( id ), args );
    #elif KMP_OS_WINDOWS
        // On Winodws, printf() family functions does not recognize GNU style parameter numbers,
        // so we have to use FormatMessage() instead. It recognizes parameter numbers, e. g.:
        // "%2!s! "%1!s!".
        {
            LPTSTR str = NULL;
            int    len;
            FormatMessage(
                FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                __kmp_i18n_catgets( id ),
                0, 0,
                (LPTSTR)( & str ),
                0,
                & args
            );
            len = ___strip_crs( str );
            __kmp_str_buf_cat( & buffer, str, len );
            LocalFree( str );
        }
    #else
        #error
    #endif
    va_end( args );
    __kmp_str_buf_detach( & buffer );

    msg.type = (kmp_msg_type_t)( id >> 16 );
    msg.num  = id & 0xFFFF;
    msg.str  = buffer.str;
    msg.len  = buffer.used;

    return msg;

} // __kmp_msg_format

// -------------------------------------------------------------------------------------------------

static
char *
sys_error(
    int err
) {

    char * message = NULL;

    #if KMP_OS_WINDOWS

        LPVOID  buffer = NULL;
        int     len;
        DWORD   rc;
        rc =
            FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                err,
                MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language.
                (LPTSTR) & buffer,
                0,
                NULL
            );
        if ( rc > 0 ) {
            // Message formatted. Copy it (so we can free it later with normal free().
            message = __kmp_str_format( "%s", (char *) buffer );
            len = ___strip_crs( message ); // Delete carriage returns if any.
            // Strip trailing newlines.
            while ( len > 0 && message[ len - 1 ] == '\n' ) {
                -- len;
            }; // while
            message[ len ] = 0;
        } else {
            // FormatMessage() failed to format system error message. GetLastError() would give us
            // error code, which we would convert to message... this it dangerous recursion, which
            // cannot clarify original error, so we will not even start it.
        }; // if
        if ( buffer != NULL ) {
            LocalFree( buffer );
        }; // if

    #else // Non-Windows* OS: Linux* OS or OS X*

        /*
            There are 2 incompatible versions of strerror_r:

                char * strerror_r( int, char *, size_t );  // GNU version
                int    strerror_r( int, char *, size_t );  // XSI version
        */

        #if KMP_OS_LINUX

            // GNU version of strerror_r.

            char   buffer[ 2048 ];
            char * const err_msg = strerror_r( err, buffer, sizeof( buffer ) );
                // Do not eliminate this assignment to temporary variable, otherwise compiler would
                // not issue warning if strerror_r() returns `int' instead of expected `char *'.
            message = __kmp_str_format( "%s", err_msg );

        #else // OS X*, FreeBSD* etc.

            // XSI version of strerror_r.

            int    size   = 2048;
            // TODO: Add checking result of malloc().
            char * buffer = (char *) KMP_INTERNAL_MALLOC( size );
            int    rc;
            rc = strerror_r( err, buffer, size );
            if ( rc == -1 ) {
                rc = errno;            // XSI version sets errno.
            }; // if
            while ( rc == ERANGE ) {   // ERANGE means the buffer is too small.
                KMP_INTERNAL_FREE( buffer );
                size *= 2;
                buffer = (char *) KMP_INTERNAL_MALLOC( size );
                rc = strerror_r( err, buffer, size );
                if ( rc == -1 ) {
                    rc = errno;        // XSI version sets errno.
                }; // if
            }; // while
            if ( rc == 0 ) {
                message = buffer;
            } else {
                // Buffer is unused. Free it.
                KMP_INTERNAL_FREE( buffer );
            }; // if

        #endif

    #endif /* KMP_OS_WINDOWS */

    if ( message == NULL ) {
        // TODO: I18n this message.
        message = __kmp_str_format( "%s", "(No system error message available)" );
    }; // if
    return message;

} // sys_error

// -------------------------------------------------------------------------------------------------

kmp_msg_t
__kmp_msg_error_code(
    int  code
) {

    kmp_msg_t      msg;
    msg.type = kmp_mt_syserr;
    msg.num  = code;
    msg.str  = sys_error( code );
    msg.len  = KMP_STRLEN( msg.str );
    return msg;

} // __kmp_msg_error_code

// -------------------------------------------------------------------------------------------------

kmp_msg_t
__kmp_msg_error_mesg(
    char const * mesg
) {

    kmp_msg_t      msg;
    msg.type = kmp_mt_syserr;
    msg.num  = 0;
    msg.str  = __kmp_str_format( "%s", mesg );
    msg.len  = KMP_STRLEN( msg.str );
    return msg;

} // __kmp_msg_error_mesg

// -------------------------------------------------------------------------------------------------

void
__kmp_msg(
    kmp_msg_severity_t  severity,
    kmp_msg_t           message,
    ...
) {

    va_list        args;
    kmp_i18n_id_t  format;      // format identifier
    kmp_msg_t      fmsg;        // formatted message
    kmp_str_buf_t  buffer;

    if ( severity != kmp_ms_fatal && __kmp_generate_warnings == kmp_warnings_off )
        return; // no reason to form a string in order to not print it

    __kmp_str_buf_init( & buffer );

    // Format the primary message.
    switch ( severity ) {
        case kmp_ms_inform : {
            format = kmp_i18n_fmt_Info;
        } break;
        case kmp_ms_warning : {
            format = kmp_i18n_fmt_Warning;
        } break;
        case kmp_ms_fatal : {
            format = kmp_i18n_fmt_Fatal;
        } break;
        default : {
            KMP_DEBUG_ASSERT( 0 );
        };
    }; // switch
    fmsg = __kmp_msg_format( format, message.num, message.str );
    KMP_INTERNAL_FREE( (void *) message.str );
    __kmp_str_buf_cat( & buffer, fmsg.str, fmsg.len );
    KMP_INTERNAL_FREE( (void *) fmsg.str );

    // Format other messages.
    va_start( args, message );
    for ( ; ; ) {
        message = va_arg( args, kmp_msg_t );
        if ( message.type == kmp_mt_dummy && message.str == NULL ) {
            break;
        }; // if
        if ( message.type == kmp_mt_dummy && message.str == __kmp_msg_empty.str ) {
            continue;
        }; // if
        switch ( message.type ) {
            case kmp_mt_hint : {
                format = kmp_i18n_fmt_Hint;
            } break;
            case kmp_mt_syserr : {
                format = kmp_i18n_fmt_SysErr;
            } break;
            default : {
                KMP_DEBUG_ASSERT( 0 );
            };
        }; // switch
        fmsg = __kmp_msg_format( format, message.num, message.str );
        KMP_INTERNAL_FREE( (void *) message.str );
        __kmp_str_buf_cat( & buffer, fmsg.str, fmsg.len );
        KMP_INTERNAL_FREE( (void *) fmsg.str );
    }; // forever
    va_end( args );

    // Print formatted messages.
    // This lock prevents multiple fatal errors on the same problem.
    // __kmp_acquire_bootstrap_lock( & lock );    // GEH - This lock causing tests to hang on OS X*.
    __kmp_printf( "%s", buffer.str );
    __kmp_str_buf_free( & buffer );

    if ( severity == kmp_ms_fatal ) {
        #if KMP_OS_WINDOWS
        __kmp_thread_sleep( 500 );   /* Delay to give message a chance to appear before reaping */
        #endif
        __kmp_abort_process();
    }; // if

    // __kmp_release_bootstrap_lock( & lock );  // GEH - this lock causing tests to hang on OS X*.

} // __kmp_msg

// -------------------------------------------------------------------------------------------------

// end of file //
