diff options
Diffstat (limited to 'locale/programs')
-rw-r--r-- | locale/programs/localedef.c | 8 | ||||
-rw-r--r-- | locale/programs/localedef.h | 1 | ||||
-rw-r--r-- | locale/programs/locarchive.c | 416 |
3 files changed, 351 insertions, 74 deletions
diff --git a/locale/programs/localedef.c b/locale/programs/localedef.c index c1d347b06a..5bbf0bf7e3 100644 --- a/locale/programs/localedef.c +++ b/locale/programs/localedef.c @@ -76,6 +76,9 @@ static const char *input_file; /* Name of the repertoire map file. */ const char *repertoire_global; +/* Name of the locale.alias file. */ +const char *alias_file; + /* List of all locales. */ static struct localedef_t *locales; @@ -140,6 +143,8 @@ static const struct argp_option options[] = { "delete-from-archive", OPT_DELETE_FROM_ARCHIVE, NULL, 0, N_("Remove locales named by parameters from archive") }, { "list-archive", OPT_LIST_ARCHIVE, NULL, 0, N_("List content of archive") }, + { "alias-file", 'A', "FILE", 0, + N_("locale.alias file to consult when making archive")}, { NULL, 0, NULL, 0, NULL } }; @@ -331,6 +336,9 @@ parse_opt (int key, char *arg, struct argp_state *state) case 'f': charmap_file = arg; break; + case 'A': + alias_file = arg; + break; case 'i': input_file = arg; break; diff --git a/locale/programs/localedef.h b/locale/programs/localedef.h index 3217338ee7..41f72860d4 100644 --- a/locale/programs/localedef.h +++ b/locale/programs/localedef.h @@ -118,6 +118,7 @@ extern int oldstyle_tables; extern const char *repertoire_global; extern int max_locarchive_open_retry; extern bool no_archive; +extern const char *alias_file; /* Prototypes for a few program-wide used functions. */ diff --git a/locale/programs/locarchive.c b/locale/programs/locarchive.c index de026b2a74..267d7baaa4 100644 --- a/locale/programs/locarchive.c +++ b/locale/programs/locarchive.c @@ -31,6 +31,7 @@ #include <locale.h> #include <stdbool.h> #include <stdio.h> +#include <stdio_ext.h> #include <stdlib.h> #include <string.h> #include <time.h> @@ -59,7 +60,7 @@ static const char *locnames[] = /* Size of the initial archive header. */ -#define INITIAL_NUM_NANES 450 +#define INITIAL_NUM_NAMES 450 #define INITIAL_SIZE_STRINGS 3500 #define INITIAL_NUM_LOCREC 350 #define INITIAL_NUM_SUMS 2000 @@ -85,7 +86,7 @@ create_archive (const char *archivefname, struct locarhandle *ah) head.magic = AR_MAGIC; head.namehash_offset = sizeof (struct locarhead); head.namehash_used = 0; - head.namehash_size = next_prime (INITIAL_NUM_NANES); + head.namehash_size = next_prime (INITIAL_NUM_NAMES); head.string_offset = (head.namehash_offset + head.namehash_size * sizeof (struct namehashent)); @@ -166,6 +167,9 @@ create_archive (const char *archivefname, struct locarhandle *ah) ah->len = total; } +/* forward decl for below */ +static uint32_t add_locale (struct locarhandle *ah, const char *name, + locale_data_t data, bool replace); static void enlarge_archive (struct locarhandle *ah, const struct locarhead *head) @@ -300,10 +304,9 @@ enlarge_archive (struct locarhandle *ah, const struct locarhead *head) old_data[idx].sum); } - if (add_locale_to_archive (&new_ah, - ((char *) ah->addr - + oldnamehashtab[cnt].name_offset), - old_data, 0) != 0) + if (add_locale (&new_ah, + ((char *) ah->addr + oldnamehashtab[cnt].name_offset), + old_data, 0) == 0) error (EXIT_FAILURE, 0, _("cannot extend locale archive file")); } @@ -446,16 +449,123 @@ close_archive (struct locarhandle *ah) } } +#include "../../intl/explodename.c" +#include "../../intl/l10nflist.c" + +static struct namehashent * +insert_name (struct locarhandle *ah, + const char *name, size_t name_len, bool replace) +{ + const struct locarhead *const head = ah->addr; + struct namehashent *namehashtab + = (struct namehashent *) ((char *) ah->addr + head->namehash_offset); + unsigned int insert_idx, idx, incr; + + /* Hash value of the locale name. */ + uint32_t hval = compute_hashval (name, name_len); + + insert_idx = -1; + idx = hval % head->namehash_size; + incr = 1 + hval % (head->namehash_size - 2); + + /* If the name_offset field is zero this means this is a + deleted entry and therefore no entry can be found. */ + while (namehashtab[idx].name_offset != 0) + { + if (namehashtab[idx].hashval == hval + && strcmp (name, + (char *) ah->addr + namehashtab[idx].name_offset) == 0) + { + /* Found the entry. */ + if (namehashtab[idx].locrec_offset != 0 && ! replace) + { + if (! be_quiet) + error (0, 0, _("locale '%s' already exists"), name); + return NULL; + } + + break; + } + + /* Remember the first place we can insert the new entry. */ + if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1) + insert_idx = idx; + + idx += incr; + if (idx >= head->namehash_size) + idx -= head->namehash_size; + } + + /* Add as early as possible. */ + if (insert_idx != -1) + idx = insert_idx; + + namehashtab[idx].hashval = hval; /* no-op if replacing an old entry. */ + return &namehashtab[idx]; +} + +static void +add_alias (struct locarhandle *ah, const char *alias, bool replace, + const char *oldname, uint32_t locrec_offset) +{ + struct locarhead *head = ah->addr; + const size_t name_len = strlen (alias); + struct namehashent *namehashent = insert_name (ah, alias, strlen (alias), + replace); + if (namehashent == NULL && ! replace) + return; + + if (namehashent->name_offset == 0) + { + /* We are adding a new hash entry for this alias. + Determine whether we have to resize the file. */ + if (head->string_used + name_len + 1 > head->string_size + || 100 * head->namehash_used > 75 * head->namehash_size) + { + /* The current archive is not large enough. */ + enlarge_archive (ah, head); + + /* The locrecent might have moved, so we have to look up + the old name afresh. */ + namehashent = insert_name (ah, oldname, strlen (oldname), true); + assert (namehashent->name_offset != 0); + assert (namehashent->locrec_offset != 0); + locrec_offset = namehashent->locrec_offset; + + /* Tail call to try the whole thing again. */ + add_alias (ah, alias, replace, oldname, locrec_offset); + return; + } + + /* Add the name string. */ + memcpy (ah->addr + head->string_offset + head->string_used, + alias, name_len + 1); + namehashent->name_offset = head->string_offset + head->string_used; + head->string_used += name_len + 1; + + ++head->namehash_used; + } + + if (namehashent->locrec_offset != 0) + { + /* Replacing an existing entry. + Mark that we are no longer using the old locrecent. */ + struct locrecent *locrecent + = (struct locrecent *) ((char *) ah->addr + + namehashent->locrec_offset); + --locrecent->refs; + } + + /* Point this entry at the locrecent installed for the main name. */ + namehashent->locrec_offset = locrec_offset; +} + /* Check the content of the archive for duplicates. Add the content - of the files if necessary. Add all the names, possibly overwriting - old files. */ -int -add_locale_to_archive (ah, name, data, replace) - struct locarhandle *ah; - const char *name; - locale_data_t data; - bool replace; + of the files if necessary. Returns the locrec_offset. */ +static uint32_t +add_locale (struct locarhandle *ah, + const char *name, locale_data_t data, bool replace) { /* First look for the name. If it already exists and we are not supposed to replace it don't do anything. If it does not exist @@ -467,9 +577,7 @@ add_locale_to_archive (ah, name, data, replace) uint32_t hval; unsigned int cnt; unsigned int idx; - unsigned int insert_idx; struct locarhead *head; - struct namehashent *namehashtab; struct namehashent *namehashent; unsigned int incr; struct locrecent *locrecent; @@ -477,8 +585,6 @@ add_locale_to_archive (ah, name, data, replace) head = ah->addr; sumhashtab = (struct sumhashent *) ((char *) ah->addr + head->sumhash_offset); - namehashtab = (struct namehashent *) ((char *) ah->addr - + head->namehash_offset); /* For each locale category data set determine whether the same data @@ -514,47 +620,10 @@ add_locale_to_archive (ah, name, data, replace) } } - - /* Hash value of the locale name. */ - hval = compute_hashval (name, name_len); - - insert_idx = -1; - idx = hval % head->namehash_size; - incr = 1 + hval % (head->namehash_size - 2); - - /* If the name_offset field is zero this means this is no - deleted entry and therefore no entry can be found. */ - while (namehashtab[idx].name_offset != 0) - { - if (namehashtab[idx].hashval == hval - && strcmp (name, - (char *) ah->addr + namehashtab[idx].name_offset) == 0) - { - /* Found the entry. */ - if (namehashtab[idx].locrec_offset != 0 && ! replace) - { - if (! be_quiet) - error (0, 0, _("locale '%s' already exists"), name); - return 1; - } - - break; - } - - /* Remember the first place we can insert the new entry. */ - if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1) - insert_idx = idx; - - idx += incr; - if (idx >= head->namehash_size) - idx -= head->namehash_size; - } - - /* Add as early as possible. */ - if (insert_idx != -1) - idx = insert_idx; - - namehashent = &namehashtab[idx]; + /* Find a slot for the locale name in the hash table. */ + namehashent = insert_name (ah, name, name_len, replace); + if (namehashent == NULL) /* Already exists and !REPLACE. */ + return 0; /* Determine whether we have to resize the file. */ if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size @@ -565,7 +634,7 @@ add_locale_to_archive (ah, name, data, replace) { /* The current archive is not large enough. */ enlarge_archive (ah, head); - return add_locale_to_archive (ah, name, data, replace); + return add_locale (ah, name, data, replace); } /* Add the locale data which is not yet in the archive. */ @@ -620,29 +689,46 @@ add_locale_to_archive (ah, name, data, replace) ++head->sumhash_used; } - - if (namehashent->locrec_offset == 0) + if (namehashent->name_offset == 0) { /* Add the name string. */ memcpy ((char *) ah->addr + head->string_offset + head->string_used, name, name_len + 1); namehashent->name_offset = head->string_offset + head->string_used; head->string_used += name_len + 1; + ++head->namehash_used; + } + if (namehashent->locrec_offset == 0) + { /* Allocate a name location record. */ namehashent->locrec_offset = (head->locrectab_offset + (head->locrectab_used++ * sizeof (struct locrecent))); - - namehashent->hashval = hval; - - ++head->namehash_used; + locrecent = (struct locrecent *) ((char *) ah->addr + + namehashent->locrec_offset); + locrecent->refs = 1; } + else + { + /* If there are other aliases pointing to this locrecent, + we still need a new one. If not, reuse the old one. */ + locrecent = (struct locrecent *) ((char *) ah->addr + + namehashent->locrec_offset); + if (locrecent->refs > 1) + { + --locrecent->refs; + namehashent->locrec_offset = (head->locrectab_offset + + (head->locrectab_used++ + * sizeof (struct locrecent))); + locrecent = (struct locrecent *) ((char *) ah->addr + + namehashent->locrec_offset); + locrecent->refs = 1; + } + } /* Fill in the table with the locations of the locale data. */ - locrecent = (struct locrecent *) ((char *) ah->addr - + namehashent->locrec_offset); for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) { @@ -650,13 +736,196 @@ add_locale_to_archive (ah, name, data, replace) locrecent->record[cnt].len = data[cnt].size; } + return namehashent->locrec_offset; +} - /* Read the locale.alias file to see whether any matching record is - found. If an entry is available check whether it is already in - the archive. If this is the case check whether the new locale's - name is more specific than the one currently referred to by the - alias. */ +/* Check the content of the archive for duplicates. Add the content + of the files if necessary. Add all the names, possibly overwriting + old files. */ +int +add_locale_to_archive (ah, name, data, replace) + struct locarhandle *ah; + const char *name; + locale_data_t data; + bool replace; +{ + char *normalized_name = NULL; + uint32_t locrec_offset; + + /* First analyze the name to decide how to archive it. */ + const char *language; + const char *modifier; + const char *territory; + const char *codeset; + const char *normalized_codeset; + int mask = _nl_explode_name (strdupa (name), + &language, &modifier, &territory, + &codeset, &normalized_codeset); + + if (mask & XPG_NORM_CODESET) + /* This name contains a codeset in unnormalized form. + We will store it in the archive with a normalized name. */ + asprintf (&normalized_name, "%s%s%s.%s%s%s", + language, territory == NULL ? "" : "_", territory ?: "", + (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset, + modifier == NULL ? "" : "@", modifier ?: ""); + + /* This call does the main work. */ + locrec_offset = add_locale (ah, normalized_name ?: name, data, replace); + free (normalized_name); + if (locrec_offset == 0) + { + if (mask & XPG_NORM_CODESET) + free ((char *) normalized_codeset); + return -1; + } + + if ((mask & XPG_CODESET) == 0) + { + /* This name lacks a codeset, so determine the locale's codeset and + add an alias for its name with normalized codeset appended. */ + + const struct + { + unsigned int magic; + unsigned int nstrings; + unsigned int strindex[0]; + } *filedata = data[LC_CTYPE].addr; + codeset = (char *) filedata + + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)]; + + normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset)); + mask |= XPG_NORM_CODESET; + + asprintf (&normalized_name, "%s%s%s.%s%s%s", + language, territory == NULL ? "" : "_", territory ?: "", + normalized_codeset, + modifier == NULL ? "" : "@", modifier ?: ""); + + add_alias (ah, normalized_name, replace, name, locrec_offset); + free (normalized_name); + } + + /* Now read the locale.alias files looking for lines whose + right hand side matches our name after normalization. */ + if (alias_file != NULL) + { + FILE *fp; + fp = fopen (alias_file, "r"); + if (fp == NULL) + error (1, errno, _("locale alias file `%s' not found"), + alias_file); + + /* No threads present. */ + __fsetlocking (fp, FSETLOCKING_BYCALLER); + + while (! feof_unlocked (fp)) + { + /* It is a reasonable approach to use a fix buffer here + because + a) we are only interested in the first two fields + b) these fields must be usable as file names and so must + not be that long */ + char buf[BUFSIZ]; + char *alias; + char *value; + char *cp; + + if (fgets_unlocked (buf, BUFSIZ, fp) == NULL) + /* EOF reached. */ + break; + + cp = buf; + /* Ignore leading white space. */ + while (isspace (cp[0]) && cp[0] != '\n') + ++cp; + + /* A leading '#' signals a comment line. */ + if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n') + { + alias = cp++; + while (cp[0] != '\0' && !isspace (cp[0])) + ++cp; + /* Terminate alias name. */ + if (cp[0] != '\0') + *cp++ = '\0'; + + /* Now look for the beginning of the value. */ + while (isspace (cp[0])) + ++cp; + + if (cp[0] != '\0') + { + value = cp++; + while (cp[0] != '\0' && !isspace (cp[0])) + ++cp; + /* Terminate value. */ + if (cp[0] == '\n') + { + /* This has to be done to make the following + test for the end of line possible. We are + looking for the terminating '\n' which do not + overwrite here. */ + *cp++ = '\0'; + *cp = '\n'; + } + else if (cp[0] != '\0') + *cp++ = '\0'; + + /* Does this alias refer to our locale? We will + normalize the right hand side and compare the + elements of the normalized form. */ + { + const char *rhs_language; + const char *rhs_modifier; + const char *rhs_territory; + const char *rhs_codeset; + const char *rhs_normalized_codeset; + int rhs_mask = _nl_explode_name (value, + &rhs_language, + &rhs_modifier, + &rhs_territory, + &rhs_codeset, + &rhs_normalized_codeset); + if (!strcmp (language, rhs_language) + && ((rhs_mask & XPG_CODESET) + /* He has a codeset, it must match normalized. */ + ? !strcmp ((mask & XPG_NORM_CODESET) + ? normalized_codeset : codeset, + (rhs_mask & XPG_NORM_CODESET) + ? rhs_normalized_codeset : rhs_codeset) + /* He has no codeset, we must also have none. */ + : (mask & XPG_CODESET) == 0) + /* Codeset (or lack thereof) matches. */ + && !strcmp (territory ?: "", rhs_territory ?: "") + && !strcmp (modifier ?: "", rhs_modifier ?: "")) + /* We have a winner. */ + add_alias (ah, alias, replace, + normalized_name ?: name, locrec_offset); + if (rhs_mask & XPG_NORM_CODESET) + free ((char *) rhs_normalized_codeset); + } + } + } + + /* Possibly not the whole line fits into the buffer. + Ignore the rest of the line. */ + while (strchr (cp, '\n') == NULL) + { + cp = buf; + if (fgets_unlocked (buf, BUFSIZ, fp) == NULL) + /* Make sure the inner loop will be left. The outer + loop will exit at the `feof' test. */ + *cp = '\n'; + } + } + + fclose (fp); + } + + if (mask & XPG_NORM_CODESET) + free ((char *) normalized_codeset); return 0; } @@ -903,7 +1172,6 @@ delete_locales_from_archive (nlist, list) /* Found the entry. Now mark it as removed by zero-ing the reference to the locale record. */ namehashtab[idx].locrec_offset = 0; - --head->namehash_used; break; } |