| /* Copyright (C) 2000-2014 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| |
| 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/charmap-dir.h" |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <error.h> |
| #include <fcntl.h> |
| #include <libintl.h> |
| #include <spawn.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "third_party/glibc_locales/programs/localedef.h" |
| |
| /* The data type of a charmap directory being traversed. */ |
| struct charmap_dir { |
| DIR *dir; |
| /* The directory pathname, ending in a slash. */ |
| char *directory; |
| size_t directory_len; |
| /* Scratch area used for returning pathnames. */ |
| char *pathname; |
| size_t pathname_size; |
| }; |
| |
| /* Starts a charmap directory traversal. |
| Returns a CHARMAP_DIR, or NULL if the directory doesn't exist. */ |
| CHARMAP_DIR *charmap_opendir(const char *directory) { |
| struct charmap_dir *cdir; |
| DIR *dir; |
| size_t len; |
| int add_slash; |
| |
| dir = opendir(directory); |
| if (dir == NULL) { |
| WITH_CUR_LOCALE( |
| error(1, errno, "cannot read character map directory `%s'", directory)); |
| return NULL; |
| } |
| |
| cdir = (struct charmap_dir *)xmalloc(sizeof(struct charmap_dir)); |
| cdir->dir = dir; |
| |
| len = strlen(directory); |
| add_slash = (len == 0 || directory[len - 1] != '/'); |
| cdir->directory = (char *)xmalloc(len + add_slash + 1); |
| memcpy(cdir->directory, directory, len); |
| if (add_slash) cdir->directory[len] = '/'; |
| cdir->directory[len + add_slash] = '\0'; |
| cdir->directory_len = len + add_slash; |
| |
| cdir->pathname = NULL; |
| cdir->pathname_size = 0; |
| |
| return cdir; |
| } |
| |
| /* Reads the next directory entry. |
| Returns its charmap name, or NULL if past the last entry or upon error. |
| The storage returned may be overwritten by a later charmap_readdir |
| call on the same CHARMAP_DIR. */ |
| const char *charmap_readdir(CHARMAP_DIR *cdir) { |
| for (;;) { |
| struct dirent64 *dirent; |
| size_t len; |
| size_t size; |
| char *filename; |
| mode_t mode; |
| |
| dirent = readdir64(cdir->dir); |
| if (dirent == NULL) return NULL; |
| if (strcmp(dirent->d_name, ".") == 0) continue; |
| if (strcmp(dirent->d_name, "..") == 0) continue; |
| |
| len = strlen(dirent->d_name); |
| |
| size = cdir->directory_len + len + 1; |
| if (size > cdir->pathname_size) { |
| free(cdir->pathname); |
| if (size < 2 * cdir->pathname_size) size = 2 * cdir->pathname_size; |
| cdir->pathname = (char *)xmalloc(size); |
| cdir->pathname_size = size; |
| } |
| |
| stpcpy(stpcpy(cdir->pathname, cdir->directory), dirent->d_name); |
| filename = cdir->pathname + cdir->directory_len; |
| |
| #ifdef _DIRENT_HAVE_D_TYPE |
| if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) |
| mode = DTTOIF(dirent->d_type); |
| else |
| #endif |
| { |
| struct stat64 statbuf; |
| |
| if (stat64(cdir->pathname, &statbuf) < 0) continue; |
| |
| mode = statbuf.st_mode; |
| } |
| |
| if (!S_ISREG(mode)) continue; |
| |
| /* For compressed charmaps, the canonical charmap name does not |
| include the extension. */ |
| if (len > 3 && memcmp(&filename[len - 3], ".gz", 3) == 0) |
| filename[len - 3] = '\0'; |
| else if (len > 4 && memcmp(&filename[len - 4], ".bz2", 4) == 0) |
| filename[len - 4] = '\0'; |
| |
| return filename; |
| } |
| } |
| |
| /* Finishes a charmap directory traversal, and frees the resources |
| attached to the CHARMAP_DIR. */ |
| int charmap_closedir(CHARMAP_DIR *cdir) { |
| DIR *dir = cdir->dir; |
| |
| free(cdir->directory); |
| free(cdir->pathname); |
| free(cdir); |
| return closedir(dir); |
| } |
| |
| /* Creates a subprocess decompressing the given pathname, and returns |
| a stream reading its output (the decompressed data). */ |
| static FILE *fopen_uncompressed(const char *pathname, const char *compressor) { |
| int pfd; |
| |
| pfd = open(pathname, O_RDONLY); |
| if (pfd >= 0) { |
| struct stat64 statbuf; |
| int fd[2]; |
| |
| if (fstat64(pfd, &statbuf) >= 0 && S_ISREG(statbuf.st_mode) && |
| pipe(fd) >= 0) { |
| char *argv[4] = {(char *)compressor, (char *)"-d", (char *)"-c", NULL}; |
| posix_spawn_file_actions_t actions; |
| |
| if (posix_spawn_file_actions_init(&actions) == 0) { |
| if (posix_spawn_file_actions_adddup2(&actions, fd[1], STDOUT_FILENO) == |
| 0 && |
| posix_spawn_file_actions_addclose(&actions, fd[1]) == 0 && |
| posix_spawn_file_actions_addclose(&actions, fd[0]) == 0 && |
| posix_spawn_file_actions_adddup2(&actions, pfd, STDIN_FILENO) == |
| 0 && |
| posix_spawn_file_actions_addclose(&actions, pfd) == 0 && |
| posix_spawnp(NULL, compressor, &actions, NULL, argv, environ) == |
| 0) { |
| posix_spawn_file_actions_destroy(&actions); |
| close(fd[1]); |
| close(pfd); |
| return fdopen(fd[0], "r"); |
| } |
| posix_spawn_file_actions_destroy(&actions); |
| } |
| close(fd[1]); |
| close(fd[0]); |
| } |
| close(pfd); |
| } |
| return NULL; |
| } |
| |
| /* Opens a charmap for reading, given its name (not an alias name). */ |
| FILE *charmap_open(const char *directory, const char *name) { |
| size_t dlen = strlen(directory); |
| int add_slash = (dlen == 0 || directory[dlen - 1] != '/'); |
| size_t nlen = strlen(name); |
| char *pathname; |
| char *p; |
| FILE *stream; |
| |
| pathname = alloca(dlen + add_slash + nlen + 5); |
| p = stpcpy(pathname, directory); |
| if (add_slash) *p++ = '/'; |
| p = stpcpy(p, name); |
| |
| stream = fopen(pathname, "rm"); |
| if (stream != NULL) return stream; |
| |
| memcpy(p, ".gz", 4); |
| stream = fopen_uncompressed(pathname, "gzip"); |
| if (stream != NULL) return stream; |
| |
| memcpy(p, ".bz2", 5); |
| stream = fopen_uncompressed(pathname, "bzip2"); |
| if (stream != NULL) return stream; |
| |
| return NULL; |
| } |
| |
| /* An empty alias list. Avoids the need to return NULL from |
| charmap_aliases. */ |
| static char *empty[1]; |
| |
| /* Returns a NULL terminated list of alias names of a charmap. */ |
| char **charmap_aliases(const char *directory, const char *name) { |
| FILE *stream; |
| char **aliases; |
| size_t naliases; |
| |
| stream = charmap_open(directory, name); |
| if (stream == NULL) return empty; |
| |
| aliases = NULL; |
| naliases = 0; |
| |
| while (!feof(stream)) { |
| char *alias = NULL; |
| char junk[BUFSIZ]; |
| |
| if (fscanf(stream, " <code_set_name> %ms", &alias) == 1 || |
| fscanf(stream, "%% alias %ms", &alias) == 1) { |
| aliases = (char **)xrealloc(aliases, (naliases + 2) * sizeof(char *)); |
| aliases[naliases++] = alias; |
| } |
| |
| /* Read the rest of the line. */ |
| if (fgets(junk, sizeof junk, stream) != NULL) { |
| if (strstr(junk, "CHARMAP") != NULL) |
| /* We cannot expect more aliases from now on. */ |
| break; |
| |
| while (strchr(junk, '\n') == NULL && |
| fgets(junk, sizeof junk, stream) != NULL) |
| continue; |
| } |
| } |
| |
| fclose(stream); |
| |
| if (naliases == 0) return empty; |
| |
| aliases[naliases] = NULL; |
| return aliases; |
| } |
| |
| /* Frees an alias list returned by charmap_aliases. */ |
| void charmap_free_aliases(char **aliases) { |
| if (aliases != empty) { |
| char **p; |
| |
| for (p = aliases; *p; p++) free(*p); |
| |
| free(aliases); |
| } |
| } |