Skip to content

File message_catalog.c

File List > base > message_catalog.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * message_catalog.c - Message catalog functions with NLS support
 */

#ident "$Id$"

#include "config.h"
#include "message_catalog.h"

/*
 * Note: stems from FreeBSD nl_type.h and msgcat.c.
 */
#if defined(WINDOWS)
#include <windows.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#if !defined(WINDOWS)
#include <sys/mman.h>
#include <netinet/in.h>
#endif

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>

#include "porting.h"

#include "environment_variable.h"
#include "error_code.h"
#include "error_manager.h"
#include "language_support.h"

#if defined(WINDOWS)
#include "intl_support.h"
#endif
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

/*
 * MESSAGE CATALOG FILE FORMAT.
 *
 * The NetBSD/FreeBSD message catalog format is similar to the format used by
 * Svr4 systems.  The differences are:
 *   * fixed byte order (big endian)
 *   * fixed data field sizes
 *
 * A message catalog contains four data types: a catalog header, one
 * or more set headers, one or more message headers, and one or more
 * text strings.
 *
 * NOTE: some changes were done to the initial code. we can no longer use the ntypes.h. and if nltypes.h is included
 *       there will be conflicts... so just rename everything.
 */

#define NLS_MAGIC      0xff88ff89

struct nls_cat_hdr
{
  INT32 _magic;
  INT32 _nsets;
  INT32 _mem;
  INT32 _msg_hdr_offset;
  INT32 _msg_txt_offset;
};

struct nls_set_hdr
{
  INT32 _setno;         /* set number: 0 < x <= NL_SETMAX */
  INT32 _nmsgs;         /* number of messages in the set */
  INT32 _index;         /* index of first msg_hdr in msg_hdr table */
};

struct nls_msg_hdr
{
  INT32 _msgno;         /* msg number: 0 < x <= NL_MSGMAX */
  INT32 _msglen;
  INT32 _offset;
};

#ifndef ENCODING_LEN
#define ENCODING_LEN    40
#endif

#define CUB_NL_SETD         0
#define CUB_NL_CAT_LOCALE   1

typedef struct _nl_cat_d
{
  void *_data;
  int _size;
#if defined(WINDOWS)
  HANDLE map_handle;
#endif
} *cub_nl_catd;

static cub_nl_catd cub_catopen (const char *, int);
static char *cub_catgets (cub_nl_catd, int, int, const char *);
static int cub_catclose (cub_nl_catd);

/*
 * Note: stems from FreeBSD nl_type.h and msgcat.c.
 */
#define DEFAULT_NLS_PATH "/usr/share/nls/%L/%N.cat:/usr/share/nls/%N/%L:/usr/local/share/nls/%L/%N.cat:/usr/local/share/nls/%N/%L"

static cub_nl_catd load_msgcat (const char *);

cub_nl_catd
cub_catopen (const char *name, int type)
{
  int spcleft, saverr;
  char path[PATH_MAX];
  char *nlspath, *lang, *base, *cptr, *pathP, *tmpptr;
  char *cptr1, *plang, *pter, *pcode;
  struct stat sbuf;

  if (name == NULL || *name == '\0')
    {
      errno = EINVAL;
      return NULL;
    }

  /* is it absolute path ? if yes, load immediately */
  if (strchr (name, '/') != NULL)
    {
      return load_msgcat (name);
    }

  if (type == CUB_NL_CAT_LOCALE)
    {
      lang = setlocale (LC_MESSAGES, NULL);
    }
  else
    {
      lang = getenv ("CHARSET");
    }

  if (lang == NULL || *lang == '\0' || strlen (lang) > ENCODING_LEN
      || (lang[0] == '.' && (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) || strchr (lang, '/') != NULL)
    {
      lang = (char *) "C";
    }

  plang = cptr1 = strdup (lang);
  if (cptr1 == NULL)
    {
      return NULL;
    }

  cptr = strchr (cptr1, '@');
  if (cptr != NULL)
    {
      *cptr = '\0';
    }

  pter = pcode = (char *) "";
  cptr = strchr (cptr1, '_');
  if (cptr != NULL)
    {
      *cptr++ = '\0';
      pter = cptr1 = cptr;
    }

  cptr = strchr (cptr1, '.');
  if (cptr != NULL)
    {
      *cptr++ = '\0';
      pcode = cptr;
    }

  nlspath = getenv ("NLSPATH");
  if (nlspath == NULL)
    {
      nlspath = (char *) DEFAULT_NLS_PATH;
    }

  base = cptr = strdup (nlspath);
  if (cptr == NULL)
    {
      saverr = errno;
      free (plang);
      errno = saverr;
      return NULL;
    }

  while ((nlspath = strsep (&cptr, ":")) != NULL)
    {
      pathP = path;
      if (*nlspath)
    {
      for (; *nlspath; ++nlspath)
        {
          if (*nlspath == '%')
        {
          switch (*(nlspath + 1))
            {
            case 'l':
              tmpptr = plang;
              break;
            case 't':
              tmpptr = pter;
              break;
            case 'c':
              tmpptr = pcode;
              break;
            case 'L':
              tmpptr = lang;
              break;
            case 'N':
              tmpptr = (char *) name;
              break;
            case '%':
              ++nlspath;
              [[fallthrough]];
            default:
              if (pathP - path >= PATH_MAX - 1)
            {
              goto too_long;
            }
              *(pathP++) = *nlspath;
              continue;
            }
          ++nlspath;

        put_tmpptr:
          spcleft = PATH_MAX - (CAST_STRLEN (pathP - path)) - 1;
          if (strlcpy (pathP, tmpptr, spcleft) >= (size_t) spcleft)
            {
            too_long:
              free (plang);
              free (base);
              errno = ENAMETOOLONG;
              return NULL;
            }
          pathP += strlen (tmpptr);
        }
          else
        {
          if (pathP - path >= PATH_MAX - 1)
            {
              goto too_long;
            }
          *(pathP++) = *nlspath;
        }
        }
      *pathP = '\0';
      if (stat (path, &sbuf) == 0)
        {
          free (plang);
          free (base);
          return load_msgcat (path);
        }
    }
      else
    {
      tmpptr = (char *) name;
      --nlspath;
      goto put_tmpptr;
    }
    }

  free (plang);
  free (base);
  errno = ENOENT;

  return NULL;
}

char *
cub_catgets (cub_nl_catd catd, int set_id, int msg_id, const char *s)
{
  struct nls_cat_hdr *cat_hdr;
  struct nls_set_hdr *set_hdr;
  struct nls_msg_hdr *msg_hdr;
  int l, u, i, r;

  if (catd == NULL)
    {
      errno = EBADF;
      /* LINTED interface problem */
      return (char *) s;
    }

  cat_hdr = (struct nls_cat_hdr *) catd->_data;
  set_hdr = (struct nls_set_hdr *) (void *) ((char *) catd->_data + sizeof (struct nls_cat_hdr));

  /* binary search, see knuth algorithm b */
  l = 0;
  u = ntohl ((UINT32) cat_hdr->_nsets) - 1;
  while (l <= u)
    {
      i = (l + u) / 2;
      r = set_id - ntohl ((UINT32) set_hdr[i]._setno);

      if (r == 0)
    {
      msg_hdr =
        (struct nls_msg_hdr *) (void *) ((char *) catd->_data + sizeof (struct nls_cat_hdr) +
                         ntohl ((UINT32) cat_hdr->_msg_hdr_offset));

      l = ntohl ((UINT32) set_hdr[i]._index);
      u = l + ntohl ((UINT32) set_hdr[i]._nmsgs) - 1;
      while (l <= u)
        {
          i = (l + u) / 2;
          r = msg_id - ntohl ((UINT32) msg_hdr[i]._msgno);
          if (r == 0)
        {
          return ((char *) catd->_data + sizeof (struct nls_cat_hdr) +
              ntohl ((UINT32) cat_hdr->_msg_txt_offset) + ntohl ((UINT32) msg_hdr[i]._offset));
        }
          else if (r < 0)
        {
          u = i - 1;
        }
          else
        {
          l = i + 1;
        }
        }

      /* not found */
      goto notfound;

    }
      else if (r < 0)
    {
      u = i - 1;
    }
      else
    {
      l = i + 1;
    }
    }

notfound:
  /* not found */
  errno = ENOMSG;
  /* LINTED interface problem */
  return (char *) s;
}

int
cub_catclose (cub_nl_catd catd)
{
  if (catd == NULL)
    {
      errno = EBADF;
      return (-1);
    }

#if defined(WINDOWS)
  UnmapViewOfFile (catd->_data);
  CloseHandle (catd->map_handle);
#else
  munmap (catd->_data, (size_t) catd->_size);
#endif
  free (catd);
  return (0);
}

/*
 * Internal support functions
 */

static cub_nl_catd
load_msgcat (const char *path)
{
  struct stat st;
  cub_nl_catd catd;
  void *data = NULL;
#if defined(WINDOWS)
  HANDLE map_handle;
  HANDLE file_handle;
#else
  int fd;
#endif

  /* XXX: path != NULL? */

#if defined(WINDOWS)
  file_handle = CreateFile (path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (file_handle == NULL)
    {
      return NULL;
    }
  if (stat (path, &st) != 0)
    {
      CloseHandle (file_handle);
      return NULL;
    }

  map_handle = CreateFileMapping ((HANDLE) file_handle, NULL, PAGE_READONLY, 0, (DWORD) st.st_size, NULL);
  if (map_handle != NULL)
    {
      data = MapViewOfFile (map_handle, FILE_MAP_READ, 0, 0, 0);
    }

  if (data == NULL)
    {
      return NULL;
    }
#else
  fd = open (path, O_RDONLY);
  if (fd == -1)
    {
      return NULL;
    }

  if (fstat (fd, &st) != 0)
    {
      close (fd);
      return NULL;
    }

  data = mmap (0, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, (off_t) 0);
  close (fd);

  if (data == MAP_FAILED)
    {
      return NULL;
    }
#endif

  if (ntohl ((UINT32) ((struct nls_cat_hdr *) data)->_magic) != NLS_MAGIC)
    {
#if defined(WINDOWS)
      UnmapViewOfFile (data);
      CloseHandle (map_handle);
#else
      munmap (data, (size_t) st.st_size);
#endif
      errno = EINVAL;
      return NULL;
    }

  catd = (cub_nl_catd) malloc (sizeof (*catd));
  if (catd == NULL)
    {
#if defined(WINDOWS)
      UnmapViewOfFile (data);
      CloseHandle (map_handle);
#else
      munmap (data, (size_t) st.st_size);
#endif
      return NULL;
    }

  catd->_data = data;
  catd->_size = (int) st.st_size;
#if defined(WINDOWS)
  catd->map_handle = map_handle;
#endif
  return catd;
}

/* system message catalog definition */
struct msgcat_def
{
  int cat_id;
  const char *name;
  MSG_CATD msg_catd;
};
struct msgcat_def msgcat_System[] = {
  {MSGCAT_CATALOG_CUBRID /* 0 */ , "cubrid.cat", NULL},
  {MSGCAT_CATALOG_CSQL /* 1 */ , "csql.cat", NULL},
  {MSGCAT_CATALOG_UTILS /* 2 */ , "utils.cat", NULL}
};

#define MSGCAT_SYSTEM_DIM \
        (sizeof(msgcat_System) / sizeof(struct msgcat_def))

/*
 * msgcat_init - initialize message catalog module
 *   return: NO_ERROR or ER_FAILED
 */
int
msgcat_init (void)
{
  size_t i;
  int rc = NO_ERROR;

  for (i = 0; i < MSGCAT_SYSTEM_DIM; i++)
    {
      if (msgcat_System[i].msg_catd == NULL)
    {
      msgcat_System[i].msg_catd = msgcat_open (msgcat_System[i].name);
      if (msgcat_System[i].msg_catd == NULL)
        {
          // TODO: Need to decide whether to return immediately when an error occurs.  
          rc = ER_FAILED;
        }
    }
    }

  return rc;
}

/*
 * msgcat_final - finalize message catalog module
 *   return: NO_ERROR or ER_FAILED
 */
int
msgcat_final (void)
{
  size_t i;
  int rc;

  rc = NO_ERROR;
  for (i = 0; i < MSGCAT_SYSTEM_DIM; i++)
    {
      if (msgcat_System[i].msg_catd != NULL)
    {
      if (msgcat_close (msgcat_System[i].msg_catd) != NO_ERROR)
        {
          rc = ER_FAILED;
        }
      msgcat_System[i].msg_catd = NULL;
    }
    }

  return rc;
}

/*
 * msgcat_message -
 *   return: a message string or NULL
 *   cat_id(in):
 *   set_id(in):
 *   msg_id(in):
 *
 * Note:
 */
char *
msgcat_message (int cat_id, int set_id, int msg_id)
{
  char *msg;
  static char *empty = (char *) "";

  if (cat_id < 0 || ((size_t) cat_id) >= MSGCAT_SYSTEM_DIM)
    {
      return NULL;
    }

  if (msgcat_System[cat_id].msg_catd == NULL)
    {
      msgcat_System[cat_id].msg_catd = msgcat_open (msgcat_System[cat_id].name);
      if (msgcat_System[cat_id].msg_catd == NULL)
    {
      return NULL;
    }
    }

  msg = msgcat_gets (msgcat_System[cat_id].msg_catd, set_id, msg_id, NULL);
  if (msg == NULL)
    {
      fprintf (stderr, "Cannot find message id %d in set id %d from the file %s(%s).", msg_id, set_id,
           msgcat_System[cat_id].name, msgcat_System[cat_id].msg_catd->file);
      /* to protect the error of copying NULL pointer, return empty string ("") rather than NULL */
      return empty;
    }

  return msg;
}

/*
 * msgcat_open - open a message catalog
 *   return: message catalog descriptor MSG_CATD or NULL
 *   name(in): message catalog file name
 *
 * Note: File name will be converted to a full path name with the the root
 *       directory prefix.
 *       The returned MSG_CATD is allocated with malloc(). It will be freed in
 *       msgcat_close().
 */
MSG_CATD
msgcat_open (const char *name)
{
  cub_nl_catd catd;
  MSG_CATD msg_catd;
  char path[PATH_MAX];

  /* $CUBRID/msg/$CUBRID_MSG_LANG/'name' */
  envvar_localedir_file (path, PATH_MAX, lang_get_msg_Loc_name (), name);
  catd = cub_catopen (path, 0);
  if (catd == NULL)
    {
      /* try once more as default language */
      envvar_localedir_file (path, PATH_MAX, LANG_NAME_DEFAULT, name);
      catd = cub_catopen (path, 0);
      if (catd == NULL)
    {
      return NULL;
    }
    }

  msg_catd = (MSG_CATD) malloc (sizeof (*msg_catd));
  if (msg_catd == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (*msg_catd));
      cub_catclose (catd);
      return NULL;
    }

  msg_catd->file = strdup (path);
  msg_catd->catd = (void *) catd;

  return msg_catd;
}

/*
 * msgcat_get_descriptor - get a message catalog descriptor
 *   return: message catalog descriptor MSG_CATD
 *   cat_id(in): message id (number)
 *
 * Note:
 */
MSG_CATD
msgcat_get_descriptor (int cat_id)
{
  return (msgcat_System[cat_id].msg_catd);
}

/*
 * msgcat_gets - read a message string from the message catalog
 *   return:
 *   msg_catd(in): message catalog descriptor
 *   set_id(in): set id (number)
 *   msg_id(in): message id (number)
 *   s(in): default message string which shall be returned if the identified
 *          message is not available
 *
 * Note:
 */
char *
msgcat_gets (MSG_CATD msg_catd, int set_id, int msg_id, const char *s)
{
  cub_nl_catd catd;

  catd = (cub_nl_catd) msg_catd->catd;
  return cub_catgets (catd, set_id, msg_id, s);
}

/*
 * msgcat_close - close a message catalog
 *   return: NO_ERROR or ER_FAILED
 *   msg_catd(in): message catalog descriptor MSG_CATD
 *
 * Note:
 */
int
msgcat_close (MSG_CATD msg_catd)
{
  cub_nl_catd catd;

  catd = (cub_nl_catd) msg_catd->catd;
  free ((void *) msg_catd->file);
  free (msg_catd);
  if (cub_catclose (catd) < 0)
    {
      return ER_FAILED;
    }

  return NO_ERROR;
}

/*
 * msgcat_open_file - open a text file in the directory of message catalogs
 *   return: FILE pointer or NULL
 *   name(in):
 */
FILE *
msgcat_open_file (const char *name)
{
  FILE *fp;
  char path[PATH_MAX];

  /* $CUBRID/msg/$CUBRID_MSG_LANG/'name' */
  envvar_localedir_file (path, PATH_MAX, lang_get_msg_Loc_name (), name);
  fp = fopen (path, "r");
  if (fp == NULL)
    {
      /* try once more as default language */
      envvar_localedir_file (path, PATH_MAX, LANG_NAME_DEFAULT, name);
      fp = fopen (path, "r");
    }

  return fp;
}