/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include "lint.h" #include "mtlib.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libc.h" #include "msgfmt.h" #include "nlspath_checks.h" #include "gettext.h" #ifdef DEBUG #include #endif /* The following symbols are just for GNU binary compatibility */ int _nl_msg_cat_cntr; int *_nl_domain_bindings; static const char *nullstr = ""; #define CHARSET_MOD "charset=" #define CHARSET_LEN (sizeof (CHARSET_MOD) - 1) #define NPLURALS_MOD "nplurals=" #define NPLURALS_LEN (sizeof (NPLURALS_MOD) - 1) #define PLURAL_MOD "plural=" #define PLURAL_LEN (sizeof (PLURAL_MOD) - 1) static uint32_t get_hash_index(uint32_t *, uint32_t, uint32_t); /* * free_conv_msgstr * * release the memory allocated for storing code-converted messages * * f * 0: do not free gmnp->conv_msgstr * 1: free gmnp->conv_msgstr */ static void free_conv_msgstr(Msg_g_node *gmnp, int f) { uint32_t i, num_of_conv; #ifdef GETTEXT_DEBUG gprintf(0, "*************** free_conv_msgstr(0x%p, %d)\n", (void *)gmnp, f); printgnumsg(gmnp, 1); #endif num_of_conv = gmnp->num_of_str + gmnp->num_of_d_str; for (i = 0; i < num_of_conv; i++) { if (gmnp->conv_msgstr[i]) { free(gmnp->conv_msgstr[i]); } gmnp->conv_msgstr[i] = NULL; } if (f) { free(gmnp->conv_msgstr); gmnp->conv_msgstr = NULL; } } /* * dfltmsgstr * * choose an appropriate message by evaluating the plural expression, * and return it. */ static char * dfltmsgstr(Msg_g_node *gmnp, const char *msgstr, uint32_t msgstr_len, struct msg_pack *mp) { unsigned int pindex; size_t len; const char *p; #ifdef GETTEXT_DEBUG gprintf(0, "*************** dfltmsgstr(0x%p, \"%s\", %u, 0x%p)\n", (void *)gmnp, msgstr ? msgstr : "(null)", msgstr_len, (void *)mp); printgnumsg(gmnp, 1); printmp(mp, 1); #endif if (mp->plural) { if (gmnp->plural) { pindex = plural_eval(gmnp->plural, mp->n); } else { /* * This mo does not have plural information. * Using the English form. */ if (mp->n == 1) pindex = 0; else pindex = 1; } #ifdef GETTEXT_DEBUG gprintf(0, "plural_eval returned: %u\n", pindex); #endif if (pindex >= gmnp->nplurals) { /* should never happen */ pindex = 0; } p = msgstr; for (; pindex != 0; pindex--) { len = msgstr_len - (p - msgstr); p = memchr(p, '\0', len); if (p == NULL) { /* * null byte not found * this should never happen */ char *result; DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural); return (result); } p++; /* skip */ } return ((char *)p); } return ((char *)msgstr); } /* * parse_header * * parse the header entry of the GNU MO file and * extract the src encoding and the plural information of the MO file */ static int parse_header(const char *header, Msg_g_node *gmnp) { char *charset = NULL; char *charset_str; size_t len; char *nplurals_str, *plural_str; plural_expr_t plural; char *p, *q; unsigned int nplurals; int ret; #ifdef GETTEXT_DEBUG gprintf(0, "*************** parse_header(\"%s\", 0x%p)\n", header ? header : "(null)", (void *)gmnp); printgnumsg(gmnp, 1); #endif if (header == NULL) { gmnp->src_encoding = (char *)nullstr; gmnp->nplurals = 2; gmnp->plural = NULL; #ifdef GETTEXT_DEBUG gprintf(0, "*************** exiting parse_header\n"); gprintf(0, "no header\n"); #endif return (0); } charset_str = strstr(header, CHARSET_MOD); if (charset_str == NULL) { gmnp->src_encoding = (char *)nullstr; } else { p = charset_str + CHARSET_LEN; q = p; while ((*q != ' ') && (*q != '\t') && (*q != '\n')) { q++; } len = q - p; if (len > 0) { charset = malloc(len + 1); if (charset == NULL) { gmnp->src_encoding = (char *)nullstr; gmnp->nplurals = 2; gmnp->plural = NULL; return (-1); } (void) memcpy(charset, p, len); charset[len] = '\0'; gmnp->src_encoding = charset; } else { gmnp->src_encoding = (char *)nullstr; } } nplurals_str = strstr(header, NPLURALS_MOD); plural_str = strstr(header, PLURAL_MOD); if (nplurals_str == NULL || plural_str == NULL) { /* no valid plural specification */ gmnp->nplurals = 2; gmnp->plural = NULL; #ifdef GETTEXT_DEBUG gprintf(0, "*************** exiting parse_header\n"); gprintf(0, "no plural entry\n"); #endif return (0); } else { p = nplurals_str + NPLURALS_LEN; while (*p && isspace((unsigned char)*p)) { p++; } nplurals = (unsigned int)strtol(p, &q, 10); if (p != q) { gmnp->nplurals = nplurals; } else { gmnp->nplurals = 2; } p = plural_str + PLURAL_LEN; #ifdef GETTEXT_DEBUG gprintf(0, "plural_str: \"%s\"\n", p); #endif ret = plural_expr(&plural, (const char *)p); if (ret == 0) { /* parse succeeded */ gmnp->plural = plural; #ifdef GETTEXT_DEBUG gprintf(0, "*************** exiting parse_header\n"); gprintf(0, "charset: \"%s\"\n", charset ? charset : "(null)"); printexpr(plural, 1); #endif return (0); } else if (ret == 1) { /* parse error */ gmnp->nplurals = 2; gmnp->plural = NULL; return (0); } else { /* fatal error */ if (charset) free(charset); gmnp->src_encoding = (char *)nullstr; gmnp->nplurals = 2; gmnp->plural = NULL; return (-1); } } /* NOTREACHED */ } /* * handle_lang * * take care of the LANGUAGE specification */ char * handle_lang(struct msg_pack *mp) { const char *p, *op, *q; size_t locale_len; char *result; char locale[MAXPATHLEN]; #ifdef GETTEXT_DEBUG gprintf(0, "*************** handle_lang(0x%p)\n", (void *)mp); printmp(mp, 1); #endif p = mp->language; while (*p) { op = p; q = strchr(p, ':'); if (q == NULL) { locale_len = strlen(p); p += locale_len; } else { locale_len = q - p; p += locale_len + 1; } if (locale_len >= MAXPATHLEN || locale_len == 0) { /* illegal locale name */ continue; } (void) memcpy(locale, op, locale_len); locale[locale_len] = '\0'; mp->locale = locale; #ifdef GETTEXT_DEBUG *mp->msgfile = '\0'; #endif if (mk_msgfile(mp) == NULL) { /* illegal locale name */ continue; } result = handle_mo(mp); if (mp->status & ST_GNU_MSG_FOUND) return (result); if (mp->status & ST_SUN_MO_FOUND) break; } /* * no valid locale found, Sun MO found, or * GNU MO found but no valid msg found there. */ if (mp->status & ST_GNU_MO_FOUND) { /* * GNU MO found but no valid msg found there. * returning DFLTMSG. */ DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural); return (result); } return (NULL); } /* * gnu_msgsearch * * Searchs the translation message for the specified msgid1. * Hash algorithm used in this function is Open Addressing * with Double Hashing: * H(k, i) = (H1(k) + i * H2(k)) mod M * H1(k) = hashvalue % M * H2(k) = 1 + (hashvalue % (M - 2)) * * Ref: The Art of Computer Programming Volume 3 * Sorting and Searching, second edition * Donald E Knuth */ static char * gnu_msgsearch(Msg_g_node *gmnp, const char *msgid1, uint32_t *msgstrlen, uint32_t *midx) { struct gnu_msg_info *header = gmnp->msg_file_info; struct gnu_msg_ent *msgid_tbl, *msgstr_tbl; uint32_t num_of_str, idx, mlen, msglen; uint32_t hash_size, hash_val, hash_id, hash_inc, hash_idx; uint32_t *hash_table; char *base; char *msg; #ifdef GETTEXT_DEBUG gprintf(0, "*************** gnu_msgsearch(0x%p, \"%s\", " "0x%p, 0x%p)\n", (void *)gmnp, msgid1, msgstrlen, midx); printgnumsg(gmnp, 1); #endif base = (char *)header; msgid_tbl = gmnp->msg_tbl[MSGID]; msgstr_tbl = gmnp->msg_tbl[MSGSTR]; hash_table = gmnp->hash_table; hash_size = gmnp->hash_size; num_of_str = gmnp->num_of_str; if (!(gmnp->flag & ST_REV1) && (hash_table == NULL || (hash_size <= 2))) { /* * Revision 0 and * No hash table exists or * hash size is enough small. */ uint32_t top, bottom; char *msg_id_str; int val; top = 0; bottom = num_of_str; while (top < bottom) { idx = (top + bottom) / 2; msg_id_str = base + SWAP(gmnp, msgid_tbl[idx].offset); val = strcmp(msg_id_str, msgid1); if (val < 0) { top = idx + 1; } else if (val > 0) { bottom = idx; } else { *msgstrlen = (unsigned int) SWAP(gmnp, msgstr_tbl[idx].len) + 1; *midx = idx; return (base + SWAP(gmnp, msgstr_tbl[idx].offset)); } } /* not found */ return ((char *)msgid1); } /* use hash table */ hash_id = get_hashid(msgid1, &msglen); hash_idx = hash_id % hash_size; hash_inc = 1 + (hash_id % (hash_size - 2)); for (;;) { hash_val = HASH_TBL(gmnp, hash_table[hash_idx]); if (hash_val == 0) { /* not found */ return ((char *)msgid1); } if (hash_val <= num_of_str) { /* static message */ idx = hash_val - 1; mlen = SWAP(gmnp, msgid_tbl[idx].len); msg = base + SWAP(gmnp, msgid_tbl[idx].offset); } else { if (!(gmnp->flag & ST_REV1)) { /* rev 0 does not have dynamic message */ return ((char *)msgid1); } /* dynamic message */ idx = hash_val - num_of_str - 1; mlen = gmnp->d_msg[MSGID][idx].len; msg = gmnp->mchunk + gmnp->d_msg[MSGID][idx].offset; } if (msglen <= mlen && strcmp(msgid1, msg) == 0) { /* found */ break; } hash_idx = (hash_idx + hash_inc) % hash_size; } /* msgstrlen should include a null termination */ if (hash_val <= num_of_str) { *msgstrlen = SWAP(gmnp, msgstr_tbl[idx].len) + 1; msg = base + SWAP(gmnp, msgstr_tbl[idx].offset); *midx = idx; } else { *msgstrlen = gmnp->d_msg[MSGSTR][idx].len + 1; msg = gmnp->mchunk + gmnp->d_msg[MSGSTR][idx].offset; *midx = idx + num_of_str; } return (msg); } /* * do_conv * * Converts the specified string from the src encoding * to the dst encoding by calling iconv() */ static uint32_t * do_conv(iconv_t fd, const char *src, uint32_t srclen) { uint32_t tolen; uint32_t *ptr, *optr; size_t oleft, ileft, bufsize, memincr; char *to, *tptr; #ifdef GETTEXT_DEBUG gprintf(0, "*************** do_conv(" "0x%p, \"%s\", %d)\n", (void *)fd, src ? src : "(null)", srclen); #endif memincr = srclen * 2; bufsize = memincr; ileft = srclen; oleft = bufsize; ptr = malloc(bufsize + sizeof (uint32_t)); if (ptr == NULL) { return (NULL); } to = (char *)(ptr + 1); for (;;) { tptr = to; errno = 0; #ifdef GETTEXT_DEBUG gprintf(0, "******* calling iconv()\n"); #endif if (iconv(fd, &src, &ileft, &tptr, &oleft) == (size_t)-1) { if (errno == E2BIG) { #ifdef GETTEXT_DEBUG gprintf(0, "******* iconv detected E2BIG\n"); gprintf(0, "old bufsize: %u\n", bufsize); #endif optr = realloc(ptr, bufsize + memincr + sizeof (uint32_t)); if (optr == NULL) { free(ptr); return (NULL); } ptr = optr; to = (char *)(optr + 1); to += bufsize - oleft; oleft += memincr; bufsize += memincr; #ifdef GETTEXT_DEBUG gprintf(0, "new bufsize: %u\n", bufsize); #endif continue; } else { tolen = (uint32_t)(bufsize - oleft); break; } } tolen = (uint32_t)(bufsize - oleft); break; } if (tolen < bufsize) { /* shrink the buffer */ optr = realloc(ptr, tolen + sizeof (uint32_t)); if (optr == NULL) { free(ptr); return (NULL); } ptr = optr; } *ptr = tolen; #ifdef GETTEXT_DEBUG gprintf(0, "******* exiting do_conv()\n"); gprintf(0, "tolen: %u\n", *ptr); gprintf(0, "return: 0x%p\n", ptr); #endif return (ptr); } /* * conv_msg */ static char * conv_msg(Msg_g_node *gmnp, char *msgstr, uint32_t msgstr_len, uint32_t midx, struct msg_pack *mp) { uint32_t *conv_dst; size_t num_of_conv, conv_msgstr_len; char *conv_msgstr, *result; if (gmnp->conv_msgstr == NULL) { num_of_conv = gmnp->num_of_str + gmnp->num_of_d_str; gmnp->conv_msgstr = calloc((size_t)num_of_conv, sizeof (uint32_t *)); if (gmnp->conv_msgstr == NULL) { /* malloc failed */ result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp); return (result); } } conv_dst = do_conv(gmnp->fd, (const char *)msgstr, msgstr_len); if (conv_dst == NULL) { result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp); return (result); } conv_msgstr_len = *conv_dst; gmnp->conv_msgstr[midx] = conv_dst; conv_msgstr = (char *)(conv_dst + 1); result = dfltmsgstr(gmnp, conv_msgstr, conv_msgstr_len, mp); return (result); } /* * gnu_key_2_text * * Extracts msgstr from the GNU MO file */ char * gnu_key_2_text(Msg_g_node *gmnp, const char *codeset, struct msg_pack *mp) { uint32_t msgstr_len, midx; iconv_t fd; char *result, *msgstr; int ret, conversion, new_encoding; #ifdef GETTEXT_DEBUG gprintf(0, "*************** gnu_key_2_text(" "0x%p, \"%s\", 0x%p)\n", (void *)gmnp, codeset ? codeset : "(null)", (void *)mp); printgnumsg(gmnp, 1); printmp(mp, 1); #endif /* first checks if header entry has been processed */ if (!(gmnp->flag & ST_CHK)) { char *msg_header; msg_header = gnu_msgsearch(gmnp, "", &msgstr_len, &midx); ret = parse_header((const char *)msg_header, gmnp); if (ret == -1) { /* fatal error */ DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural); return (result); } gmnp->flag |= ST_CHK; } msgstr = gnu_msgsearch(gmnp, mp->msgid1, &msgstr_len, &midx); if (msgstr == mp->msgid1) { /* not found */ DFLTMSG(result, mp->msgid1, mp->msgid2, mp->n, mp->plural); return (result); } #ifdef GETTEXT_DEBUG printgnumsg(gmnp, 1); #endif if (gmnp->dst_encoding == NULL) { /* * destination encoding has not been set. */ char *dupcodeset = strdup(codeset); if (dupcodeset == NULL) { /* strdup failed */ result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp); return (result); } gmnp->dst_encoding = dupcodeset; if (strcmp(gmnp->dst_encoding, gmnp->src_encoding) == 0) { /* * target encoding and src encoding * are the same. * No conversion required. */ conversion = 0; } else { /* * target encoding is different from * src encoding. * New conversion required. */ /* sanity check */ if (gmnp->fd && (gmnp->fd != (iconv_t)-1)) { (void) iconv_close(gmnp->fd); gmnp->fd = (iconv_t)-1; } if (gmnp->conv_msgstr) free_conv_msgstr(gmnp, 0); conversion = 1; new_encoding = 1; } } else { /* * dst encoding has been already set. */ if (strcmp(gmnp->dst_encoding, codeset) == 0) { /* * dst encoding and target encoding are the same. */ if (strcmp(gmnp->dst_encoding, gmnp->src_encoding) == 0) { /* * dst encoding and src encoding are the same. * No conversion required. */ conversion = 0; } else { /* * dst encoding is different from src encoding. * current conversion is valid. */ conversion = 1; new_encoding = 0; /* checks if iconv_open has succeeded before */ if (gmnp->fd == (iconv_t)-1) { /* * iconv_open should have failed before * Assume this conversion is invalid */ conversion = 0; } else { if (gmnp->conv_msgstr == NULL) { /* * memory allocation for * conv_msgstr should * have failed before. */ new_encoding = 1; if (gmnp->fd) (void) iconv_close( gmnp->fd); gmnp->fd = (iconv_t)-1; } } } } else { /* * dst encoding is different from target encoding. * It has changed since before. */ char *dupcodeset = strdup(codeset); if (dupcodeset == NULL) { result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp); return (result); } free(gmnp->dst_encoding); gmnp->dst_encoding = dupcodeset; if (strcmp(gmnp->dst_encoding, gmnp->src_encoding) == 0) { /* * dst encoding and src encoding are the same. * now, no conversion required. */ conversion = 0; if (gmnp->conv_msgstr) free_conv_msgstr(gmnp, 1); } else { /* * dst encoding is different from src encoding. * new conversion required. */ conversion = 1; new_encoding = 1; if (gmnp->conv_msgstr) free_conv_msgstr(gmnp, 0); } if (gmnp->fd && (gmnp->fd != (iconv_t)-1)) { (void) iconv_close(gmnp->fd); } if (gmnp->fd != (iconv_t)-1) { gmnp->fd = (iconv_t)-1; } } } if (conversion == 0) { /* no conversion */ result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp); return (result); } /* conversion required */ if (new_encoding == 0) { /* dst codeset hasn't been changed since before */ uint32_t *cmsg; uint32_t conv_msgstr_len; char *conv_msgstr; if (gmnp->conv_msgstr[midx] == NULL) { /* this msgstr hasn't been converted yet */ result = conv_msg(gmnp, msgstr, msgstr_len, midx, mp); return (result); } /* this msgstr is in the conversion cache */ cmsg = (uint32_t *)(uintptr_t)gmnp->conv_msgstr[midx]; conv_msgstr_len = *cmsg; conv_msgstr = (char *)(cmsg + 1); result = dfltmsgstr(gmnp, conv_msgstr, conv_msgstr_len, mp); return (result); } /* new conversion */ #ifdef GETTEXT_DEBUG gprintf(0, "******* calling iconv_open()\n"); gprintf(0, " dst: \"%s\", src: \"%s\"\n", gmnp->dst_encoding, gmnp->src_encoding); #endif fd = iconv_open(gmnp->dst_encoding, gmnp->src_encoding); gmnp->fd = fd; if (fd == (iconv_t)-1) { /* * iconv_open() failed. * no conversion */ result = dfltmsgstr(gmnp, msgstr, msgstr_len, mp); return (result); } result = conv_msg(gmnp, msgstr, msgstr_len, midx, mp); return (result); } #define PRI_STR(x, n) PRI##x##n #define PRI_LEN(x, n) (char)(sizeof (PRI_STR(x, n)) - 1) #define PRIS(P, x) {\ /* x/N/ */ P(x, 8), P(x, 16), P(x, 32), P(x, 64), \ /* xLEAST/N/ */ P(x, LEAST8), P(x, LEAST16), P(x, LEAST32), P(x, LEAST64), \ /* xFAST/N/ */ P(x, FAST8), P(x, FAST16), P(x, FAST32), P(x, FAST64), \ /* xMAX,PTR */ P(x, MAX), P(x, PTR) \ } #define PRI_BIAS_LEAST 4 #define PRI_BIAS_FAST 8 #define PRI_BIAS_MAX 12 #define PRI_BIAS_PTR 13 static const char *pri_d[] = PRIS(PRI_STR, d); static const char *pri_i[] = PRIS(PRI_STR, i); static const char *pri_o[] = PRIS(PRI_STR, o); static const char *pri_u[] = PRIS(PRI_STR, u); static const char *pri_x[] = PRIS(PRI_STR, x); static const char *pri_X[] = PRIS(PRI_STR, X); static const char pri_d_len[] = PRIS(PRI_LEN, d); static const char pri_i_len[] = PRIS(PRI_LEN, i); static const char pri_o_len[] = PRIS(PRI_LEN, o); static const char pri_u_len[] = PRIS(PRI_LEN, u); static const char pri_x_len[] = PRIS(PRI_LEN, x); static const char pri_X_len[] = PRIS(PRI_LEN, X); static struct { const char type; const char **str_table; const char *len_table; } pri_table[] = { {'d', pri_d, pri_d_len}, {'i', pri_i, pri_i_len}, {'o', pri_o, pri_o_len}, {'u', pri_u, pri_u_len}, {'x', pri_x, pri_x_len}, {'X', pri_X, pri_X_len}, }; static struct { const char *name; const char nlen; const char want_digits; const char bias; } special_table[] = { {"LEAST", 5, 1, PRI_BIAS_LEAST}, {"FAST", 4, 1, PRI_BIAS_FAST}, {"MAX", 3, 0, PRI_BIAS_MAX}, {"PTR", 3, 0, PRI_BIAS_PTR}, }; /* * conv_macro() returns the conversion specifier corresponding * to the macro name specified in 'name'. 'len' contains the * length of the macro name including the null termination. * '*elen' will be set to the length of the returning conversion * specifier without the null termination. */ static const char * conv_macro(const char *str, uint32_t len, uint32_t *lenp) { const char **tbl; const char *ltbl; char *next; int n, i, num, bias, idx, want_digits; if (len == 2) { if (*str == 'I') { /* Solaris does not support %I */ *lenp = 0; return (""); } return (NULL); } if (len <= 4 || strncmp(str, "PRI", 3) != 0) return (NULL); str += 3; n = sizeof (pri_table) / sizeof (pri_table[0]); for (i = 0; i < n; i++) { if (pri_table[i].type == *str) break; } if (i == n) return (NULL); tbl = pri_table[i].str_table; ltbl = pri_table[i].len_table; str++; idx = want_digits = 0; if (isdigit((unsigned char)*str)) { /* PRIx/N/ */ bias = 0; want_digits = 1; } else { n = sizeof (special_table) / sizeof (special_table[0]); for (i = 0; i < n; i++) { if (strncmp(special_table[i].name, str, special_table[i].nlen) == 0) { break; } } if (i == n) return (NULL); bias = special_table[i].bias; want_digits = special_table[i].want_digits; str += special_table[i].nlen; } if (want_digits) { if (!isdigit((unsigned char)*str)) return (NULL); num = strtol(str, &next, 10); /* see if it is 8/16/32/64 */ for (n = 8, idx = 0; idx < 4; idx++, n *= 2) { if (n == num) break; } if (idx == 4) return (NULL); str = next; } if (*str != '\0') { /* unknow format */ return (NULL); } *lenp = (uint32_t)ltbl[bias + idx]; return (tbl[bias + idx]); } static gnu_d_macro_t * expand_macros(Msg_g_node *p) { char *base = (char *)p->msg_file_info; struct gnu_msg_rev1_info *rev1_header = p->rev1_header; struct gnu_msg_ent *d_macro_tbl; gnu_d_macro_t *d_macro; uint32_t num_of_d_macro, e_maclen, maclen, i; const char *e_macname; char *macname; /* number of the dynamic macros */ num_of_d_macro = SWAP(p, rev1_header->num_of_dynamic_macro); d_macro = malloc((size_t)num_of_d_macro * sizeof (gnu_d_macro_t)); if (d_macro == NULL) return (NULL); /* pointer to the dynamic strings table */ d_macro_tbl = (struct gnu_msg_ent *)(uintptr_t) (base + SWAP(p, rev1_header->off_dynamic_macro)); for (i = 0; i < num_of_d_macro; i++) { macname = base + SWAP(p, d_macro_tbl[i].offset); maclen = SWAP(p, d_macro_tbl[i].len); /* * sanity check * maclen includes a null termination. */ if (maclen != strlen(macname) + 1) { free(d_macro); return (NULL); } e_macname = conv_macro(macname, maclen, &e_maclen); if (e_macname == NULL) { free(d_macro); return (NULL); } d_macro[i].len = e_maclen; d_macro[i].ptr = e_macname; } return (d_macro); } static char * expand_dynamic_message(Msg_g_node *p, struct gnu_msg_ent **e_msgs) { char *base = (char *)p->msg_file_info; struct gnu_msg_rev1_info *rev1_header = p->rev1_header; struct gnu_dynamic_tbl *d_info; struct gnu_dynamic_ent *entry; gnu_d_macro_t *d_macro; uint32_t num_of_d_str, mlen, dlen, didx, i, j; uint32_t off_d_tbl; uint32_t *d_msg_off_tbl; size_t mchunk_size, used, need; char *mchunk, *msg; #define MEM_INCR (1024) d_macro = expand_macros(p); if (d_macro == NULL) return (NULL); /* number of dynamic messages */ num_of_d_str = p->num_of_d_str; mchunk = NULL; mchunk_size = 0; /* size of the allocated memory in mchunk */ used = 0; /* size of the used memory in mchunk */ for (i = MSGID; i <= MSGSTR; i++) { /* pointer to the offset table of dynamic msgids/msgstrs */ off_d_tbl = SWAP(p, i == MSGID ? rev1_header->off_dynamic_msgid_tbl : rev1_header->off_dynamic_msgstr_tbl); /* pointer to the dynamic msgids/msgstrs */ d_msg_off_tbl = (uint32_t *)(uintptr_t)(base + off_d_tbl); for (j = 0; j < num_of_d_str; j++) { e_msgs[i][j].offset = used; d_info = (struct gnu_dynamic_tbl *)(uintptr_t) (base + SWAP(p, d_msg_off_tbl[j])); entry = d_info->entry; msg = base + SWAP(p, d_info->offset); for (;;) { mlen = SWAP(p, entry->len); didx = SWAP(p, entry->idx); dlen = (didx == NOMORE_DYNAMIC_MACRO) ? 0 : d_macro[didx].len; need = used + mlen + dlen; if (need >= mchunk_size) { char *t; size_t n = mchunk_size; do { n += MEM_INCR; } while (n <= need); t = realloc(mchunk, n); if (t == NULL) { free(d_macro); free(mchunk); return (NULL); } mchunk = t; mchunk_size = n; } (void) memcpy(mchunk + used, msg, (size_t)mlen); msg += mlen; used += mlen; if (didx == NOMORE_DYNAMIC_MACRO) { /* * Last segment of a static * msg string contains a null * termination, so an explicit * null termination is not required * here. */ break; } (void) memcpy(mchunk + used, d_macro[didx].ptr, (size_t)dlen); used += dlen; entry++; /* to next entry */ } /* * e_msgs[][].len does not include a null termination */ e_msgs[i][j].len = used - e_msgs[i][j].offset - 1; } } free(d_macro); /* shrink mchunk to 'used' */ { char *t; t = realloc(mchunk, used); if (t == NULL) { free(mchunk); return (NULL); } mchunk = t; } return (mchunk); } static int build_rev1_info(Msg_g_node *p) { uint32_t *d_hash; uint32_t num_of_d_str, num_of_str; uint32_t idx, hash_value, hash_size; size_t hash_mem_size; size_t d_msgid_size, d_msgstr_size; char *chunk, *mchunk; int i; #ifdef GETTEXT_DEBUG gprintf(0, "******* entering build_rev1_info(0x%p)\n", p); printgnumsg(p, 1); #endif if (p->hash_table == NULL) { /* Revision 1 always requires the hash table */ return (-1); } num_of_str = p->num_of_str; hash_size = p->hash_size; num_of_d_str = p->num_of_d_str; hash_mem_size = hash_size * sizeof (uint32_t); ROUND(hash_mem_size, sizeof (struct gnu_msg_ent)); d_msgid_size = num_of_d_str * sizeof (struct gnu_msg_ent); d_msgstr_size = num_of_d_str * sizeof (struct gnu_msg_ent); chunk = malloc(hash_mem_size + d_msgid_size + d_msgstr_size); if (chunk == NULL) { return (-1); } d_hash = (uint32_t *)(uintptr_t)chunk; p->d_msg[MSGID] = (struct gnu_msg_ent *)(uintptr_t) (chunk + hash_mem_size); p->d_msg[MSGSTR] = (struct gnu_msg_ent *)(uintptr_t) (chunk + hash_mem_size + d_msgid_size); if ((mchunk = expand_dynamic_message(p, p->d_msg)) == NULL) { free(chunk); return (-1); } /* copy the original hash table into the dynamic hash table */ for (i = 0; i < hash_size; i++) { d_hash[i] = SWAP(p, p->hash_table[i]); } /* fill in the dynamic hash table with dynamic messages */ for (i = 0; i < num_of_d_str; i++) { hash_value = get_hashid(mchunk + p->d_msg[MSGID][i].offset, NULL); idx = get_hash_index(d_hash, hash_value, hash_size); d_hash[idx] = num_of_str + i + 1; } p->mchunk = mchunk; p->hash_table = d_hash; #ifdef GETTEXT_DEBUG print_rev1_info(p); gprintf(0, "******* exiting build_rev1_info()\n"); printgnumsg(p, 1); #endif return (0); } /* * gnu_setmsg * * INPUT * mnp - message node * addr - address to the mmapped file * size - size of the file * * RETURN * 0 - either T_GNU_MO or T_ILL_MO has been set * -1 - failed */ int gnu_setmsg(Msg_node *mnp, char *addr, size_t size) { struct gnu_msg_info *gnu_header; Msg_g_node *p; #ifdef GETTEXT_DEBUG gprintf(0, "******** entering gnu_setmsg(0x%p, 0x%p, %lu)\n", (void *)mnp, addr, size); printmnp(mnp, 1); #endif /* checks the GNU MAGIC number */ if (size < sizeof (struct gnu_msg_info)) { /* invalid mo file */ mnp->type = T_ILL_MO; #ifdef GETTEXT_DEBUG gprintf(0, "********* exiting gnu_setmsg\n"); printmnp(mnp, 1); #endif return (0); } gnu_header = (struct gnu_msg_info *)(uintptr_t)addr; p = calloc(1, sizeof (Msg_g_node)); if (p == NULL) { return (-1); } p->msg_file_info = gnu_header; if (gnu_header->magic == GNU_MAGIC) { switch (gnu_header->revision) { case GNU_REVISION_0_1: case GNU_REVISION_1_1: p->flag |= ST_REV1; break; } } else if (gnu_header->magic == GNU_MAGIC_SWAPPED) { p->flag |= ST_SWP; switch (gnu_header->revision) { case GNU_REVISION_0_1_SWAPPED: case GNU_REVISION_1_1_SWAPPED: p->flag |= ST_REV1; break; } } else { /* invalid mo file */ free(p); mnp->type = T_ILL_MO; #ifdef GETTEXT_DEBUG gprintf(0, "********* exiting gnu_setmsg\n"); printmnp(mnp, 1); #endif return (0); } p->fsize = size; p->num_of_str = SWAP(p, gnu_header->num_of_str); p->hash_size = SWAP(p, gnu_header->sz_hashtbl); p->hash_table = p->hash_size <= 2 ? NULL : (uint32_t *)(uintptr_t) (addr + SWAP(p, gnu_header->off_hashtbl)); p->msg_tbl[MSGID] = (struct gnu_msg_ent *)(uintptr_t) (addr + SWAP(p, gnu_header->off_msgid_tbl)); p->msg_tbl[MSGSTR] = (struct gnu_msg_ent *)(uintptr_t) (addr + SWAP(p, gnu_header->off_msgstr_tbl)); if (p->flag & ST_REV1) { /* Revision 1 */ struct gnu_msg_rev1_info *rev1_header; rev1_header = (struct gnu_msg_rev1_info *) (uintptr_t)(addr + sizeof (struct gnu_msg_info)); p->rev1_header = rev1_header; p->num_of_d_str = SWAP(p, rev1_header->num_of_dynamic_str); if (build_rev1_info(p) == -1) { free(p); #ifdef GETTEXT_DEBUG gprintf(0, "******** exiting gnu_setmsg: " "build_rev1_info() failed\n"); #endif return (-1); } } mnp->msg.gnumsg = p; mnp->type = T_GNU_MO; #ifdef GETTEXT_DEBUG gprintf(0, "********* exiting gnu_setmsg\n"); printmnp(mnp, 1); #endif return (0); } /* * get_hash_index * * Returns the index to an empty slot in the hash table * for the specified hash_value. */ static uint32_t get_hash_index(uint32_t *hash_tbl, uint32_t hash_value, uint32_t hash_size) { uint32_t idx, inc; idx = hash_value % hash_size; inc = 1 + (hash_value % (hash_size - 2)); for (;;) { if (hash_tbl[idx] == 0) { /* found an empty slot */ return (idx); } idx = (idx + inc) % hash_size; } /* NOTREACHED */ }