#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <libintl.h>
#include <langinfo.h>
#include <nl_types.h>
#include <err.h>
#include <unistd.h>
#include <pthread.h>
#include <note.h>
#include <errno.h>
#include <sys/mman.h>
#include "test_common.h"
struct ldata {
const char *locale;
const char *day1;
const char *cursym;
} ldata[] = {
{ "C", "Sunday", "" },
{ "en_US.UTF-8", "Sunday", "$" },
{ "de_DE.UTF-8", "Sonntag", "€" },
{ "ru_RU.UTF-8", "воскресенье", "₽" },
{ "ja_JP.UTF-8", "日曜日", "¥" },
};
static uint8_t *guarded_page;
static size_t page_size;
void
setup_trap(void)
{
page_size = getpagesize();
guarded_page = mmap(NULL, 2 * page_size, PROT_READ|PROT_WRITE,
MAP_ANON|MAP_PRIVATE, -1, 0);
if (guarded_page == MAP_FAILED) {
perror("mmap");
guarded_page = NULL;
return;
}
if (mprotect(guarded_page + page_size, page_size, PROT_NONE) < 0) {
perror("mprotect");
guarded_page = NULL;
}
}
const char *
guarded_str(const char *str)
{
size_t len = strlen(str) + 1;
char *loc;
if (guarded_page == NULL)
return (str);
if (len > page_size) {
errx(EXIT_FAILURE, "%zd byte string exceeds page size %zd",
len, page_size);
}
memset(guarded_page, 0xa5, page_size);
loc = (char *)(guarded_page + page_size - len);
memcpy(loc, str, len);
return ((const char *)loc);
}
#define G(s) (guarded_str(s))
#define NUM_LDATA 5
#define NUMTHR 20
#define NUMITR 200
int extra_debug = 0;
static void
testlocale_thr_one(test_t t, void *arg)
{
_NOTE(ARGUNUSED(arg));
locale_t cloc, loc;
struct lconv *lc;
char *day;
for (int i = 0; i < NUMITR; i++) {
struct ldata *l = &ldata[i % NUM_LDATA];
cloc = uselocale(NULL);
loc = newlocale(LC_ALL_MASK, l->locale, NULL);
if (loc == NULL) {
test_failed(t, "newlocale %s failed", l->locale);
}
day = nl_langinfo_l(DAY_1, loc);
if (strcmp(day, l->day1) != 0) {
test_failed(t, "newlocale data mismatch (%s != %s)",
day, l->day1);
}
if (extra_debug)
test_debugf(t, "DAY1: %s", day);
day = nl_langinfo(DAY_1);
if (strcmp(day, "Sunday") != 0) {
test_failed(t, "C locale day wrong %s != Sunday",
day);
}
lc = localeconv();
if (strcmp(lc->currency_symbol, "") != 0) {
test_failed(t, "C cursym mismatch (%s != %s)",
lc->currency_symbol, "");
}
(void) usleep(rand() % 10);
(void) uselocale(loc);
day = nl_langinfo(DAY_1);
if (strcmp(day, l->day1) != 0) {
test_failed(t, "uselocale data mismatch (%s != %s)",
day, l->day1);
}
lc = localeconv();
if (strcmp(lc->currency_symbol, l->cursym) != 0) {
test_failed(t, "uselocal cursym %s != %s",
lc->currency_symbol, l->cursym);
}
if (extra_debug)
test_debugf(t, "CSYM: %s", lc->currency_symbol);
(void) usleep(rand() % 10);
if (uselocale(cloc) != loc) {
test_failed(t, "revert old locale mismatch");
}
freelocale(loc);
if (uselocale(LC_GLOBAL_LOCALE) != cloc) {
test_failed(t, "revert GLOBAL_LOCALE mismatch");
}
}
test_passed(t);
}
static void
test_newlocale_threaded(void)
{
test_run(NUMTHR, testlocale_thr_one, NULL, "newlocale_threaded");
}
static void
test_newlocale_negative(void)
{
locale_t loc, bad;
char *day;
char *tname = "newlocale_negative";
test_t t;
t = test_start(tname);
loc = newlocale(LC_ALL_MASK, "de_DE.UTF-8", NULL);
if (loc == NULL) {
test_failed(t, "cannot set de_DE.UTF-8");
}
day = nl_langinfo_l(DAY_1, loc);
if (strcmp(day, "Sonntag") != 0) {
test_failed(t, "incorrect Sonntag != %s", day);
}
bad = newlocale(LC_ALL_MASK, "cn_US.BIZRRE", loc);
if (bad != NULL) {
test_failed(t, "passed setting bogus locale");
}
day = nl_langinfo_l(DAY_1, loc);
if (strcmp(day, "Sonntag") != 0) {
test_failed(t, "incorrect Sonntag != %s", day);
}
test_passed(t);
}
static void
test_newlocale_categories(void)
{
locale_t loc;
char *day, *cur, *yes;
char *tname = "newlocale_categories";
test_t t;
t = test_start(tname);
loc = NULL;
loc = newlocale(LC_TIME_MASK, "de_DE.UTF-8", loc);
loc = newlocale(LC_MESSAGES_MASK, "ru_RU.UTF-8", loc);
loc = newlocale(LC_MONETARY_MASK, "en_US.UTF-8", loc);
if (loc == NULL) {
test_failed(t, "failed to set locale");
}
day = nl_langinfo_l(DAY_1, loc);
if ((day == NULL) || (strcmp(day, "Sonntag") != 0)) {
test_failed(t, "day1 mismatch %s != %s", day, "Sonntag");
}
yes = nl_langinfo_l(YESSTR, loc);
if ((yes == NULL) || (strcmp(yes, "да") != 0)) {
test_failed(t, "currency mismatch");
}
cur = nl_langinfo_l(CRNCYSTR, loc);
if ((cur == NULL) || (strcmp(cur, "-$") != 0)) {
test_failed(t, "currency mismatch [%s] != [%s]", cur, "-$");
}
test_passed(t);
}
static void
test_newlocale_composite(void)
{
locale_t loc;
char *day, *cur, *yes;
char *tname = "newlocale_composite";
test_t t;
t = test_start(tname);
loc = newlocale(LC_ALL_MASK,
"C/C/de_DE.UTF-8/C/en_US.UTF-8/ru_RU.UTF-8", NULL);
if (loc == NULL) {
test_failed(t, "failed to set composite locale");
}
day = nl_langinfo_l(DAY_1, loc);
if ((day == NULL) || (strcmp(day, "Sonntag") != 0)) {
test_failed(t, "day1 mismatch %s != %s", day, "Sonntag");
}
yes = nl_langinfo_l(YESSTR, loc);
if ((yes == NULL) || (strcmp(yes, "да") != 0)) {
test_failed(t, "currency mismatch");
}
cur = nl_langinfo_l(CRNCYSTR, loc);
if ((cur == NULL) || (strcmp(cur, "-$") != 0)) {
test_failed(t, "currency mismatch [%s] != [%s]", cur, "-$");
}
test_passed(t);
}
static void
test_newlocale_composite_glibc(void)
{
locale_t loc;
char *day, *cur;
const char *name;
char *tname = "newlocale_composite_glibc";
test_t t;
t = test_start(tname);
loc = newlocale(LC_ALL_MASK, "C", NULL);
if (loc == NULL) {
test_failed(t, "failed to set base locale");
}
loc = newlocale(LC_TIME_MASK | LC_MONETARY_MASK,
"LC_FRUIT=banana;LC_TIME=de_DE.UTF-8;LC_MONETARY=en_US.UTF-8",
loc);
if (loc == NULL) {
test_failed(t, "failed to set composite locale");
}
day = nl_langinfo_l(DAY_1, loc);
if ((day == NULL) || (strcmp(day, "Sonntag") != 0)) {
test_failed(t, "day1 mismatch %s != %s", day, "Sonntag");
}
cur = nl_langinfo_l(CRNCYSTR, loc);
if ((cur == NULL) || (strcmp(cur, "-$") != 0)) {
test_failed(t, "currency mismatch [%s] != [%s]", cur, "-$");
}
name = getlocalename_l(LC_ALL, loc);
if (name == NULL) {
test_failed(t, "empty locale name returned");
}
if (strcmp(name, "C/C/de_DE.UTF-8/C/en_US.UTF-8/C") != 0) {
test_failed(t, "unexpected locale name [%s] returned", name);
}
loc = newlocale(LC_TIME_MASK | LC_MONETARY_MASK,
"LC_ALL=C;LC_TIME=de_DE.UTF-8;LC_MONETARY=en_US.UTF-8",
NULL);
if (loc == NULL) {
test_failed(t, "failed to set composite locale (with LC_ALL)");
}
day = nl_langinfo_l(DAY_1, loc);
if ((day == NULL) || (strcmp(day, "Sonntag") != 0)) {
test_failed(t, "day1 mismatch %s != %s", day, "Sonntag");
}
loc = newlocale(LC_TIME_MASK | LC_MONETARY_MASK,
"LC_TIME=C;LC_MONETARY=en_US.UTF-8;LC_TIME=de_DE.UTF-8",
NULL);
if (loc == NULL) {
test_failed(t, "failed to set composite locale (dup category)");
}
day = nl_langinfo_l(DAY_1, loc);
if ((day == NULL) || (strcmp(day, "Sonntag") != 0)) {
test_failed(t, "day1 mismatch %s != %s", day, "Sonntag");
}
test_passed(t);
}
static void
test_newlocale_all_categories(void)
{
locale_t loc;
char *tname = "newlocale_all_categories";
const char *name;
test_t t;
t = test_start(tname);
loc = newlocale(LC_ALL_MASK,
"LC_MESSAGES=ru_RU.UTF-8;LC_MONETARY=en_US.UTF-8;LC_COLLATE=C;"
"LC_TIME=ja_JP.UTF-8;LC_NUMERIC=C;LC_CTYPE=C.UTF-8",
NULL);
name = getlocalename_l(LC_ALL, loc);
if (name == NULL) {
test_failed(t, "empty locale name returned");
}
if (strcmp(name,
"C.UTF-8/C/ja_JP.UTF-8/C/en_US.UTF-8/ru_RU.UTF-8") != 0) {
test_failed(t, "unexpected locale name [%s] returned", name);
}
name = setlocale(LC_ALL,
"LC_MESSAGES=en_US.UTF-8;LC_MONETARY=ru_RU.UTF-8;LC_COLLATE=C;"
"LC_TIME=ja_JP.UTF-8;LC_NUMERIC=C;LC_CTYPE=C.UTF-8");
if (name == NULL) {
test_failed(t, "setlocale returned NULL");
}
if (strcmp(name,
"C.UTF-8/C/ja_JP.UTF-8/C/ru_RU.UTF-8/en_US.UTF-8") != 0) {
test_failed(t,
"unexpected locale name [%s] returned by setlocale",
name);
}
name = getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE);
if (name == NULL) {
test_failed(t, "empty locale name returned");
}
if (strcmp(name,
"C.UTF-8/C/ja_JP.UTF-8/C/ru_RU.UTF-8/en_US.UTF-8") != 0) {
test_failed(t,
"unexpected locale name [%s] returned by getlocalename_l",
name);
}
test_passed(t);
}
static void
test_newlocale_environment(void)
{
locale_t loc;
char *day;
char *tname = "newlocale_environment";
test_t t;
static const char *envs[] = {
"LC_ALL",
"LC_TIME",
"LANG",
NULL
};
static const char **envp;
t = test_start(tname);
for (envp = envs; *envp != NULL; envp++) {
if (unsetenv(*envp) != 0) {
errx(EXIT_FAILURE, "Failed to unset %s in environment",
*envp);
}
}
for (envp = envs; *envp != NULL; envp++) {
if (setenv(*envp, "de_DE.UTF-8", 1) != 0) {
errx(EXIT_FAILURE, "Failed to set %s in environment",
*envp);
}
loc = newlocale(LC_TIME_MASK, G(";LC_TIME="), NULL);
if (loc == NULL) {
test_failed(t, "expected newlocale success with %s",
*envp);
}
day = nl_langinfo_l(DAY_1, loc);
if ((day == NULL) || (strcmp(day, "Sonntag") != 0)) {
test_failed(t, "day1 mismatch %s != %s with %s",
day, "Sonntag", *envp);
}
if (unsetenv(*envp) != 0)
errx(EXIT_FAILURE, "Failed to unset %s in environment",
*envp);
}
test_passed(t);
}
#define CHECK_ERRNO(e) do { \
if (errno != e) \
test_failed(t, "expected " #e " in errno, got %s", \
strerrorname_np(errno)); \
errno = 0; \
} while (0)
static void
test_newlocale_parser_coverage(void)
{
locale_t loc;
char *tname = "newlocale_parser_coverage";
test_t t;
t = test_start(tname);
setup_trap();
errno = 0;
loc = newlocale(LC_TIME_MASK, G(";"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK, G(";;;;"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK, G("LC;LC_TIM"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK, G("LC_DATE=C;LC_CTYPE=C"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK | LC_MONETARY_MASK,
G("LC_TIME=C;LC_CTYPE=C"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK, G("LC_TIME!=C;"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK, G(";LANG=en_US.UTF-8"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(EINVAL);
loc = newlocale(LC_TIME_MASK,
G(";LC_TIME=supercalifragilisticexpialadocious"), NULL);
if (loc != NULL) {
test_failed(t, "expected NULL newlocale return");
}
CHECK_ERRNO(ENOENT);
test_passed(t);
}
int
main(int argc, char **argv)
{
int optc;
while ((optc = getopt(argc, argv, "Ddf")) != EOF) {
switch (optc) {
case 'd':
test_set_debug();
break;
case 'f':
test_set_force();
break;
case 'D':
test_set_debug();
extra_debug++;
break;
default:
(void) fprintf(stderr, "Usage: %s [-df]\n", argv[0]);
exit(1);
}
}
test_newlocale_threaded();
test_newlocale_negative();
test_newlocale_categories();
test_newlocale_composite();
test_newlocale_composite_glibc();
test_newlocale_all_categories();
test_newlocale_environment();
test_newlocale_parser_coverage();
exit(0);
}