/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1995 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984 AT&T */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include "codeset.h" #include #include #include #include #include /* for MAXPATHLEN */ #include #include #include #define TRAILER ".ci" struct _code_set_info _code_set_info = { NULL, CODESET_NONE, /* no codeset */ NULL, /* not defined */ 0, }; /* tolower() and toupper() conversion table * is hidden here to avoid being placed in the * extern .sa file in the dynamic version of libc */ char _ctype_ul[] = { 0, /* 0 1 2 3 4 5 6 7 */ '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027', '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037', ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', '\\', ']', '^', '_', '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', '\177', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; /* following layout is: * LC_NUMERIC LC_TIME LC_MONETARY LANGINFO LC_COLLATE LC_MESSAGES */ char _locales[MAXLOCALE - 1][MAXLOCALENAME + 1] ; char _my_time[MAXLOCALENAME + 1]; /* The array Default holds the systems notion of default locale. It is normally * found in {LOCALE}/.default and moved to here. Note there is only one * default locale spanning all categories */ static char Default[MAXLOCALENAME+1]; struct langinfo _langinfo; struct dtconv *_dtconv = NULL; static char *realmonths = NULL; static char *realdays = NULL; static char *realfmts = NULL; static short lang_succ = ON; /* setlocale success */ /* Set the values here to guarantee stdio use of the decimal point */ static struct lconv lconv_arr = { ".", "", "", "", "", "", "", "", "", "", CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX, CHAR_MAX }; /* lconv is externally defined by ANSI C */ struct lconv *lconv = &lconv_arr; static char *lconv_numeric_str = NULL; static char *lconv_monetary_str = NULL; int openlocale(char *, int, char *, char *); int getlocale_ctype(char *, char *, char *); char *getlocale_numeric(char *, struct lconv *, char *); void init_statics(void); static char *getlocale_monetary(char *, struct lconv *, char *); static char *getstr(char *, char **); static char *getgrouping(char *, char **); static char *getnum(char *, char *); static char *getbool(char *, char *); static void set_default(void); char * setlocale(int category, char *locale) { static char buf[MAXLOCALE*(MAXLOCALENAME + 1) + 1]; /* buffer for current LC_ALL value */ int nonuniform; short ret; char my_ctype[CTYPE_SIZE]; /* local copy */ struct lconv my_lconv; /* local copy */ char *my_lconv_numeric_str; char *my_lconv_monetary_str; int i; char *p; /* initialize my_lconv to lconv */ memcpy(&my_lconv, lconv, sizeof(my_lconv)); /* * Following code is to avoid static initialisation of * strings which would otherwise blow up "xstr". */ if (_locales[0][0] == '\0') init_statics(); if (locale == NULL) { if (category == LC_ALL) { /* * Assume all locales are set to the same value. Then * scan through the locales to see if any are * different. If they are the same, return the common * value; otherwise, construct a "composite" value. */ nonuniform = 0; /* assume all locales set the same */ for (i = 0; i < MAXLOCALE - 2; i++) { if (strcmp(_locales[i], _locales[i + 1]) != 0) { nonuniform = 1; break; } } if (nonuniform) { /* * They're not all the same. Construct a list * of all the locale values, in order, * separated by slashes. Return that value. */ (void) strcpy(buf, _locales[0]); for (i = 1; i < MAXLOCALE - 1; i++) { (void) strcat(buf, "/"); (void) strcat(buf, _locales[i]); } return (buf); } else { /* * They're all the same; any one you return is * OK. */ return (_locales[0]); } } else return (_locales[category - 1]); } switch (category) { case LC_ALL: if (strchr(locale, '/') != NULL) { /* * Composite value; extract each category. */ if (strlen(locale) > sizeof buf - 1) return (NULL); /* too long */ (void) strcpy(buf, locale); p = buf; /* * LC_CTYPE and LC_NUMERIC are set here. * Others locales won't be set here, * they will be just marked. */ for (i = 0; i < MAXLOCALE - 1; i++) { p = strtok(p, "/"); if (p == NULL) return (NULL); /* missing item */ switch (i) { case LC_CTYPE - 1: if (setlocale(LC_CTYPE,p) == NULL) return (NULL); break; case LC_NUMERIC - 1: if (setlocale(LC_NUMERIC,p) == NULL) return (NULL); break; case LC_TIME - 1: if (setlocale(LC_TIME,p) == NULL) return (NULL); break; case LC_MONETARY - 1: if (setlocale(LC_MONETARY,p) == NULL) return (NULL); break; case LANGINFO - 1: if (setlocale(LANGINFO,p) == NULL) return (NULL); break; case LC_COLLATE - 1: if (setlocale(LC_COLLATE,p) == NULL) return (NULL); break; case LC_MESSAGES - 1: if (setlocale(LC_MESSAGES,p) == NULL) return (NULL); break; } p = NULL; } if (strtok((char *)NULL, "/") != NULL) return (NULL); /* extra stuff at end */ } /* If category = LC_ALL, Drop through to test each individual * category, one at a time. Note default rules where env vars * are not set */ case LC_CTYPE: if ((ret = getlocale_ctype(locale , my_ctype, _locales[LC_CTYPE - 1])) < 0) return (NULL); if (ret) { (void) memcpy(_ctype_, my_ctype, CTYPE_SIZE/2); (void) memcpy(_ctype_ul, my_ctype+(CTYPE_SIZE/2), CTYPE_SIZE/2); } if (category != LC_ALL) break; case LC_NUMERIC: if ((my_lconv_numeric_str = getlocale_numeric(locale, &my_lconv, _locales[LC_NUMERIC - 1])) == NULL) return (NULL); if (*my_lconv_numeric_str) { if (lconv_numeric_str != NULL) free((malloc_t)lconv_numeric_str); lconv_numeric_str = my_lconv_numeric_str; memcpy(lconv, my_lconv, sizeof(my_lconv)); } if (category != LC_ALL) break; case LC_TIME: if ((ret = openlocale("LC_TIME", LC_TIME, locale, _locales[LC_TIME -1])) < 0) return (NULL); if (ret) (void) close(ret); if (category != LC_ALL) break; case LC_MONETARY: if ((my_lconv_monetary_str = getlocale_monetary(locale, &my_lconv, _locales[LC_MONETARY - 1])) == NULL) return (NULL); if (*my_lconv_monetary_str) { if (lconv_monetary_str != NULL) free((malloc_t)lconv_monetary_str); lconv_monetary_str = my_lconv_monetary_str; memcpy(lconv, &my_lconv, sizeof(my_lconv)); } if (category != LC_ALL) break; case LANGINFO: if ((ret = openlocale("LANGINFO", LANGINFO, locale, _locales[LANGINFO - 1])) < 0) { lang_succ = OFF; return (NULL); } if (ret) { lang_succ = OFF; (void) close(ret); } if (category != LC_ALL) break; case LC_COLLATE: if ((ret = openlocale("LC_COLLATE", LC_COLLATE, locale, _locales[LC_COLLATE - 1])) < 0) return (NULL); if (ret) { (void) close(ret); } if (category != LC_ALL) break; case LC_MESSAGES: if ((ret = openlocale("LC_MESSAGES", LC_MESSAGES, locale, _locales[LC_MESSAGES - 1])) < 0) return (NULL); if (ret) { (void) close(ret); } } return (setlocale(category, (char *)NULL)); } int getlocale_ctype(char *locale, char *ctypep, char *newlocale) { int fd; if ((fd = openlocale("LC_CTYPE", LC_CTYPE, locale, newlocale)) > 0) { if (read(fd, (char *)ctypep, CTYPE_SIZE) != CTYPE_SIZE) { (void) close(fd); fd = -1; } (void) close(fd); } return (fd); } /* open and load the numeric information */ char * getlocale_numeric(char *locale, struct lconv *lconvp, char *newlocale) { int fd; struct stat buf; char *str; char *p; if ((fd = openlocale("LC_NUMERIC", LC_NUMERIC, locale, newlocale)) < 0) return (NULL); if (fd == 0) return ""; if ((fstat(fd, &buf)) != 0) return (NULL); if ((str = (char*)malloc((unsigned)buf.st_size + 2)) == NULL) return (NULL); if ((read(fd, str, (int)buf.st_size)) != buf.st_size) { free((malloc_t)str); return (NULL); } /* Set last character of str to '\0' */ p = &str[buf.st_size]; *p++ = '\n'; *p = '\0'; /* p will "walk thru" str */ p = str; p = getstr(p, &lconvp->decimal_point); if (p == NULL) goto fail; p = getstr(p, &lconvp->thousands_sep); if (p == NULL) goto fail; p = getgrouping(p, &lconvp->grouping); if (p == NULL) goto fail; (void) close(fd); return (str); fail: (void) close(fd); free((malloc_t)str); return (NULL); } static char * getlocale_monetary(char *locale, struct lconv *lconvp, char *newlocale) { int fd; struct stat buf; char *str; char *p; if ((fd = openlocale("LC_MONETARY", LC_MONETARY, locale, newlocale)) < 0) return (NULL); if (fd == 0) return (""); if ((fstat(fd, &buf)) != 0) return (NULL); if ((str = (char*)malloc((unsigned)buf.st_size + 2)) == NULL) return (NULL); if ((read(fd, str, (int)buf.st_size)) != buf.st_size) { free((malloc_t)str); return (NULL); } /* Set last character of str to '\0' */ p = &str[buf.st_size]; *p++ = '\n'; *p = '\0'; /* p will "walk thru" str */ p = str; p = getstr(p, &lconvp->int_curr_symbol); if (p == NULL) goto fail; p = getstr(p, &lconvp->currency_symbol); if (p == NULL) goto fail; p = getstr(p, &lconvp->mon_decimal_point); if (p == NULL) goto fail; p = getstr(p, &lconvp->mon_thousands_sep); if (p == NULL) goto fail; p = getgrouping(p, &lconvp->mon_grouping); if (p == NULL) goto fail; p = getstr(p, &lconvp->positive_sign); if (p == NULL) goto fail; p = getstr(p, &lconvp->negative_sign); if (p == NULL) goto fail; p = getnum(p, &lconvp->frac_digits); if (p == NULL) goto fail; p = getbool(p, &lconvp->p_cs_precedes); if (p == NULL) goto fail; p = getbool(p, &lconvp->p_sep_by_space); if (p == NULL) goto fail; p = getbool(p, &lconvp->n_cs_precedes); if (p == NULL) goto fail; p = getbool(p, &lconvp->n_sep_by_space); if (p == NULL) goto fail; p = getnum(p, &lconvp->p_sign_posn); if (p == NULL) goto fail; p = getnum(p, &lconvp->n_sign_posn); if (p == NULL) goto fail; (void) close(fd); return (str); fail: (void) close(fd); free((malloc_t)str); return (NULL); } static char * getstr(char *p, char **strp) { *strp = p; p = strchr(p, '\n'); if (p == NULL) return (NULL); /* no end-of-line */ *p++ = '\0'; return (p); } static char * getgrouping(char *p, char **groupingp) { int c; if (*p == '\0') return (NULL); /* no grouping */ *groupingp = p; while ((c = *p) != '\n') { if (c == '\0') return (NULL); /* no end-of-line */ if (c >= '0' && c <= '9') *p++ = c - '0'; else *p++ = '\177'; } *p++ = '\0'; return (p); } static char * getnum(char *p, char *nump) { int num; int c; if (*p == '\0') return (NULL); /* no number */ if (*p == '\n') *nump = '\177'; /* blank line - no value */ else { num = 0; while ((c = *p) != '\n') { if (c < '0' || c > '9') return (NULL); /* bad number */ num = num*10 + c - '0'; p++; } *nump = num; } *p++ = '\0'; return (p); } static char * getbool(char *p, char *boolp) { if (*p == '\0') return (NULL); /* no number */ if (*p == '\n') *boolp = '\177'; /* blank line - no value */ else { switch (*p++) { case 'y': case 'Y': case 't': case 'T': *boolp = 1; /* true */ break; case 'n': case 'N': case 'f': case 'F': *boolp = 0; /* false */ break; default: return (NULL); /* bad boolean */ } if (*p != '\n') return (NULL); /* noise at end of line */ } *p++ = '\0'; return (p); } /* * Open a locale file. First, check the value of "locale"; if it's a null * string, first check the environment variable with the same name as the * category, and then check the environment variable "LANG". If neither of * them are set to non-null strings, use the LC_default env.var and if this * has no meaning then assume we are running in the C locale. It is expected * That LC_default is set across the whole system. If the resulting locale is * longer than MAXLOCALENAME characters, reject it. Then, try looking in the * per-machine locale directory for the file in question; if it's not found * there, try looking in the shared locale directory. * If there is no work to do, that is, the last setting of locales is equal * to the current request, then we don't do anything, and exit with value 0. * Copy the name of the locale used into "newlocale". * Exit with positive value if we opened a file * Exit with -1 if an error occured (invalid locale). * Exit with 0 if there is no need to look at the disk file. * (Assumption - there is always at least one fd open before setlocale * is called) */ int openlocale(char *category, int cat_id, char *locale, char *newlocale) { char pathname[MAXPATHLEN], *defp; int fd, fd2; struct _code_header code_header; char *my_info; if (*locale == '\0') { locale = getenv(category); if (locale == NULL || *locale == '\0') { locale = getenv("LANG"); if (locale == NULL || *locale == '\0') { if (*Default == '\0') { defp = getenv("LC_default"); if (defp == NULL || *defp == '\0') strcpy(Default,"C"); else strcpy(Default, defp); } locale = Default; } } } if (strcmp(locale,_locales[cat_id-1]) == 0) { (void) strcpy(newlocale, locale); return (0); } if (strlen(locale) > MAXLOCALENAME) return (-1); (void) strcpy(pathname, PRIVATE_LOCALE_DIR); (void) strcat(pathname, category); (void) strcat(pathname, "/"); (void) strcat(pathname, locale); if ((fd = open(pathname, O_RDONLY)) < 0 && errno == ENOENT) { (void) strcpy(pathname, LOCALE_DIR); (void) strcat(pathname, category); (void) strcat(pathname, "/"); (void) strcat(pathname, locale); fd = open(pathname, O_RDONLY); } if (fd >= 0) (void) strcpy(newlocale, locale); /* * bug id 1072740; if by some chance the actual fd we're going to * return is 0, change it to be some non-zero descriptor, because * returning 0 means something different. If '0' is the only * descriptor left, return an error. */ if (fd == 0) { int dupfd; if ((dupfd = dup(fd)) < 1) { (void) close(fd); fd = -1; } else { (void) close(fd); fd = dupfd; } } if (cat_id == LC_CTYPE) { /* Go and get the trailer file */ (void) strcat(pathname, TRAILER); fd2 = open(pathname, O_RDONLY); if ( fd2 == 0 ) { fd2 = dup(fd2); close(0); } if (fd2 == -1) { set_default(); return (fd); } /* * ctype trailer file exists - read it */ if (read (fd2, (char *)&code_header, sizeof (code_header)) != sizeof (code_header)) { /* * File format not correct */ set_default(); close(fd2); return (-1); } /* * set up trailer file */ strcpy(_code_set_info.code_name, code_header.code_name); _code_set_info.code_id = code_header.code_id; if (_code_set_info.code_info != NULL) free (_code_set_info.code_info); if (code_header.code_info_size > 0) { my_info = malloc(code_header.code_info_size); if (read (fd2, (char *)my_info, code_header.code_info_size) != code_header.code_info_size) { close(fd2); set_default(); return (-1); } _code_set_info.code_info = my_info; } else { /* * We have a corrupted file too */ _code_set_info.code_info = NULL; close(fd2); set_default(); return (-1); } close (fd2); } return (fd); } struct lconv * localeconv(void) { return (lconv); } struct dtconv * localdtconv(void) { char *p; short i; char *rawmonths = "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug\nSep\nOct\nNov\nDec\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember"; char *rawdays = "Sun\nMon\nTue\nWed\nThu\nFri\nSat\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday"; char *rawfmts = "%H:%M:%S\n%m/%d/%y\n%a %b %e %T %Z %Y\nAM\nPM\n%A, %B %e, %Y\n"; /* fix for bugid 1067574 ... robinson */ (void)getlocale_time(); if (_dtconv == NULL) { /* We malloc both the space for the dtconv struct and the * copy of the strings above because this program is later run * through xstr and the resultant strings are put in read-only * text segment. Therefore we cannot write to the original * raw strings but we can to their copies. */ _dtconv = (struct dtconv*)malloc(sizeof (struct dtconv)); if (_dtconv == NULL) return (NULL); if ((realmonths = malloc(strlen(rawmonths)+1)) == NULL) return (NULL); strcpy(realmonths, rawmonths); if ((realdays = malloc(strlen(rawdays)+1)) == NULL) return (NULL); strcpy(realdays, rawdays); if ((realfmts = malloc(strlen(rawfmts)+1)) == NULL) return (NULL); strcpy(realfmts, rawfmts); /* p will "walk thru" str */ p = realmonths; for (i = 0; i < 12; i++) p = getstr(p, &(_dtconv->abbrev_month_names[i])); for (i = 0; i < 12; i++) p = getstr(p, &(_dtconv->month_names[i])); p = realdays; for (i= 0; i < 7; i++) p = getstr(p, &(_dtconv->abbrev_weekday_names[i])); for (i = 0; i < 7; i++) p = getstr(p, &(_dtconv->weekday_names[i])); p = realfmts; p = getstr(p, &_dtconv->time_format); p = getstr(p, &_dtconv->sdate_format); p = getstr(p, &_dtconv->dtime_format); p = getstr(p, &_dtconv->am_string); p = getstr(p, &_dtconv->pm_string); p = getstr(p, &_dtconv->ldate_format); } return (_dtconv); } static void set_default(void) { strcpy(_code_set_info.code_name, Default); _code_set_info.code_id = CODESET_NONE; if (_code_set_info.code_info != NULL) free (_code_set_info.code_info); _code_set_info.code_info = NULL; _code_set_info.open_flag = 0; } void init_statics(void) { short i; for (i=0; i