1*004345e4SRobert Mustacchi /*
2*004345e4SRobert Mustacchi * This file and its contents are supplied under the terms of the
3*004345e4SRobert Mustacchi * Common Development and Distribution License ("CDDL"), version 1.0.
4*004345e4SRobert Mustacchi * You may only use this file in accordance with the terms of version
5*004345e4SRobert Mustacchi * 1.0 of the CDDL.
6*004345e4SRobert Mustacchi *
7*004345e4SRobert Mustacchi * A full copy of the text of the CDDL should have accompanied this
8*004345e4SRobert Mustacchi * source. A copy of the CDDL is also available via the Internet at
9*004345e4SRobert Mustacchi * http://www.illumos.org/license/CDDL.
10*004345e4SRobert Mustacchi */
11*004345e4SRobert Mustacchi
12*004345e4SRobert Mustacchi /*
13*004345e4SRobert Mustacchi * Copyright 2025 Oxide Computer Company
14*004345e4SRobert Mustacchi */
15*004345e4SRobert Mustacchi
16*004345e4SRobert Mustacchi /*
17*004345e4SRobert Mustacchi * Tests for getlocalename_l(3C). The libc tests depends on locale/ar,
18*004345e4SRobert Mustacchi * locale/de, locale/en, and locale/ja. We limit ourselves to these locales,
19*004345e4SRobert Mustacchi * plus C.UTF-8. We use a combination of global locales set via setlocale(),
20*004345e4SRobert Mustacchi * locales set with uselocale(), and composite locale.
21*004345e4SRobert Mustacchi */
22*004345e4SRobert Mustacchi
23*004345e4SRobert Mustacchi #include <err.h>
24*004345e4SRobert Mustacchi #include <stdlib.h>
25*004345e4SRobert Mustacchi #include <locale.h>
26*004345e4SRobert Mustacchi #include <sys/sysmacros.h>
27*004345e4SRobert Mustacchi #include <sys/debug.h>
28*004345e4SRobert Mustacchi #include <stdbool.h>
29*004345e4SRobert Mustacchi #include <string.h>
30*004345e4SRobert Mustacchi
31*004345e4SRobert Mustacchi typedef struct {
32*004345e4SRobert Mustacchi int lc_cat;
33*004345e4SRobert Mustacchi const char *lc_name;
34*004345e4SRobert Mustacchi } lc_cat_t;
35*004345e4SRobert Mustacchi
36*004345e4SRobert Mustacchi static const lc_cat_t lc_cats[] = {
37*004345e4SRobert Mustacchi { LC_CTYPE, "LC_CTYPE" },
38*004345e4SRobert Mustacchi { LC_NUMERIC, "LC_NUMERIC" },
39*004345e4SRobert Mustacchi { LC_TIME, "LC_TIME" },
40*004345e4SRobert Mustacchi { LC_COLLATE, "LC_COLLATE" },
41*004345e4SRobert Mustacchi { LC_MONETARY, "LC_MONETARY" },
42*004345e4SRobert Mustacchi { LC_MESSAGES, "LC_MESSAGES" },
43*004345e4SRobert Mustacchi { LC_ALL, "LC_ALL" }
44*004345e4SRobert Mustacchi };
45*004345e4SRobert Mustacchi CTASSERT(ARRAY_SIZE(lc_cats) == LC_ALL + 1);
46*004345e4SRobert Mustacchi
47*004345e4SRobert Mustacchi typedef struct {
48*004345e4SRobert Mustacchi int lc_cat;
49*004345e4SRobert Mustacchi int lc_mask;
50*004345e4SRobert Mustacchi const char *lc_loc;
51*004345e4SRobert Mustacchi } lc_comp_t;
52*004345e4SRobert Mustacchi
53*004345e4SRobert Mustacchi static const lc_comp_t composite[] = {
54*004345e4SRobert Mustacchi { LC_CTYPE, LC_CTYPE_MASK, "en_US.UTF-8" },
55*004345e4SRobert Mustacchi { LC_NUMERIC, LC_NUMERIC_MASK, "en_GB.UTF-8" },
56*004345e4SRobert Mustacchi { LC_TIME, LC_TIME_MASK, "de_DE.UTF-8" },
57*004345e4SRobert Mustacchi { LC_COLLATE, LC_COLLATE_MASK, "ar_EG.UTF-8" },
58*004345e4SRobert Mustacchi { LC_MONETARY, LC_MONETARY_MASK, "ja_JP.UTF-8" },
59*004345e4SRobert Mustacchi { LC_MESSAGES, LC_MESSAGES_MASK, "C.UTF-8" }
60*004345e4SRobert Mustacchi };
61*004345e4SRobert Mustacchi
62*004345e4SRobert Mustacchi static const char *locales[] = { "C.UTF-8", "ja_JP.UTF-8", "de_DE.UTF-8",
63*004345e4SRobert Mustacchi "en_US.UTF-8", "en_GB.UTF-8" };
64*004345e4SRobert Mustacchi
65*004345e4SRobert Mustacchi /*
66*004345e4SRobert Mustacchi * Check each category of a locale. These are ordered in exp by their LC_ type
67*004345e4SRobert Mustacchi * category.
68*004345e4SRobert Mustacchi */
69*004345e4SRobert Mustacchi static bool
locname_check(const char * desc,locale_t loc,const char ** exp_names)70*004345e4SRobert Mustacchi locname_check(const char *desc, locale_t loc, const char **exp_names)
71*004345e4SRobert Mustacchi {
72*004345e4SRobert Mustacchi bool ret = true;
73*004345e4SRobert Mustacchi
74*004345e4SRobert Mustacchi for (size_t i = 0; i < ARRAY_SIZE(lc_cats); i++) {
75*004345e4SRobert Mustacchi const char *catname = lc_cats[i].lc_name;
76*004345e4SRobert Mustacchi const char *exp = exp_names[lc_cats[i].lc_cat];
77*004345e4SRobert Mustacchi const char *name = getlocalename_l(lc_cats[i].lc_cat, loc);
78*004345e4SRobert Mustacchi if (name == NULL) {
79*004345e4SRobert Mustacchi warnx("TEST FAILED: %s: failed to get locale name for "
80*004345e4SRobert Mustacchi "category %s: expected %s", desc, catname, exp);
81*004345e4SRobert Mustacchi ret = false;
82*004345e4SRobert Mustacchi continue;
83*004345e4SRobert Mustacchi }
84*004345e4SRobert Mustacchi
85*004345e4SRobert Mustacchi if (strcmp(name, exp) != 0) {
86*004345e4SRobert Mustacchi warnx("TEST FAILED: %s: got incorrect value for "
87*004345e4SRobert Mustacchi "category %s: expected %s, but got %s", desc,
88*004345e4SRobert Mustacchi catname, exp, name);
89*004345e4SRobert Mustacchi ret = false;
90*004345e4SRobert Mustacchi } else {
91*004345e4SRobert Mustacchi (void) printf("TEST PASSED: %s: category %s\n", desc,
92*004345e4SRobert Mustacchi catname);
93*004345e4SRobert Mustacchi }
94*004345e4SRobert Mustacchi }
95*004345e4SRobert Mustacchi
96*004345e4SRobert Mustacchi return (ret);
97*004345e4SRobert Mustacchi }
98*004345e4SRobert Mustacchi
99*004345e4SRobert Mustacchi /*
100*004345e4SRobert Mustacchi * A small wrapper for names that are uniform.
101*004345e4SRobert Mustacchi */
102*004345e4SRobert Mustacchi static bool
locname_check_all(const char * desc,locale_t loc,const char * exp)103*004345e4SRobert Mustacchi locname_check_all(const char *desc, locale_t loc, const char *exp)
104*004345e4SRobert Mustacchi {
105*004345e4SRobert Mustacchi const char *names[LC_ALL + 1] = { exp, exp, exp, exp, exp, exp, exp };
106*004345e4SRobert Mustacchi
107*004345e4SRobert Mustacchi return (locname_check(desc, loc, names));
108*004345e4SRobert Mustacchi }
109*004345e4SRobert Mustacchi
110*004345e4SRobert Mustacchi /*
111*004345e4SRobert Mustacchi * Change each locale category one at a time and ensure that we get the expected
112*004345e4SRobert Mustacchi * locale. Then set it as the global locale and ensure that this is what we
113*004345e4SRobert Mustacchi * expect. We start from C. We track the names based on an array indexed by the
114*004345e4SRobert Mustacchi * different categories and use the fact that they're ordered to know what to
115*004345e4SRobert Mustacchi * check against.
116*004345e4SRobert Mustacchi *
117*004345e4SRobert Mustacchi * We also test setting the global locale to the result of this to ensure that
118*004345e4SRobert Mustacchi * the results of composite locales are usable.
119*004345e4SRobert Mustacchi */
120*004345e4SRobert Mustacchi static bool
locname_composite(void)121*004345e4SRobert Mustacchi locname_composite(void)
122*004345e4SRobert Mustacchi {
123*004345e4SRobert Mustacchi bool ret = true;
124*004345e4SRobert Mustacchi const char *names[LC_ALL + 1] = { "C", "C", "C", "C", "C", "C", NULL };
125*004345e4SRobert Mustacchi locale_t loc = newlocale(LC_ALL_MASK, "C", NULL);
126*004345e4SRobert Mustacchi
127*004345e4SRobert Mustacchi if (loc == NULL) {
128*004345e4SRobert Mustacchi errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to create "
129*004345e4SRobert Mustacchi "base C locale for composite tests");
130*004345e4SRobert Mustacchi }
131*004345e4SRobert Mustacchi
132*004345e4SRobert Mustacchi for (size_t i = 0; i < ARRAY_SIZE(composite); i++) {
133*004345e4SRobert Mustacchi char cname[1024], desc[1024];
134*004345e4SRobert Mustacchi
135*004345e4SRobert Mustacchi names[composite[i].lc_cat] = composite[i].lc_loc;
136*004345e4SRobert Mustacchi (void) snprintf(cname, sizeof (cname), "%s/%s/%s/%s/%s/%s",
137*004345e4SRobert Mustacchi names[0], names[1], names[2], names[3], names[4], names[5]);
138*004345e4SRobert Mustacchi names[LC_ALL] = cname;
139*004345e4SRobert Mustacchi
140*004345e4SRobert Mustacchi loc = newlocale(composite[i].lc_mask, composite[i].lc_loc,
141*004345e4SRobert Mustacchi loc);
142*004345e4SRobert Mustacchi if (loc == NULL) {
143*004345e4SRobert Mustacchi err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
144*004345e4SRobert Mustacchi "evolve composite locale to %s", cname);
145*004345e4SRobert Mustacchi }
146*004345e4SRobert Mustacchi
147*004345e4SRobert Mustacchi (void) snprintf(desc, sizeof (desc), "composite %s (%d)",
148*004345e4SRobert Mustacchi composite[i].lc_loc, composite[i].lc_cat);
149*004345e4SRobert Mustacchi if (!locname_check(desc, loc, names))
150*004345e4SRobert Mustacchi ret = false;
151*004345e4SRobert Mustacchi
152*004345e4SRobert Mustacchi if (setlocale(LC_ALL, getlocalename_l(LC_ALL, loc)) == NULL) {
153*004345e4SRobert Mustacchi err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
154*004345e4SRobert Mustacchi "set global locale to composite %s", cname);
155*004345e4SRobert Mustacchi }
156*004345e4SRobert Mustacchi
157*004345e4SRobert Mustacchi (void) snprintf(desc, sizeof (desc), "global composite %s (%d)",
158*004345e4SRobert Mustacchi composite[i].lc_loc, composite[i].lc_cat);
159*004345e4SRobert Mustacchi if (!locname_check(desc, loc, names))
160*004345e4SRobert Mustacchi ret = false;
161*004345e4SRobert Mustacchi }
162*004345e4SRobert Mustacchi freelocale(loc);
163*004345e4SRobert Mustacchi
164*004345e4SRobert Mustacchi return (ret);
165*004345e4SRobert Mustacchi }
166*004345e4SRobert Mustacchi
167*004345e4SRobert Mustacchi static bool
locname_invalid(void)168*004345e4SRobert Mustacchi locname_invalid(void)
169*004345e4SRobert Mustacchi {
170*004345e4SRobert Mustacchi bool ret = true;
171*004345e4SRobert Mustacchi
172*004345e4SRobert Mustacchi if (getlocalename_l(LC_ALL, NULL) != NULL) {
173*004345e4SRobert Mustacchi ret = false;
174*004345e4SRobert Mustacchi warnx("TEST FAILED: passing invalid locale: string incorrectly "
175*004345e4SRobert Mustacchi "returned");
176*004345e4SRobert Mustacchi } else {
177*004345e4SRobert Mustacchi (void) printf("TEST PASSED: invalid locale\n");
178*004345e4SRobert Mustacchi }
179*004345e4SRobert Mustacchi
180*004345e4SRobert Mustacchi if (getlocalename_l(12345, LC_GLOBAL_LOCALE) != NULL) {
181*004345e4SRobert Mustacchi ret = false;
182*004345e4SRobert Mustacchi warnx("TEST FAILED: passing invalid category (1): string "
183*004345e4SRobert Mustacchi "incorrectly returned");
184*004345e4SRobert Mustacchi } else {
185*004345e4SRobert Mustacchi (void) printf("TEST PASSED: invalid category (1)\n");
186*004345e4SRobert Mustacchi }
187*004345e4SRobert Mustacchi
188*004345e4SRobert Mustacchi if (getlocalename_l(0x7777, uselocale(NULL)) != NULL) {
189*004345e4SRobert Mustacchi ret = false;
190*004345e4SRobert Mustacchi warnx("TEST FAILED: passing invalid category (2): string "
191*004345e4SRobert Mustacchi "incorrectly returned");
192*004345e4SRobert Mustacchi } else {
193*004345e4SRobert Mustacchi (void) printf("TEST PASSED: invalid category (2)\n");
194*004345e4SRobert Mustacchi }
195*004345e4SRobert Mustacchi
196*004345e4SRobert Mustacchi return (ret);
197*004345e4SRobert Mustacchi }
198*004345e4SRobert Mustacchi
199*004345e4SRobert Mustacchi int
main(void)200*004345e4SRobert Mustacchi main(void)
201*004345e4SRobert Mustacchi {
202*004345e4SRobert Mustacchi int ret = EXIT_SUCCESS;
203*004345e4SRobert Mustacchi
204*004345e4SRobert Mustacchi /*
205*004345e4SRobert Mustacchi * The default locale should be C.
206*004345e4SRobert Mustacchi */
207*004345e4SRobert Mustacchi if (!locname_check_all("global locale: default", LC_GLOBAL_LOCALE, "C"))
208*004345e4SRobert Mustacchi ret = EXIT_FAILURE;
209*004345e4SRobert Mustacchi
210*004345e4SRobert Mustacchi /*
211*004345e4SRobert Mustacchi * Test non-composite locales. We always do the thread-specific locale
212*004345e4SRobert Mustacchi * first so that way we can catch an erroneous locale.
213*004345e4SRobert Mustacchi */
214*004345e4SRobert Mustacchi for (size_t i = 0; i < ARRAY_SIZE(locales); i++) {
215*004345e4SRobert Mustacchi char desc[1024];
216*004345e4SRobert Mustacchi locale_t loc;
217*004345e4SRobert Mustacchi
218*004345e4SRobert Mustacchi loc = newlocale(LC_ALL_MASK, locales[i], NULL);
219*004345e4SRobert Mustacchi if (loc == NULL) {
220*004345e4SRobert Mustacchi err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
221*004345e4SRobert Mustacchi "construct locale %s", locales[i]);
222*004345e4SRobert Mustacchi }
223*004345e4SRobert Mustacchi
224*004345e4SRobert Mustacchi (void) snprintf(desc, sizeof (desc),
225*004345e4SRobert Mustacchi "%s: newlocale", locales[i]);
226*004345e4SRobert Mustacchi if (!locname_check_all(desc, loc, locales[i]))
227*004345e4SRobert Mustacchi ret = EXIT_FAILURE;
228*004345e4SRobert Mustacchi
229*004345e4SRobert Mustacchi (void) snprintf(desc, sizeof (desc),
230*004345e4SRobert Mustacchi "%s: thread locale", locales[i]);
231*004345e4SRobert Mustacchi if (uselocale(loc) == NULL) {
232*004345e4SRobert Mustacchi err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
233*004345e4SRobert Mustacchi "transition thread to thread-specific locale %s, "
234*004345e4SRobert Mustacchi "cannot continue", locales[i]);
235*004345e4SRobert Mustacchi }
236*004345e4SRobert Mustacchi if (!locname_check_all(desc, uselocale(NULL), locales[i]))
237*004345e4SRobert Mustacchi ret = EXIT_FAILURE;
238*004345e4SRobert Mustacchi if (uselocale(LC_GLOBAL_LOCALE) == NULL) {
239*004345e4SRobert Mustacchi err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
240*004345e4SRobert Mustacchi "transition thread back to global locale, cannot "
241*004345e4SRobert Mustacchi "continue");
242*004345e4SRobert Mustacchi }
243*004345e4SRobert Mustacchi freelocale(loc);
244*004345e4SRobert Mustacchi
245*004345e4SRobert Mustacchi (void) snprintf(desc, sizeof (desc),
246*004345e4SRobert Mustacchi "%s: global locale", locales[i]);
247*004345e4SRobert Mustacchi if (setlocale(LC_ALL, locales[i]) == NULL) {
248*004345e4SRobert Mustacchi errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
249*004345e4SRobert Mustacchi "set global locale to %s", locales[i]);
250*004345e4SRobert Mustacchi }
251*004345e4SRobert Mustacchi if (!locname_check_all(desc, LC_GLOBAL_LOCALE, locales[i]))
252*004345e4SRobert Mustacchi ret = EXIT_FAILURE;
253*004345e4SRobert Mustacchi }
254*004345e4SRobert Mustacchi
255*004345e4SRobert Mustacchi if (!locname_composite())
256*004345e4SRobert Mustacchi ret = EXIT_FAILURE;
257*004345e4SRobert Mustacchi
258*004345e4SRobert Mustacchi if (!locname_invalid())
259*004345e4SRobert Mustacchi ret = EXIT_FAILURE;
260*004345e4SRobert Mustacchi
261*004345e4SRobert Mustacchi if (ret == EXIT_SUCCESS) {
262*004345e4SRobert Mustacchi (void) printf("All tests completed successfully\n");
263*004345e4SRobert Mustacchi }
264*004345e4SRobert Mustacchi
265*004345e4SRobert Mustacchi return (ret);
266*004345e4SRobert Mustacchi }
267