/*
 * kmp_i18n.cpp
 */

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "kmp_i18n.h"

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

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

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

#undef KMP_I18N_OK

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

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

static void __kmp_msg(kmp_msg_severity_t severity, kmp_msg_t message,
                      va_list ap);

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();
    }
    __kmp_release_bootstrap_lock(&lock);
  }
} // 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);
  }

  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_t err_code = KMP_ERR(error);
      __kmp_msg(kmp_ms_warning, KMP_MSG(CantOpenMessageCatalog, name), err_code,
                KMP_HNT(CheckEnvVar, "NLSPATH", nlspath),
                KMP_HNT(CheckEnvVar, "LANG", lang), __kmp_msg_null);
      if (__kmp_generate_warnings == kmp_warnings_off) {
        __kmp_str_free(&err_code.str);
      }

      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 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(CCAST(char *, nlspath));
      } // __kmp_generate_warnings
    }
    __kmp_str_buf_free(&version);
  }
} // 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;
  }
  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 (status == KMP_I18N_OPENED) {
        message = catgets(cat, section, number,
                          __kmp_i18n_default_table.sect[section].str[number]);
      }
      if (message == NULL) {
        message = __kmp_i18n_default_table.sect[section].str[number];
      }
    }
  }
  if (message == NULL) {
    message = no_message_available;
  }
  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?
    }
  }
  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;
    }
    table->sect[s].size = 0;
    // Free section itself.
    KMP_INTERNAL_FREE((void *)table->sect[s].str);
    table->sect[s].str = NULL;
  }
  table->size = 0;
  KMP_INTERNAL_FREE((void *)table->sect);
  table->sect = NULL;
} // kmp_i18n_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;
  }

  // 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.
    }

    // 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 (drc < path.size) {
        path.used = drc;
        break;
      }
      __kmp_str_buf_reserve(&path, path.size * 2);
    }

    // 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 hint in this case so cause of trouble is more understandable. */
      kmp_msg_t err_code = KMP_SYSERRCODE(error);
      __kmp_msg(kmp_ms_warning, KMP_MSG(CantOpenMessageCatalog, path.str),
                err_code,
                (error == ERROR_BAD_EXE_FORMAT
                     ? KMP_HNT(BadExeFormat, path.str, KMP_ARCH_STR)
                     : __kmp_msg_null),
                __kmp_msg_null);
      if (__kmp_generate_warnings == kmp_warnings_off) {
        __kmp_str_free(&err_code.str);
      }
      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 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
    }
    __kmp_str_buf_free(&version);
  }
  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;
  }
  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 (str[in] == 0) {
      break;
    }
    ++in;
  }
  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;
  }
  wmsg = (wchar_t *)addr; // Warning: wmsg may be not nul-terminated!

  // Calculate length of multibyte message.
  // Since wlen does not include terminating null, len does not include it also.
  len = 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;
  }

  // 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;
  }
  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;
  }

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

end:

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

  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 (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 (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 (table.sect[section].str[number] == NULL) {
          table.sect[section].str[number] = ___catgets(id);
        }
        message = table.sect[section].str[number];
      }
      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 (message == NULL) {
    message = no_message_available;
  }
  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));
    }
  }

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

} // __kmp_i18n_dump_catalog

// -----------------------------------------------------------------------------
kmp_msg_t __kmp_msg_format(unsigned id_arg, ...) {

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

  va_start(args, id_arg);

  // We use unsigned for the ID argument and explicitly cast it here to the
  // right enumerator because variadic functions are not compatible with
  // default promotions.
  kmp_i18n_id_t id = (kmp_i18n_id_t)id_arg;

#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 Windows, 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;
    }
    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 (buffer != NULL) {
    LocalFree(buffer);
  }

#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 (defined(__GLIBC__) && defined(_GNU_SOURCE)) ||                            \
    (defined(__BIONIC__) && defined(_GNU_SOURCE) &&                            \
     __ANDROID_API__ >= __ANDROID_API_M__)
  // 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;
  char *buffer = (char *)KMP_INTERNAL_MALLOC(size);
  int rc;
  if (buffer == NULL) {
    KMP_FATAL(MemoryAllocFailed);
  }
  rc = strerror_r(err, buffer, size);
  if (rc == -1) {
    rc = errno; // XSI version sets errno.
  }
  while (rc == ERANGE) { // ERANGE means the buffer is too small.
    KMP_INTERNAL_FREE(buffer);
    size *= 2;
    buffer = (char *)KMP_INTERNAL_MALLOC(size);
    if (buffer == NULL) {
      KMP_FATAL(MemoryAllocFailed);
    }
    rc = strerror_r(err, buffer, size);
    if (rc == -1) {
      rc = errno; // XSI version sets errno.
    }
  }
  if (rc == 0) {
    message = buffer;
  } else { // Buffer is unused. Free it.
    KMP_INTERNAL_FREE(buffer);
  }

#endif

#endif /* KMP_OS_WINDOWS */

  if (message == NULL) {
    // TODO: I18n this message.
    message = __kmp_str_format("%s", "(No system error message available)");
  }
  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);
  }
  }
  fmsg = __kmp_msg_format(format, message.num, message.str);
  __kmp_str_free(&message.str);
  __kmp_str_buf_cat(&buffer, fmsg.str, fmsg.len);
  __kmp_str_free(&fmsg.str);

  // Format other messages.
  for (;;) {
    message = va_arg(args, kmp_msg_t);
    if (message.type == kmp_mt_dummy && message.str == NULL) {
      break;
    }
    switch (message.type) {
    case kmp_mt_hint: {
      format = kmp_i18n_fmt_Hint;
      // we cannot skip %1$ and only use %2$ to print the message without the
      // number
      fmsg = __kmp_msg_format(format, message.str);
    } break;
    case kmp_mt_syserr: {
      format = kmp_i18n_fmt_SysErr;
      fmsg = __kmp_msg_format(format, message.num, message.str);
    } break;
    default: {
      KMP_DEBUG_ASSERT(0);
    }
    }
    __kmp_str_free(&message.str);
    __kmp_str_buf_cat(&buffer, fmsg.str, fmsg.len);
    __kmp_str_free(&fmsg.str);
  }

  // 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);

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

} // __kmp_msg

void __kmp_msg(kmp_msg_severity_t severity, kmp_msg_t message, ...) {
  va_list args;
  va_start(args, message);
  __kmp_msg(severity, message, args);
  va_end(args);
}

void __kmp_fatal(kmp_msg_t message, ...) {
  va_list args;
  va_start(args, message);
  __kmp_msg(kmp_ms_fatal, message, args);
  va_end(args);
#if KMP_OS_WINDOWS
  // Delay to give message a chance to appear before reaping
  __kmp_thread_sleep(500);
#endif
  __kmp_abort_process();
} // __kmp_fatal

// end of file //
