/* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #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 <unistd.h> #include <sys/stat.h> #include "charmap-dir.h" extern void *xmalloc (size_t n); extern void *xrealloc (void *p, size_t n); /* 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) { error (1, errno, gettext ("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 dirent *dirent; size_t len; size_t size; char *filename; mode_t mode; dirent = readdir (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 stat statbuf; if (stat (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, char *compressor) { int pfd; pfd = open (pathname, O_RDONLY); if (pfd >= 0) { struct stat statbuf; int fd[2]; if (fstat (pfd, &statbuf) >= 0 && S_ISREG (statbuf.st_mode) && pipe (fd) >= 0) { char *argv[4] = { compressor, "-d", "-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, "r"); 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> %as", &alias) == 1 || fscanf (stream, "%% alias %as", &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); } }