nls.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief National language support (NLS)
4  *
5  * Copyright (c) 2012-2021 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(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(POSIX_NL_MSGMAX < nls_max_message) { nls_max_message = POSIX_NL_MSGMAX; }
109 
110  /* Message number zero is reserved */
111  for(i = 1; i <= (size_t) nls_max_message; ++i)
112  {
113  do
114  {
115  posix_errno = 0;
116  rv = posix_catgets(catd, 1, (int) i, NULL);
117  }
118  while(NULL == rv && POSIX_EINTR == posix_errno);
119  len = sizeof(const char*) * i;
120  array = (const char**) posix_realloc(nls_message, len);
121  if(NULL == array)
122  {
123  PRINT_ERROR("Out of memory while creating message array");
124  res = -1;
125  break;
126  }
127  else
128  {
129  nls_message = array;
130  nls_last_message = (int) i;
131  s = NULL;
132  if(rv)
133  {
134  /* Copy string */
135  len = strlen(rv) + (size_t) 1;
136  s = (const char*) posix_malloc(len);
137  if(s) { memcpy((void*) s, (void*) rv, len); }
138  }
139  nls_message[i - (size_t) 1] = s;
140  }
141  }
142 
143  return(res);
144 }
145 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
146 
147 
148 /* ========================================================================== */
149 /*! \brief Init NLS subsystem
150  *
151  * First the locale of the program is set. If this fails the locale \c POSIX is
152  * used. The locale \c C is also mapped to \c POSIX and no NLS catalog is used
153  * for the locale \c POSIX.
154  * Then we try to extract the language code \c xx and the country code \c YY
155  * of the message locale name from \c LC_MESSAGES. If the locale has no country
156  * code (old 2 digit style), \c XX is used.
157  * If a valid locale was found, a catalog with the name \c xx_YY.cat is searched
158  * in the location \c CFG_NLS_PATH that was compiled into the program.
159  *
160  * \attention
161  * The environment variable \c NLSPATH is ignored for security reasons. The
162  * administrator who installed the program should have control over the NLS
163  * strings that are processed by the program. Think twice before changing this
164  * because we use Unicode NLS catalogs and the validity of the encoding must be
165  * verfied at runtime otherwise.
166  *
167  * \return
168  * - 0 on success
169  * - Negative value on error
170  */
171 
172 
173 int nls_init(void)
174 {
175 #if CFG_USE_XSI && !CFG_NLS_DISABLE
176  int res = 0;
177  const char* loc_sys;
178  enum nls_locale rv = NLS_LOCALE_VALID;
179  posix_nl_catd catd = (posix_nl_catd) -1;
180  int rv2;
181  size_t len;
182  long int len_max;
183  const char* subdir = "/";
184  const char* catext = ".cat";
185  char* catname = NULL;
186 
187  /* Check whether we are already initialized */
188  if(nls_ready)
189  {
190  PRINT_ERROR("Subsystem is already initialized");
191  res = -1;
192  }
193 
194  /* Set all locale categories from environment */
195  if(!res)
196  {
197  loc_sys = posix_setlocale(POSIX_LC_ALL, "");
198  if(NULL == loc_sys)
199  {
200  /* Failed => Fall back to POSIX */
201  PRINT_ERROR("Cannot set locale (check 'LANG' and 'LC_*' variables)");
202  PRINT_ERROR("Using default POSIX locale");
203  strcpy(nls_loc, "POSIX");
204  res = -1;
205  }
206  }
207 
208  /* Get message locale */
209  if(!res)
210  {
211  /* Get raw value of locale category 'LC_MESSAGES' */
212  loc_sys = posix_setlocale(POSIX_LC_MESSAGES, "");
213  printf("%s: %sMessage locale: %s\n", CFG_NAME, MAIN_ERR_PREFIX, loc_sys);
214  /* Cook internal locale from first 5 characters */
215  strncpy(nls_loc, loc_sys, 5); nls_loc[5] = 0;
216  /* Check for POSIX and C locale */
217  if(!strcmp(nls_loc, "C") || !strcmp(nls_loc, "POSIX"))
218  {
219  strcpy(nls_loc, "POSIX");
220  rv = NLS_LOCALE_DEFAULT;
221  }
222  /* Check whether locale is in "*_*" format */
223  else if('_' == nls_loc[2])
224  {
225  /* Yes => Try to convert it to "xx_YY" */
226  if(!islower((unsigned char) nls_loc[0]))
227  {
228  if(isupper((unsigned char) nls_loc[0]))
229  {
230  nls_loc[0] = (char) tolower((unsigned char) nls_loc[0]);
231  }
232  else rv = NLS_LOCALE_INVALID;
233  }
234  if(!islower((unsigned char) nls_loc[1]))
235  {
236  if (isupper((unsigned char) nls_loc[1]))
237  {
238  nls_loc[1] = (char) tolower((unsigned char) nls_loc[1]);
239  }
240  else rv = NLS_LOCALE_INVALID;
241  }
242  if(!isupper((unsigned char) nls_loc[3]))
243  {
244  if(islower((unsigned char) nls_loc[3]))
245  {
246  nls_loc[3] = (char) toupper((unsigned char) nls_loc[3]);
247  }
248  else rv = NLS_LOCALE_INVALID;
249  }
250  if(!isupper((unsigned char) nls_loc[4]))
251  {
252  if(islower((unsigned char) nls_loc[4]))
253  {
254  nls_loc[4] = (char) toupper((unsigned char) nls_loc[4]);
255  }
256  else rv = NLS_LOCALE_INVALID;
257  }
258  }
259  /* Check for old "xx" format */
260  else if(2 == strlen(nls_loc) || ('.' == nls_loc[2]))
261  {
262  /* Yes => Try to convert it to "xx_YY" */
263  if(!islower((unsigned char) nls_loc[0]))
264  {
265  if(isupper((unsigned char) nls_loc[0]))
266  {
267  nls_loc[0] = (char) tolower((unsigned char) nls_loc[0]);
268  }
269  else rv = NLS_LOCALE_INVALID;
270  }
271  if(!islower((unsigned char) nls_loc[1]))
272  {
273  if (isupper((unsigned char) nls_loc[1]))
274  {
275  nls_loc[1] = (char) tolower((unsigned char) nls_loc[1]);
276  }
277  else rv = NLS_LOCALE_INVALID;
278  }
279  if(NLS_LOCALE_VALID == rv)
280  {
281  nls_loc[2] = '_';
282  nls_loc[3] = (char) toupper((unsigned char) nls_loc[0]);
283  nls_loc[4] = (char) toupper((unsigned char) nls_loc[1]);
284  nls_loc[5] = 0;
285  }
286  }
287  else rv = NLS_LOCALE_INVALID;
288  /* Check whether we have a valid locale */
289  switch(rv)
290  {
291  case NLS_LOCALE_INVALID:
292  {
293  /* Print error if locale format is not supported */
294  PRINT_ERROR("Locale name format not supported");
295  PRINT_ERROR("Format must be 'xx' or start with 'xx_YY'");
296  res = -1;
297  break;
298  }
299  case NLS_LOCALE_VALID:
300  case NLS_LOCALE_DEFAULT:
301  {
302  printf("%s: %sCooked message locale: %s\n",
303  CFG_NAME, MAIN_ERR_PREFIX, nls_loc);
304  if(NLS_LOCALE_DEFAULT == rv)
305  {
306  /* Return error to use default strings instead of NLS catalog */
307  printf("%s: %sNo catalog required\n", CFG_NAME, MAIN_ERR_PREFIX);
308  res = -1;
309  }
310  break;
311  }
312  default:
313  {
314  PRINT_ERROR("Error while processing locale");
315  res = -1;
316  break;
317  }
318  }
319  }
320 
321  /* Allocate memory and prepare NLS catalog pathname */
322  if(!res)
323  {
324  /* Calculate pathname length (including termination character) */
325  len = strlen(CFG_NLS_PATH);
326  len += strlen(subdir);
327  len += strlen(nls_loc);
328  len += strlen(catext);
329  ++len;
330  /* Allocate memory for NLS catalog pathname */
331  catname = posix_malloc(len);
332  if(NULL == catname)
333  {
334  PRINT_ERROR("Out of memory while creating catalog pathname");
335  res = -1;
336  }
337  else
338  {
339  strcpy(catname, CFG_NLS_PATH);
340  strcat(catname, subdir);
341  strcat(catname, nls_loc);
342  strcat(catname, catext);
343  printf("%s: %sUsing catalog: %s\n", CFG_NAME, MAIN_ERR_PREFIX,
344  catname);
345  }
346  /* Check whether system supports path length */
347  len_max = posix_pathconf(CFG_NLS_PATH, POSIX_PC_PATH_MAX);
348  if(0L > len_max)
349  {
350  /* Path length check failed */
351  PRINT_ERROR("Catalog path check failed");
352  res = -1;
353  }
354  else if((size_t) len_max < len)
355  {
356  /* Pathname too long (not supported by OS) */
357  PRINT_ERROR("Catalog pathname too long");
358  res = -1;
359  }
360  }
361 
362  /* Open NLS catalog */
363  if(!res)
364  {
365  /*
366  * Workaround for old GNU systems:
367  * They report a positive catalog descriptor even if there is no catalog.
368  * As an additional check we open the catalog with 'open()' to check
369  * whether it is present.
370  */
371  rv2 = posix_open(catname, POSIX_O_RDONLY);
372  if(-1 == rv2)
373  {
374  PRINT_ERROR("Catalog not found, using default strings");
375  res = -1;
376  }
377  else
378  {
379  posix_close(rv2);
380  catd = posix_catopen(catname, POSIX_NL_CAT_LOCALE);
381  if((posix_nl_catd) -1 == catd)
382  {
383  switch(posix_errno)
384  {
385  case POSIX_EACCES:
386  {
387  PRINT_ERROR("Permission denied to open catalog");
388  break;
389  }
390  case POSIX_ENOENT:
391  {
392  PRINT_ERROR("Catalog not found");
393  break;
394  }
395  case POSIX_ENOMEM:
396  {
397  PRINT_ERROR("Out of memory while opening catalog");
398  break;
399  }
400  default:
401  {
402  PRINT_ERROR("Failed to open catalog");
403  break;
404  }
405  }
406  res = -1;
407  }
408  }
409  }
410 
411  /* Release memory for NLS catalog pathname */
412  posix_free(catname);
413 
414  if(!res)
415  {
416  /* Import NLS catalog data to local buffer */
417  res = nls_import_catalog(catd);
418 
419  /* Close NLS catalog */
420  do
421  {
422  posix_errno = 0;
423  rv2 = posix_catclose(catd);
424  }
425  while(-1 == rv2 && POSIX_EINTR == posix_errno);
426  }
427 
428  nls_ready = !res;
429 
430  return(res);
431 #else /* CFG_USE_XSI && !CFG_NLS_DISABLE */
432  strcpy(nls_loc, "POSIX");
433  PRINT_ERROR("Disabled by configuration");
434 
435  return(-1);
436 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
437 }
438 
439 
440 /* ========================================================================== */
441 /*! \brief Shutdown NLS subsystem
442  *
443  * It is allowed to call this function even if \ref nls_init() have failed.
444  * In this case it simply does nothing.
445  */
446 
447 void nls_exit(void)
448 {
449 #if CFG_USE_XSI && !CFG_NLS_DISABLE
450  size_t i;
451 
452  if(nls_ready && nls_last_message)
453  {
454  for(i = 0; i < (size_t) nls_last_message; ++i)
455  {
456  posix_free((void*) nls_message[i]);
457  }
458  posix_free((void*) nls_message);
459  }
460 
461  nls_ready = 0;
462 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
463 }
464 
465 
466 /* ========================================================================== */
467 /*! \brief Get NLS string
468  *
469  * \param[in] n NLS string number
470  * \param[in] s Replacement string if translation for string \e n was not found
471  *
472  * It is allowed to call this function even if nls_init() have failed or after
473  * \ref nls_exit() . In this case always \e s is returned.
474  *
475  * \return
476  * This functions returns the message string number \e n (or the default \e s
477  * if no corresponding message is found in the catalog)
478  */
479 
480 const char* nls_getstring(int n, const char* s)
481 {
482 #if CFG_USE_XSI && !CFG_NLS_DISABLE
483  const char* res = s;
484 
485  if(nls_ready)
486  {
487  if(1 > n || n > nls_last_message)
488  {
489  PRINT_ERROR("Value of NLS_MAX_MESSAGE too low (Bug)");
490  }
491  else
492  {
493  /* Decrement index (message number zero doesn't exist in array) */
494  if(nls_message[--n]) { res = nls_message[n]; }
495  }
496  if(res == s)
497  {
498  PRINT_ERROR("Translation not found in catalog");
499  }
500  }
501 
502  return(res);
503 #else /* CFG_USE_XSI && !CFG_NLS_DISABLE */
504  return(s);
505 #endif /* CFG_USE_XSI && !CFG_NLS_DISABLE */
506 }
507 
508 
509 /*! @} */
510 
511 /* EOF */
nls_getstring
const char * nls_getstring(int n, const char *s)
Get NLS string.
Definition: nls.c:480
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:447
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:173

Generated at 2024-04-27 using  doxygen