root/intl/dcigettext.c

Revision 4343:2bccf51eedac, 33.2 kB (checked in by Brendan Cully <brendan@…>, 3 years ago)

Gah, forgot the zip code when updating the FSF address...

Line 
1/* Implementation of the internal dcigettext function.
2   Copyright (C) 1995-1999, 2000, 2001 Free Software Foundation, Inc.
3
4   This program is free software; you can redistribute it and/or modify it
5   under the terms of the GNU Library General Public License as published
6   by the Free Software Foundation; either version 2, or (at your option)
7   any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   Library General Public License for more details.
13
14   You should have received a copy of the GNU Library General Public
15   License along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
17   USA.  */
18
19/* Tell glibc's <string.h> to provide a prototype for mempcpy().
20   This must come before <config.h> because <config.h> may include
21   <features.h>, and once <features.h> has been included, it's too late.  */
22#ifndef _GNU_SOURCE
23# define _GNU_SOURCE    1
24#endif
25
26#ifdef HAVE_CONFIG_H
27# include <config.h>
28#endif
29
30#include <sys/types.h>
31
32#ifdef __GNUC__
33# define alloca __builtin_alloca
34# define HAVE_ALLOCA 1
35#else
36# if defined HAVE_ALLOCA_H || defined _LIBC
37#  include <alloca.h>
38# else
39#  ifdef _AIX
40 #pragma alloca
41#  else
42#   ifndef alloca
43char *alloca ();
44#   endif
45#  endif
46# endif
47#endif
48
49#include <errno.h>
50#ifndef errno
51extern int errno;
52#endif
53#ifndef __set_errno
54# define __set_errno(val) errno = (val)
55#endif
56
57#include <stddef.h>
58#include <stdlib.h>
59
60#include <string.h>
61#if !HAVE_STRCHR && !defined _LIBC
62# ifndef strchr
63#  define strchr index
64# endif
65#endif
66
67#if defined HAVE_UNISTD_H || defined _LIBC
68# include <unistd.h>
69#endif
70
71#include <locale.h>
72
73#if defined HAVE_SYS_PARAM_H || defined _LIBC
74# include <sys/param.h>
75#endif
76
77#include "gettextP.h"
78#ifdef _LIBC
79# include <libintl.h>
80#else
81# include "libgnuintl.h"
82#endif
83#include "hash-string.h"
84
85/* Thread safetyness.  */
86#ifdef _LIBC
87# include <bits/libc-lock.h>
88#else
89/* Provide dummy implementation if this is outside glibc.  */
90# define __libc_lock_define_initialized(CLASS, NAME)
91# define __libc_lock_lock(NAME)
92# define __libc_lock_unlock(NAME)
93# define __libc_rwlock_define_initialized(CLASS, NAME)
94# define __libc_rwlock_rdlock(NAME)
95# define __libc_rwlock_unlock(NAME)
96#endif
97
98/* Alignment of types.  */
99#if defined __GNUC__ && __GNUC__ >= 2
100# define alignof(TYPE) __alignof__ (TYPE)
101#else
102# define alignof(TYPE) \
103    ((int) &((struct { char dummy1; TYPE dummy2; } *) 0)->dummy2)
104#endif
105
106/* The internal variables in the standalone libintl.a must have different
107   names than the internal variables in GNU libc, otherwise programs
108   using libintl.a cannot be linked statically.  */
109#if !defined _LIBC
110# define _nl_default_default_domain _nl_default_default_domain__
111# define _nl_current_default_domain _nl_current_default_domain__
112# define _nl_default_dirname _nl_default_dirname__
113# define _nl_domain_bindings _nl_domain_bindings__
114#endif
115
116/* Some compilers, like SunOS4 cc, don't have offsetof in <stddef.h>.  */
117#ifndef offsetof
118# define offsetof(type,ident) ((size_t)&(((type*)0)->ident))
119#endif
120
121/* @@ end of prolog @@ */
122
123#ifdef _LIBC
124/* Rename the non ANSI C functions.  This is required by the standard
125   because some ANSI C functions will require linking with this object
126   file and the name space must not be polluted.  */
127# define getcwd __getcwd
128# ifndef stpcpy
129#  define stpcpy __stpcpy
130# endif
131# define tfind __tfind
132#else
133# if !defined HAVE_GETCWD
134char *getwd ();
135#  define getcwd(buf, max) getwd (buf)
136# else
137char *getcwd ();
138# endif
139# ifndef HAVE_STPCPY
140static char *stpcpy PARAMS ((char *dest, const char *src));
141# endif
142# ifndef HAVE_MEMPCPY
143static void *mempcpy PARAMS ((void *dest, const void *src, size_t n));
144# endif
145#endif
146
147/* Amount to increase buffer size by in each try.  */
148#define PATH_INCR 32
149
150/* The following is from pathmax.h.  */
151/* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define
152   PATH_MAX but might cause redefinition warnings when sys/param.h is
153   later included (as on MORE/BSD 4.3).  */
154#if defined _POSIX_VERSION || (defined HAVE_LIMITS_H && !defined __GNUC__)
155# include <limits.h>
156#endif
157
158#ifndef _POSIX_PATH_MAX
159# define _POSIX_PATH_MAX 255
160#endif
161
162#if !defined PATH_MAX && defined _PC_PATH_MAX
163# define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX))
164#endif
165
166/* Don't include sys/param.h if it already has been.  */
167#if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN
168# include <sys/param.h>
169#endif
170
171#if !defined PATH_MAX && defined MAXPATHLEN
172# define PATH_MAX MAXPATHLEN
173#endif
174
175#ifndef PATH_MAX
176# define PATH_MAX _POSIX_PATH_MAX
177#endif
178
179/* Pathname support.
180   ISSLASH(C)           tests whether C is a directory separator character.
181   IS_ABSOLUTE_PATH(P)  tests whether P is an absolute path.  If it is not,
182                        it may be concatenated to a directory pathname.
183   IS_PATH_WITH_DIR(P)  tests whether P contains a directory specification.
184 */
185#if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
186  /* Win32, OS/2, DOS */
187# define ISSLASH(C) ((C) == '/' || (C) == '\\')
188# define HAS_DEVICE(P) \
189    ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) \
190     && (P)[1] == ':')
191# define IS_ABSOLUTE_PATH(P) (ISSLASH ((P)[0]) || HAS_DEVICE (P))
192# define IS_PATH_WITH_DIR(P) \
193    (strchr (P, '/') != NULL || strchr (P, '\\') != NULL || HAS_DEVICE (P))
194#else
195  /* Unix */
196# define ISSLASH(C) ((C) == '/')
197# define IS_ABSOLUTE_PATH(P) ISSLASH ((P)[0])
198# define IS_PATH_WITH_DIR(P) (strchr (P, '/') != NULL)
199#endif
200
201/* XPG3 defines the result of `setlocale (category, NULL)' as:
202   ``Directs `setlocale()' to query `category' and return the current
203     setting of `local'.''
204   However it does not specify the exact format.  Neither do SUSV2 and
205   ISO C 99.  So we can use this feature only on selected systems (e.g.
206   those using GNU C Library).  */
207#if defined _LIBC || (defined __GNU_LIBRARY__ && __GNU_LIBRARY__ >= 2)
208# define HAVE_LOCALE_NULL
209#endif
210
211/* This is the type used for the search tree where known translations
212   are stored.  */
213struct known_translation_t
214{
215  /* Domain in which to search.  */
216  char *domainname;
217
218  /* The category.  */
219  int category;
220
221  /* State of the catalog counter at the point the string was found.  */
222  int counter;
223
224  /* Catalog where the string was found.  */
225  struct loaded_l10nfile *domain;
226
227  /* And finally the translation.  */
228  const char *translation;
229  size_t translation_length;
230
231  /* Pointer to the string in question.  */
232  char msgid[ZERO];
233};
234
235/* Root of the search tree with known translations.  We can use this
236   only if the system provides the `tsearch' function family.  */
237#if defined HAVE_TSEARCH || defined _LIBC
238# include <search.h>
239
240static void *root;
241
242# ifdef _LIBC
243#  define tsearch __tsearch
244# endif
245
246/* Function to compare two entries in the table of known translations.  */
247static int transcmp PARAMS ((const void *p1, const void *p2));
248static int
249transcmp (p1, p2)
250     const void *p1;
251     const void *p2;
252{
253  const struct known_translation_t *s1;
254  const struct known_translation_t *s2;
255  int result;
256
257  s1 = (const struct known_translation_t *) p1;
258  s2 = (const struct known_translation_t *) p2;
259
260  result = strcmp (s1->msgid, s2->msgid);
261  if (result == 0)
262    {
263      result = strcmp (s1->domainname, s2->domainname);
264      if (result == 0)
265        /* We compare the category last (though this is the cheapest
266           operation) since it is hopefully always the same (namely
267           LC_MESSAGES).  */
268        result = s1->category - s2->category;
269    }
270
271  return result;
272}
273#endif
274
275/* Name of the default domain used for gettext(3) prior any call to
276   textdomain(3).  The default value for this is "messages".  */
277const char _nl_default_default_domain[] = "messages";
278
279/* Value used as the default domain for gettext(3).  */
280const char *_nl_current_default_domain = _nl_default_default_domain;
281
282/* Contains the default location of the message catalogs.  */
283const char _nl_default_dirname[] = LOCALEDIR;
284
285/* List with bindings of specific domains created by bindtextdomain()
286   calls.  */
287struct binding *_nl_domain_bindings;
288
289/* Prototypes for local functions.  */
290static char *plural_lookup PARAMS ((struct loaded_l10nfile *domain,
291                                    unsigned long int n,
292                                    const char *translation,
293                                    size_t translation_len))
294     internal_function;
295static unsigned long int plural_eval PARAMS ((struct expression *pexp,
296                                              unsigned long int n))
297     internal_function;
298static const char *category_to_name PARAMS ((int category)) internal_function;
299static const char *guess_category_value PARAMS ((int category,
300                                                 const char *categoryname))
301     internal_function;
302
303
304/* For those loosing systems which don't have `alloca' we have to add
305   some additional code emulating it.  */
306#ifdef HAVE_ALLOCA
307/* Nothing has to be done.  */
308# define ADD_BLOCK(list, address) /* nothing */
309# define FREE_BLOCKS(list) /* nothing */
310#else
311struct block_list
312{
313  void *address;
314  struct block_list *next;
315};
316# define ADD_BLOCK(list, addr)                                                \
317  do {                                                                        \
318    struct block_list *newp = (struct block_list *) malloc (sizeof (*newp));  \
319    /* If we cannot get a free block we cannot add the new element to         \
320       the list.  */                                                          \
321    if (newp != NULL) {                                                       \
322      newp->address = (addr);                                                 \
323      newp->next = (list);                                                    \
324      (list) = newp;                                                          \
325    }                                                                         \
326  } while (0)
327# define FREE_BLOCKS(list)                                                    \
328  do {                                                                        \
329    while (list != NULL) {                                                    \
330      struct block_list *old = list;                                          \
331      list = list->next;                                                      \
332      free (old);                                                             \
333    }                                                                         \
334  } while (0)
335# undef alloca
336# define alloca(size) (malloc (size))
337#endif  /* have alloca */
338
339
340#ifdef _LIBC
341/* List of blocks allocated for translations.  */
342typedef struct transmem_list
343{
344  struct transmem_list *next;
345  char data[ZERO];
346} transmem_block_t;
347static struct transmem_list *transmem_list;
348#else
349typedef unsigned char transmem_block_t;
350#endif
351
352
353/* Names for the libintl functions are a problem.  They must not clash
354   with existing names and they should follow ANSI C.  But this source
355   code is also used in GNU C Library where the names have a __
356   prefix.  So we have to make a difference here.  */
357#ifdef _LIBC
358# define DCIGETTEXT __dcigettext
359#else
360# define DCIGETTEXT dcigettext__
361#endif
362
363/* Lock variable to protect the global data in the gettext implementation.  */
364#ifdef _LIBC
365__libc_rwlock_define_initialized (, _nl_state_lock)
366#endif
367
368/* Checking whether the binaries runs SUID must be done and glibc provides
369   easier methods therefore we make a difference here.  */
370#ifdef _LIBC
371# define ENABLE_SECURE __libc_enable_secure
372# define DETERMINE_SECURE
373#else
374# ifndef HAVE_GETUID
375#  define getuid() 0
376# endif
377# ifndef HAVE_GETGID
378#  define getgid() 0
379# endif
380# ifndef HAVE_GETEUID
381#  define geteuid() getuid()
382# endif
383# ifndef HAVE_GETEGID
384#  define getegid() getgid()
385# endif
386static int enable_secure;
387# define ENABLE_SECURE (enable_secure == 1)
388# define DETERMINE_SECURE \
389  if (enable_secure == 0)                                                     \
390    {                                                                         \
391      if (getuid () != geteuid () || getgid () != getegid ())                 \
392        enable_secure = 1;                                                    \
393      else                                                                    \
394        enable_secure = -1;                                                   \
395    }
396#endif
397
398/* Look up MSGID in the DOMAINNAME message catalog for the current
399   CATEGORY locale and, if PLURAL is nonzero, search over string
400   depending on the plural form determined by N.  */
401char *
402DCIGETTEXT (domainname, msgid1, msgid2, plural, n, category)
403     const char *domainname;
404     const char *msgid1;
405     const char *msgid2;
406     int plural;
407     unsigned long int n;
408     int category;
409{
410#ifndef HAVE_ALLOCA
411  struct block_list *block_list = NULL;
412#endif
413  struct loaded_l10nfile *domain;
414  struct binding *binding;
415  const char *categoryname;
416  const char *categoryvalue;
417  char *dirname, *xdomainname;
418  char *single_locale;
419  char *retval;
420  size_t retlen;
421  int saved_errno;
422#if defined HAVE_TSEARCH || defined _LIBC
423  struct known_translation_t *search;
424  struct known_translation_t **foundp = NULL;
425  size_t msgid_len;
426#endif
427  size_t domainname_len;
428
429  /* If no real MSGID is given return NULL.  */
430  if (msgid1 == NULL)
431    return NULL;
432
433  __libc_rwlock_rdlock (_nl_state_lock);
434
435  /* If DOMAINNAME is NULL, we are interested in the default domain.  If
436     CATEGORY is not LC_MESSAGES this might not make much sense but the
437     definition left this undefined.  */
438  if (domainname == NULL)
439    domainname = _nl_current_default_domain;
440
441#if defined HAVE_TSEARCH || defined _LIBC
442  msgid_len = strlen (msgid1) + 1;
443
444  /* Try to find the translation among those which we found at
445     some time.  */
446  search = (struct known_translation_t *)
447           alloca (offsetof (struct known_translation_t, msgid) + msgid_len);
448  memcpy (search->msgid, msgid1, msgid_len);
449  search->domainname = (char *) domainname;
450  search->category = category;
451
452  foundp = (struct known_translation_t **) tfind (search, &root, transcmp);
453  if (foundp != NULL && (*foundp)->counter == _nl_msg_cat_cntr)
454    {
455      /* Now deal with plural.  */
456      if (plural)
457        retval = plural_lookup ((*foundp)->domain, n, (*foundp)->translation,
458                                (*foundp)->translation_length);
459      else
460        retval = (char *) (*foundp)->translation;
461
462      __libc_rwlock_unlock (_nl_state_lock);
463      return retval;
464    }
465#endif
466
467  /* Preserve the `errno' value.  */
468  saved_errno = errno;
469
470  /* See whether this is a SUID binary or not.  */
471  DETERMINE_SECURE;
472
473  /* First find matching binding.  */
474  for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next)
475    {
476      int compare = strcmp (domainname, binding->domainname);
477      if (compare == 0)
478        /* We found it!  */
479        break;
480      if (compare < 0)
481        {
482          /* It is not in the list.  */
483          binding = NULL;
484          break;
485        }
486    }
487
488  if (binding == NULL)
489    dirname = (char *) _nl_default_dirname;
490  else if (IS_ABSOLUTE_PATH (binding->dirname))
491    dirname = binding->dirname;
492  else
493    {
494      /* We have a relative path.  Make it absolute now.  */
495      size_t dirname_len = strlen (binding->dirname) + 1;
496      size_t path_max;
497      char *ret;
498
499      path_max = (unsigned int) PATH_MAX;
500      path_max += 2;            /* The getcwd docs say to do this.  */
501
502      for (;;)
503        {
504          dirname = (char *) alloca (path_max + dirname_len);
505          ADD_BLOCK (block_list, dirname);
506
507          __set_errno (0);
508          ret = getcwd (dirname, path_max);
509          if (ret != NULL || errno != ERANGE)
510            break;
511
512          path_max += path_max / 2;
513          path_max += PATH_INCR;
514        }
515
516      if (ret == NULL)
517        {
518          /* We cannot get the current working directory.  Don't signal an
519             error but simply return the default string.  */
520          FREE_BLOCKS (block_list);
521          __libc_rwlock_unlock (_nl_state_lock);
522          __set_errno (saved_errno);
523          return (plural == 0
524                  ? (char *) msgid1
525                  /* Use the Germanic plural rule.  */
526                  : n == 1 ? (char *) msgid1 : (char *) msgid2);
527        }
528
529      stpcpy (stpcpy (strchr (dirname, '\0'), "/"), binding->dirname);
530    }
531
532  /* Now determine the symbolic name of CATEGORY and its value.  */
533  categoryname = category_to_name (category);
534  categoryvalue = guess_category_value (category, categoryname);
535
536  domainname_len = strlen (domainname);
537  xdomainname = (char *) alloca (strlen (categoryname)
538                                 + domainname_len + 5);
539  ADD_BLOCK (block_list, xdomainname);
540
541  stpcpy (mempcpy (stpcpy (stpcpy (xdomainname, categoryname), "/"),
542                  domainname, domainname_len),
543          ".mo");
544
545  /* Creating working area.  */
546  single_locale = (char *) alloca (strlen (categoryvalue) + 1);
547  ADD_BLOCK (block_list, single_locale);
548
549
550  /* Search for the given string.  This is a loop because we perhaps
551     got an ordered list of languages to consider for the translation.  */
552  while (1)
553    {
554      /* Make CATEGORYVALUE point to the next element of the list.  */
555      while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
556        ++categoryvalue;
557      if (categoryvalue[0] == '\0')
558        {
559          /* The whole contents of CATEGORYVALUE has been searched but
560             no valid entry has been found.  We solve this situation
561             by implicitly appending a "C" entry, i.e. no translation
562             will take place.  */
563          single_locale[0] = 'C';
564          single_locale[1] = '\0';
565        }
566      else
567        {
568          char *cp = single_locale;
569          while (categoryvalue[0] != '\0' && categoryvalue[0] != ':')
570            *cp++ = *categoryvalue++;
571          *cp = '\0';
572
573          /* When this is a SUID binary we must not allow accessing files
574             outside the dedicated directories.  */
575          if (ENABLE_SECURE && IS_PATH_WITH_DIR (single_locale))
576            /* Ingore this entry.  */
577            continue;
578        }
579
580      /* If the current locale value is C (or POSIX) we don't load a
581         domain.  Return the MSGID.  */
582      if (strcmp (single_locale, "C") == 0
583          || strcmp (single_locale, "POSIX") == 0)
584        {
585          FREE_BLOCKS (block_list);
586          __libc_rwlock_unlock (_nl_state_lock);
587          __set_errno (saved_errno);
588          return (plural == 0
589                  ? (char *) msgid1
590                  /* Use the Germanic plural rule.  */
591                  : n == 1 ? (char *) msgid1 : (char *) msgid2);
592        }
593
594
595      /* Find structure describing the message catalog matching the
596         DOMAINNAME and CATEGORY.  */
597      domain = _nl_find_domain (dirname, single_locale, xdomainname, binding);
598
599      if (domain != NULL)
600        {
601          retval = _nl_find_msg (domain, binding, msgid1, &retlen);
602
603          if (retval == NULL)
604            {
605              int cnt;
606
607              for (cnt = 0; domain->successor[cnt] != NULL; ++cnt)
608                {
609                  retval = _nl_find_msg (domain->successor[cnt], binding,
610                                         msgid1, &retlen);
611
612                  if (retval != NULL)
613                    {
614                      domain = domain->successor[cnt];
615                      break;
616                    }
617                }
618            }
619
620          if (retval != NULL)
621            {
622              /* Found the translation of MSGID1 in domain DOMAIN:
623                 starting at RETVAL, RETLEN bytes.  */
624              FREE_BLOCKS (block_list);
625              __set_errno (saved_errno);
626#if defined HAVE_TSEARCH || defined _LIBC
627              if (foundp == NULL)
628                {
629                  /* Create a new entry and add it to the search tree.  */
630                  struct known_translation_t *newp;
631
632                  newp = (struct known_translation_t *)
633                    malloc (offsetof (struct known_translation_t, msgid)
634                            + msgid_len + domainname_len + 1);
635                  if (newp != NULL)
636                    {
637                      newp->domainname =
638                        mempcpy (newp->msgid, msgid1, msgid_len);
639                      memcpy (newp->domainname, domainname, domainname_len + 1);
640                      newp->category = category;
641                      newp->counter = _nl_msg_cat_cntr;
642                      newp->domain = domain;
643                      newp->translation = retval;
644                      newp->translation_length = retlen;
645
646                      /* Insert the entry in the search tree.  */
647                      foundp = (struct known_translation_t **)
648                        tsearch (newp, &root, transcmp);
649                      if (foundp == NULL
650                          || __builtin_expect (*foundp != newp, 0))
651                        /* The insert failed.  */
652                        free (newp);
653                    }
654                }
655              else
656                {
657                  /* We can update the existing entry.  */
658                  (*foundp)->counter = _nl_msg_cat_cntr;
659                  (*foundp)->domain = domain;
660                  (*foundp)->translation = retval;
661                  (*foundp)->translation_length = retlen;
662                }
663#endif
664              /* Now deal with plural.  */
665              if (plural)
666                retval = plural_lookup (domain, n, retval, retlen);
667
668              __libc_rwlock_unlock (_nl_state_lock);
669              return retval;
670            }
671        }
672    }
673  /* NOTREACHED */
674}
675
676
677char *
678internal_function
679_nl_find_msg (domain_file, domainbinding, msgid, lengthp)
680     struct loaded_l10nfile *domain_file;
681     struct binding *domainbinding;
682     const char *msgid;
683     size_t *lengthp;
684{
685  struct loaded_domain *domain;
686  size_t act;
687  char *result;
688  size_t resultlen;
689
690  if (domain_file->decided == 0)
691    _nl_load_domain (domain_file, domainbinding);
692
693  if (domain_file->data == NULL)
694    return NULL;
695
696  domain = (struct loaded_domain *) domain_file->data;
697
698  /* Locate the MSGID and its translation.  */
699  if (domain->hash_size > 2 && domain->hash_tab != NULL)
700    {
701      /* Use the hashing table.  */
702      nls_uint32 len = strlen (msgid);
703      nls_uint32 hash_val = hash_string (msgid);
704      nls_uint32 idx = hash_val % domain->hash_size;
705      nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2));
706
707      while (1)
708        {
709          nls_uint32 nstr = W (domain->must_swap, domain->hash_tab[idx]);
710
711          if (nstr == 0)
712            /* Hash table entry is empty.  */
713            return NULL;
714
715          /* Compare msgid with the original string at index nstr-1.
716             We compare the lengths with >=, not ==, because plural entries
717             are represented by strings with an embedded NUL.  */
718          if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) >= len
719              && (strcmp (msgid,
720                          domain->data + W (domain->must_swap,
721                                            domain->orig_tab[nstr - 1].offset))
722                  == 0))
723            {
724              act = nstr - 1;
725              goto found;
726            }
727
728          if (idx >= domain->hash_size - incr)
729            idx -= domain->hash_size - incr;
730          else
731            idx += incr;
732        }
733      /* NOTREACHED */
734    }
735  else
736    {
737      /* Try the default method:  binary search in the sorted array of
738         messages.  */
739      size_t top, bottom;
740
741      bottom = 0;
742      top = domain->nstrings;
743      while (bottom < top)
744        {
745          int cmp_val;
746
747          act = (bottom + top) / 2;
748          cmp_val = strcmp (msgid, (domain->data
749                                    + W (domain->must_swap,
750                                         domain->orig_tab[act].offset)));
751          if (cmp_val < 0)
752            top = act;
753          else if (cmp_val > 0)
754            bottom = act + 1;
755          else
756            goto found;
757        }
758      /* No translation was found.  */
759      return NULL;
760    }
761
762 found:
763  /* The translation was found at index ACT.  If we have to convert the
764     string to use a different character set, this is the time.  */
765  result = ((char *) domain->data
766            + W (domain->must_swap, domain->trans_tab[act].offset));
767  resultlen = W (domain->must_swap, domain->trans_tab[act].length) + 1;
768
769#if defined _LIBC || HAVE_ICONV
770  if (domain->codeset_cntr
771      != (domainbinding != NULL ? domainbinding->codeset_cntr : 0))
772    {
773      /* The domain's codeset has changed through bind_textdomain_codeset()
774         since the message catalog was initialized or last accessed.  We
775         have to reinitialize the converter.  */
776      _nl_free_domain_conv (domain);
777      _nl_init_domain_conv (domain_file, domain, domainbinding);
778    }
779
780  if (
781