nls.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief National language support (NLS)
4  *
5  * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
6  *
7  * If nothing else is specified, functions return zero to indicate success
8  * and a negative value to indicate an error.
9  */
10 
11 
12 /* ========================================================================== */
13 /* Include headers */
14 
15 #include "posix.h" /* Include this first beause of feature test macros */
16 
17 #include <ctype.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 
22 #include "config.h"
23 #include "main.h"
24 #include "nls.h"
25 
26 
27 /* ========================================================================== */
28 /*! \defgroup NLS NLS: National Language Support
29  *
30  * This implementation of NLS does not use GNU gettext. The reasons are:
31  * - No additional library dependency for stuff that is already in libc
32  * - No string searching via compare, hash or other algorithm at runtime
33  * (Strings are not searched at all but instead directly accessed via index)
34  *
35  * This should waste minimum ressources and give best runtime performance.
36  * All the expensive work is done only once at build time.
37  */
38 /*! @{ */
39 
40 
41 /* ========================================================================== */
42 /* Data types */
43 
44 enum nls_locale
45 {
46  NLS_LOCALE_VALID,
47  NLS_LOCALE_DEFAULT,
48  NLS_LOCALE_INVALID
49 };
50 
51 
52 /* ========================================================================== */
53 /* Constants */
54 
55 /*! \brief Message prefix for NLS module */
56 #define MAIN_ERR_PREFIX "NLS: "
57 
58 /*! \brief Maximum number of messages loaded from NLS catalog.
59  *
60  * This value must at least match the number of messages in the NLS catalogs
61  * used by the program.
62  * The limit of this implementation is INT_MAX. If the system has a lower
63  * limit than the configured value, the value is clamped to the system limit.
64  *
65  * \warning
66  * Making this value much higher than required will waste memory and slow
67  * down startup.
68  */
69 #define NLS_MAX_MESSAGE 384
70 
71 
72 /* ========================================================================== */
73 /* Variables */
74 
75 /*! \brief Current NLS locale */
76 char nls_loc[6] = "";
77 #if CFG_USE_XSI && !CFG_NLS_DISABLE
78 static int nls_ready = 0;
79 static int nls_last_message = 0;
80 static const char** nls_message = NULL;
81 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
82 
83 
84 /* ========================================================================== */
85 /* Copy NLS catalog to local memory */
86 /*
87  * The internal buffer of \c catgets() may get overwritten by the next call,
88  * this is the case e.g. on HP-UX. POSIX.1 doesn't explicitly forbid this
89  * behaviour and it must therefore considered as legal.
90  *
91  * Because we need NLS strings that behave like 'static const char' arrays
92  * until \ref nls_exit() is called, we copy the whole catalog to local memory
93  * and work with the copy.
94  */
95 
96 #if CFG_USE_XSI && !CFG_NLS_DISABLE
97 static int nls_import_catalog(api_posix_nl_catd catd)
98 {
99  int res = 0;
100  size_t i;
101  char* rv;
102  int nls_max_message = NLS_MAX_MESSAGE;
103  size_t len;
104  const char** array;
105  const char* s;
106 
107  /* Clamp maximum number of messages to system limit */
108  if(API_POSIX_NL_MSGMAX < nls_max_message)
109  {
110  nls_max_message = API_POSIX_NL_MSGMAX;
111  }
112 
113  /* Message number zero is reserved */
114  for(i = 1; i <= (size_t) nls_max_message; ++i)
115  {
116  do
117  {
118  api_posix_errno = 0;
119  rv = api_posix_catgets(catd, 1, (int) i, NULL);
120  }
121  while(NULL == rv && API_POSIX_EINTR == api_posix_errno);
122  len = sizeof(const char*) * i;
123  array = (const char**) api_posix_realloc(nls_message, len);
124  if(NULL == array)
125  {
126  PRINT_ERROR("Out of memory while creating message array");
127  res = -1;
128  break;
129  }
130  else
131  {
132  nls_message = array;
133  nls_last_message = (int) i;
134  s = NULL;
135  if(rv)
136  {
137  /* Copy string */
138  len = strlen(rv) + (size_t) 1;
139  s = (const char*) api_posix_malloc(len);
140  if(s) { memcpy((void*) s, (void*) rv, len); }
141  }
142  nls_message[i - (size_t) 1] = s;
143  }
144  }
145 
146  return(res);
147 }
148 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
149 
150 
151 /* ========================================================================== */
152 /*! \brief Init NLS subsystem
153  *
154  * First the locale of the program is set. If this fails the locale \c POSIX is
155  * used. The locale \c C is also mapped to \c POSIX and no NLS catalog is used
156  * for the locale \c POSIX.
157  * Then we try to extract the language code \c xx and the country code \c YY
158  * of the message locale name from \c LC_MESSAGES. If the locale has no country
159  * code (old 2 digit style), \c XX is used.
160  * If a valid locale was found, a catalog with the name \c xx_YY.cat is searched
161  * in the location \c CFG_NLS_PATH that was compiled into the program.
162  *
163  * \attention
164  * The environment variable \c NLSPATH is ignored for security reasons. The
165  * administrator who installed the program should have control over the NLS
166  * strings that are processed by the program. Think twice before changing this
167  * because we use Unicode NLS catalogs and the validity of the encoding must be
168  * verfied at runtime otherwise.
169  *
170  * \return
171  * - 0 on success
172  * - Negative value on error
173  */
174 
175 
176 int nls_init(void)
177 {
178 #if CFG_USE_XSI && !CFG_NLS_DISABLE
179  int res = 0;
180  const char* loc_sys;
181  enum nls_locale rv = NLS_LOCALE_VALID;
182  api_posix_nl_catd catd = (api_posix_nl_catd) -1;
183  int rv2;
184  size_t len;
185  long int len_max;
186  const char* subdir = "/";
187  const char* catext = ".cat";
188  char* catname = NULL;
189 
190  /* Check whether we are already initialized */
191  if(nls_ready)
192  {
193  PRINT_ERROR("Subsystem is already initialized");
194  res = -1;
195  }
196 
197  /* Set all locale categories from environment */
198  if(!res)
199  {
200  loc_sys = api_posix_setlocale(API_POSIX_LC_ALL, "");
201  if(NULL == loc_sys)
202  {
203  /* Failed => Fall back to POSIX */
204  PRINT_ERROR("Cannot set locale (check 'LANG' and 'LC_*' variables)");
205  PRINT_ERROR("Using default POSIX locale");
206  strcpy(nls_loc, "POSIX");
207  res = -1;
208  }
209  }
210 
211  /* Get message locale */
212  if(!res)
213  {
214  /* Get raw value of locale category 'LC_MESSAGES' */
215  loc_sys = api_posix_setlocale(API_POSIX_LC_MESSAGES, "");
216  printf("%s: %sMessage locale: %s\n", CFG_NAME, MAIN_ERR_PREFIX, loc_sys);
217  /* Cook internal locale from first 5 characters */
218  strncpy(nls_loc, loc_sys, 5); nls_loc[5] = 0;
219  /* Check for POSIX and C locale */
220  if(!strcmp(nls_loc, "C") || !strcmp(nls_loc, "POSIX"))
221  {
222  strcpy(nls_loc, "POSIX");
223  rv = NLS_LOCALE_DEFAULT;
224  }
225  /* Check whether locale is in "*_*" format */
226  else if('_' == nls_loc[2])
227  {
228  /* Yes => Try to convert it to "xx_YY" */
229  if(!islower((unsigned char) nls_loc[0]))
230  {
231  if(isupper((unsigned char) nls_loc[0]))
232  {
233  nls_loc[0] = (char) tolower((unsigned char) nls_loc[0]);
234  }
235  else rv = NLS_LOCALE_INVALID;
236  }
237  if(!islower((unsigned char) nls_loc[1]))
238  {
239  if (isupper((unsigned char) nls_loc[1]))
240  {
241  nls_loc[1] = (char) tolower((unsigned char) nls_loc[1]);
242  }
243  else rv = NLS_LOCALE_INVALID;
244  }
245  if(!isupper((unsigned char) nls_loc[3]))
246  {
247  if(islower((unsigned char) nls_loc[3]))
248  {
249  nls_loc[3] = (char) toupper((unsigned char) nls_loc[3]);
250  }
251  else rv = NLS_LOCALE_INVALID;
252  }
253  if(!isupper((unsigned char) nls_loc[4]))
254  {
255  if(islower((unsigned char) nls_loc[4]))
256  {
257  nls_loc[4] = (char) toupper((unsigned char) nls_loc[4]);
258  }
259  else rv = NLS_LOCALE_INVALID;
260  }
261  }
262  /* Check for old "xx" format */
263  else if(2 == strlen(nls_loc) || ('.' == nls_loc[2]))
264  {
265  /* Yes => Try to convert it to "xx_YY" */
266  if(!islower((unsigned char) nls_loc[0]))
267  {
268  if(isupper((unsigned char) nls_loc[0]))
269  {
270  nls_loc[0] = (char) tolower((unsigned char) nls_loc[0]);
271  }
272  else rv = NLS_LOCALE_INVALID;
273  }
274  if(!islower((unsigned char) nls_loc[1]))
275  {
276  if (isupper((unsigned char) nls_loc[1]))
277  {
278  nls_loc[1] = (char) tolower((unsigned char) nls_loc[1]);
279  }
280  else rv = NLS_LOCALE_INVALID;
281  }
282  if(NLS_LOCALE_VALID == rv)
283  {
284  nls_loc[2] = '_';
285  nls_loc[3] = (char) toupper((unsigned char) nls_loc[0]);
286  nls_loc[4] = (char) toupper((unsigned char) nls_loc[1]);
287  nls_loc[5] = 0;
288  }
289  }
290  else rv = NLS_LOCALE_INVALID;
291  /* Check whether we have a valid locale */
292  switch(rv)
293  {
294  case NLS_LOCALE_INVALID:
295  {
296  /* Print error if locale format is not supported */
297  PRINT_ERROR("Locale name format not supported");
298  PRINT_ERROR("Format must be 'xx' or start with 'xx_YY'");
299  res = -1;
300  break;
301  }
302  case NLS_LOCALE_VALID:
303  case NLS_LOCALE_DEFAULT:
304  {
305  printf("%s: %sCooked message locale: %s\n",
306  CFG_NAME, MAIN_ERR_PREFIX, nls_loc);
307  if(NLS_LOCALE_DEFAULT == rv)
308  {
309  /* Return error to use default strings instead of NLS catalog */
310  printf("%s: %sNo catalog required\n", CFG_NAME, MAIN_ERR_PREFIX);
311  res = -1;
312  }
313  break;
314  }
315  default:
316  {
317  PRINT_ERROR("Error while processing locale");
318  res = -1;
319  break;
320  }
321  }
322  }
323 
324  /* Allocate memory and prepare NLS catalog pathname */
325  if(!res)
326  {
327  /* Calculate pathname length (including termination character) */
328  len = strlen(CFG_NLS_PATH);
329  len += strlen(subdir);
330  len += strlen(nls_loc);
331  len += strlen(catext);
332  ++len;
333  /* Allocate memory for NLS catalog pathname */
334  catname = api_posix_malloc(len);
335  if(NULL == catname)
336  {
337  PRINT_ERROR("Out of memory while creating catalog pathname");
338  res = -1;
339  }
340  else
341  {
342  strcpy(catname, CFG_NLS_PATH);
343  strcat(catname, subdir);
344  strcat(catname, nls_loc);
345  strcat(catname, catext);
346  printf("%s: %sUsing catalog: %s\n", CFG_NAME, MAIN_ERR_PREFIX,
347  catname);
348  }
349  /* Check whether system supports path length */
350  len_max = api_posix_pathconf(CFG_NLS_PATH, API_POSIX_PC_PATH_MAX);
351  if(0L > len_max)
352  {
353  /* Path length check failed */
354  PRINT_ERROR("Catalog path check failed");
355  res = -1;
356  }
357  else if((size_t) len_max < len)
358  {
359  /* Pathname too long (not supported by OS) */
360  PRINT_ERROR("Catalog pathname too long");
361  res = -1;
362  }
363  }
364 
365  /* Open NLS catalog */
366  if(!res)
367  {
368  /*
369  * Workaround for old GNU systems:
370  * They report a positive catalog descriptor even if there is no catalog.
371  * As an additional check we open the catalog with 'open()' to check
372  * whether it is present.
373  */
374  rv2 = api_posix_open(catname, API_POSIX_O_RDONLY);
375  if(-1 == rv2)
376  {
377  PRINT_ERROR("Catalog not found, using default strings");
378  res = -1;
379  }
380  else
381  {
382  api_posix_close(rv2);
383  catd = api_posix_catopen(catname, API_POSIX_NL_CAT_LOCALE);
384  if((api_posix_nl_catd) -1 == catd)
385  {
386  switch(api_posix_errno)
387  {
388  case API_POSIX_EACCES:
389  {
390  PRINT_ERROR("Permission denied to open catalog");
391  break;
392  }
393  case API_POSIX_ENOENT:
394  {
395  PRINT_ERROR("Catalog not found");
396  break;
397  }
398  case API_POSIX_ENOMEM:
399  {
400  PRINT_ERROR("Out of memory while opening catalog");
401  break;
402  }
403  default:
404  {
405  PRINT_ERROR("Failed to open catalog");
406  break;
407  }
408  }
409  res = -1;
410  }
411  }
412  }
413 
414  /* Release memory for NLS catalog pathname */
415  api_posix_free(catname);
416 
417  if(!res)
418  {
419  /* Import NLS catalog data to local buffer */
420  res = nls_import_catalog(catd);
421 
422  /* Close NLS catalog */
423  do
424  {
425  api_posix_errno = 0;
426  rv2 = api_posix_catclose(catd);
427  }
428  while(-1 == rv2 && API_POSIX_EINTR == api_posix_errno);
429  }
430 
431  nls_ready = !res;
432 
433  return(res);
434 #else /* CFG_USE_XSI && !CFG_NLS_DISABLE */
435  strcpy(nls_loc, "POSIX");
436  PRINT_ERROR("Disabled by configuration");
437 
438  return(-1);
439 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
440 }
441 
442 
443 /* ========================================================================== */
444 /*! \brief Shutdown NLS subsystem
445  *
446  * It is allowed to call this function even if \ref nls_init() have failed.
447  * In this case it simply does nothing.
448  */
449 
450 void nls_exit(void)
451 {
452 #if CFG_USE_XSI && !CFG_NLS_DISABLE
453  size_t i;
454 
455  if(nls_ready && nls_last_message)
456  {
457  for(i = 0; i < (size_t) nls_last_message; ++i)
458  {
459  api_posix_free((void*) nls_message[i]);
460  }
461  api_posix_free((void*) nls_message);
462  }
463 
464  nls_ready = 0;
465 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
466 }
467 
468 
469 /* ========================================================================== */
470 /*! \brief Get NLS string
471  *
472  * \param[in] n NLS string number
473  * \param[in] s Replacement string if translation for string \e n was not found
474  *
475  * It is allowed to call this function even if nls_init() have failed or after
476  * \ref nls_exit() . In this case always \e s is returned.
477  *
478  * \return
479  * This functions returns the message string number \e n (or the default \e s
480  * if no corresponding message is found in the catalog)
481  */
482 
483 const char* nls_getstring(int n, const char* s)
484 {
485 #if CFG_USE_XSI && !CFG_NLS_DISABLE
486  const char* res = s;
487 
488  if(nls_ready)
489  {
490  if(1 > n || n > nls_last_message)
491  {
492  PRINT_ERROR("Value of NLS_MAX_MESSAGE too low (Bug)");
493  }
494  else
495  {
496  /* Decrement index (message number zero doesn't exist in array) */
497  if(nls_message[--n]) { res = nls_message[n]; }
498  }
499  if(res == s)
500  {
501  PRINT_ERROR("Translation not found in catalog");
502  }
503  }
504 
505  return(res);
506 #else /* CFG_USE_XSI && !CFG_NLS_DISABLE */
507  (void) n;
508 
509  return(s);
510 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
511 }
512 
513 
514 /*! @} */
515 
516 /* EOF */
nls_getstring
const char * nls_getstring(int n, const char *s)
Get NLS string.
Definition: nls.c:483
MAIN_ERR_PREFIX
#define MAIN_ERR_PREFIX
Message prefix for NLS module.
Definition: nls.c:56
nls_exit
void nls_exit(void)
Shutdown NLS subsystem.
Definition: nls.c:450
nls_loc
char nls_loc[6]
Current NLS locale.
Definition: nls.c:76
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
NLS_MAX_MESSAGE
#define NLS_MAX_MESSAGE
Maximum number of messages loaded from NLS catalog.
Definition: nls.c:69
nls_init
int nls_init(void)
Init NLS subsystem.
Definition: nls.c:176

Generated at 2026-01-27 using  doxygen