nl_langinfo.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /* nl_langinfo() replacement: query locale dependent information.
  2. Copyright (C) 2007-2021 Free Software Foundation, Inc.
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Lesser General Public License as published by
  5. the Free Software Foundation; either version 3 of the License, or
  6. (at your option) any later version.
  7. This program 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