| /* Copyright (C) 1996-2014 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| Contributed by Ulrich Drepper <drepper@gnu.org>, 1996. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published |
| by the Free Software Foundation; version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "third_party/glibc_locales/programs/locfile.h" |
| |
| #include <assert.h> |
| #include <dirent.h> |
| #include <error.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <wchar.h> |
| |
| #include "third_party/glibc_locales/common/localeinfo.h" |
| #include "third_party/glibc_locales/programs/localedef.h" |
| #include "third_party/glibc_locales/programs/locfile-kw.h" |
| #include "third_party/glibc_locales/programs/simple-hash.h" |
| #include "third_party/openssl/md5.h" |
| |
| #define obstack_chunk_alloc xmalloc |
| #define obstack_chunk_free free |
| |
| /* Parser for an unknown LC_* section. */ |
| static void unknown_section_read(struct linereader *ldfile, |
| struct localedef_t *result, |
| const struct charmap_t *charmap, |
| const struct token *start) { |
| struct token *now; |
| enum token_t nowtok; |
| |
| /* The rest of the line containing the opening token must be free. */ |
| lr_ignore_rest(ldfile, 1); |
| |
| while (1) { |
| now = lr_token(ldfile, charmap, result, NULL, verbose); |
| nowtok = now->tok; |
| |
| /* Of course we don't proceed beyond the end of file. */ |
| if (nowtok == tok_eof) break; |
| |
| /* Otherwise, we're looking for the end of the section. */ |
| if (nowtok == tok_end) { |
| now = lr_token(ldfile, charmap, result, NULL, verbose); |
| if (now->tok == tok_eof) break; |
| if (now->tok == tok_eol) |
| lr_error(ldfile, "unknown section: incomplete `END' line"); |
| else if (now->tok != start->tok) |
| lr_error(ldfile, |
| "unknown section: definition does have matching `END'"); |
| else if (now->tok == tok_ident && |
| strncmp(start->val.str.startmb, now->val.str.startmb, |
| now->val.str.lenmb)) |
| lr_error(ldfile, "%1$s: definition does not end with `END %1$s'", |
| start->val.str.startmb); |
| lr_ignore_rest(ldfile, 1); |
| return; |
| } |
| } |
| |
| /* When we come here we reached the end of the file. */ |
| lr_error(ldfile, "unknown section: premature end of file"); |
| } |
| |
| /* Temporary storage of the locale data before writing it to the archive. */ |
| static locale_data_t to_archive; |
| |
| int locfile_read(struct localedef_t *result, const struct charmap_t *charmap) { |
| const char *filename = result->name; |
| const char *repertoire_name = result->repertoire_name; |
| int locale_mask = result->needed & ~result->avail; |
| struct linereader *ldfile; |
| int not_here = ALL_LOCALES; |
| |
| /* If no repertoire name was specified use the global one. */ |
| if (repertoire_name == NULL) repertoire_name = repertoire_global; |
| |
| /* Open the locale definition file. */ |
| ldfile = lr_open(filename, locfile_hash); |
| if (ldfile == NULL) { |
| if (filename != NULL && filename[0] != '/') { |
| char *i18npath = getenv("I18NPATH"); |
| if (i18npath != NULL && *i18npath != '\0') { |
| const size_t pathlen = strlen(i18npath); |
| char i18npathbuf[pathlen + 1]; |
| char path[strlen(filename) + 1 + pathlen + sizeof("/locales/") - 1]; |
| char *next; |
| i18npath = memcpy(i18npathbuf, i18npath, pathlen + 1); |
| |
| while (ldfile == NULL && (next = strsep(&i18npath, ":")) != NULL) { |
| stpcpy(stpcpy(stpcpy(path, next), "/locales/"), filename); |
| |
| ldfile = lr_open(path, locfile_hash); |
| |
| if (ldfile == NULL) { |
| stpcpy(stpcpy(path, next), filename); |
| |
| ldfile = lr_open(path, locfile_hash); |
| } |
| } |
| } |
| |
| /* Test in the default directory. */ |
| if (ldfile == NULL) { |
| char path[strlen(filename) + 1 + sizeof(LOCSRCDIR)]; |
| |
| stpcpy(stpcpy(stpcpy(path, LOCSRCDIR), "/"), filename); |
| ldfile = lr_open(path, locfile_hash); |
| } |
| } |
| |
| if (ldfile == NULL) return 1; |
| } |
| |
| /* Parse locale definition file and store result in RESULT. */ |
| while (1) { |
| struct token *now = lr_token(ldfile, charmap, NULL, NULL, verbose); |
| enum token_t nowtok = now->tok; |
| struct token *arg; |
| |
| if (nowtok == tok_eof) break; |
| |
| if (nowtok == tok_eol) /* Ignore empty lines. */ |
| continue; |
| |
| switch (nowtok) { |
| case tok_escape_char: |
| case tok_comment_char: |
| /* We need an argument. */ |
| arg = lr_token(ldfile, charmap, NULL, NULL, verbose); |
| |
| if (arg->tok != tok_ident) { |
| SYNTAX_ERROR("bad argument"); |
| continue; |
| } |
| |
| if (arg->val.str.lenmb != 1) { |
| lr_error(ldfile, "argument to `%s' must be a single character", |
| nowtok == tok_escape_char ? "escape_char" : "comment_char"); |
| |
| lr_ignore_rest(ldfile, 0); |
| continue; |
| } |
| |
| if (nowtok == tok_escape_char) |
| ldfile->escape_char = *arg->val.str.startmb; |
| else |
| ldfile->comment_char = *arg->val.str.startmb; |
| break; |
| |
| case tok_repertoiremap: |
| /* We need an argument. */ |
| arg = lr_token(ldfile, charmap, NULL, NULL, verbose); |
| |
| if (arg->tok != tok_ident) { |
| SYNTAX_ERROR("bad argument"); |
| continue; |
| } |
| |
| if (repertoire_name == NULL) { |
| char *newp = alloca(arg->val.str.lenmb + 1); |
| |
| *((char *)mempcpy(newp, arg->val.str.startmb, arg->val.str.lenmb)) = |
| '\0'; |
| repertoire_name = newp; |
| } |
| break; |
| |
| case tok_lc_ctype: |
| ctype_read(ldfile, result, charmap, repertoire_name, |
| (locale_mask & CTYPE_LOCALE) == 0); |
| result->avail |= locale_mask & CTYPE_LOCALE; |
| not_here ^= CTYPE_LOCALE; |
| continue; |
| |
| case tok_lc_collate: |
| collate_read(ldfile, result, charmap, repertoire_name, |
| (locale_mask & COLLATE_LOCALE) == 0); |
| result->avail |= locale_mask & COLLATE_LOCALE; |
| not_here ^= COLLATE_LOCALE; |
| continue; |
| |
| case tok_lc_monetary: |
| case tok_lc_numeric: |
| case tok_lc_time: |
| case tok_lc_messages: |
| case tok_lc_paper: |
| case tok_lc_name: |
| case tok_lc_address: |
| case tok_lc_telephone: |
| case tok_lc_measurement: |
| case tok_lc_identification: |
| unknown_section_read(ldfile, result, charmap, now); |
| continue; |
| |
| case tok_ident: |
| if (now->val.str.lenmb > 3 && now->val.str.startmb[0] == 'L' && |
| now->val.str.startmb[1] == 'C' && now->val.str.startmb[2] == '_') { |
| /* This appears to be an LC_* section name. */ |
| unknown_section_read(ldfile, result, charmap, now); |
| continue; |
| } |
| /* If it's not a section name, fall through to the error. */ |
| __attribute__((fallthrough)); |
| |
| default: |
| SYNTAX_ERROR("syntax error: not inside a locale definition section"); |
| continue; |
| } |
| |
| /* The rest of the line must be empty. */ |
| lr_ignore_rest(ldfile, 1); |
| } |
| |
| /* We read all of the file. */ |
| lr_close(ldfile); |
| |
| /* Mark the categories which are not contained in the file. We assume |
| them to be available and the default data will be used. */ |
| result->avail |= not_here; |
| |
| return 0; |
| } |
| |
| /* Semantic checking of locale specifications. */ |
| |
| static void (*const check_funcs[])(struct localedef_t *, |
| const struct charmap_t *) = { |
| [LC_CTYPE] = ctype_finish, [LC_COLLATE] = collate_finish}; |
| |
| void check_all_categories(struct localedef_t *definitions, |
| const struct charmap_t *charmap) { |
| int cnt; |
| |
| for (cnt = 0; cnt < sizeof(check_funcs) / sizeof(check_funcs[0]); ++cnt) |
| if (check_funcs[cnt] != NULL) check_funcs[cnt](definitions, charmap); |
| } |
| |
| /* Writing the locale data files. All files use the same output_path. */ |
| |
| static void (*const write_funcs[])(struct localedef_t *, |
| const struct charmap_t *, const char *) = { |
| [LC_CTYPE] = ctype_output, [LC_COLLATE] = collate_output}; |
| |
| void write_all_categories(struct localedef_t *definitions, |
| const struct charmap_t *charmap, const char *locname, |
| const char *output_path) { |
| int cnt; |
| |
| for (cnt = 0; cnt < sizeof(write_funcs) / sizeof(write_funcs[0]); ++cnt) |
| if (write_funcs[cnt] != NULL) |
| write_funcs[cnt](definitions, charmap, output_path); |
| |
| if (archive_file != NULL) { |
| /* The data has to be added to the archive. Do this now. */ |
| struct locarhandle ah; |
| |
| /* Open the archive. This call never returns if we cannot |
| successfully open the archive. */ |
| ah.fname = NULL; |
| open_archive(&ah, false); |
| |
| if (add_locale_to_archive(&ah, locname, to_archive, true) != 0) |
| error(EXIT_FAILURE, errno, "cannot add to locale archive"); |
| |
| /* We are done. */ |
| close_archive(&ah); |
| } |
| } |
| |
| /* Return a NULL terminated list of the directories next to output_path |
| that have the same owner, group, permissions and device as output_path. */ |
| static const char **siblings_uncached(const char *output_path) { |
| size_t len; |
| char *base, *p; |
| struct stat64 output_stat; |
| DIR *dirp; |
| int nelems; |
| const char **elems; |
| |
| /* Remove trailing slashes and trailing pathname component. */ |
| len = strlen(output_path); |
| base = (char *)alloca(len); |
| memcpy(base, output_path, len); |
| p = base + len; |
| while (p > base && p[-1] == '/') p--; |
| if (p == base) return NULL; |
| do p--; |
| while (p > base && p[-1] != '/'); |
| if (p == base) return NULL; |
| *--p = '\0'; |
| len = p - base; |
| |
| /* Get the properties of output_path. */ |
| if (lstat64(output_path, &output_stat) < 0 || !S_ISDIR(output_stat.st_mode)) |
| return NULL; |
| |
| /* Iterate through the directories in base directory. */ |
| dirp = opendir(base); |
| if (dirp == NULL) return NULL; |
| nelems = 0; |
| elems = NULL; |
| for (;;) { |
| struct dirent64 *other_dentry; |
| const char *other_name; |
| char *other_path; |
| struct stat64 other_stat; |
| |
| other_dentry = readdir64(dirp); |
| if (other_dentry == NULL) break; |
| |
| other_name = other_dentry->d_name; |
| if (strcmp(other_name, ".") == 0 || strcmp(other_name, "..") == 0) continue; |
| |
| other_path = (char *)xmalloc(len + 1 + strlen(other_name) + 2); |
| memcpy(other_path, base, len); |
| other_path[len] = '/'; |
| strcpy(other_path + len + 1, other_name); |
| |
| if (lstat64(other_path, &other_stat) >= 0 && S_ISDIR(other_stat.st_mode) && |
| other_stat.st_uid == output_stat.st_uid && |
| other_stat.st_gid == output_stat.st_gid && |
| other_stat.st_mode == output_stat.st_mode && |
| other_stat.st_dev == output_stat.st_dev) { |
| /* Found a subdirectory. Add a trailing slash and store it. */ |
| p = other_path + len + 1 + strlen(other_name); |
| *p++ = '/'; |
| *p = '\0'; |
| elems = (const char **)xrealloc((char *)elems, |
| (nelems + 2) * sizeof(char **)); |
| elems[nelems++] = other_path; |
| } else |
| free(other_path); |
| } |
| closedir(dirp); |
| |
| if (elems != NULL) elems[nelems] = NULL; |
| return elems; |
| } |
| |
| /* Return a NULL terminated list of the directories next to output_path |
| that have the same owner, group, permissions and device as output_path. |
| Cache the result for future calls. */ |
| static const char **siblings(const char *output_path) { |
| static const char *last_output_path; |
| static const char **last_result; |
| |
| if (output_path != last_output_path) { |
| if (last_result != NULL) { |
| const char **p; |
| |
| for (p = last_result; *p != NULL; p++) free((char *)*p); |
| free(last_result); |
| } |
| |
| last_output_path = output_path; |
| last_result = siblings_uncached(output_path); |
| } |
| return last_result; |
| } |
| |
| /* Read as many bytes from a file descriptor as possible. */ |
| static ssize_t full_read(int fd, void *bufarea, size_t nbyte) { |
| char *buf = (char *)bufarea; |
| |
| while (nbyte > 0) { |
| ssize_t retval = read(fd, buf, nbyte); |
| |
| if (retval == 0) |
| break; |
| else if (retval > 0) { |
| buf += retval; |
| nbyte -= retval; |
| } else if (errno != EINTR) |
| return retval; |
| } |
| return buf - (char *)bufarea; |
| } |
| |
| /* Compare the contents of two regular files of the same size. Return 0 |
| if they are equal, 1 if they are different, or -1 if an error occurs. */ |
| static int compare_files(const char *filename1, const char *filename2, |
| size_t size, size_t blocksize) { |
| int fd1, fd2; |
| int ret = -1; |
| |
| fd1 = open(filename1, O_RDONLY); |
| if (fd1 >= 0) { |
| fd2 = open(filename2, O_RDONLY); |
| if (fd2 >= 0) { |
| char *buf1 = (char *)xmalloc(2 * blocksize); |
| char *buf2 = buf1 + blocksize; |
| |
| ret = 0; |
| while (size > 0) { |
| size_t bytes = (size < blocksize ? size : blocksize); |
| |
| if (full_read(fd1, buf1, bytes) < (ssize_t)bytes) { |
| ret = -1; |
| break; |
| } |
| if (full_read(fd2, buf2, bytes) < (ssize_t)bytes) { |
| ret = -1; |
| break; |
| } |
| if (memcmp(buf1, buf2, bytes) != 0) { |
| ret = 1; |
| break; |
| } |
| size -= bytes; |
| } |
| |
| free(buf1); |
| close(fd2); |
| } |
| close(fd1); |
| } |
| return ret; |
| } |
| |
| /* True if the locale files use the opposite endianness to the |
| machine running localedef. */ |
| bool swap_endianness_p; |
| |
| /* When called outside a start_locale_structure/end_locale_structure |
| or start_locale_prelude/end_locale_prelude block, record that the |
| next byte in FILE's obstack will be the first byte of a new element. |
| Do likewise for the first call inside a start_locale_structure/ |
| end_locale_structure block. */ |
| static void record_offset(struct locale_file *file) { |
| if (file->structure_stage < 2) { |
| assert(file->next_element < file->n_elements); |
| file->offsets[file->next_element++] = |
| (obstack_object_size(&file->data) + |
| (file->n_elements + 2) * sizeof(uint32_t)); |
| if (file->structure_stage == 1) file->structure_stage = 2; |
| } |
| } |
| |
| /* Initialize FILE for a new output file. N_ELEMENTS is the number |
| of elements in the file. */ |
| void init_locale_data(struct locale_file *file, size_t n_elements) { |
| file->n_elements = n_elements; |
| file->next_element = 0; |
| file->offsets = xmalloc(sizeof(uint32_t) * n_elements); |
| obstack_init(&file->data); |
| file->structure_stage = 0; |
| } |
| |
| /* Align the size of FILE's obstack object to BOUNDARY bytes. */ |
| void align_locale_data(struct locale_file *file, size_t boundary) { |
| size_t size = -obstack_object_size(&file->data) & (boundary - 1); |
| obstack_blank(&file->data, size); |
| memset(obstack_next_free(&file->data) - size, 0, size); |
| } |
| |
| /* Record that FILE's next element contains no data. */ |
| void add_locale_empty(struct locale_file *file) { record_offset(file); } |
| |
| /* Record that FILE's next element consists of SIZE bytes starting at DATA. */ |
| void add_locale_raw_data(struct locale_file *file, const void *data, |
| size_t size) { |
| record_offset(file); |
| obstack_grow(&file->data, data, size); |
| } |
| |
| /* Finish the current object on OBSTACK and use it as the data for FILE's |
| next element. */ |
| void add_locale_raw_obstack(struct locale_file *file, struct obstack *obstack) { |
| size_t size = obstack_object_size(obstack); |
| record_offset(file); |
| obstack_grow(&file->data, obstack_finish(obstack), size); |
| } |
| |
| /* Use STRING as FILE's next element. */ |
| void add_locale_string(struct locale_file *file, const char *string) { |
| record_offset(file); |
| obstack_grow(&file->data, string, strlen(string) + 1); |
| } |
| |
| /* Likewise for wide strings. */ |
| void add_locale_wstring(struct locale_file *file, const uint32_t *string) { |
| add_locale_uint32_array(file, string, wcslen((const wchar_t *)string) + 1); |
| } |
| |
| /* Record that FILE's next element is the 32-bit integer VALUE. */ |
| void add_locale_uint32(struct locale_file *file, uint32_t value) { |
| align_locale_data(file, LOCFILE_ALIGN); |
| record_offset(file); |
| obstack_grow(&file->data, &value, sizeof(value)); |
| } |
| |
| /* Record that FILE's next element is an array of N_ELEMS integers |
| starting at DATA. */ |
| void add_locale_uint32_array(struct locale_file *file, const uint32_t *data, |
| size_t n_elems) { |
| align_locale_data(file, LOCFILE_ALIGN); |
| record_offset(file); |
| obstack_grow(&file->data, data, n_elems * sizeof(uint32_t)); |
| } |
| |
| /* Record that FILE's next element is the single byte given by VALUE. */ |
| void add_locale_char(struct locale_file *file, char value) { |
| record_offset(file); |
| obstack_1grow(&file->data, value); |
| } |
| |
| /* Start building an element that contains several different pieces of data. |
| Subsequent calls to add_locale_* will add data to the same element up |
| till the next call to end_locale_structure. The element's alignment |
| is dictated by the first piece of data added to it. */ |
| void start_locale_structure(struct locale_file *file) { |
| assert(file->structure_stage == 0); |
| file->structure_stage = 1; |
| } |
| |
| /* Finish a structure element that was started by start_locale_structure. |
| Empty structures are OK and behave like add_locale_empty. */ |
| void end_locale_structure(struct locale_file *file) { |
| record_offset(file); |
| assert(file->structure_stage == 2); |
| file->structure_stage = 0; |
| } |
| |
| /* Start building data that goes before the next element's recorded offset. |
| Subsequent calls to add_locale_* will add data to the file without |
| treating any of it as the start of a new element. Calling |
| end_locale_prelude switches back to the usual behavior. */ |
| void start_locale_prelude(struct locale_file *file) { |
| assert(file->structure_stage == 0); |
| file->structure_stage = 3; |
| } |
| |
| /* End a block started by start_locale_prelude. */ |
| void end_locale_prelude(struct locale_file *file) { |
| assert(file->structure_stage == 3); |
| file->structure_stage = 0; |
| } |
| |
| /* Write a locale file, with contents given by FILE. */ |
| void write_locale_data(const char *output_path, int catidx, |
| const char *category, struct locale_file *file) { |
| size_t cnt, step, maxiov; |
| int fd; |
| char *fname; |
| const char **other_paths; |
| uint32_t header[2]; |
| size_t n_elem; |
| struct iovec vec[3]; |
| |
| assert(file->n_elements == file->next_element); |
| header[0] = LIMAGIC(catidx); |
| header[1] = file->n_elements; |
| vec[0].iov_len = sizeof(header); |
| vec[0].iov_base = header; |
| vec[1].iov_len = sizeof(uint32_t) * file->n_elements; |
| vec[1].iov_base = file->offsets; |
| vec[2].iov_len = obstack_object_size(&file->data); |
| vec[2].iov_base = obstack_finish(&file->data); |
| n_elem = 3; |
| if (archive_file != NULL) { |
| /* The data will be added to the archive. For now we simply |
| generate the image which will be written. First determine |
| the size. */ |
| int cnt; |
| void *endp; |
| |
| to_archive[catidx].size = 0; |
| for (cnt = 0; cnt < n_elem; ++cnt) |
| to_archive[catidx].size += vec[cnt].iov_len; |
| |
| /* Allocate the memory for it. */ |
| to_archive[catidx].addr = xmalloc(to_archive[catidx].size); |
| |
| /* Fill it in. */ |
| for (cnt = 0, endp = to_archive[catidx].addr; cnt < n_elem; ++cnt) |
| endp = mempcpy(endp, vec[cnt].iov_base, vec[cnt].iov_len); |
| |
| /* Compute the MD5 sum for the data. */ |
| MD5(to_archive[catidx].addr, to_archive[catidx].size, |
| to_archive[catidx].sum); |
| |
| return; |
| } |
| |
| fname = xmalloc(strlen(output_path) + 2 * strlen(category) + 7); |
| |
| /* Normally we write to the directory pointed to by the OUTPUT_PATH. |
| But for LC_MESSAGES we have to take care for the translation |
| data. This means we need to have a directory LC_MESSAGES in |
| which we place the file under the name SYS_LC_MESSAGES. */ |
| sprintf(fname, "%s%s", output_path, category); |
| fd = -2; |
| if (strcmp(category, "LC_MESSAGES") == 0) { |
| struct stat64 st; |
| |
| if (stat64(fname, &st) < 0) { |
| if (mkdir(fname, 0777) >= 0) { |
| fd = -1; |
| errno = EISDIR; |
| } |
| } else if (!S_ISREG(st.st_mode)) { |
| fd = -1; |
| errno = EISDIR; |
| } |
| } |
| |
| /* Create the locale file with nlinks == 1; this avoids crashing processes |
| which currently use the locale and damaging files belonging to other |
| locales as well. */ |
| if (fd == -2) { |
| unlink(fname); |
| fd = creat(fname, 0666); |
| } |
| |
| if (fd == -1) { |
| int save_err = errno; |
| |
| if (errno == EISDIR) { |
| sprintf(fname, "%1$s%2$s/SYS_%2$s", output_path, category); |
| unlink(fname); |
| fd = creat(fname, 0666); |
| if (fd == -1) save_err = errno; |
| } |
| |
| if (fd == -1) { |
| if (!be_quiet) |
| WITH_CUR_LOCALE(error(0, save_err, |
| "cannot open output file `%s' for category `%s'", |
| fname, category)); |
| free(fname); |
| return; |
| } |
| } |
| |
| #ifdef UIO_MAXIOV |
| maxiov = UIO_MAXIOV; |
| #else |
| maxiov = sysconf(_SC_UIO_MAXIOV); |
| #endif |
| |
| /* Write the data using writev. But we must take care for the |
| limitation of the implementation. */ |
| for (cnt = 0; cnt < n_elem; cnt += step) { |
| step = n_elem - cnt; |
| if (maxiov > 0) step = MIN(maxiov, step); |
| |
| if (writev(fd, &vec[cnt], step) < 0) { |
| if (!be_quiet) |
| WITH_CUR_LOCALE(error(0, errno, |
| "failure while writing data for category `%s'", |
| category)); |
| break; |
| } |
| } |
| |
| close(fd); |
| |
| /* Compare the file with the locale data files for the same category in |
| other locales, and see if we can reuse it, to save disk space. */ |
| other_paths = siblings(output_path); |
| if (other_paths != NULL) { |
| struct stat64 fname_stat; |
| |
| if (lstat64(fname, &fname_stat) >= 0 && S_ISREG(fname_stat.st_mode)) { |
| const char *fname_tail = fname + strlen(output_path); |
| const char **other_p; |
| int seen_count; |
| ino_t *seen_inodes; |
| |
| seen_count = 0; |
| for (other_p = other_paths; *other_p; other_p++) seen_count++; |
| seen_inodes = (ino_t *)xmalloc(seen_count * sizeof(ino_t)); |
| seen_count = 0; |
| |
| for (other_p = other_paths; *other_p; other_p++) { |
| const char *other_path = *other_p; |
| size_t other_path_len = strlen(other_path); |
| char *other_fname; |
| struct stat64 other_fname_stat; |
| |
| other_fname = (char *)xmalloc(other_path_len + strlen(fname_tail) + 1); |
| memcpy(other_fname, other_path, other_path_len); |
| strcpy(other_fname + other_path_len, fname_tail); |
| |
| if (lstat64(other_fname, &other_fname_stat) >= 0 && |
| S_ISREG(other_fname_stat.st_mode) |
| /* Consider only files on the same device. |
| Otherwise hard linking won't work anyway. */ |
| && other_fname_stat.st_dev == fname_stat.st_dev |
| /* Consider only files with the same permissions. |
| Otherwise there are security risks. */ |
| && other_fname_stat.st_uid == fname_stat.st_uid && |
| other_fname_stat.st_gid == fname_stat.st_gid && |
| other_fname_stat.st_mode == fname_stat.st_mode |
| /* Don't compare fname with itself. */ |
| && other_fname_stat.st_ino != fname_stat.st_ino |
| /* Files must have the same size, otherwise they |
| cannot be the same. */ |
| && other_fname_stat.st_size == fname_stat.st_size) { |
| /* Skip this file if we have already read it (under a |
| different name). */ |
| int i; |
| |
| for (i = seen_count - 1; i >= 0; i--) |
| if (seen_inodes[i] == other_fname_stat.st_ino) break; |
| if (i < 0) { |
| /* Now compare fname and other_fname for real. */ |
| blksize_t blocksize; |
| |
| #ifdef _STATBUF_ST_BLKSIZE |
| blocksize = MAX(fname_stat.st_blksize, other_fname_stat.st_blksize); |
| if (blocksize > 8 * 1024) blocksize = 8 * 1024; |
| #else |
| blocksize = 8 * 1024; |
| #endif |
| |
| if (compare_files(fname, other_fname, fname_stat.st_size, |
| blocksize) == 0) { |
| /* Found! other_fname is identical to fname. */ |
| /* Link other_fname to fname. But use a temporary |
| file, in case hard links don't work on the |
| particular filesystem. */ |
| char *tmp_fname = (char *)xmalloc(strlen(fname) + 4 + 1); |
| |
| strcpy(stpcpy(tmp_fname, fname), ".tmp"); |
| |
| if (link(other_fname, tmp_fname) >= 0) { |
| unlink(fname); |
| if (rename(tmp_fname, fname) < 0) { |
| if (!be_quiet) |
| WITH_CUR_LOCALE(error( |
| 0, errno, |
| "cannot create output file `%s' for category `%s'", |
| fname, category)); |
| } |
| free(tmp_fname); |
| free(other_fname); |
| break; |
| } |
| free(tmp_fname); |
| } |
| |
| /* Don't compare with this file a second time. */ |
| seen_inodes[seen_count++] = other_fname_stat.st_ino; |
| } |
| } |
| free(other_fname); |
| } |
| free(seen_inodes); |
| } |
| } |
| |
| free(fname); |
| } |
| |
| /* General handling of `copy'. */ |
| void handle_copy(struct linereader *ldfile, const struct charmap_t *charmap, |
| const char *repertoire_name, struct localedef_t *result, |
| enum token_t token, int locale, const char *locale_name, |
| int ignore_content) { |
| struct token *now; |
| int warned = 0; |
| |
| now = lr_token(ldfile, charmap, result, NULL, verbose); |
| if (now->tok != tok_string) |
| lr_error(ldfile, "expecting string argument for `copy'"); |
| else if (!ignore_content) { |
| if (now->val.str.startmb == NULL) |
| lr_error(ldfile, |
| "locale name should consist only of portable characters"); |
| else { |
| (void)add_to_readlist(locale, now->val.str.startmb, repertoire_name, 1, |
| NULL); |
| result->copy_name[locale] = now->val.str.startmb; |
| } |
| } |
| |
| lr_ignore_rest(ldfile, now->tok == tok_string); |
| |
| /* The rest of the line must be empty and the next keyword must be |
| `END xxx'. */ |
| while ((now = lr_token(ldfile, charmap, result, NULL, verbose))->tok != |
| tok_end && |
| now->tok != tok_eof) { |
| if (warned == 0) { |
| lr_error(ldfile, |
| "no other keyword shall be specified when `copy' is used"); |
| warned = 1; |
| } |
| |
| lr_ignore_rest(ldfile, 0); |
| } |
| |
| if (now->tok != tok_eof) { |
| /* Handle `END xxx'. */ |
| now = lr_token(ldfile, charmap, result, NULL, verbose); |
| |
| if (now->tok != token) |
| lr_error(ldfile, "`%1$s' definition does not end with `END %1$s'", |
| locale_name); |
| |
| lr_ignore_rest(ldfile, now->tok == token); |
| } else |
| /* When we come here we reached the end of the file. */ |
| lr_error(ldfile, "%s: premature end of file", locale_name); |
| } |