#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../locale/hashval.h" #define __LC_LAST 13 #include "../locale/locarchive.h" #include "../crypt/md5.h" const char *alias_file = DATADIR "/locale/locale.alias"; const char *locar_file = PREFIX "/lib/locale/locale-archive"; const char *tmpl_file = PREFIX "/lib/locale/locale-archive.tmpl"; const char *loc_path = PREFIX "/lib/locale/"; int be_quiet = 1; int verbose = 0; int max_locarchive_open_retry = 10; const char *output_prefix; /* Endianness should have been taken care of by localedef. We don't need to do additional swapping. We need this variable exported however, since locarchive.c uses it to determine if it needs to swap endianness of a value before writing to or reading from the archive. */ bool swap_endianness_p = false; static const char *locnames[] = { #define DEFINE_CATEGORY(category, category_name, items, a) \ [category] = category_name, #include "../locale/categories.def" #undef DEFINE_CATEGORY }; static int is_prime (unsigned long candidate) { /* No even number and none less than 10 will be passed here. */ unsigned long int divn = 3; unsigned long int sq = divn * divn; while (sq < candidate && candidate % divn != 0) { ++divn; sq += 4 * divn; ++divn; } return candidate % divn != 0; } unsigned long next_prime (unsigned long seed) { /* Make it definitely odd. */ seed |= 1; while (!is_prime (seed)) seed += 2; return seed; } void error (int status, int errnum, const char *message, ...) { va_list args; va_start (args, message); fflush (stdout); fprintf (stderr, "%s: ", program_invocation_name); vfprintf (stderr, message, args); va_end (args); if (errnum) fprintf (stderr, ": %s", strerror (errnum)); putc ('\n', stderr); fflush (stderr); if (status) exit (errnum == EROFS ? 0 : status); } void * xmalloc (size_t size) { void *p = malloc (size); if (p == NULL) error (EXIT_FAILURE, errno, "could not allocate %zd bytes of memory", size); return p; } static void open_tmpl_archive (struct locarhandle *ah) { struct stat64 st; int fd; struct locarhead head; const char *archivefname = ah->fname == NULL ? tmpl_file : ah->fname; /* Open the archive. We must have exclusive write access. */ fd = open64 (archivefname, O_RDONLY); if (fd == -1) error (EXIT_FAILURE, errno, "cannot open locale archive template file \"%s\"", archivefname); if (fstat64 (fd, &st) < 0) error (EXIT_FAILURE, errno, "cannot stat locale archive template file \"%s\"", archivefname); /* Read the header. */ if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head)) error (EXIT_FAILURE, errno, "cannot read archive header"); ah->fd = fd; ah->mmaped = (head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent)); if (ah->mmaped > (unsigned long) st.st_size) error (EXIT_FAILURE, 0, "locale archite template file truncated"); ah->mmaped = st.st_size; ah->reserved = st.st_size; /* Now we know how large the administrative information part is. Map all of it. */ ah->addr = mmap64 (NULL, ah->mmaped, PROT_READ, MAP_SHARED, fd, 0); if (ah->addr == MAP_FAILED) error (EXIT_FAILURE, errno, "cannot map archive header"); } /* Open the locale archive. */ extern void open_archive (struct locarhandle *ah, bool readonly); /* Close the locale archive. */ extern void close_archive (struct locarhandle *ah); /* Add given locale data to the archive. */ extern int add_locale_to_archive (struct locarhandle *ah, const char *name, locale_data_t data, bool replace); extern void add_alias (struct locarhandle *ah, const char *alias, bool replace, const char *oldname, uint32_t *locrec_offset_p); extern struct namehashent * insert_name (struct locarhandle *ah, const char *name, size_t name_len, bool replace); struct nameent { char *name; struct locrecent *locrec; }; struct dataent { const unsigned char *sum; uint32_t file_offset; }; static int nameentcmp (const void *a, const void *b) { struct locrecent *la = ((const struct nameent *) a)->locrec; struct locrecent *lb = ((const struct nameent *) b)->locrec; uint32_t start_a = -1, end_a = 0; uint32_t start_b = -1, end_b = 0; int cnt; for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) { if (la->record[cnt].offset < start_a) start_a = la->record[cnt].offset; if (la->record[cnt].offset + la->record[cnt].len > end_a) end_a = la->record[cnt].offset + la->record[cnt].len; } assert (start_a != (uint32_t)-1); assert (end_a != 0); for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) { if (lb->record[cnt].offset < start_b) start_b = lb->record[cnt].offset; if (lb->record[cnt].offset + lb->record[cnt].len > end_b) end_b = lb->record[cnt].offset + lb->record[cnt].len; } assert (start_b != (uint32_t)-1); assert (end_b != 0); if (start_a != start_b) return (int)start_a - (int)start_b; return (int)end_a - (int)end_b; } static int dataentcmp (const void *a, const void *b) { if (((const struct dataent *) a)->file_offset < ((const struct dataent *) b)->file_offset) return -1; if (((const struct dataent *) a)->file_offset > ((const struct dataent *) b)->file_offset) return 1; return 0; } static int sumsearchfn (const void *key, const void *ent) { uint32_t keyn = *(uint32_t *)key; uint32_t entn = ((struct dataent *)ent)->file_offset; if (keyn < entn) return -1; if (keyn > entn) return 1; return 0; } static void compute_data (struct locarhandle *ah, struct nameent *name, size_t sumused, struct dataent *files, locale_data_t data) { int cnt; struct locrecent *locrec = name->locrec; struct dataent *file; data[LC_ALL].addr = ((char *) ah->addr) + locrec->record[LC_ALL].offset; data[LC_ALL].size = locrec->record[LC_ALL].len; for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) { data[cnt].addr = ((char *) ah->addr) + locrec->record[cnt].offset; data[cnt].size = locrec->record[cnt].len; if (data[cnt].addr >= data[LC_ALL].addr && data[cnt].addr + data[cnt].size <= data[LC_ALL].addr + data[LC_ALL].size) __md5_buffer (data[cnt].addr, data[cnt].size, data[cnt].sum); else { file = bsearch (&locrec->record[cnt].offset, files, sumused, sizeof (*files), sumsearchfn); if (file == NULL) error (EXIT_FAILURE, 0, "inconsistent template file"); memcpy (data[cnt].sum, file->sum, sizeof (data[cnt].sum)); } } } static int fill_archive (struct locarhandle *tmpl_ah, const char *fname, size_t nlist, char *list[], const char *primary) { struct locarhandle ah; struct locarhead *head; int result = 0; struct nameent *names; struct namehashent *namehashtab; size_t cnt, used; struct dataent *files; struct sumhashent *sumhashtab; size_t sumused; struct locrecent *primary_locrec = NULL; struct nameent *primary_nameent = NULL; head = tmpl_ah->addr; names = (struct nameent *) malloc (head->namehash_used * sizeof (struct nameent)); files = (struct dataent *) malloc (head->sumhash_used * sizeof (struct dataent)); if (names == NULL || files == NULL) error (EXIT_FAILURE, errno, "could not allocate tables"); namehashtab = (struct namehashent *) ((char *) tmpl_ah->addr + head->namehash_offset); sumhashtab = (struct sumhashent *) ((char *) tmpl_ah->addr + head->sumhash_offset); for (cnt = used = 0; cnt < head->namehash_size; ++cnt) if (namehashtab[cnt].locrec_offset != 0) { assert (used < head->namehash_used); names[used].name = tmpl_ah->addr + namehashtab[cnt].name_offset; names[used++].locrec = (struct locrecent *) ((char *) tmpl_ah->addr + namehashtab[cnt].locrec_offset); } /* Sort the names. */ qsort (names, used, sizeof (struct nameent), nameentcmp); for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt) if (sumhashtab[cnt].file_offset != 0) { assert (sumused < head->sumhash_used); files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum; files[sumused++].file_offset = sumhashtab[cnt].file_offset; } /* Sort by file locations. */ qsort (files, sumused, sizeof (struct dataent), dataentcmp); /* Open the archive. This call never returns if we cannot successfully open the archive. */ ah.fname = NULL; if (fname != NULL) ah.fname = fname; open_archive (&ah, false); if (primary != NULL) { for (cnt = 0; cnt < used; ++cnt) if (strcmp (names[cnt].name, primary) == 0) break; if (cnt < used) { locale_data_t data; compute_data (tmpl_ah, &names[cnt], sumused, files, data); result |= add_locale_to_archive (&ah, primary, data, 0); primary_locrec = names[cnt].locrec; primary_nameent = &names[cnt]; } } for (cnt = 0; cnt < used; ++cnt) if (&names[cnt] == primary_nameent) continue; else if ((cnt > 0 && names[cnt - 1].locrec == names[cnt].locrec) || names[cnt].locrec == primary_locrec) { const char *oldname; struct namehashent *namehashent; uint32_t locrec_offset; if (names[cnt].locrec == primary_locrec) oldname = primary; else oldname = names[cnt - 1].name; namehashent = insert_name (&ah, oldname, strlen (oldname), true); assert (namehashent->name_offset != 0); assert (namehashent->locrec_offset != 0); locrec_offset = namehashent->locrec_offset; add_alias (&ah, names[cnt].name, 0, oldname, &locrec_offset); } else { locale_data_t data; compute_data (tmpl_ah, &names[cnt], sumused, files, data); result |= add_locale_to_archive (&ah, names[cnt].name, data, 0); } while (nlist-- > 0) { const char *fname = *list++; size_t fnamelen = strlen (fname); struct stat64 st; DIR *dirp; struct dirent64 *d; int seen; locale_data_t data; int cnt; /* First see whether this really is a directory and whether it contains all the require locale category files. */ if (stat64 (fname, &st) < 0) { error (0, 0, "stat of \"%s\" failed: %s: ignored", fname, strerror (errno)); continue; } if (!S_ISDIR (st.st_mode)) { error (0, 0, "\"%s\" is no directory; ignored", fname); continue; } dirp = opendir (fname); if (dirp == NULL) { error (0, 0, "cannot open directory \"%s\": %s: ignored", fname, strerror (errno)); continue; } seen = 0; while ((d = readdir64 (dirp)) != NULL) { for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) if (strcmp (d->d_name, locnames[cnt]) == 0) { unsigned char d_type; /* We have an object of the required name. If it's a directory we have to look at a file with the prefix "SYS_". Otherwise we have found what we are looking for. */ #ifdef _DIRENT_HAVE_D_TYPE d_type = d->d_type; if (d_type != DT_REG) #endif { char fullname[fnamelen + 2 * strlen (d->d_name) + 7]; #ifdef _DIRENT_HAVE_D_TYPE if (d_type == DT_UNKNOWN) #endif { strcpy (stpcpy (stpcpy (fullname, fname), "/"), d->d_name); if (stat64 (fullname, &st) == -1) /* We cannot stat the file, ignore it. */ break; d_type = IFTODT (st.st_mode); } if (d_type == DT_DIR) { /* We have to do more tests. The file is a directory and it therefore must contain a regular file with the same name except a "SYS_" prefix. */ char *t = stpcpy (stpcpy (fullname, fname), "/"); strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"), d->d_name); if (stat64 (fullname, &st) == -1) /* There is no SYS_* file or we cannot access it. */ break; d_type = IFTODT (st.st_mode); } } /* If we found a regular file (eventually after following a symlink) we are successful. */ if (d_type == DT_REG) ++seen; break; } } closedir (dirp); if (seen != __LC_LAST - 1) { /* We don't have all locale category files. Ignore the name. */ error (0, 0, "incomplete set of locale files in \"%s\"", fname); continue; } /* Add the files to the archive. To do this we first compute sizes and the MD5 sums of all the files. */ for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) { char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7]; int fd; strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]); fd = open64 (fullname, O_RDONLY); if (fd == -1 || fstat64 (fd, &st) == -1) { /* Cannot read the file. */ if (fd != -1) close (fd); break; } if (S_ISDIR (st.st_mode)) { char *t; close (fd); t = stpcpy (stpcpy (fullname, fname), "/"); strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"), locnames[cnt]); fd = open64 (fullname, O_RDONLY); if (fd == -1 || fstat64 (fd, &st) == -1 || !S_ISREG (st.st_mode)) { if (fd != -1) close (fd); break; } } /* Map the file. */ data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (data[cnt].addr == MAP_FAILED) { /* Cannot map it. */ close (fd); break; } data[cnt].size = st.st_size; __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum); /* We don't need the file descriptor anymore. */ close (fd); } if (cnt != __LC_LAST) { while (cnt-- > 0) if (cnt != LC_ALL) munmap (data[cnt].addr, data[cnt].size); error (0, 0, "cannot read all files in \"%s\": ignored", fname); continue; } result |= add_locale_to_archive (&ah, basename (fname), data, 0); for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) munmap (data[cnt].addr, data[cnt].size); } /* We are done. */ close_archive (&ah); return result; } int main (int argc, char *argv[]) { char path[4096]; DIR *dirp; struct dirent64 *d; struct stat64 st; char *list[16384], *primary; unsigned int cnt = 0; struct locarhandle tmpl_ah; size_t loc_path_len = strlen (loc_path); dirp = opendir (loc_path); if (dirp == NULL) error (EXIT_FAILURE, errno, "cannot open directory \"%s\"", loc_path); /* Use the template file as specified on the command line. */ tmpl_ah.fname = NULL; if (argc > 1) tmpl_ah.fname = argv[1]; open_tmpl_archive (&tmpl_ah); unlink (locar_file); primary = getenv ("LC_ALL"); if (primary == NULL) primary = getenv ("LANG"); if (primary != NULL) { if (strncmp (primary, "ja", 2) != 0 && strncmp (primary, "ko", 2) != 0 && strncmp (primary, "zh", 2) != 0) { char *ptr = malloc (strlen (primary) + strlen (".utf8") + 1), *p, *q; if (ptr != NULL) { p = ptr; q = primary; while (*q && *q != '.' && *q != '@') *p++ = *q++; if (*q == '.') while (*q && *q != '@') q++; p = stpcpy (p, ".utf8"); strcpy (p, q); primary = ptr; } else primary = NULL; } } memcpy (path, loc_path, loc_path_len); while ((d = readdir64 (dirp)) != NULL) { if (strcmp (d->d_name, ".") == 0 || strcmp (d->d_name, "..") == 0) continue; if (strchr (d->d_name, '_') == NULL) continue; size_t d_name_len = strlen (d->d_name); if (loc_path_len + d_name_len + 1 > sizeof (path)) { error (0, 0, "too long filename \"%s\"", d->d_name); continue; } memcpy (path + loc_path_len, d->d_name, d_name_len + 1); if (stat64 (path, &st) < 0) { error (0, errno, "cannot stat \"%s\"", path); continue; } if (! S_ISDIR (st.st_mode)) continue; if (cnt == 16384) { error (0, 0, "too many directories in \"%s\"", loc_path); break; } list[cnt] = strdup (path); if (list[cnt] == NULL) { error (0, errno, "cannot add file to list \"%s\"", path); continue; } if (primary != NULL && cnt > 0 && strcmp (primary, d->d_name) == 0) { char *p = list[0]; list[0] = list[cnt]; list[cnt] = p; } cnt++; } closedir (dirp); /* Store the archive to the file specified as the second argument on the command line or the default locale archive. */ fill_archive (&tmpl_ah, argc > 2 ? argv[2] : NULL, cnt, list, primary); close_archive (&tmpl_ah); truncate (tmpl_file, 0); char *tz_argv[] = { "/usr/sbin/tzdata-update", NULL }; execve (tz_argv[0], (char *const *)tz_argv, (char *const *)&tz_argv[1]); exit (0); }