source: git/src/message.c @ 04078a7

faster-cavernlogwalls-datawalls-data-hanging-as-warning
Last change on this file since 04078a7 was 7dbc68f, checked in by Olly Betts <olly@…>, 4 weeks ago

Revert "Merge osdepend.c and .h into filename.c and .h"

This change breaks the win32 build. Not seeing why currently but
it was just a code cleanup so revert for now.

This reverts commit ed04c55304f1fd95c126daf9612665b517eab4e7.

  • Property mode set to 100644
File size: 36.6 KB
Line 
1/* message.c
2 * Fairly general purpose message and error routines
3 * Copyright (C) 1993-2022 Olly Betts
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 */
19
20/*#define DEBUG 1*/
21
22#ifdef HAVE_CONFIG_H
23# include <config.h>
24#endif
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <ctype.h>
30#include <limits.h>
31#include <errno.h>
32#include <locale.h>
33
34#include "cmdline.h"
35#include "whichos.h"
36#include "filename.h"
37#include "message.h"
38#include "osdepend.h"
39#include "filelist.h"
40#include "debug.h"
41#include "str.h"
42
43#ifdef AVEN
44# include "aven.h"
45#endif
46
47#if OS_WIN32
48# define WIN32_LEAN_AND_MEAN
49# include <windows.h>
50#elif OS_UNIX
51# include <sys/types.h>
52#endif
53
54#include <sys/stat.h>
55
56/* For funcs which want to be immune from messing around with different
57 * calling conventions */
58#ifndef CDECL
59# define CDECL
60#endif
61
62int msg_warnings = 0; /* keep track of how many warnings we've given */
63int msg_errors = 0;   /* and how many (non-fatal) errors */
64
65/* in case osmalloc() fails before appname_copy is set up */
66static const char *appname_copy = "anonymous program";
67
68/* Path to use to look for executables (used by aven to find cavern). */
69static const char *exe_pth = "";
70
71/* error code for failed osmalloc and osrealloc calls */
72static void
73outofmem(OSSIZE_T size)
74{
75   /* TRANSLATORS: "%lu" is a placeholder for the number of bytes which Survex
76    * was trying to allocate space for. */
77   fatalerror(/*Out of memory (couldn’t find %lu bytes).*/1,
78              (unsigned long)size);
79}
80
81#ifdef TOMBSTONES
82#define TOMBSTONE_SIZE 16
83static const char tombstone[TOMBSTONE_SIZE] = "012345\xfftombstone";
84#endif
85
86/* malloc with error catching if it fails. Also allows us to write special
87 * versions easily eg for MS Windows.
88 */
89void *
90osmalloc(OSSIZE_T size)
91{
92   void *p;
93#ifdef TOMBSTONES
94   size += TOMBSTONE_SIZE * 2;
95   p = malloc(size);
96#else
97   p = xosmalloc(size);
98#endif
99   if (p == NULL) outofmem(size);
100#ifdef TOMBSTONES
101   printf("osmalloc truep=%p truesize=%d\n", p, size);
102   memcpy(p, tombstone, TOMBSTONE_SIZE);
103   memcpy(p + size - TOMBSTONE_SIZE, tombstone, TOMBSTONE_SIZE);
104   *(size_t *)p = size;
105   p += TOMBSTONE_SIZE;
106#endif
107   return p;
108}
109
110/* realloc with error catching if it fails. */
111void *
112osrealloc(void *p, OSSIZE_T size)
113{
114   /* some pre-ANSI realloc implementations don't cope with a NULL pointer */
115   if (p == NULL) {
116      p = xosmalloc(size);
117   } else {
118#ifdef TOMBSTONES
119      int true_size;
120      size += TOMBSTONE_SIZE * 2;
121      p -= TOMBSTONE_SIZE;
122      true_size = *(size_t *)p;
123      printf("osrealloc (in truep=%p truesize=%d)\n", p, true_size);
124      if (memcmp(p + sizeof(size_t), tombstone + sizeof(size_t),
125                 TOMBSTONE_SIZE - sizeof(size_t)) != 0) {
126         printf("start tombstone for block %p, size %d corrupted!",
127                p + TOMBSTONE_SIZE, true_size - TOMBSTONE_SIZE * 2);
128      }
129      if (memcmp(p + true_size - TOMBSTONE_SIZE, tombstone,
130                 TOMBSTONE_SIZE) != 0) {
131         printf("end tombstone for block %p, size %d corrupted!",
132                p + TOMBSTONE_SIZE, true_size - TOMBSTONE_SIZE * 2);
133      }
134      p = realloc(p, size);
135      if (p == NULL) outofmem(size);
136      printf("osrealloc truep=%p truesize=%d\n", p, size);
137      memcpy(p, tombstone, TOMBSTONE_SIZE);
138      memcpy(p + size - TOMBSTONE_SIZE, tombstone, TOMBSTONE_SIZE);
139      *(size_t *)p = size;
140      p += TOMBSTONE_SIZE;
141#else
142      p = xosrealloc(p, size);
143#endif
144   }
145   if (p == NULL) outofmem(size);
146   return p;
147}
148
149char *
150osstrdup(const char *str)
151{
152   char *p;
153   OSSIZE_T len;
154   len = strlen(str) + 1;
155   p = osmalloc(len);
156   memcpy(p, str, len);
157   return p;
158}
159
160/* osfree is usually just a macro in osalloc.h */
161#ifdef TOMBSTONES
162void
163osfree(void *p)
164{
165   int true_size;
166   if (!p) return;
167   p -= TOMBSTONE_SIZE;
168   true_size = *(size_t *)p;
169   printf("osfree truep=%p truesize=%d\n", p, true_size);
170   if (memcmp(p + sizeof(size_t), tombstone + sizeof(size_t),
171              TOMBSTONE_SIZE - sizeof(size_t)) != 0) {
172      printf("start tombstone for block %p, size %d corrupted!",
173             p + TOMBSTONE_SIZE, true_size - TOMBSTONE_SIZE * 2);
174   }
175   if (memcmp(p + true_size - TOMBSTONE_SIZE, tombstone,
176              TOMBSTONE_SIZE) != 0) {
177      printf("end tombstone for block %p, size %d corrupted!",
178             p + TOMBSTONE_SIZE, true_size - TOMBSTONE_SIZE * 2);
179   }
180   free(p);
181}
182#endif
183
184static int
185default_charset(void)
186{
187   if (getenv("SURVEX_UTF8")) return CHARSET_UTF8;
188#if OS_WIN32
189# ifdef AVEN
190#  define CODEPAGE GetACP()
191# else
192#  define CODEPAGE GetConsoleOutputCP()
193# endif
194   switch (CODEPAGE) {
195    case 0: return CHARSET_UTF8;
196    case 1252: return CHARSET_WINCP1252;
197    case 1250: return CHARSET_WINCP1250;
198    case 850: return CHARSET_DOSCP850;
199   }
200   return CHARSET_USASCII;
201#elif OS_UNIX
202#ifdef AVEN
203   return CHARSET_UTF8;
204#else
205   const char *p = getenv("LC_ALL");
206   if (p == NULL || p[0] == '\0') {
207      p = getenv("LC_CTYPE");
208      if (p == NULL || p[0] == '\0') {
209         p = getenv("LANG");
210         /* Something (AutoCAD?) on Microsoft Windows sets LANG to a number. */
211         if (p == NULL || !isalpha((unsigned char)p[0]))
212            p = msg_lang;
213      }
214   }
215
216   if (p) {
217      const char *q = strchr(p, '.');
218      if (q) p = q + 1;
219   }
220
221   if (p) {
222      const char *chset = p;
223      size_t name_len;
224
225      while (*p != '\0' && *p != '@') p++;
226
227      name_len = p - chset;
228
229      if (name_len) {
230         int only_digit = 1;
231         size_t cnt;
232
233         for (cnt = 0; cnt < name_len; ++cnt)
234            if (isalpha((unsigned char)chset[cnt])) {
235               only_digit = 0;
236               break;
237            }
238
239         if (only_digit) goto iso;
240
241         switch (tolower((unsigned char)chset[0])) {
242          case 'i':
243            if (tolower((unsigned char)chset[1]) == 's' &&
244                tolower((unsigned char)chset[2]) == 'o') {
245               chset += 3;
246               iso:
247               if (strncmp(chset, "8859", 4) == 0) {
248                  chset += 4;
249                  while (chset < p && *chset && !isdigit((unsigned char)*chset))
250                     chset++;
251                  switch (atoi(chset)) {
252                   case 1: return CHARSET_ISO_8859_1;
253                   case 2: return CHARSET_ISO_8859_2;
254                   case 15: return CHARSET_ISO_8859_15;
255                   default: return CHARSET_USASCII;
256                  }
257               }
258            }
259            break;
260          case 'u':
261            if (tolower((unsigned char)chset[1]) == 't' &&
262                tolower((unsigned char)chset[2]) == 'f') {
263               chset += 3;
264               while (chset < p && *chset && !isdigit((unsigned char)*chset))
265                  chset++;
266               switch (atoi(chset)) {
267                case 8: return CHARSET_UTF8;
268                default: return CHARSET_USASCII;
269               }
270            }
271         }
272      }
273   }
274   return CHARSET_USASCII;
275#endif
276#else
277# error Do not know operating system!
278#endif
279}
280
281/* It seems that Swedish and maybe some other scandanavian languages don't
282 * transliterate &auml; to ae - but it seems there may be conflicting views
283 * on this...
284 */
285#define umlaut_to_e() 1
286
287/* values <= 127 already dealt with */
288static int
289add_unicode(int charset, unsigned char *p, int value)
290{
291#ifdef DEBUG
292   fprintf(stderr, "add_unicode(%d, %p, %d)\n", charset, p, value);
293#endif
294   if (value == 0) return 0;
295   switch (charset) {
296   case CHARSET_USASCII:
297      if (value < 0x80) {
298         *p = value;
299         return 1;
300      }
301      break;
302   case CHARSET_ISO_8859_1:
303      if (value < 0x100) {
304         *p = value;
305         return 1;
306      }
307      break;
308   case CHARSET_ISO_8859_2:
309      if (value >= 0xa0) {
310         int v = 0;
311         switch (value) {
312            case 0xa0: case 0xa4: case 0xa7: case 0xa8: case 0xad: case 0xb0:
313            case 0xb4: case 0xb8: case 0xc1: case 0xc2: case 0xc4: case 0xc7:
314            case 0xc9: case 0xcb: case 0xcd: case 0xce: case 0xd3: case 0xd4:
315            case 0xd6: case 0xd7: case 0xda: case 0xdc: case 0xdd: case 0xdf:
316            case 0xe1: case 0xe2: case 0xe4: case 0xe7: case 0xe9: case 0xeb:
317            case 0xed: case 0xee: case 0xf3: case 0xf4: case 0xf6: case 0xf7:
318            case 0xfa: case 0xfc: case 0xfd:
319               v = value; break;
320            case 0x104: v = '\xa1'; break;
321            case 0x2d8: v = '\xa2'; break;
322            case 0x141: v = '\xa3'; break;
323            case 0x13d: v = '\xa5'; break;
324            case 0x15a: v = '\xa6'; break;
325            case 0x160: v = '\xa9'; break;
326            case 0x15e: v = '\xaa'; break; /* Scedil */
327            case 0x164: v = '\xab'; break;
328            case 0x179: v = '\xac'; break;
329            case 0x17d: v = '\xae'; break;
330            case 0x17b: v = '\xaf'; break;
331            case 0x105: v = '\xb1'; break;
332            case 0x2db: v = '\xb2'; break;
333            case 0x142: v = '\xb3'; break;
334            case 0x13e: v = '\xb5'; break;
335            case 0x15b: v = '\xb6'; break;
336            case 0x2c7: v = '\xb7'; break;
337            case 0x161: v = '\xb9'; break;
338            case 0x15f: v = '\xba'; break; /* scedil */
339            case 0x165: v = '\xbb'; break;
340            case 0x17a: v = '\xbc'; break;
341            case 0x2dd: v = '\xbd'; break;
342            case 0x17e: v = '\xbe'; break;
343            case 0x17c: v = '\xbf'; break;
344            case 0x154: v = '\xc0'; break;
345            case 0x102: v = '\xc3'; break;
346            case 0x139: v = '\xc5'; break;
347            case 0x106: v = '\xc6'; break;
348            case 0x10c: v = '\xc8'; break;
349            case 0x118: v = '\xca'; break;
350            case 0x11a: v = '\xcc'; break;
351            case 0x10e: v = '\xcf'; break;
352            case 0x110: v = '\xd0'; break;
353            case 0x143: v = '\xd1'; break;
354            case 0x147: v = '\xd2'; break;
355            case 0x150: v = '\xd5'; break;
356            case 0x158: v = '\xd8'; break;
357            case 0x16e: v = '\xd9'; break;
358            case 0x170: v = '\xdb'; break;
359            case 0x162: v = '\xde'; break; /* &Tcedil; */
360            case 0x155: v = '\xe0'; break;
361            case 0x103: v = '\xe3'; break;
362            case 0x13a: v = '\xe5'; break;
363            case 0x107: v = '\xe6'; break;
364            case 0x10d: v = '\xe8'; break;
365            case 0x119: v = '\xea'; break;
366            case 0x11b: v = '\xec'; break;
367            case 0x10f: v = '\xef'; break;
368            case 0x111: v = '\xf0'; break;
369            case 0x144: v = '\xf1'; break;
370            case 0x148: v = '\xf2'; break;
371            case 0x151: v = '\xf5'; break;
372            case 0x159: v = '\xf8'; break;
373            case 0x16f: v = '\xf9'; break;
374            case 0x171: v = '\xfb'; break;
375            case 0x163: v = '\xfe'; break; /* tcedil */
376            case 0x2d9: v = '\xff'; break;
377         }
378         if (v == 0) break;
379         value = v;
380      }
381      *p = value;
382      return 1;
383   case CHARSET_ISO_8859_15:
384      switch (value) {
385       case 0xa4: case 0xa6: case 0xb0: case 0xc4:
386       case 0xd0: case 0xd4: case 0xd5: case 0xd6:
387         goto donthave;
388       case 0x152: value = 0xd4; break; /* &OElig; */
389       case 0x153: value = 0xd5; break; /* &oelig; */
390#if 0
391       case 0x0: value = 0xa4; break; /* euro */
392#endif
393       case 0x160: value = 0xa6; break; /* Scaron */
394       case 0x161: value = 0xb0; break; /* scaron */
395       case 0x17d: value = 0xc4; break; /* Zcaron */
396       case 0x17e: value = 0xd0; break; /* zcaron */
397#if 0
398       case 0x0: value = 0xd6; break; /* Ydiersis */
399#endif
400      }
401      if (value < 0x100) {
402         *p = value;
403         return 1;
404      }
405      donthave:
406      break;
407#if OS_WIN32
408   case CHARSET_WINCP1250:
409      /* MS Windows rough equivalent to ISO-8859-2 */
410      if (value >= 0x80) {
411         int v = 0;
412         /* This mapping is complete - there are 5 unused positions:
413          * 0x81 0x83 0x88 0x90 0x98 */
414         switch (value) {
415            case 0xa0: case 0xa4: case 0xa6: case 0xa7: case 0xa8: case 0xa9:
416            case 0xab: case 0xac: case 0xad: case 0xae: case 0xb0: case 0xb1:
417            case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xbb:
418            case 0xc1: case 0xc2: case 0xc4: case 0xc7: case 0xc9: case 0xcb:
419            case 0xcd: case 0xce: case 0xd3: case 0xd4: case 0xd6: case 0xd7:
420            case 0xda: case 0xdc: case 0xdd: case 0xdf: case 0xe1: case 0xe2:
421            case 0xe4: case 0xe7: case 0xe9: case 0xeb: case 0xed: case 0xee:
422            case 0xf3: case 0xf4: case 0xf6: case 0xf7: case 0xfa: case 0xfc:
423            case 0xfd:
424               v = value; break;
425            case 0x20ac: v = '\x80'; break;
426            case 0x201a: v = '\x82'; break;
427            case 0x201e: v = '\x84'; break;
428            case 0x2026: v = '\x85'; break;
429            case 0x2020: v = '\x86'; break;
430            case 0x2021: v = '\x87'; break;
431            case 0x2030: v = '\x89'; break;
432            case 0x0160: v = '\x8a'; break;
433            case 0x2039: v = '\x8b'; break;
434            case 0x015a: v = '\x8c'; break;
435            case 0x0164: v = '\x8d'; break;
436            case 0x017d: v = '\x8e'; break;
437            case 0x0179: v = '\x8f'; break;
438            case 0x2018: v = '\x91'; break;
439            case 0x2019: v = '\x92'; break;
440            case 0x201c: v = '\x93'; break;
441            case 0x201d: v = '\x94'; break;
442            case 0x2022: v = '\x95'; break;
443            case 0x2013: v = '\x96'; break;
444            case 0x2014: v = '\x97'; break;
445            case 0x2122: v = '\x99'; break;
446            case 0x0161: v = '\x9a'; break;
447            case 0x203a: v = '\x9b'; break;
448            case 0x015b: v = '\x9c'; break;
449            case 0x0165: v = '\x9d'; break;
450            case 0x017e: v = '\x9e'; break;
451            case 0x017a: v = '\x9f'; break;
452            case 0x02c7: v = '\xa1'; break;
453            case 0x02d8: v = '\xa2'; break;
454            case 0x0141: v = '\xa3'; break;
455            case 0x0104: v = '\xa5'; break;
456            case 0x015e: v = '\xaa'; break; /* Scedil */
457            case 0x017b: v = '\xaf'; break;
458            case 0x02db: v = '\xb2'; break;
459            case 0x0142: v = '\xb3'; break;
460            case 0x0105: v = '\xb9'; break;
461            case 0x015f: v = '\xba'; break; /* scedil */
462            case 0x013d: v = '\xbc'; break;
463            case 0x02dd: v = '\xbd'; break;
464            case 0x013e: v = '\xbe'; break;
465            case 0x017c: v = '\xbf'; break;
466            case 0x0154: v = '\xc0'; break;
467            case 0x0102: v = '\xc3'; break;
468            case 0x0139: v = '\xc5'; break;
469            case 0x0106: v = '\xc6'; break;
470            case 0x010c: v = '\xc8'; break;
471            case 0x0118: v = '\xca'; break;
472            case 0x011a: v = '\xcc'; break;
473            case 0x010e: v = '\xcf'; break;
474            case 0x0110: v = '\xd0'; break;
475            case 0x0143: v = '\xd1'; break;
476            case 0x0147: v = '\xd2'; break;
477            case 0x0150: v = '\xd5'; break;
478            case 0x0158: v = '\xd8'; break;
479            case 0x016e: v = '\xd9'; break;
480            case 0x0170: v = '\xdb'; break;
481            case 0x0162: v = '\xde'; break; /* &Tcedil; */
482            case 0x0155: v = '\xe0'; break;
483            case 0x0103: v = '\xe3'; break;
484            case 0x013a: v = '\xe5'; break;
485            case 0x0107: v = '\xe6'; break;
486            case 0x010d: v = '\xe8'; break;
487            case 0x0119: v = '\xea'; break;
488            case 0x011b: v = '\xec'; break;
489            case 0x010f: v = '\xef'; break;
490            case 0x0111: v = '\xf0'; break;
491            case 0x0144: v = '\xf1'; break;
492            case 0x0148: v = '\xf2'; break;
493            case 0x0151: v = '\xf5'; break;
494            case 0x0159: v = '\xf8'; break;
495            case 0x016f: v = '\xf9'; break;
496            case 0x0171: v = '\xfb'; break;
497            case 0x0163: v = '\xfe'; break; /* tcedil */
498            case 0x02d9: v = '\xff'; break;
499         }
500         if (v == 0) break;
501         value = v;
502      }
503      *p = value;
504      return 1;
505   case CHARSET_WINCP1252:
506      /* MS Windows extensions to ISO-8859-1 */
507      /* This mapping is complete - there are 5 unused positions:
508       * 0x81 0x8d 0x8f 0x90 0x9d */
509      switch (value) {
510       case 0x2026: value = 0x85; break; /* hellip */
511       case 0x0160: value = 0x8a; break; /* Scaron */
512       case 0x0152: value = 0x8c; break; /* OElig */
513       case 0x017d: value = 0x8e; break; /* Zcaron */
514       case 0x2019: value = 0x92; break; /* rsquo */
515       case 0x201c: value = 0x93; break; /* ldquo */
516       case 0x201d: value = 0x94; break; /* rdquo */
517       case 0x0161: value = 0x9a; break; /* scaron */
518       case 0x0153: value = 0x9c; break; /* oelig */
519       case 0x017e: value = 0x9e; break; /* zcaron */
520#if 0
521       /* there are a few other obscure ones we don't currently need */
522       case 0x20ac: value = 0x80; break; /* euro */
523       case 0x201a: value = 0x82; break; /* sbquo */
524       case 0x0192: value = 0x83; break; /* fnof */
525       case 0x201e: value = 0x84; break; /* bdquo */
526       case 0x2020: value = 0x86; break; /* dagger */
527       case 0x2021: value = 0x87; break; /* Dagger */
528       case 0x02c6: value = 0x88; break; /* circ */
529       case 0x2030: value = 0x89; break; /* permil */
530       case 0x2039: value = 0x8b; break; /* lsaquo */
531       case 0x2018: value = 0x91; break; /* lsquo */
532       case 0x2022: value = 0x95; break; /* bull */
533       case 0x2013: value = 0x96; break; /* ndash */
534       case 0x2014: value = 0x97; break; /* mdash */
535       case 0x02dc: value = 0x98; break; /* tilde */
536       case 0x2122: value = 0x99; break; /* trade */
537       case 0x203a: value = 0x9b; break; /* rsaquo */
538       case 0x0178: value = 0x9f; break; /* Yuml */
539#endif
540      }
541      if (value < 0x100) {
542         *p = value;
543         return 1;
544      }
545      break;
546#endif
547#if OS_WIN32
548   case CHARSET_DOSCP850: {
549      static const unsigned char uni2dostab[] = {
550         255, 173, 189, 156, 207, 190, 221, 245,
551         249, 184, 166, 174, 170, 240, 169, 238,
552         248, 241, 253, 252, 239, 230, 244, 250,
553         247, 251, 167, 175, 172, 171, 243, 168,
554         183, 181, 182, 199, 142, 143, 146, 128,
555         212, 144, 210, 211, 222, 214, 215, 216,
556         209, 165, 227, 224, 226, 229, 153, 158,
557         157, 235, 233, 234, 154, 237, 232, 225,
558         133, 160, 131, 198, 132, 134, 145, 135,
559         138, 130, 136, 137, 141, 161, 140, 139,
560         208, 164, 149, 162, 147, 228, 148, 246,
561         155, 151, 163, 150, 129, 236, 231, 152
562      };
563      if (value >= 160 && value < 256) {
564         *p = (int)uni2dostab[value - 160];
565         return 1;
566      }
567#if 0
568      if (value == 305) { /* LATIN SMALL LETTER DOTLESS I */
569         *p = 213; /* "Modified CP850" has the Euro sign here. */
570         return 1;
571      }
572      if (value == 402) { /* LATIN SMALL LETTER F WITH HOOK */
573         *p = 159;
574         return 1;
575      }
576#endif
577      break;
578   }
579#endif
580   }
581   /* Transliterate characters we can't represent */
582#ifdef DEBUG
583   fprintf(stderr, "transliterate “%c” 0x%x\n", value, value);
584#endif
585   switch (value) {
586    case 160:
587      *p = ' '; return 1;
588    case 161 /* ¡ */:
589      *p = '!'; return 1;
590    case 176 /* ° */:
591      *p = 'd'; p[1] = 'g'; return 2;
592    case 191 /* ¿ */:
593      *p = '?'; return 1;
594    case 192 /* À */: case 193 /* Á */: case 194 /* Â */: case 195 /* Ã */:
595      *p = 'A'; return 1;
596    case 197 /* Å */:
597      p[1] = *p = 'A'; return 2;
598    case 196 /* Ä */: /* &Auml; */
599      *p = 'A';
600      if (!umlaut_to_e()) return 1;
601      p[1] = 'E'; return 2;
602    case 198 /* Æ */:
603      *p = 'A'; p[1] = 'E'; return 2;
604    case 199 /* Ç */: case 268: /* &Ccaron; */
605      *p = 'C'; return 1;
606    case 270: /* &Dcaron; */
607      *p = 'D'; return 1;
608    case 200 /* È */: case 201 /* É */: case 202 /* Ê */: case 203 /* Ë */:
609      *p = 'E'; return 1;
610    case 204 /* Ì */: case 205 /* Í */: case 206 /* Î */: case 207 /* Ï */:
611      *p = 'I'; return 1;
612    case 208 /* Ð */: case 222 /* Þ */:
613      *p = 'T'; p[1] = 'H'; return 2;
614    case 315: /* &Lacute; */
615    case 317: /* &Lcaron; */
616      *p = 'L'; return 1;
617    case 209 /* Ñ */:
618      *p = 'N'; return 1;
619    case 210 /* Ò */: case 211 /* Ó */: case 212 /* Ô */: case 213 /* Õ */:
620      *p = 'O'; return 1;
621    case 214 /* Ö */: /* &Ouml; */ case 0x152: /* &OElig; */
622      *p = 'O'; p[1] = 'E'; return 2;
623    case 352: /* &Scaron; */
624    case 0x15e: /* &Scedil; */
625      *p = 'S'; return 1;
626    case 0x162: /* &Tcedil; */
627    case 0x164: /* &Tcaron; */
628      *p = 'T'; return 1;
629    case 217 /* Ù */: case 218 /* Ú */: case 219 /* Û */:
630      *p = 'U'; return 1;
631    case 220 /* Ü */: /* &Uuml; */
632      *p = 'U'; p[1] = 'E'; return 2;
633    case 221 /* Ý */:
634      *p = 'Y'; return 1;
635    case 381: /* &Zcaron; */
636      *p = 'Z'; return 1;
637    case 223 /* ß */:
638      p[1] = *p = 's'; return 2;
639    case 224 /* à */: case 225 /* á */: case 226 /* â */: case 227 /* ã */:
640    case 259: /* &abreve; */
641      *p = 'a'; return 1;
642    case 228 /* ä */: /* &auml; */ case 230 /* æ */:
643      *p = 'a'; p[1] = 'e'; return 2;
644    case 229 /* å */:
645      p[1] = *p = 'a'; return 2;
646    case 231 /* ç */: case 269 /* &ccaron; */:
647      *p = 'c'; return 1;
648    case 271: /* &dcaron; */
649      *p = 'd'; return 1;
650    case 232 /* è */: case 233 /* é */: case 234 /* ê */: case 235 /* ë */:
651    case 283 /* &ecaron; */:
652      *p = 'e'; return 1;
653    case 236 /* ì */: case 237 /* í */: case 238 /* î */: case 239 /* ï */:
654      *p = 'i'; return 1;
655    case 316 /* &lacute; */:
656    case 318 /* &lcaron; */:
657      *p = 'l'; return 1;
658    case 241 /* ñ */: case 328 /* &ncaron; */:
659      *p = 'n'; return 1;
660    case 345: /* &rcaron; */
661      *p = 'r'; return 1;
662    case 353: /* &scaron; */
663    case 0x15f: /* &scedil; */
664      *p = 's'; return 1;
665    case 357: /* &tcaron; */
666    case 0x163: /* &tcedil; */
667      *p = 't'; return 1;
668    case 240 /* ð */: case 254 /* þ */:
669      *p = 't'; p[1] = 'h'; return 2;
670    case 242 /* ò */: case 243 /* ó */: case 244 /* ô */: case 245 /* õ */:
671      *p = 'o'; return 1;
672    case 246 /* ö */: /* &ouml; */ case 0x153: /* &oelig; */
673      *p = 'o'; p[1] = 'e'; return 2;
674    case 249 /* ù */: case 250 /* ú */: case 251 /* û */:
675    case 367 /* &uring; */:
676      *p = 'u'; return 1;
677    case 252 /* ü */: /* &uuml; */
678      *p = 'u'; p[1] = 'e'; return 2;
679    case 253 /* ý */: case 255 /* ÿ */:
680      *p = 'y'; return 1;
681    case 382: /* &zcaron; */
682      *p = 'z'; return 1;
683    case 0x2019: /* &lsquo; */
684      *p = '\''; return 1;
685    case 171: /* « */ case 187: /* » */
686    case 0x201c: /* &ldquo; */ case 0x201d: /* &rdquo; */
687      *p = '"'; return 1;
688    case 0x2026: /* &hellip; */
689      *p = '.'; p[1] = '.'; p[2] = '.'; return 3;
690    case 0x2192: /* &rarr; */
691      *p = '-'; p[1] = '>'; return 2;
692    case 0x1d4d: /* gradient symbol */
693      *p = 'g'; p[1] = 'r'; p[2] = 'd'; return 3;
694    case 0x221e: /* infinity symbol */
695      *p = 'i'; p[1] = 'n'; p[2] = 'f'; return 3;
696   }
697#ifdef DEBUG
698   /* 169 is reported (copyright symbol), but there isn't a good <= 2 ASCII
699    * character transliteration for that, so we handle that elsewhere. */
700   fprintf(stderr, "failed to transliterate codepoint %d\n", value);
701#endif
702   return 0;
703}
704
705#if OS_UNIX && defined DATADIR && defined PACKAGE
706/* Under Unix, we compile in the configured path */
707static const char *pth_cfg_files = DATADIR "/" PACKAGE;
708#else
709/* On other platforms, we fall back on looking in the current directory */
710static const char *pth_cfg_files = "";
711#endif
712
713static int num_msgs = 0;
714static char **msg_array = NULL;
715
716static bool msg_lang_explicit = false;
717const char *msg_lang = NULL;
718const char *msg_lang2 = NULL;
719
720static char **
721parse_msgs(int n, unsigned char *p, int charset_code) {
722   int i;
723
724   char **msgs = osmalloc(n * sizeof(char *));
725
726   for (i = 0; i < n; i++) {
727      unsigned char *to = p;
728      int ch;
729      msgs[i] = (char *)p;
730
731      /* If we want UTF8 anyway, we just need to find the start of each
732       * message */
733      if (charset_code == CHARSET_UTF8) {
734         p += strlen((char *)p) + 1;
735         continue;
736      }
737
738      while ((ch = *p++) != 0) {
739         /* A byte in the range 0x80-0xbf or 0xf0-0xff isn't valid in
740          * this state, (0xf0-0xfd mean values > 0xffff) so treat as
741          * literal and try to resync so we cope better when fed
742          * non-utf-8 data.  Similarly we abandon a multibyte sequence
743          * if we hit an invalid character. */
744         if (ch >= 0xc0 && ch < 0xf0) {
745            int ch1 = *p;
746            if ((ch1 & 0xc0) != 0x80) goto resync;
747
748            if (ch < 0xe0) {
749               /* 2 byte sequence */
750               ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
751               p++;
752            } else {
753               /* 3 byte sequence */
754               int ch2 = p[1];
755               if ((ch2 & 0xc0) != 0x80) goto resync;
756               ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6) | (ch2 & 0x3f);
757               p += 2;
758            }
759         }
760
761         resync:
762
763         if (ch < 127) {
764            *to++ = (char)ch;
765         } else {
766            /* We assume an N byte UTF-8 code never transliterates to more
767             * than N characters (so we can't transliterate © to (C) or
768             * ® to (R) for example) */
769            to += add_unicode(charset_code, to, ch);
770         }
771      }
772      *to++ = '\0';
773   }
774   return msgs;
775}
776
777/* This is the name of the default language, which can be set like so:
778 * ./configure --enable-defaultlang=fr
779 */
780#ifdef DEFAULTLANG
781/* No point extracting these errors as they won't get used if file opens */
782# include "../lib/defaultlang.h"
783#else
784#define N_DONTEXTRACTMSGS 5
785static unsigned char dontextractmsgs[] =
786   "Can't open message file \"%s\" using path \"%s\"\0"/*1000*/
787   "Problem with message file \"%s\"\0"/*1001*/
788   "I don't understand this message file version\0"/*1002*/
789   "Message file truncated?\0"/*1003*/
790   "Out of memory (couldn't find %lu bytes).\0"/*1004*/;
791#endif
792
793static char **dontextract = NULL;
794
795static void
796parse_msg_file(int charset_code)
797{
798   FILE *fh;
799   unsigned char header[20];
800   int i;
801   unsigned len;
802   unsigned char *p;
803   char *fnm, *s;
804   int n;
805
806#ifdef DEBUG
807   fprintf(stderr, "parse_msg_file(%d)\n", charset_code);
808#endif
809
810   /* sort out messages we need to print if we can't open the message file */
811   dontextract = parse_msgs(N_DONTEXTRACTMSGS, dontextractmsgs, charset_code);
812
813   fnm = osstrdup(msg_lang);
814   /* trim off charset from stuff like "de_DE.iso8859_1" */
815   s = strchr(fnm, '.');
816   if (s) *s = '\0';
817   /* trim off any "@<something>" modifier. */
818   s = strchr(fnm, '@');
819   if (s) *s = '\0';
820
821   fh = fopenWithPthAndExt(pth_cfg_files, fnm, EXT_SVX_MSG, "rb", NULL);
822
823   if (!fh && strlen(fnm) > 3 && fnm[2] == '_') {
824      /* e.g. if 'en_GB' is unknown, see if we know 'en' */
825      fnm[2] = '\0';
826      fh = fopenWithPthAndExt(pth_cfg_files, fnm, EXT_SVX_MSG, "rb", NULL);
827      if (!fh) fnm[2] = '_'; /* for error reporting */
828   }
829
830   if (!fh && !msg_lang_explicit) {
831      /* If msg_lang wasn't specified using environment variable SURVEXLANG,
832       * then default to 'en' if we don't find messages for language msg_lang.
833       */
834      if (fnm[0] && fnm[1]) {
835         strcpy(fnm, "en");
836      } else {
837         osfree(fnm);
838         fnm = osstrdup("en");
839      }
840      fh = fopenWithPthAndExt(pth_cfg_files, fnm, EXT_SVX_MSG, "rb", NULL);
841   }
842
843   if (!fh) {
844      fatalerror(/*Can't open message file “%s” using path “%s”*/1000,
845                 fnm, pth_cfg_files);
846   }
847
848   if (fread(header, 1, 20, fh) < 20 ||
849       memcmp(header, "Svx\nMsg\r\n\xfe\xff", 12) != 0) {
850      fatalerror(/*Problem with message file “%s”*/1001, fnm);
851   }
852
853   if (header[12] != 0)
854      fatalerror(/*I don't understand this message file version*/1002);
855
856   n = (header[14] << 8) | header[15];
857
858   len = 0;
859   for (i = 16; i < 20; i++) len = (len << 8) | header[i];
860
861   p = osmalloc(len);
862   if (fread(p, 1, len, fh) < len)
863      fatalerror(/*Message file truncated?*/1003);
864
865   fclose(fh);
866
867#ifdef DEBUG
868   fprintf(stderr, "fnm = “%s”, n = %d, len = %d\n", fnm, n, len);
869#endif
870   osfree(fnm);
871
872   msg_array = parse_msgs(n, p, charset_code);
873   num_msgs = n;
874}
875
876const char *
877msg_cfgpth(void)
878{
879   return pth_cfg_files;
880}
881
882const char *
883msg_exepth(void)
884{
885   return exe_pth;
886}
887
888const char *
889msg_appname(void)
890{
891   return appname_copy;
892}
893
894void
895(msg_init)(char * const *argv)
896{
897   char *p;
898   SVX_ASSERT(argv);
899
900   /* Point to argv[0] itself so we report a more helpful error if the
901    * code to work out the clean appname generates a signal */
902   appname_copy = argv[0];
903#if OS_UNIX
904   /* use name as-is on Unix - programs run from path get name as supplied */
905   appname_copy = osstrdup(argv[0]);
906#else
907   /* use the lower-cased leafname on other platforms */
908   p = leaf_from_fnm(argv[0]);
909   appname_copy = p;
910   while (*p) {
911      *p = tolower((unsigned char)*p);
912      ++p;
913   }
914#endif
915
916   /* shortcut --version so you can check the version number even when the
917    * correct message file can't be found... */
918   if (argv[1] && strcmp(argv[1], "--version") == 0) {
919      cmdline_version();
920      exit(0);
921   }
922   if (argv[0]) {
923      exe_pth = path_from_fnm(argv[0]);
924#if OS_UNIX && defined DATADIR && defined PACKAGE
925      bool free_pth = false;
926      char *pth = getenv("srcdir");
927      if (!pth || !pth[0]) {
928         pth = path_from_fnm(argv[0]);
929         free_pth = true;
930      }
931      if (pth[0]) {
932         struct stat buf;
933#if OS_UNIX_MACOS
934# ifndef AVEN
935         /* On macOS the programs may be installed anywhere, with the
936          * share directory and the binaries in the same directory. */
937         p = use_path(pth, "share/survex/en.msg");
938         if (stat(p, &buf) == 0 && S_ISREG(buf.st_mode)) {
939            pth_cfg_files = use_path(pth, "share/survex");
940            goto macos_got_msg;
941         }
942         osfree(p);
943# endif
944         /* In the diskimage package, this case is used for aven, and for
945          * the hardlinked copies of cavern and extend alongside the aven
946          * binary, which are the ones which aven runs.
947          */
948         p = use_path(pth, "../Resources/en.msg");
949         if (stat(p, &buf) == 0 && S_ISREG(buf.st_mode)) {
950            pth_cfg_files = use_path(pth, "../Resources");
951            goto macos_got_msg;
952         }
953         osfree(p);
954#endif
955         /* If we're run with an explicit path, check if "../lib/en.msg"
956          * from the program's path exists, and if so look there for
957          * support files - this allows us to test binaries in the build
958          * tree easily. */
959         p = use_path(pth, "../lib/en.msg");
960         if (stat(p, &buf) == 0) {
961#ifdef S_ISREG
962            /* POSIX way */
963            if (S_ISREG(buf.st_mode)) {
964               pth_cfg_files = use_path(pth, "../lib");
965            }
966#else
967            /* BSD way */
968            if ((buf.st_mode & S_IFMT) == S_IFREG) {
969               pth_cfg_files = use_path(pth, "../lib");
970            }
971#endif
972         }
973#if defined(__GNUC__) && defined(__APPLE_CC__)
974macos_got_msg:
975#endif
976         osfree(p);
977      }
978
979      if (free_pth) osfree(pth);
980#elif OS_WIN32
981      DWORD len = 256;
982      char *buf = NULL, *modname;
983      while (1) {
984          DWORD got;
985          buf = osrealloc(buf, len);
986          got = GetModuleFileName(NULL, buf, len);
987          if (got < len) break;
988          len += len;
989      }
990      modname = buf;
991      /* Strange Win32 nastiness - strip prefix "\\?\" if present */
992      if (strncmp(modname, "\\\\?\\", 4) == 0) modname += 4;
993      pth_cfg_files = path_from_fnm(modname);
994      osfree(buf);
995#else
996      /* Get the path to the support files from argv[0] */
997      pth_cfg_files = path_from_fnm(argv[0]);
998#endif
999   }
1000
1001   msg_lang = getenv("SURVEXLANG");
1002#ifdef DEBUG
1003   fprintf(stderr, "msg_lang = %p (= \"%s\")\n", msg_lang, msg_lang?msg_lang:"(null)");
1004#endif
1005
1006   msg_lang_explicit = true;
1007   if (!msg_lang || !*msg_lang) {
1008      msg_lang_explicit = false;
1009      msg_lang = getenv("LC_ALL");
1010   }
1011   if (!msg_lang || !*msg_lang) {
1012      msg_lang = getenv("LC_MESSAGES");
1013      if (!msg_lang || !*msg_lang) {
1014         msg_lang = getenv("LANG");
1015         /* Something (AutoCAD?) on Microsoft Windows sets LANG to a number. */
1016         if (msg_lang && !isalpha(msg_lang[0])) msg_lang = NULL;
1017      }
1018      if (!msg_lang || !*msg_lang) {
1019#if OS_WIN32
1020         LCID locid;
1021#endif
1022#ifdef DEFAULTLANG
1023         msg_lang = STRING(DEFAULTLANG);
1024#else
1025         msg_lang = "en";
1026#endif
1027#if OS_WIN32
1028         /* GetUserDefaultUILanguage() requires Microsoft Windows 2000 or
1029          * newer, but we don't support anything earlier than Vista.
1030          */
1031         locid = GetUserDefaultUILanguage();
1032         if (locid) {
1033            WORD langid = LANGIDFROMLCID(locid);
1034            switch (PRIMARYLANGID(langid)) {
1035             case LANG_BULGARIAN:
1036               msg_lang = "bg";
1037               break;
1038/* older mingw compilers don't seem to supply this value */
1039#ifndef LANG_CATALAN
1040# define LANG_CATALAN 0x03
1041#endif
1042             case LANG_CATALAN:
1043               msg_lang = "ca";
1044               break;
1045             case LANG_CHINESE:
1046               msg_lang = "zh_CN";
1047               break;
1048             case LANG_ENGLISH:
1049               if (SUBLANGID(langid) == SUBLANG_ENGLISH_US)
1050                  msg_lang = "en_US";
1051               else
1052                  msg_lang = "en";
1053               break;
1054             case LANG_FRENCH:
1055               msg_lang = "fr";
1056               break;
1057             case LANG_GERMAN:
1058               switch (SUBLANGID(langid)) {
1059                case SUBLANG_GERMAN_SWISS:
1060                  msg_lang = "de_CH";
1061                  break;
1062                default:
1063                  msg_lang = "de";
1064               }
1065               break;
1066             case LANG_GREEK:
1067               msg_lang = "el";
1068               break;
1069             case LANG_HUNGARIAN:
1070               msg_lang = "hu";
1071               break;
1072             case LANG_INDONESIAN:
1073               msg_lang = "id";
1074               break;
1075             case LANG_ITALIAN:
1076               msg_lang = "it";
1077               break;
1078             case LANG_POLISH:
1079               msg_lang = "pl";
1080               break;
1081             case LANG_PORTUGUESE:
1082               if (SUBLANGID(langid) == SUBLANG_PORTUGUESE_BRAZILIAN)
1083                  msg_lang = "pt_BR";
1084               else
1085                  msg_lang = "pt";
1086               break;
1087             case LANG_ROMANIAN:
1088               msg_lang = "ro";
1089               break;
1090             case LANG_RUSSIAN:
1091               msg_lang = "ru";
1092               break;
1093             case LANG_SLOVAK:
1094               msg_lang = "sk";
1095               break;
1096             case LANG_SPANISH:
1097               msg_lang = "es";
1098               break;
1099            }
1100         }
1101#endif
1102      }
1103   }
1104#ifdef DEBUG
1105   fprintf(stderr, "msg_lang = %p (= \"%s\")\n", msg_lang, msg_lang?msg_lang:"(null)");
1106#endif
1107
1108   /* On Mandrake LANG defaults to C */
1109   if (strcmp(msg_lang, "C") == 0) msg_lang = "en";
1110
1111   { /* If msg_lang has a country code, snip it out to give msg_lang2. */
1112      size_t b = 0;
1113      while (isalpha((unsigned char)msg_lang[b])) {
1114         ++b;
1115      }
1116      if (msg_lang[b] == '_') {
1117         char * tmp;
1118         size_t e = b + 1;
1119         while (isalpha((unsigned char)msg_lang[e])) {
1120            ++e;
1121         }
1122         tmp = osstrdup(msg_lang);
1123         memmove(tmp + b, tmp + e, strlen(tmp + e) + 1);
1124         msg_lang2 = tmp;
1125      }
1126   }
1127
1128#ifdef LC_MESSAGES
1129   /* try to setlocale() appropriately too */
1130   if (!setlocale(LC_MESSAGES, msg_lang)) {
1131      if (msg_lang2) {
1132         (void)setlocale(LC_MESSAGES, msg_lang2);
1133      }
1134   }
1135#endif
1136
1137   select_charset(default_charset());
1138}
1139
1140#ifndef AVEN
1141/* Return message if messages available, else a fallback value. */
1142static const char *
1143msg_opt(int en, const char * fallback)
1144{
1145   /* NB can't use SVX_ASSERT here! */
1146   if (!msg_array || en <= 0 || en >= num_msgs) {
1147      return fallback;
1148   }
1149
1150   return msg_array[en];
1151}
1152#endif
1153
1154const char *
1155msg(int en)
1156{
1157   /* NB can't use SVX_ASSERT here! */
1158   if (dontextract && en >= 1000 && en < 1000 + N_DONTEXTRACTMSGS)
1159      return dontextract[en - 1000];
1160   if (!msg_array) {
1161      if (en != 1)  {
1162         fprintf(STDERR, "Message %d requested before fully initialised\n", en);
1163         return "Message requested before fully initialised\n";
1164      }
1165      /* this should be the only other message which can be requested before
1166       * the message file is opened and read... */
1167      if (!dontextract) return "Out of memory (couldn't find %lu bytes).";
1168      return dontextract[(/*Out of memory (couldn't find %lu bytes).*/1004)
1169                         - 1000];
1170   }
1171
1172   if (en < 0 || en >= num_msgs) {
1173      fprintf(STDERR, "Message %d out of range\n", en);
1174      return "Message out of range\n";
1175   }
1176
1177   if (en == 0) {
1178      const char *p = msg_array[0];
1179      if (!*p) p = "(C)";
1180      return p;
1181   }
1182
1183   return msg_array[en];
1184}
1185
1186void
1187v_report(int severity, const char *fnm, int line, int col, int en, va_list ap)
1188{
1189#ifdef AVEN
1190   (void)col;
1191   aven_v_report(severity, fnm, line, en, ap);
1192#else
1193   const char * level;
1194   if (fnm) {
1195      fputs(fnm, STDERR);
1196      if (line) fprintf(STDERR, ":%d", line);
1197      if (col > 0) fprintf(STDERR, ":%d", col);
1198   } else {
1199      fputs(appname_copy, STDERR);
1200   }
1201   fputs(": ", STDERR);
1202
1203   switch (severity) {
1204    case DIAG_INFO:
1205      /* TRANSLATORS: Indicates a informational message e.g.:
1206       * "spoon.svx:12: info: Declination: [...]" */
1207      level = msg_opt(/*info*/485, "info");
1208      break;
1209    case DIAG_WARN:
1210      /* TRANSLATORS: Indicates a warning message e.g.:
1211       * "spoon.svx:12: warning: *prefix is deprecated" */
1212      level = msg_opt(/*warning*/4, "warning");
1213      break;
1214    default:
1215      /* TRANSLATORS: Indicates an error message e.g.:
1216       * "spoon.svx:13:4: error: Field may not be omitted" */
1217      level = msg_opt(/*error*/93, "error");
1218      break;
1219   }
1220   fputs(level, STDERR);
1221   fputs(": ", STDERR);
1222
1223   vfprintf(STDERR, msg(en), ap);
1224   fputnl(STDERR);
1225#endif
1226
1227   switch (severity) {
1228    case DIAG_WARN:
1229      msg_warnings++;
1230      break;
1231    case DIAG_ERR:
1232      msg_errors++;
1233      if (msg_errors == 50)
1234         fatalerror_in_file(fnm, 0, /*Too many errors - giving up*/19);
1235      break;
1236    case DIAG_FATAL:
1237      exit(EXIT_FAILURE);
1238   }
1239}
1240
1241void
1242diag(int severity, int en, ...)
1243{
1244   va_list ap;
1245   va_start(ap, en);
1246   v_report(severity, NULL, 0, 0, en, ap);
1247   va_end(ap);
1248}
1249
1250void
1251diag_in_file(int severity, const char *fnm, int line, int en, ...)
1252{
1253   va_list ap;
1254   va_start(ap, en);
1255   v_report(severity, fnm, line, 0, en, ap);
1256   va_end(ap);
1257}
1258
1259/* Code to support switching character set at runtime (e.g. for a printer
1260 * driver to support different character sets on screen and on the printer)
1261 */
1262typedef struct charset_li {
1263   struct charset_li *next;
1264   int code;
1265   char **msg_array;
1266} charset_li;
1267
1268static charset_li *charset_head = NULL;
1269
1270static int charset = CHARSET_BAD;
1271
1272int
1273select_charset(int charset_code)
1274{
1275   int old_charset = charset;
1276   charset_li *p;
1277
1278#ifdef DEBUG
1279   fprintf(stderr, "select_charset(%d), old charset = %d\n", charset_code,
1280           charset);
1281#endif
1282
1283   charset = charset_code;
1284
1285   /* check if we've already parsed messages for new charset */
1286   for (p = charset_head; p; p = p->next) {
1287#ifdef DEBUG
1288      printf("%p: code %d msg_array %p\n", p, p->code, p->msg_array);
1289#endif
1290      if (p->code == charset) {
1291         msg_array = p->msg_array;
1292         return old_charset;
1293      }
1294   }
1295
1296   /* nope, got to reparse message file */
1297   parse_msg_file(charset_code);
1298
1299   /* add to list */
1300   p = osnew(charset_li);
1301   p->code = charset;
1302   p->msg_array = msg_array;
1303   p->next = charset_head;
1304   charset_head = p;
1305
1306   return old_charset;
1307}
Note: See TracBrowser for help on using the repository browser.