nl_langinfo.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /* nl_langinfo() replacement: query locale dependent information.
  2. Copyright (C) 2007-2023 Free Software Foundation, Inc.
  3. This file is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Lesser General Public License as
  5. published by the Free Software Foundation; either version 2.1 of the
  6. License, or (at your option) any later version.
  7. This file is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>. */
  13. #include <config.h>
  14. /* Specification. */
  15. #include <langinfo.h>
  16. #include <locale.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #if defined _WIN32 && ! defined __CYGWIN__
  20. # define WIN32_LEAN_AND_MEAN /* avoid including junk */
  21. # include <windows.h>
  22. # include <stdio.h>
  23. #endif
  24. #if REPLACE_NL_LANGINFO && !NL_LANGINFO_MTSAFE
  25. # if defined _WIN32 && !defined __CYGWIN__
  26. # define WIN32_LEAN_AND_MEAN /* avoid including junk */
  27. # include <windows.h>
  28. # elif HAVE_PTHREAD_API
  29. # include <pthread.h>
  30. # if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS
  31. # include <threads.h>
  32. # pragma weak thrd_exit
  33. # define c11_threads_in_use() (thrd_exit != NULL)
  34. # else
  35. # define c11_threads_in_use() 0
  36. # endif
  37. # elif HAVE_THREADS_H
  38. # include <threads.h>
  39. # endif
  40. #endif
  41. /* nl_langinfo() must be multithread-safe. To achieve this without using
  42. thread-local storage:
  43. 1. We use a specific static buffer for each possible argument.
  44. So that different threads can call nl_langinfo with different arguments,
  45. without interfering.
  46. 2. We use a simple strcpy or memcpy to fill this static buffer. Filling it
  47. through, for example, strcpy + strcat would not be guaranteed to leave
  48. the buffer's contents intact if another thread is currently accessing
  49. it. If necessary, the contents is first assembled in a stack-allocated
  50. buffer. */
  51. #if !REPLACE_NL_LANGINFO || GNULIB_defined_CODESET
  52. /* Return the codeset of the current locale, if this is easily deducible.
  53. Otherwise, return "". */
  54. static char *
  55. ctype_codeset (void)
  56. {
  57. static char result[2 + 10 + 1];
  58. char buf[2 + 10 + 1];
  59. char locale[SETLOCALE_NULL_MAX];
  60. char *codeset;
  61. size_t codesetlen;
  62. if (setlocale_null_r (LC_CTYPE, locale, sizeof (locale)))
  63. locale[0] = '\0';
  64. codeset = buf;
  65. codeset[0] = '\0';
  66. if (locale[0])
  67. {
  68. /* If the locale name contains an encoding after the dot, return it. */
  69. char *dot = strchr (locale, '.');
  70. if (dot)
  71. {
  72. /* Look for the possible @... trailer and remove it, if any. */
  73. char *codeset_start = dot + 1;
  74. char const *modifier = strchr (codeset_start, '@');
  75. if (! modifier)
  76. codeset = codeset_start;
  77. else
  78. {
  79. codesetlen = modifier - codeset_start;
  80. if (codesetlen < sizeof buf)
  81. {
  82. codeset = memcpy (buf, codeset_start, codesetlen);
  83. codeset[codesetlen] = '\0';
  84. }
  85. }
  86. }
  87. }
  88. # if defined _WIN32 && ! defined __CYGWIN__
  89. /* If setlocale is successful, it returns the number of the
  90. codepage, as a string. Otherwise, fall back on Windows API
  91. GetACP, which returns the locale's codepage as a number (although
  92. this doesn't change according to what the 'setlocale' call specified).
  93. Either way, prepend "CP" to make it a valid codeset name. */
  94. codesetlen = strlen (codeset);
  95. if (0 < codesetlen && codesetlen < sizeof buf - 2)
  96. memmove (buf + 2, codeset, codesetlen + 1);
  97. else
  98. sprintf (buf + 2, "%u", GetACP ());
  99. /* For a locale name such as "French_France.65001", in Windows 10,
  100. setlocale now returns "French_France.utf8" instead. */
  101. if (strcmp (buf + 2, "65001") == 0 || strcmp (buf + 2, "utf8") == 0)
  102. return (char *) "UTF-8";
  103. else
  104. {
  105. memcpy (buf, "CP", 2);
  106. strcpy (result, buf);
  107. return result;
  108. }
  109. # else
  110. strcpy (result, codeset);
  111. return result;
  112. #endif
  113. }
  114. #endif
  115. #if REPLACE_NL_LANGINFO
  116. /* Override nl_langinfo with support for added nl_item values. */
  117. # undef nl_langinfo
  118. /* Without locking, on Solaris 11.3, test-nl_langinfo-mt fails, with message
  119. "thread5 disturbed by threadN!", even when threadN invokes only
  120. nl_langinfo (CODESET);
  121. nl_langinfo (CRNCYSTR);
  122. Similarly on Solaris 10. */
  123. # if !NL_LANGINFO_MTSAFE /* Solaris */
  124. # define ITEMS (MAXSTRMSG + 1)
  125. # define MAX_RESULT_LEN 80
  126. static char *
  127. nl_langinfo_unlocked (nl_item item)
  128. {
  129. static char result[ITEMS][MAX_RESULT_LEN];
  130. /* The result of nl_langinfo is in storage that can be overwritten by
  131. other calls to nl_langinfo. */
  132. char *tmp = nl_langinfo (item);
  133. if (item >= 0 && item < ITEMS && tmp != NULL)
  134. {
  135. size_t tmp_len = strlen (tmp);
  136. if (tmp_len < MAX_RESULT_LEN)
  137. strcpy (result[item], tmp);
  138. else
  139. {
  140. /* Produce a truncated result. Oh well... */
  141. result[item][MAX_RESULT_LEN - 1] = '\0';
  142. memcpy (result[item], tmp, MAX_RESULT_LEN - 1);
  143. }
  144. return result[item];
  145. }
  146. else
  147. return tmp;
  148. }
  149. /* Use a lock, so that no two threads can invoke nl_langinfo_unlocked
  150. at the same time. */
  151. /* Prohibit renaming this symbol. */
  152. # undef gl_get_nl_langinfo_lock
  153. # if defined _WIN32 && !defined __CYGWIN__
  154. extern __declspec(dllimport) CRITICAL_SECTION *gl_get_nl_langinfo_lock (void);
  155. static char *
  156. nl_langinfo_with_lock (nl_item item)
  157. {
  158. CRITICAL_SECTION *lock = gl_get_nl_langinfo_lock ();
  159. char *ret;
  160. EnterCriticalSection (lock);
  161. ret = nl_langinfo_unlocked (item);
  162. LeaveCriticalSection (lock);
  163. return ret;
  164. }
  165. # elif HAVE_PTHREAD_API
  166. extern
  167. # if defined _WIN32 || defined __CYGWIN__
  168. __declspec(dllimport)
  169. # endif
  170. pthread_mutex_t *gl_get_nl_langinfo_lock (void);
  171. # if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */
  172. /* Avoid the need to link with '-lpthread'. */
  173. # pragma weak pthread_mutex_lock
  174. # pragma weak pthread_mutex_unlock
  175. /* Determine whether libpthread is in use. */
  176. # pragma weak pthread_mutexattr_gettype
  177. /* See the comments in lock.h. */
  178. # define pthread_in_use() \
  179. (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
  180. # else
  181. # define pthread_in_use() 1
  182. # endif
  183. static char *
  184. nl_langinfo_with_lock (nl_item item)
  185. {
  186. if (pthread_in_use())
  187. {
  188. pthread_mutex_t *lock = gl_get_nl_langinfo_lock ();
  189. char *ret;
  190. if (pthread_mutex_lock (lock))
  191. abort ();
  192. ret = nl_langinfo_unlocked (item);
  193. if (pthread_mutex_unlock (lock))
  194. abort ();
  195. return ret;
  196. }
  197. else
  198. return nl_langinfo_unlocked (item);
  199. }
  200. # elif HAVE_THREADS_H
  201. extern mtx_t *gl_get_nl_langinfo_lock (void);
  202. static char *
  203. nl_langinfo_with_lock (nl_item item)
  204. {
  205. mtx_t *lock = gl_get_nl_langinfo_lock ();
  206. char *ret;
  207. if (mtx_lock (lock) != thrd_success)
  208. abort ();
  209. ret = nl_langinfo_unlocked (item);
  210. if (mtx_unlock (lock) != thrd_success)
  211. abort ();
  212. return ret;
  213. }
  214. # endif
  215. # else
  216. /* On other platforms, no lock is needed. */
  217. # define nl_langinfo_with_lock nl_langinfo
  218. # endif
  219. char *
  220. rpl_nl_langinfo (nl_item item)
  221. {
  222. switch (item)
  223. {
  224. # if GNULIB_defined_CODESET
  225. case CODESET:
  226. return ctype_codeset ();
  227. # endif
  228. # if GNULIB_defined_T_FMT_AMPM
  229. case T_FMT_AMPM:
  230. return (char *) "%I:%M:%S %p";
  231. # endif
  232. # if GNULIB_defined_ALTMON
  233. case ALTMON_1:
  234. case ALTMON_2:
  235. case ALTMON_3:
  236. case ALTMON_4:
  237. case ALTMON_5:
  238. case ALTMON_6:
  239. case ALTMON_7:
  240. case ALTMON_8:
  241. case ALTMON_9:
  242. case ALTMON_10:
  243. case ALTMON_11:
  244. case ALTMON_12:
  245. /* We don't ship the appropriate localizations with gnulib. Therefore,
  246. treat ALTMON_i like MON_i. */
  247. item = item - ALTMON_1 + MON_1;
  248. break;
  249. # endif
  250. # if GNULIB_defined_ERA
  251. case ERA:
  252. /* The format is not standardized. In glibc it is a sequence of strings
  253. of the form "direction:offset:start_date:end_date:era_name:era_format"
  254. with an empty string at the end. */
  255. return (char *) "";
  256. case ERA_D_FMT:
  257. /* The %Ex conversion in strftime behaves like %x if the locale does not
  258. have an alternative time format. */
  259. item = D_FMT;
  260. break;
  261. case ERA_D_T_FMT:
  262. /* The %Ec conversion in strftime behaves like %c if the locale does not
  263. have an alternative time format. */
  264. item = D_T_FMT;
  265. break;
  266. case ERA_T_FMT:
  267. /* The %EX conversion in strftime behaves like %X if the locale does not
  268. have an alternative time format. */
  269. item = T_FMT;
  270. break;
  271. case ALT_DIGITS:
  272. /* The format is not standardized. In glibc it is a sequence of 10
  273. strings, appended in memory. */
  274. return (char *) "\0\0\0\0\0\0\0\0\0\0";
  275. # endif
  276. # if GNULIB_defined_YESEXPR || !FUNC_NL_LANGINFO_YESEXPR_WORKS
  277. case YESEXPR:
  278. return (char *) "^[yY]";
  279. case NOEXPR:
  280. return (char *) "^[nN]";
  281. # endif
  282. default:
  283. break;
  284. }
  285. return nl_langinfo_with_lock (item);
  286. }
  287. #else
  288. /* Provide nl_langinfo from scratch, either for native MS-Windows, or
  289. for old Unix platforms without locales, such as Linux libc5 or
  290. BeOS. */
  291. # include <time.h>
  292. char *
  293. nl_langinfo (nl_item item)
  294. {
  295. char buf[100];
  296. struct tm tmm = { 0 };
  297. switch (item)
  298. {
  299. /* nl_langinfo items of the LC_CTYPE category */
  300. case CODESET:
  301. {
  302. char *codeset = ctype_codeset ();
  303. if (*codeset)
  304. return codeset;
  305. }
  306. # ifdef __BEOS__
  307. return (char *) "UTF-8";
  308. # else
  309. return (char *) "ISO-8859-1";
  310. # endif
  311. /* nl_langinfo items of the LC_NUMERIC category */
  312. case RADIXCHAR:
  313. return localeconv () ->decimal_point;
  314. case THOUSEP:
  315. return localeconv () ->thousands_sep;
  316. # ifdef GROUPING
  317. case GROUPING:
  318. return localeconv () ->grouping;
  319. # endif
  320. /* nl_langinfo items of the LC_TIME category.
  321. TODO: Really use the locale. */
  322. case D_T_FMT:
  323. case ERA_D_T_FMT:
  324. return (char *) "%a %b %e %H:%M:%S %Y";
  325. case D_FMT:
  326. case ERA_D_FMT:
  327. return (char *) "%m/%d/%y";
  328. case T_FMT:
  329. case ERA_T_FMT:
  330. return (char *) "%H:%M:%S";
  331. case T_FMT_AMPM:
  332. return (char *) "%I:%M:%S %p";
  333. case AM_STR:
  334. {
  335. static char result[80];
  336. if (!strftime (buf, sizeof result, "%p", &tmm))
  337. return (char *) "AM";
  338. strcpy (result, buf);
  339. return result;
  340. }
  341. case PM_STR:
  342. {
  343. static char result[80];
  344. tmm.tm_hour = 12;
  345. if (!strftime (buf, sizeof result, "%p", &tmm))
  346. return (char *) "PM";
  347. strcpy (result, buf);
  348. return result;
  349. }
  350. case DAY_1:
  351. case DAY_2:
  352. case DAY_3:
  353. case DAY_4:
  354. case DAY_5:
  355. case DAY_6:
  356. case DAY_7:
  357. {
  358. static char result[7][50];
  359. static char const days[][sizeof "Wednesday"] = {
  360. "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
  361. "Friday", "Saturday"
  362. };
  363. tmm.tm_wday = item - DAY_1;
  364. if (!strftime (buf, sizeof result[0], "%A", &tmm))
  365. return (char *) days[item - DAY_1];
  366. strcpy (result[item - DAY_1], buf);
  367. return result[item - DAY_1];
  368. }
  369. case ABDAY_1:
  370. case ABDAY_2:
  371. case ABDAY_3:
  372. case ABDAY_4:
  373. case ABDAY_5:
  374. case ABDAY_6:
  375. case ABDAY_7:
  376. {
  377. static char result[7][30];
  378. static char const abdays[][sizeof "Sun"] = {
  379. "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
  380. };
  381. tmm.tm_wday = item - ABDAY_1;
  382. if (!strftime (buf, sizeof result[0], "%a", &tmm))
  383. return (char *) abdays[item - ABDAY_1];
  384. strcpy (result[item - ABDAY_1], buf);
  385. return result[item - ABDAY_1];
  386. }
  387. {
  388. static char const months[][sizeof "September"] = {
  389. "January", "February", "March", "April", "May", "June", "July",
  390. "September", "October", "November", "December"
  391. };
  392. case MON_1:
  393. case MON_2:
  394. case MON_3:
  395. case MON_4:
  396. case MON_5:
  397. case MON_6:
  398. case MON_7:
  399. case MON_8:
  400. case MON_9:
  401. case MON_10:
  402. case MON_11:
  403. case MON_12:
  404. {
  405. static char result[12][50];
  406. tmm.tm_mon = item - MON_1;
  407. if (!strftime (buf, sizeof result[0], "%B", &tmm))
  408. return (char *) months[item - MON_1];
  409. strcpy (result[item - MON_1], buf);
  410. return result[item - MON_1];
  411. }
  412. case ALTMON_1:
  413. case ALTMON_2:
  414. case ALTMON_3:
  415. case ALTMON_4:
  416. case ALTMON_5:
  417. case ALTMON_6:
  418. case ALTMON_7:
  419. case ALTMON_8:
  420. case ALTMON_9:
  421. case ALTMON_10:
  422. case ALTMON_11:
  423. case ALTMON_12:
  424. {
  425. static char result[12][50];
  426. tmm.tm_mon = item - ALTMON_1;
  427. /* The platforms without nl_langinfo() don't support strftime with
  428. %OB. We don't even need to try. */
  429. #if 0
  430. if (!strftime (buf, sizeof result[0], "%OB", &tmm))
  431. #endif
  432. if (!strftime (buf, sizeof result[0], "%B", &tmm))
  433. return (char *) months[item - ALTMON_1];
  434. strcpy (result[item - ALTMON_1], buf);
  435. return result[item - ALTMON_1];
  436. }
  437. }
  438. case ABMON_1:
  439. case ABMON_2:
  440. case ABMON_3:
  441. case ABMON_4:
  442. case ABMON_5:
  443. case ABMON_6:
  444. case ABMON_7:
  445. case ABMON_8:
  446. case ABMON_9:
  447. case ABMON_10:
  448. case ABMON_11:
  449. case ABMON_12:
  450. {
  451. static char result[12][30];
  452. static char const abmonths[][sizeof "Jan"] = {
  453. "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
  454. "Sep", "Oct", "Nov", "Dec"
  455. };
  456. tmm.tm_mon = item - ABMON_1;
  457. if (!strftime (buf, sizeof result[0], "%b", &tmm))
  458. return (char *) abmonths[item - ABMON_1];
  459. strcpy (result[item - ABMON_1], buf);
  460. return result[item - ABMON_1];
  461. }
  462. case ERA:
  463. return (char *) "";
  464. case ALT_DIGITS:
  465. return (char *) "\0\0\0\0\0\0\0\0\0\0";
  466. /* nl_langinfo items of the LC_MONETARY category. */
  467. case CRNCYSTR:
  468. return localeconv () ->currency_symbol;
  469. # ifdef INT_CURR_SYMBOL
  470. case INT_CURR_SYMBOL:
  471. return localeconv () ->int_curr_symbol;
  472. case MON_DECIMAL_POINT:
  473. return localeconv () ->mon_decimal_point;
  474. case MON_THOUSANDS_SEP:
  475. return localeconv () ->mon_thousands_sep;
  476. case MON_GROUPING:
  477. return localeconv () ->mon_grouping;
  478. case POSITIVE_SIGN:
  479. return localeconv () ->positive_sign;
  480. case NEGATIVE_SIGN:
  481. return localeconv () ->negative_sign;
  482. case FRAC_DIGITS:
  483. return & localeconv () ->frac_digits;
  484. case INT_FRAC_DIGITS:
  485. return & localeconv () ->int_frac_digits;
  486. case P_CS_PRECEDES:
  487. return & localeconv () ->p_cs_precedes;
  488. case N_CS_PRECEDES:
  489. return & localeconv () ->n_cs_precedes;
  490. case P_SEP_BY_SPACE:
  491. return & localeconv () ->p_sep_by_space;
  492. case N_SEP_BY_SPACE:
  493. return & localeconv () ->n_sep_by_space;
  494. case P_SIGN_POSN:
  495. return & localeconv () ->p_sign_posn;
  496. case N_SIGN_POSN:
  497. return & localeconv () ->n_sign_posn;
  498. # endif
  499. /* nl_langinfo items of the LC_MESSAGES category
  500. TODO: Really use the locale. */
  501. case YESEXPR:
  502. return (char *) "^[yY]";
  503. case NOEXPR:
  504. return (char *) "^[nN]";
  505. default:
  506. return (char *) "";
  507. }
  508. }
  509. #endif