Index: configure =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: tests/test_locale.c =================================================================== --- tests/test_locale.c (revision 0) +++ tests/test_locale.c (revision 0) @@ -0,0 +1,183 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Digium, Inc. + * + * Tilghman Lesher + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Locale Test + * + * \author\verbatim Tilghman Lesher \endverbatim + * + * \ingroup tests + */ + +/*** MODULEINFO + no + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 191140 $") + +#include +#include +#ifndef __USE_GNU +#define __USE_GNU 1 +#endif +#include + +#include "asterisk/cli.h" +#include "asterisk/linkedlists.h" +#include "asterisk/localtime.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" + + +static char *handle_cli_test_locales(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + DIR *localedir; + struct dirent *dent; + struct ast_tm atm; + struct timeval tv; + char *orig_locale; + char origlocalformat[200] = "", localformat[200] = ""; + struct test_locales { + AST_LIST_ENTRY(test_locales) list; + char *localformat; + char name[0]; + } *tl = NULL; + AST_LIST_HEAD_NOLOCK(locales, test_locales) locales; + int varies = 0, all_successful = 1, count = 0, count_fail = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "test locale"; + e->usage = "" + "Usage: test locale\n" + " Test thread safety of locale functions.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != e->args) { + return CLI_SHOWUSAGE; + } + + /* First we run a set of tests with the global locale, which isn't thread-safe. */ + if (!(localedir = opendir( +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined( __NetBSD__ ) || defined(__APPLE__) + "/usr/share/locale" +#else /* Linux */ + "/usr/lib/locale" +#endif + ))) { + ast_cli(a->fd, "No locales seem to exist on this platform.\n"); + return CLI_SUCCESS; + } + + tv = ast_tvnow(); + ast_localtime(&tv, &atm, NULL); + orig_locale = setlocale(LC_ALL, NULL); + AST_LIST_HEAD_SET_NOLOCK(&locales, NULL); + + /* Get something different, to compare against. */ + ast_strftime(origlocalformat, sizeof(origlocalformat), "%c", &atm); + + while ((dent = readdir(localedir))) { + size_t namelen; + + if (dent->d_name[0] == '.') { + continue; + } + + setlocale(LC_ALL, dent->d_name); + ast_strftime(localformat, sizeof(localformat), "%c", &atm); + + /* Store values */ + if (!(tl = ast_calloc(1, sizeof(*tl) + strlen(localformat) + (namelen = strlen(dent->d_name)) + 2))) { + continue; + } + + strcpy(tl->name, dent->d_name); /* SAFE */ + tl->localformat = tl->name + namelen + 1; + strcpy(tl->localformat, localformat); /* SAFE */ + + AST_LIST_INSERT_TAIL(&locales, tl, list); + + /* Ensure that at least two entries differ, otherwise this test doesn't mean much. */ + if (!varies && strcmp(AST_LIST_FIRST(&locales)->localformat, localformat)) { + varies = 1; + } + } + + setlocale(LC_ALL, orig_locale); + + closedir(localedir); + + if (!varies) { + if (!strcmp(origlocalformat, localformat)) { + + ast_cli(a->fd, "WARNING: the locales on your system don't differ. Install more locales if you want this test to mean something.\n"); + } + + orig_locale = ast_setlocale(AST_LIST_FIRST(&locales)->name); + + while ((tl = AST_LIST_REMOVE_HEAD(&locales, list))) { + ast_setlocale(tl->name); + ast_strftime(localformat, sizeof(localformat), "%c", &atm); + if (strcmp(localformat, tl->localformat)) { + ast_cli(a->fd, "WARNING: locale test fails for locale %s\n", tl->name); + all_successful = 0; + count_fail++; + } + ast_free(tl); + count++; + } + + ast_setlocale(orig_locale); + + if (all_successful) { + ast_cli(a->fd, "All %d locale tests successful\n", count); + } else if (count_fail == count && count > 0) { + ast_cli(a->fd, "No locale tests successful out of %d tries\n", count); + } else if (count > 0) { + ast_cli(a->fd, "Partial failure (%d/%d) for a %.0f%% failure rate\n", count_fail, count, count_fail * 100.0 / count); + } else { + ast_cli(a->fd, "No locales tested. Install more locales.\n"); + } + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_locales[] = { + AST_CLI_DEFINE(handle_cli_test_locales, "Test locales for thread-safety"), +}; + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_locales, ARRAY_LEN(cli_locales)); + return 0; +} + +static int load_module(void) +{ + ast_cli_register_multiple(cli_locales, ARRAY_LEN(cli_locales)); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Locale tests"); Index: configure.ac =================================================================== --- configure.ac (revision 194137) +++ configure.ac (working copy) @@ -351,7 +351,7 @@ AC_FUNC_STRTOD AC_FUNC_UTIME_NULL AC_FUNC_VPRINTF -AC_CHECK_FUNCS([asprintf atexit closefrom dup2 eaccess endpwent euidaccess ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtol strtoq unsetenv utime vasprintf getpeereid sysctl swapctl]) +AC_CHECK_FUNCS([asprintf atexit closefrom dup2 eaccess endpwent euidaccess ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap newlocale putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtol strtoq unsetenv utime vasprintf getpeereid sysctl swapctl]) AC_CHECK_FUNCS([glob]) Index: include/asterisk/autoconfig.h.in =================================================================== --- include/asterisk/autoconfig.h.in (revision 194137) +++ include/asterisk/autoconfig.h.in (working copy) @@ -602,6 +602,9 @@ /* Define if your system has the NETSNMP libraries. */ #undef HAVE_NETSNMP +/* Define to 1 if you have the `newlocale' function. */ +#undef HAVE_NEWLOCALE + /* Define this to indicate the ${NEWT_DESCRIP} library */ #undef HAVE_NEWT Index: include/asterisk/localtime.h =================================================================== --- include/asterisk/localtime.h (revision 194137) +++ include/asterisk/localtime.h (working copy) @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 1999 - 2005, Digium, Inc. + * Copyright (C) 1999 - 2009, Digium, Inc. * * Mark Spencer * Tilghman Lesher @@ -24,6 +24,12 @@ #ifndef _ASTERISK_LOCALTIME_H #define _ASTERISK_LOCALTIME_H +#ifdef HAVE_NEWLOCALE +#include +#else +typedef void * locale_t; +#endif + struct ast_tm { int tm_sec; /*!< Seconds. [0-60] (1 leap second) */ int tm_min; /*!< Minutes. [0-59] */ @@ -57,6 +63,9 @@ */ struct timeval ast_mktime(struct ast_tm * const tmp, const char *zone); +/*!\brief Set the thread-local representation of the current locale. */ +const char *ast_setlocale(const char *locale); + /*!\brief Special version of strftime(3) that handles fractions of a second. * Takes the same arguments as strftime(3), with the addition of %q, which * specifies microseconds. @@ -67,6 +76,7 @@ * \retval An integer value specifying the number of bytes placed into buf or -1 on error. */ int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm); +int ast_strftime_locale(char *buf, size_t len, const char *format, const struct ast_tm *tm, const char *locale); /*!\brief Special version of strptime(3) which places the answer in the common * structure ast_tm. Also, unlike strptime(3), ast_strptime() initializes its @@ -77,5 +87,6 @@ * \retval A pointer to the first character within s not used to parse the date and time. */ char *ast_strptime(const char *s, const char *format, struct ast_tm *tm); +char *ast_strptime_locale(const char *s, const char *format, struct ast_tm *tm, const char *locale); #endif /* _ASTERISK_LOCALTIME_H */ Index: main/stdtime/localtime.c =================================================================== --- main/stdtime/localtime.c (revision 194137) +++ main/stdtime/localtime.c (working copy) @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 1999 - 2005, Digium, Inc. + * Copyright (C) 1999 - 2009, Digium, Inc. * * Mark Spencer * @@ -158,6 +158,12 @@ AST_LIST_ENTRY(state) list; }; +struct locale_entry { + AST_LIST_ENTRY(locale_entry) list; + locale_t locale; + char name[0]; +}; + struct rule { int r_type; /* type of rule--see below */ int r_day; /* day number of rule */ @@ -220,6 +226,7 @@ int lastditch)); static AST_LIST_HEAD_STATIC(zonelist, state); +static AST_LIST_HEAD_STATIC(localelist, locale_entry); #ifndef TZ_STRLEN_MAX #define TZ_STRLEN_MAX 255 @@ -1925,12 +1932,100 @@ return time1(tmp, localsub, 0L, sp); } -int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm) +#ifdef HAVE_NEWLOCALE +static struct locale_entry *find_by_locale(locale_t locale) { + struct locale_entry *cur; + AST_LIST_TRAVERSE(&localelist, cur, list) { + if (locale == cur->locale) { + return cur; + } + } + return NULL; +} + +static struct locale_entry *find_by_name(const char *name) +{ + struct locale_entry *cur; + AST_LIST_TRAVERSE(&localelist, cur, list) { + if (strcmp(name, cur->name) == 0) { + return cur; + } + } + return NULL; +} + +static const char *store_by_locale(locale_t prevlocale) +{ + struct locale_entry *cur; + if (prevlocale == LC_GLOBAL_LOCALE) { + return NULL; + } else { + /* Get a handle for this entry, if any */ + if ((cur = find_by_locale(prevlocale))) { + return cur->name; + } else { + /* Create an entry, so it can be restored later */ + int x; + cur = NULL; + AST_LIST_LOCK(&localelist); + for (x = 0; x < 10000; x++) { + char name[5]; + snprintf(name, sizeof(name), "%04d", x); + if (!find_by_name(name)) { + if ((cur = ast_calloc(1, sizeof(*cur) + strlen(name) + 1))) { + cur->locale = prevlocale; + strcpy(cur->name, name); /* SAFE */ + AST_LIST_INSERT_TAIL(&localelist, cur, list); + } + break; + } + } + AST_LIST_UNLOCK(&localelist); + return cur ? cur->name : NULL; + } + } +} + +const char *ast_setlocale(const char *locale) +{ + struct locale_entry *cur; + locale_t prevlocale = LC_GLOBAL_LOCALE; + + if (locale == NULL) { + return store_by_locale(uselocale(LC_GLOBAL_LOCALE)); + } + + AST_LIST_LOCK(&localelist); + if ((cur = find_by_name(locale))) { + prevlocale = uselocale(cur->locale); + } + + if (!cur) { + if ((cur = ast_calloc(1, sizeof(*cur) + strlen(locale) + 1))) { + cur->locale = newlocale(LC_ALL_MASK, locale, NULL); + strcpy(cur->name, locale); /* SAFE */ + AST_LIST_INSERT_TAIL(&localelist, cur, list); + prevlocale = uselocale(cur->locale); + } + } + AST_LIST_UNLOCK(&localelist); + return store_by_locale(prevlocale); +} +#else +const char *ast_setlocale(const char *unused) +{ + return NULL; +} +#endif + +int ast_strftime_locale(char *buf, size_t len, const char *tmp, const struct ast_tm *tm, const char *locale) +{ size_t fmtlen = strlen(tmp) + 1; char *format = ast_calloc(1, fmtlen), *fptr = format, *newfmt; int decimals = -1, i, res; long fraction; + const char *prevlocale; if (!format) return -1; @@ -1979,15 +2074,31 @@ } *fptr = '\0'; #undef strftime + if (locale) { + prevlocale = ast_setlocale(locale); + } res = (int)strftime(buf, len, format, (struct tm *)tm); + if (locale) { + ast_setlocale(prevlocale); + } ast_free(format); return res; } -char *ast_strptime(const char *s, const char *format, struct ast_tm *tm) +int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm) { + return ast_strftime_locale(buf, len, tmp, tm, NULL); +} + +char *ast_strptime_locale(const char *s, const char *format, struct ast_tm *tm, const char *locale) +{ struct tm tm2 = { 0, }; - char *res = strptime(s, format, &tm2); + char *res; + const char *prevlocale; + + prevlocale = ast_setlocale(locale); + res = strptime(s, format, &tm2); + ast_setlocale(prevlocale); memcpy(tm, &tm2, sizeof(*tm)); tm->tm_usec = 0; /* strptime(3) doesn't set .tm_isdst correctly, so to force ast_mktime(3) @@ -1996,3 +2107,8 @@ return res; } +char *ast_strptime(const char *s, const char *format, struct ast_tm *tm) +{ + return ast_strptime_locale(s, format, tm, NULL); +} +