| /* Implementation of the dcgettext(3) function. | 
 |    Copyright (C) 1995, 1996, 1997, 1998 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.  */ | 
 |  | 
 | #ifdef HAVE_CONFIG_H | 
 | # include <config.h> | 
 | #endif | 
 |  | 
 | #include <sys/types.h> | 
 |  | 
 | #if defined __GNUC__ && !defined C_ALLOCA | 
 | # define alloca __builtin_alloca | 
 | # define HAVE_ALLOCA 1 | 
 | #else | 
 | # if (defined HAVE_ALLOCA_H || defined _LIBC) && !defined C_ALLOCA | 
 | #  include <alloca.h> | 
 | # else | 
 | #  ifdef _AIX | 
 |  #pragma alloca | 
 | #  else | 
 | #   ifndef alloca | 
 | char *alloca (); | 
 | #   endif | 
 | #  endif | 
 | # endif | 
 | #endif | 
 |  | 
 | #include <errno.h> | 
 | #ifndef errno | 
 | extern int errno; | 
 | #endif | 
 | #ifndef __set_errno | 
 | # define __set_errno(val) errno = (val) | 
 | #endif | 
 |  | 
 | #if defined STDC_HEADERS || defined _LIBC | 
 | # include <stdlib.h> | 
 | #else | 
 | char *getenv (); | 
 | # ifdef HAVE_MALLOC_H | 
 | #  include <malloc.h> | 
 | # else | 
 | void free (); | 
 | # endif | 
 | #endif | 
 |  | 
 | #if defined HAVE_STRING_H || defined _LIBC | 
 | # ifndef _GNU_SOURCE | 
 | #  define _GNU_SOURCE	1 | 
 | # endif | 
 | # include <string.h> | 
 | #else | 
 | # include <strings.h> | 
 | #endif | 
 | #if !HAVE_STRCHR && !defined _LIBC | 
 | # ifndef strchr | 
 | #  define strchr index | 
 | # endif | 
 | #endif | 
 |  | 
 | #if defined HAVE_UNISTD_H || defined _LIBC | 
 | # include <unistd.h> | 
 | #endif | 
 |  | 
 | #include "gettext.h" | 
 | #include "gettextP.h" | 
 | #ifdef _LIBC | 
 | # include <libintl.h> | 
 | #else | 
 | # include "libgettext.h" | 
 | #endif | 
 | #include "hash-string.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 getcwd __getcwd | 
 | # ifndef stpcpy | 
 | #  define stpcpy __stpcpy | 
 | # endif | 
 | #else | 
 | # if !defined HAVE_GETCWD | 
 | char *getwd (); | 
 | #  define getcwd(buf, max) getwd (buf) | 
 | # else | 
 | char *getcwd (); | 
 | # endif | 
 | # ifndef HAVE_STPCPY | 
 | static char *stpcpy PARAMS ((char *dest, const char *src)); | 
 | # endif | 
 | #endif | 
 |  | 
 | /* Amount to increase buffer size by in each try.  */ | 
 | #define PATH_INCR 32 | 
 |  | 
 | /* The following is from pathmax.h.  */ | 
 | /* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define | 
 |    PATH_MAX but might cause redefinition warnings when sys/param.h is | 
 |    later included (as on MORE/BSD 4.3).  */ | 
 | #if defined(_POSIX_VERSION) || (defined(HAVE_LIMITS_H) && !defined(__GNUC__)) | 
 | # include <limits.h> | 
 | #endif | 
 |  | 
 | #ifndef _POSIX_PATH_MAX | 
 | # define _POSIX_PATH_MAX 255 | 
 | #endif | 
 |  | 
 | #if !defined(PATH_MAX) && defined(_PC_PATH_MAX) | 
 | # define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX)) | 
 | #endif | 
 |  | 
 | /* Don't include sys/param.h if it already has been.  */ | 
 | #if defined(HAVE_SYS_PARAM_H) && !defined(PATH_MAX) && !defined(MAXPATHLEN) | 
 | # include <sys/param.h> | 
 | #endif | 
 |  | 
 | #if !defined(PATH_MAX) && defined(MAXPATHLEN) | 
 | # define PATH_MAX MAXPATHLEN | 
 | #endif | 
 |  | 
 | #ifndef PATH_MAX | 
 | # define PATH_MAX _POSIX_PATH_MAX | 
 | #endif | 
 |  | 
 | /* XPG3 defines the result of `setlocale (category, NULL)' as: | 
 |    ``Directs `setlocale()' to query `category' and return the current | 
 |      setting of `local'.'' | 
 |    However it does not specify the exact format.  And even worse: POSIX | 
 |    defines this not at all.  So we can use this feature only on selected | 
 |    system (e.g. those using GNU C Library).  */ | 
 | #ifdef _LIBC | 
 | # define HAVE_LOCALE_NULL | 
 | #endif | 
 |  | 
 | /* Name of the default domain used for gettext(3) prior any call to | 
 |    textdomain(3).  The default value for this is "messages".  */ | 
 | const char _nl_default_default_domain[] = "messages"; | 
 |  | 
 | /* Value used as the default domain for gettext(3).  */ | 
 | const char *_nl_current_default_domain = _nl_default_default_domain; | 
 |  | 
 | /* Contains the default location of the message catalogs.  */ | 
 | const char _nl_default_dirname[] = GNULOCALEDIR; | 
 |  | 
 | /* List with bindings of specific domains created by bindtextdomain() | 
 |    calls.  */ | 
 | struct binding *_nl_domain_bindings; | 
 |  | 
 | /* Prototypes for local functions.  */ | 
 | static char *find_msg PARAMS ((struct loaded_l10nfile *domain_file, | 
 | 			       const char *msgid)) internal_function; | 
 | static const char *category_to_name PARAMS ((int category)) internal_function; | 
 | static const char *guess_category_value PARAMS ((int category, | 
 | 						 const char *categoryname)) | 
 |      internal_function; | 
 |  | 
 |  | 
 | /* For those loosing systems which don't have `alloca' we have to add | 
 |    some additional code emulating it.  */ | 
 | #ifdef HAVE_ALLOCA | 
 | /* Nothing has to be done.  */ | 
 | # define ADD_BLOCK(list, address) /* nothing */ | 
 | # define FREE_BLOCKS(list) /* nothing */ | 
 | #else | 
 | struct block_list | 
 | { | 
 |   void *address; | 
 |   struct block_list *next; | 
 | }; | 
 | # define ADD_BLOCK(list, addr)						      \ | 
 |   do {									      \ | 
 |     struct block_list *newp = (struct block_list *) malloc (sizeof (*newp));  \ | 
 |     /* If we cannot get a free block we cannot add the new element to	      \ | 
 |        the list.  */							      \ | 
 |     if (newp != NULL) {							      \ | 
 |       newp->address = (addr);						      \ | 
 |       newp->next = (list);						      \ | 
 |       (list) = newp;							      \ | 
 |     }									      \ | 
 |   } while (0) | 
 | # define FREE_BLOCKS(list)						      \ | 
 |   do {									      \ | 
 |     while (list != NULL) {						      \ | 
 |       struct block_list *old = list;					      \ | 
 |       list = list->next;						      \ | 
 |       free (old);							      \ | 
 |     }									      \ | 
 |   } while (0) | 
 | # undef alloca | 
 | # define alloca(size) (malloc (size)) | 
 | #endif	/* have alloca */ | 
 |  | 
 |  | 
 | /* Names for the libintl functions are a problem.  They must not clash | 
 |    with existing names and they should follow ANSI C.  But this source | 
 |    code is also used in GNU C Library where the names have a __ | 
 |    prefix.  So we have to make a difference here.  */ | 
 | #ifdef _LIBC | 
 | # define DCGETTEXT __dcgettext | 
 | #else | 
 | # define DCGETTEXT dcgettext__ | 
 | #endif | 
 |  | 
 | /* Checking whether the binaries runs SUID must be done and glibc provides | 
 |    easier methods therefore we make a difference here.  */ | 
 | #ifdef _LIBC | 
 | # define ENABLE_SECURE __libc_enable_secure | 
 | # define DETERMINE_SECURE | 
 | #else | 
 | static int enable_secure; | 
 | # define ENABLE_SECURE (enable_secure == 1) | 
 | # define DETERMINE_SECURE \ | 
 |   if (enable_secure == 0)						      \ | 
 |     {									      \ | 
 |       if (getuid () != geteuid () || getgid () != getegid ())		      \ | 
 | 	enable_secure = 1;						      \ | 
 |       else								      \ | 
 | 	enable_secure = -1;						      \ | 
 |     } | 
 | #endif | 
 |  | 
 | /* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY | 
 |    locale.  */ | 
 | char * | 
 | DCGETTEXT (domainname, msgid, category) | 
 |      const char *domainname; | 
 |      const char *msgid; | 
 |      int category; | 
 | { | 
 | #ifndef HAVE_ALLOCA | 
 |   struct block_list *block_list = NULL; | 
 | #endif | 
 |   struct loaded_l10nfile *domain; | 
 |   struct binding *binding; | 
 |   const char *categoryname; | 
 |   const char *categoryvalue; | 
 |   char *dirname, *xdomainname; | 
 |   char *single_locale; | 
 |   char *retval; | 
 |   int saved_errno = errno; | 
 |  | 
 |   /* If no real MSGID is given return NULL.  */ | 
 |   if (msgid == NULL) | 
 |     return NULL; | 
 |  | 
 |   /* See whether this is a SUID binary or not.  */ | 
 |   DETERMINE_SECURE; | 
 |  | 
 |   /* If DOMAINNAME is NULL, we are interested in the default domain.  If | 
 |      CATEGORY is not LC_MESSAGES this might not make much sense but the | 
 |      definition left this undefined.  */ | 
 |   if (domainname == NULL) | 
 |     domainname = _nl_current_default_domain; | 
 |  | 
 |   /* First find matching binding.  */ | 
 |   for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next) | 
 |     { | 
 |       int compare = strcmp (domainname, binding->domainname); | 
 |       if (compare == 0) | 
 | 	/* We found it!  */ | 
 | 	break; | 
 |       if (compare < 0) | 
 | 	{ | 
 | 	  /* It is not in the list.  */ | 
 | 	  binding = NULL; | 
 | 	  break; | 
 | 	} | 
 |     } | 
 |  | 
 |   if (binding == NULL) | 
 |     dirname = (char *) _nl_default_dirname; | 
 |   else if (binding->dirname[0] == '/') | 
 |     dirname = binding->dirname; | 
 |   else | 
 |     { | 
 |       /* We have a relative path.  Make it absolute now.  */ | 
 |       size_t dirname_len = strlen (binding->dirname) + 1; | 
 |       size_t path_max; | 
 |       char *ret; | 
 |  | 
 |       path_max = (unsigned int) PATH_MAX; | 
 |       path_max += 2;		/* The getcwd docs say to do this.  */ | 
 |  | 
 |       dirname = (char *) alloca (path_max + dirname_len); | 
 |       ADD_BLOCK (block_list, dirname); | 
 |  | 
 |       __set_errno (0); | 
 |       while ((ret = getcwd (dirname, path_max)) == NULL && errno == ERANGE) | 
 | 	{ | 
 | 	  path_max += PATH_INCR; | 
 | 	  dirname = (char *) alloca (path_max + dirname_len); | 
 | 	  ADD_BLOCK (block_list, dirname); | 
 | 	  __set_errno (0); | 
 | 	} | 
 |  | 
 |       if (ret == NULL) | 
 | 	{ | 
 | 	  /* We cannot get the current working directory.  Don't signal an | 
 | 	     error but simply return the default string.  */ | 
 | 	  FREE_BLOCKS (block_list); | 
 | 	  __set_errno (saved_errno); | 
 | 	  return (char *) msgid; | 
 | 	} | 
 |  | 
 |       stpcpy (stpcpy (strchr (dirname, '\0'), "/"), binding->dirname); | 
 |     } | 
 |  | 
 |   /* Now determine the symbolic name of CATEGORY and its value.  */ | 
 |   categoryname = category_to_name (category); | 
 |   categoryvalue = guess_category_value (category, categoryname); | 
 |  | 
 |   xdomainname = (char *) alloca (strlen (categoryname) | 
 | 				 + strlen (domainname) + 5); | 
 |   ADD_BLOCK (block_list, xdomainname); | 
 |  | 
 |   stpcpy (stpcpy (stpcpy (stpcpy (xdomainname, categoryname), "/"), | 
 | 		  domainname), | 
 | 	  ".mo"); | 
 |  | 
 |   /* Creating working area.  */ | 
 |   single_locale = (char *) alloca (strlen (categoryvalue) + 1); | 
 |   ADD_BLOCK (block_list, single_locale); | 
 |  | 
 |  | 
 |   /* Search for the given string.  This is a loop because we perhaps | 
 |      got an ordered list of languages to consider for the translation.  */ | 
 |   while (1) | 
 |     { | 
 |       /* Make CATEGORYVALUE point to the next element of the list.  */ | 
 |       while (categoryvalue[0] != '\0' && categoryvalue[0] == ':') | 
 | 	++categoryvalue; | 
 |       if (categoryvalue[0] == '\0') | 
 | 	{ | 
 | 	  /* The whole contents of CATEGORYVALUE has been searched but | 
 | 	     no valid entry has been found.  We solve this situation | 
 | 	     by implicitly appending a "C" entry, i.e. no translation | 
 | 	     will take place.  */ | 
 | 	  single_locale[0] = 'C'; | 
 | 	  single_locale[1] = '\0'; | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  char *cp = single_locale; | 
 | 	  while (categoryvalue[0] != '\0' && categoryvalue[0] != ':') | 
 | 	    *cp++ = *categoryvalue++; | 
 | 	  *cp = '\0'; | 
 |  | 
 | 	  /* When this is a SUID binary we must not allow accessing files | 
 | 	     outside the dedicated directories.  */ | 
 | 	  if (ENABLE_SECURE | 
 | 	      && (memchr (single_locale, '/', | 
 | 			  _nl_find_language (single_locale) - single_locale) | 
 | 		  != NULL)) | 
 | 	    /* Ingore this entry.  */ | 
 | 	    continue; | 
 | 	} | 
 |  | 
 |       /* If the current locale value is C (or POSIX) we don't load a | 
 | 	 domain.  Return the MSGID.  */ | 
 |       if (strcmp (single_locale, "C") == 0 | 
 | 	  || strcmp (single_locale, "POSIX") == 0) | 
 | 	{ | 
 | 	  FREE_BLOCKS (block_list); | 
 | 	  __set_errno (saved_errno); | 
 | 	  return (char *) msgid; | 
 | 	} | 
 |  | 
 |  | 
 |       /* Find structure describing the message catalog matching the | 
 | 	 DOMAINNAME and CATEGORY.  */ | 
 |       domain = _nl_find_domain (dirname, single_locale, xdomainname); | 
 |  | 
 |       if (domain != NULL) | 
 | 	{ | 
 | 	  retval = find_msg (domain, msgid); | 
 |  | 
 | 	  if (retval == NULL) | 
 | 	    { | 
 | 	      int cnt; | 
 |  | 
 | 	      for (cnt = 0; domain->successor[cnt] != NULL; ++cnt) | 
 | 		{ | 
 | 		  retval = find_msg (domain->successor[cnt], msgid); | 
 |  | 
 | 		  if (retval != NULL) | 
 | 		    break; | 
 | 		} | 
 | 	    } | 
 |  | 
 | 	  if (retval != NULL) | 
 | 	    { | 
 | 	      FREE_BLOCKS (block_list); | 
 | 	      __set_errno (saved_errno); | 
 | 	      return retval; | 
 | 	    } | 
 | 	} | 
 |     } | 
 |   /* NOTREACHED */ | 
 | } | 
 |  | 
 | #ifdef _LIBC | 
 | /* Alias for function name in GNU C Library.  */ | 
 | weak_alias (__dcgettext, dcgettext); | 
 | #endif | 
 |  | 
 |  | 
 | static char * | 
 | internal_function | 
 | find_msg (domain_file, msgid) | 
 |      struct loaded_l10nfile *domain_file; | 
 |      const char *msgid; | 
 | { | 
 |   size_t act = 0; | 
 |   size_t top, bottom; | 
 |   struct loaded_domain *domain; | 
 |  | 
 |   if (domain_file->decided == 0) | 
 |     _nl_load_domain (domain_file); | 
 |  | 
 |   if (domain_file->data == NULL) | 
 |     return NULL; | 
 |  | 
 |   domain = (struct loaded_domain *) domain_file->data; | 
 |  | 
 |   /* Locate the MSGID and its translation.  */ | 
 |   if (domain->hash_size > 2 && domain->hash_tab != NULL) | 
 |     { | 
 |       /* Use the hashing table.  */ | 
 |       nls_uint32 len = strlen (msgid); | 
 |       nls_uint32 hash_val = hash_string (msgid); | 
 |       nls_uint32 idx = hash_val % domain->hash_size; | 
 |       nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2)); | 
 |       nls_uint32 nstr = W (domain->must_swap, domain->hash_tab[idx]); | 
 |  | 
 |       if (nstr == 0) | 
 | 	/* Hash table entry is empty.  */ | 
 | 	return NULL; | 
 |  | 
 |       if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len | 
 | 	  && strcmp (msgid, | 
 | 		     domain->data + W (domain->must_swap, | 
 | 				       domain->orig_tab[nstr - 1].offset)) == 0) | 
 | 	return (char *) domain->data + W (domain->must_swap, | 
 | 					  domain->trans_tab[nstr - 1].offset); | 
 |  | 
 |       while (1) | 
 | 	{ | 
 | 	  if (idx >= domain->hash_size - incr) | 
 | 	    idx -= domain->hash_size - incr; | 
 | 	  else | 
 | 	    idx += incr; | 
 |  | 
 | 	  nstr = W (domain->must_swap, domain->hash_tab[idx]); | 
 | 	  if (nstr == 0) | 
 | 	    /* Hash table entry is empty.  */ | 
 | 	    return NULL; | 
 |  | 
 | 	  if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len | 
 | 	      && strcmp (msgid, | 
 | 			 domain->data + W (domain->must_swap, | 
 | 					   domain->orig_tab[nstr - 1].offset)) | 
 | 	         == 0) | 
 | 	    return (char *) domain->data | 
 | 	      + W (domain->must_swap, domain->trans_tab[nstr - 1].offset); | 
 | 	} | 
 |       /* NOTREACHED */ | 
 |     } | 
 |  | 
 |   /* Now we try the default method:  binary search in the sorted | 
 |      array of messages.  */ | 
 |   bottom = 0; | 
 |   top = domain->nstrings; | 
 |   while (bottom < top) | 
 |     { | 
 |       int cmp_val; | 
 |  | 
 |       act = (bottom + top) / 2; | 
 |       cmp_val = strcmp (msgid, domain->data | 
 | 			       + W (domain->must_swap, | 
 | 				    domain->orig_tab[act].offset)); | 
 |       if (cmp_val < 0) | 
 | 	top = act; | 
 |       else if (cmp_val > 0) | 
 | 	bottom = act + 1; | 
 |       else | 
 | 	break; | 
 |     } | 
 |  | 
 |   /* If an translation is found return this.  */ | 
 |   return bottom >= top ? NULL : (char *) domain->data | 
 |                                 + W (domain->must_swap, | 
 | 				     domain->trans_tab[act].offset); | 
 | } | 
 |  | 
 |  | 
 | /* Return string representation of locale CATEGORY.  */ | 
 | static const char * | 
 | internal_function | 
 | category_to_name (category) | 
 |      int category; | 
 | { | 
 |   const char *retval; | 
 |  | 
 |   switch (category) | 
 |   { | 
 | #ifdef LC_COLLATE | 
 |   case LC_COLLATE: | 
 |     retval = "LC_COLLATE"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_CTYPE | 
 |   case LC_CTYPE: | 
 |     retval = "LC_CTYPE"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_MONETARY | 
 |   case LC_MONETARY: | 
 |     retval = "LC_MONETARY"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_NUMERIC | 
 |   case LC_NUMERIC: | 
 |     retval = "LC_NUMERIC"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_TIME | 
 |   case LC_TIME: | 
 |     retval = "LC_TIME"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_MESSAGES | 
 |   case LC_MESSAGES: | 
 |     retval = "LC_MESSAGES"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_RESPONSE | 
 |   case LC_RESPONSE: | 
 |     retval = "LC_RESPONSE"; | 
 |     break; | 
 | #endif | 
 | #ifdef LC_ALL | 
 |   case LC_ALL: | 
 |     /* This might not make sense but is perhaps better than any other | 
 |        value.  */ | 
 |     retval = "LC_ALL"; | 
 |     break; | 
 | #endif | 
 |   default: | 
 |     /* If you have a better idea for a default value let me know.  */ | 
 |     retval = "LC_XXX"; | 
 |   } | 
 |  | 
 |   return retval; | 
 | } | 
 |  | 
 | /* Guess value of current locale from value of the environment variables.  */ | 
 | static const char * | 
 | internal_function | 
 | guess_category_value (category, categoryname) | 
 |      int category; | 
 |      const char *categoryname; | 
 | { | 
 |   const char *retval; | 
 |  | 
 |   /* The highest priority value is the `LANGUAGE' environment | 
 |      variable.  This is a GNU extension.  */ | 
 |   retval = getenv ("LANGUAGE"); | 
 |   if (retval != NULL && retval[0] != '\0') | 
 |     return retval; | 
 |  | 
 |   /* `LANGUAGE' is not set.  So we have to proceed with the POSIX | 
 |      methods of looking to `LC_ALL', `LC_xxx', and `LANG'.  On some | 
 |      systems this can be done by the `setlocale' function itself.  */ | 
 | #if defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL | 
 |   return setlocale (category, NULL); | 
 | #else | 
 |   /* Setting of LC_ALL overwrites all other.  */ | 
 |   retval = getenv ("LC_ALL"); | 
 |   if (retval != NULL && retval[0] != '\0') | 
 |     return retval; | 
 |  | 
 |   /* Next comes the name of the desired category.  */ | 
 |   retval = getenv (categoryname); | 
 |   if (retval != NULL && retval[0] != '\0') | 
 |     return retval; | 
 |  | 
 |   /* Last possibility is the LANG environment variable.  */ | 
 |   retval = getenv ("LANG"); | 
 |   if (retval != NULL && retval[0] != '\0') | 
 |     return retval; | 
 |  | 
 |   /* We use C as the default domain.  POSIX says this is implementation | 
 |      defined.  */ | 
 |   return "C"; | 
 | #endif | 
 | } | 
 |  | 
 | /* @@ begin of epilog @@ */ | 
 |  | 
 | /* We don't want libintl.a to depend on any other library.  So we | 
 |    avoid the non-standard function stpcpy.  In GNU C Library this | 
 |    function is available, though.  Also allow the symbol HAVE_STPCPY | 
 |    to be defined.  */ | 
 | #if !_LIBC && !HAVE_STPCPY | 
 | static char * | 
 | stpcpy (dest, src) | 
 |      char *dest; | 
 |      const char *src; | 
 | { | 
 |   while ((*dest++ = *src++) != '\0') | 
 |     /* Do nothing. */ ; | 
 |   return dest - 1; | 
 | } | 
 | #endif | 
 |  | 
 |  | 
 | #ifdef _LIBC | 
 | /* If we want to free all resources we have to do some work at | 
 |    program's end.  */ | 
 | static void __attribute__ ((unused)) | 
 | free_mem (void) | 
 | { | 
 |   struct binding *runp; | 
 |  | 
 |   for (runp = _nl_domain_bindings; runp != NULL; runp = runp->next) | 
 |     { | 
 |       free (runp->domainname); | 
 |       if (runp->dirname != _nl_default_dirname) | 
 | 	/* Yes, this is a pointer comparison.  */ | 
 | 	free (runp->dirname); | 
 |     } | 
 |  | 
 |   if (_nl_current_default_domain != _nl_default_default_domain) | 
 |     /* Yes, again a pointer comparison.  */ | 
 |     free ((char *) _nl_current_default_domain); | 
 | } | 
 |  | 
 | text_set_element (__libc_subfreeres, free_mem); | 
 | #endif |