/* Handle aliases for locale names.
   Copyright (C) 1995-1999, 2000, 2001 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* Tell glibc's <string.h> to provide a prototype for mempcpy().
   This must come before <config.h> because <config.h> may include
   <features.h>, and once <features.h> has been included, it's too late.  */
#ifndef _GNU_SOURCE
# define _GNU_SOURCE    1
#endif

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>

#ifdef __GNUC__
# define alloca __builtin_alloca
# define HAVE_ALLOCA 1
#else
# if defined HAVE_ALLOCA_H || defined _LIBC
#  include <alloca.h>
# else
#  ifdef _AIX
 #pragma alloca
#  else
#   ifndef alloca
char *alloca ();
#   endif
#  endif
# endif
#endif

#include <stdlib.h>

#include <string.h>
#if !HAVE_STRCHR && !defined _LIBC
# ifndef strchr
#  define strchr index
# endif
#endif

#include "gettextP.h"

/* @@ end of prolog @@ */

#ifdef _LIBC
/* Rename the non ANSI C functions.  This is required by the standard
   because some ANSI C functions will require linking with this object
   file and the name space must not be polluted.  */
# define strcasecmp __strcasecmp

# ifndef mempcpy
#  define mempcpy __mempcpy
# endif
# define HAVE_MEMPCPY	1

/* We need locking here since we can be called from different places.  */
# include <bits/libc-lock.h>

__libc_lock_define_initialized (static, lock);
#endif

#ifndef internal_function
# define internal_function
#endif

/* For those losing systems which don't have `alloca' we have to add
   some additional code emulating it.  */
#ifdef HAVE_ALLOCA
# define freea(p) /* nothing */
#else
# define alloca(n) malloc (n)
# define freea(p) free (p)
#endif

#if defined _LIBC_REENTRANT || defined HAVE_FGETS_UNLOCKED
# undef fgets
# define fgets(buf, len, s) fgets_unlocked (buf, len, s)
#endif
#if defined _LIBC_REENTRANT || defined HAVE_FEOF_UNLOCKED
# undef feof
# define feof(s) feof_unlocked (s)
#endif


struct alias_map
{
  const char *alias;
  const char *value;
};


static char *string_space;
static size_t string_space_act;
static size_t string_space_max;
static struct alias_map *map;
static size_t nmap;
static size_t maxmap;


/* Prototypes for local functions.  */
static size_t read_alias_file PARAMS ((const char *fname, int fname_len))
     internal_function;
static int extend_alias_table PARAMS ((void));
static int alias_compare PARAMS ((const struct alias_map *map1,
				  const struct alias_map *map2));


#ifdef __EMX__
# ifndef PATH_SEPARATOR
#  define PATH_SEPARATOR ';'
# endif

/* result is 0 if dirname is no absolute path, 1 otherwise */
static int
_os2_is_abs_path(const char *dirname)
{
  int result = 0;
  if (dirname != NULL && dirname[0] != '\0') {
    /* if dirname contains a valid drive letter like "c:" */
    if (((dirname[0] >= 'A' && dirname[0] <= 'Z') || (dirname[0] >= 'a' && dirname[0] <= 'z'))
        && dirname[1] == ':') dirname += 2; /* remove the drive letter */

    if (dirname[0] == '/' || dirname[0] == '\\') result = 1; /* asbolute path */
  }

  return result;
}


/* path is assumed to be a list of directories separated by PATH_SEPARATOR.
   This function determines if the first directory of path is on the
   drive specified by the environment variable UNIXROOT.
   If it is the case, NULL is returned, otherwise a new directory name
   is allocated using the drive letter from UNIXROOT and returned as result.
   If the first directory is a relative path NULL is returned, too.
   The new directory name is allocated by malloc().
   Example (UNIXROOT is set to "e:"):
     "c:/usr/share" -> "e:/usr/share"
     "e:/usr/share" -> NULL             (already on the $UNIXROOT drive)
     "/usr/share"   -> "e:/usr/share"
     "."            -> NULL             (not an absolute path)
     "usr/share"    -> NULL             (not an absolute path)
     "c:usr/share"  -> NULL             (not an absolute path)
     "c:/usr/share;d:/etc" -> "e:/usr/share" (only the first directory in path is used) */
char*
_os2_unixroot_gettext(const char *path)
{
  static const char *unixroot = NULL;
  static int unixroot_init = 0;
  char *result = NULL;

  if (unixroot_init == 0) {
    /* get $UNIXROOT only one time */
    unixroot = getenv("UNIXROOT");

    /* check whether unixroot is valid (must be "x:") */
    if (unixroot != NULL) {
      int drive = toupper(unixroot[0]);
      if (drive < 'A' || drive > 'Z' || unixroot[1] != ':' || unixroot[2] != '\0')
        unixroot = NULL; /* unixroot not valid */
    }

    unixroot_init = 1; /* initialized */
  }

  /* note: if unixroot != NULL then it contains a valid drive letter */
  if (unixroot != NULL && _os2_is_abs_path(path)) {
    /* dirname is an absolute path and unixroot is a drive letter, "c:" for example */
    size_t old_path_len = strlen(path);

    /* end points to the first ';' in path or to NULL */
    const char *end = strchr(path, PATH_SEPARATOR);

    /* dir_len is the length of the first directory in path */
    size_t dir_len = (end) ? end - path : old_path_len;

    if (toupper(unixroot[0]) != toupper(path[0]) || path[1] != ':') {
      /* the first directory of path does not start with the string $UNIXROOT */
      if (path[1] == ':') {
        /* if there is a drive letter remove it */
        dir_len -= 2;
        path += 2;
      }

      result = malloc(dir_len + 3);
      if (result) { /* do nothing if we are out of memory */
        result[0] = unixroot[0];
        result[1] = unixroot[1];
        memcpy(result + 2, path, dir_len);
        result[dir_len + 2] = '\0';
      }
    }
  }
  return result;
}

/* path is assumed to be a list of directories separated by PATH_SEPARATOR.
   Every directory is processed. _os2_unixroot_gettext() is used to find out whether
   these directories are on the drive specified by the environment variable
   UNIXROOT. If this is not the case the same directory on the UNIXROOT drive
   is added to the end of path. If path is a valid path this function returns a valid path, too.
   Example ($UNIXROOT is set to "e:"):
   ".;c:/usr/local;d:/usr/local;d:/etc;e:/etc"
   -> ".;c:/usr/local;d:/usr/local;d:/etc;e:/etc;e:/usr/local;e:/usr/local;e:/etc" */
static const char*
_os2_unixroot_path(const char *path)
{
  char *result = NULL;
  const char *p = path;
  unsigned dir_count = 1;

  if (path == NULL || path[0] == '\0') return NULL; /* empty path */

  /* save number of path components in dir_count */
  while(*p) {
    if (*p++ == PATH_SEPARATOR && *p != '\0' && *p != PATH_SEPARATOR)
      dir_count += 1;
  }

  {
    const char *list[dir_count]; /* list of char pointers */
    size_t dir_len[dir_count]; /* the according directory length */
    size_t old_path_len = strlen(path); /* the old path length */
    size_t total_len;
    unsigned i = 0;

    if (path[old_path_len - 1] == PATH_SEPARATOR) /* last character is ';' */
      old_path_len--;

    list[0] = p = path; /* first directory */

    while(*p) {
    if (*p++ == PATH_SEPARATOR && *p != '\0' && *p != PATH_SEPARATOR)
      list[++i] = p;
    }
    /* now list[i] contains the ith directory of path (no 0-terminated strings!!!) */

    /* determine the total length for the new path */
    total_len = old_path_len;

    for(i = 0; i < dir_count; i++) {
      list[i] = _os2_unixroot_gettext(list[i]);
      if (list[i] != NULL) {
        dir_len[i] = strlen(list[i]);
        total_len += dir_len[i] + 1; /* one character for ';' or '\0' */
      }
      else dir_len[i] = 0;
    }
    /* now list[] contains the according directories on the UNIXROOT drive or NULL
       total_len contains the total length for the new path */
    result = malloc(total_len + 1);

    if (result) {
      /* copy the old path and the new directories into the new path */
      char *q = result;
      memcpy(q, path, old_path_len);
      q += old_path_len;

      for(i = 0; i < dir_count; i++) {
        if (dir_len[i] != 0) {
          *q++ = PATH_SEPARATOR;
          memcpy(q, list[i], dir_len[i]);
          q += dir_len[i];
        }
      }

      *q = '\0'; /* terminating '\0' */
    }

    for(i = 0; i++; i < dir_count) free((void*) list[i]);
  }

  return (result) ? (const char*) result : path;
}


#endif /* __EMX__ */


const char *
_nl_expand_alias (name)
    const char *name;
{
#ifndef __EMX__
  static const char *locale_alias_path = LOCALE_ALIAS_PATH;
#else
  static const char *locale_alias_path = NULL;
#endif
  struct alias_map *retval;
  const char *result = NULL;
  size_t added;

#ifdef __EMX__
  if (locale_alias_path == NULL) locale_alias_path = _os2_unixroot_path(LOCALE_ALIAS_PATH);
#endif

#ifdef _LIBC
  __libc_lock_lock (lock);
#endif

  do
    {
      struct alias_map item;

      item.alias = name;

      if (nmap > 0)
	retval = (struct alias_map *) bsearch (&item, map, nmap,
					       sizeof (struct alias_map),
					       (int (*) PARAMS ((const void *,
								 const void *))
						) alias_compare);
      else
	retval = NULL;

      /* We really found an alias.  Return the value.  */
      if (retval != NULL)
	{
	  result = retval->value;
	  break;
	}

      /* Perhaps we can find another alias file.  */
      added = 0;
      while (added == 0 && locale_alias_path[0] != '\0')
	{
	  const char *start;

	  while (locale_alias_path[0] == PATH_SEPARATOR)
	    ++locale_alias_path;
	  start = locale_alias_path;

	  while (locale_alias_path[0] != '\0'
		 && locale_alias_path[0] != PATH_SEPARATOR)
	    ++locale_alias_path;

	  if (start < locale_alias_path)
	    added = read_alias_file (start, locale_alias_path - start);
	}
    }
  while (added != 0);

#ifdef _LIBC
  __libc_lock_unlock (lock);
#endif

  return result;
}


static size_t
internal_function
read_alias_file (fname, fname_len)
     const char *fname;
     int fname_len;
{
  FILE *fp;
  char *full_fname;
  size_t added;
  static const char aliasfile[] = "/locale.alias";

  full_fname = (char *) alloca (fname_len + sizeof aliasfile);
#ifdef HAVE_MEMPCPY
  mempcpy (mempcpy (full_fname, fname, fname_len),
	   aliasfile, sizeof aliasfile);
#else
  memcpy (full_fname, fname, fname_len);
  memcpy (&full_fname[fname_len], aliasfile, sizeof aliasfile);
#endif

  fp = fopen (full_fname, "r");

  freea (full_fname);
  if (fp == NULL)
    return 0;

  added = 0;
  while (!feof (fp))
    {
      /* It is a reasonable approach to use a fix buffer here because
	 a) we are only interested in the first two fields
	 b) these fields must be usable as file names and so must not
	    be that long
       */
      char buf[BUFSIZ];
      char *alias;
      char *value;
      char *cp;

      if (fgets (buf, sizeof buf, fp) == NULL)
	/* EOF reached.  */
	break;

      /* Possibly not the whole line fits into the buffer.  Ignore
	 the rest of the line.  */
      if (strchr (buf, '\n') == NULL)
	{
	  char altbuf[BUFSIZ];
	  do
	    if (fgets (altbuf, sizeof altbuf, fp) == NULL)
	      /* Make sure the inner loop will be left.  The outer loop
		 will exit at the `feof' test.  */
	      break;
	  while (strchr (altbuf, '\n') == NULL);
	}

      cp = buf;
      /* Ignore leading white space.  */
      while (isspace (cp[0]))
	++cp;

      /* A leading '#' signals a comment line.  */
      if (cp[0] != '\0' && cp[0] != '#')
	{
	  alias = cp++;
	  while (cp[0] != '\0' && !isspace (cp[0]))
	    ++cp;
	  /* Terminate alias name.  */
	  if (cp[0] != '\0')
	    *cp++ = '\0';

	  /* Now look for the beginning of the value.  */
	  while (isspace (cp[0]))
	    ++cp;

	  if (cp[0] != '\0')
	    {
	      size_t alias_len;
	      size_t value_len;

	      value = cp++;
	      while (cp[0] != '\0' && !isspace (cp[0]))
		++cp;
	      /* Terminate value.  */
	      if (cp[0] == '\n')
		{
		  /* This has to be done to make the following test
		     for the end of line possible.  We are looking for
		     the terminating '\n' which do not overwrite here.  */
		  *cp++ = '\0';
		  *cp = '\n';
		}
	      else if (cp[0] != '\0')
		*cp++ = '\0';

	      if (nmap >= maxmap)
		if (__builtin_expect (extend_alias_table (), 0))
		  return added;

	      alias_len = strlen (alias) + 1;
	      value_len = strlen (value) + 1;

	      if (string_space_act + alias_len + value_len > string_space_max)
		{
		  /* Increase size of memory pool.  */
		  size_t new_size = (string_space_max
				     + (alias_len + value_len > 1024
					? alias_len + value_len : 1024));
		  char *new_pool = (char *) realloc (string_space, new_size);
		  if (new_pool == NULL)
		    return added;

		  if (__builtin_expect (string_space != new_pool, 0))
		    {
		      size_t i;

		      for (i = 0; i < nmap; i++)
			{
			  map[i].alias += new_pool - string_space;
			  map[i].value += new_pool - string_space;
			}
		    }

		  string_space = new_pool;
		  string_space_max = new_size;
		}

	      map[nmap].alias = memcpy (&string_space[string_space_act],
					alias, alias_len);
	      string_space_act += alias_len;

	      map[nmap].value = memcpy (&string_space[string_space_act],
					value, value_len);
	      string_space_act += value_len;

	      ++nmap;
	      ++added;
	    }
	}
    }

  /* Should we test for ferror()?  I think we have to silently ignore
     errors.  --drepper  */
  fclose (fp);

  if (added > 0)
    qsort (map, nmap, sizeof (struct alias_map),
	   (int (*) PARAMS ((const void *, const void *))) alias_compare);

  return added;
}


static int
extend_alias_table ()
{
  size_t new_size;
  struct alias_map *new_map;

  new_size = maxmap == 0 ? 100 : 2 * maxmap;
  new_map = (struct alias_map *) realloc (map, (new_size
						* sizeof (struct alias_map)));
  if (new_map == NULL)
    /* Simply don't extend: we don't have any more core.  */
    return -1;

  map = new_map;
  maxmap = new_size;
  return 0;
}


#ifdef _LIBC
static void __attribute__ ((unused))
free_mem (void)
{
  if (string_space != NULL)
    free (string_space);
  if (map != NULL)
    free (map);
}
text_set_element (__libc_subfreeres, free_mem);
#endif


static int
alias_compare (map1, map2)
     const struct alias_map *map1;
     const struct alias_map *map2;
{
#if defined _LIBC || defined HAVE_STRCASECMP
  return strcasecmp (map1->alias, map2->alias);
#else
  const unsigned char *p1 = (const unsigned char *) map1->alias;
  const unsigned char *p2 = (const unsigned char *) map2->alias;
  unsigned char c1, c2;

  if (p1 == p2)
    return 0;

  do
    {
      /* I know this seems to be odd but the tolower() function in
	 some systems libc cannot handle nonalpha characters.  */
      c1 = isupper (*p1) ? tolower (*p1) : *p1;
      c2 = isupper (*p2) ? tolower (*p2) : *p2;
      if (c1 == '\0')
	break;
      ++p1;
      ++p2;
    }
  while (c1 == c2);

  return c1 - c2;
#endif
}
