e192eddde3d95cab48876f95ceeb858e12c4a655
[kopensolaris-gnu/glibc.git] / locale / programs / charmap-dir.c
1 /* Copyright (C) 2000, 2001, 2002, 2003, 2005 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published
6    by the Free Software Foundation; version 2 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 #include <dirent.h>
19 #include <errno.h>
20 #include <error.h>
21 #include <fcntl.h>
22 #include <libintl.h>
23 #include <spawn.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29
30 #include "localedef.h"
31 #include "charmap-dir.h"
32
33 /* The data type of a charmap directory being traversed.  */
34 struct charmap_dir
35 {
36   DIR *dir;
37   /* The directory pathname, ending in a slash.  */
38   char *directory;
39   size_t directory_len;
40   /* Scratch area used for returning pathnames.  */
41   char *pathname;
42   size_t pathname_size;
43 };
44
45 /* Starts a charmap directory traversal.
46    Returns a CHARMAP_DIR, or NULL if the directory doesn't exist.  */
47 CHARMAP_DIR *
48 charmap_opendir (const char *directory)
49 {
50   struct charmap_dir *cdir;
51   DIR *dir;
52   size_t len;
53   int add_slash;
54
55   dir = opendir (directory);
56   if (dir == NULL)
57     {
58       WITH_CUR_LOCALE (error (1, errno, gettext ("\
59 cannot read character map directory `%s'"), directory));
60       return NULL;
61     }
62
63   cdir = (struct charmap_dir *) xmalloc (sizeof (struct charmap_dir));
64   cdir->dir = dir;
65
66   len = strlen (directory);
67   add_slash = (len == 0 || directory[len - 1] != '/');
68   cdir->directory = (char *) xmalloc (len + add_slash + 1);
69   memcpy (cdir->directory, directory, len);
70   if (add_slash)
71     cdir->directory[len] = '/';
72   cdir->directory[len + add_slash] = '\0';
73   cdir->directory_len = len + add_slash;
74
75   cdir->pathname = NULL;
76   cdir->pathname_size = 0;
77
78   return cdir;
79 }
80
81 /* Reads the next directory entry.
82    Returns its charmap name, or NULL if past the last entry or upon error.
83    The storage returned may be overwritten by a later charmap_readdir
84    call on the same CHARMAP_DIR.  */
85 const char *
86 charmap_readdir (CHARMAP_DIR *cdir)
87 {
88   for (;;)
89     {
90       struct dirent64 *dirent;
91       size_t len;
92       size_t size;
93       char *filename;
94       mode_t mode;
95
96       dirent = readdir64 (cdir->dir);
97       if (dirent == NULL)
98         return NULL;
99       if (strcmp (dirent->d_name, ".") == 0)
100         continue;
101       if (strcmp (dirent->d_name, "..") == 0)
102         continue;
103
104       len = strlen (dirent->d_name);
105
106       size = cdir->directory_len + len + 1;
107       if (size > cdir->pathname_size)
108         {
109           free (cdir->pathname);
110           if (size < 2 * cdir->pathname_size)
111             size = 2 * cdir->pathname_size;
112           cdir->pathname = (char *) xmalloc (size);
113           cdir->pathname_size = size;
114         }
115
116       stpcpy (stpcpy (cdir->pathname, cdir->directory), dirent->d_name);
117       filename = cdir->pathname + cdir->directory_len;
118
119 #ifdef _DIRENT_HAVE_D_TYPE
120       if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
121         mode = DTTOIF (dirent->d_type);
122       else
123 #endif
124         {
125           struct stat statbuf;
126
127           if (stat (cdir->pathname, &statbuf) < 0)
128             continue;
129
130           mode = statbuf.st_mode;
131         }
132
133       if (!S_ISREG (mode))
134         continue;
135
136       /* For compressed charmaps, the canonical charmap name does not
137          include the extension.  */
138       if (len > 3 && memcmp (&filename[len - 3], ".gz", 3) == 0)
139         filename[len - 3] = '\0';
140       else if (len > 4 && memcmp (&filename[len - 4], ".bz2", 4) == 0)
141         filename[len - 4] = '\0';
142
143       return filename;
144     }
145 }
146
147 /* Finishes a charmap directory traversal, and frees the resources
148    attached to the CHARMAP_DIR.  */
149 int
150 charmap_closedir (CHARMAP_DIR *cdir)
151 {
152   DIR *dir = cdir->dir;
153
154   free (cdir->directory);
155   free (cdir->pathname);
156   free (cdir);
157   return closedir (dir);
158 }
159
160 /* Creates a subprocess decompressing the given pathname, and returns
161    a stream reading its output (the decompressed data).  */
162 static
163 FILE *
164 fopen_uncompressed (const char *pathname, const char *compressor)
165 {
166   int pfd;
167
168   pfd = open (pathname, O_RDONLY);
169   if (pfd >= 0)
170     {
171       struct stat statbuf;
172       int fd[2];
173
174       if (fstat (pfd, &statbuf) >= 0
175           && S_ISREG (statbuf.st_mode)
176           && pipe (fd) >= 0)
177         {
178           char *argv[4]
179             = { (char *) compressor, (char *) "-d", (char *) "-c", NULL };
180           posix_spawn_file_actions_t actions;
181
182           if (posix_spawn_file_actions_init (&actions) == 0)
183             {
184               if (posix_spawn_file_actions_adddup2 (&actions,
185                                                     fd[1], STDOUT_FILENO) == 0
186                   && posix_spawn_file_actions_addclose (&actions, fd[1]) == 0
187                   && posix_spawn_file_actions_addclose (&actions, fd[0]) == 0
188                   && posix_spawn_file_actions_adddup2 (&actions,
189                                                        pfd, STDIN_FILENO) == 0
190                   && posix_spawn_file_actions_addclose (&actions, pfd) == 0
191                   && posix_spawnp (NULL, compressor, &actions, NULL,
192                                    argv, environ) == 0)
193                 {
194                   posix_spawn_file_actions_destroy (&actions);
195                   close (fd[1]);
196                   close (pfd);
197                   return fdopen (fd[0], "r");
198                 }
199               posix_spawn_file_actions_destroy (&actions);
200             }
201           close (fd[1]);
202           close (fd[0]);
203         }
204       close (pfd);
205     }
206   return NULL;
207 }
208
209 /* Opens a charmap for reading, given its name (not an alias name).  */
210 FILE *
211 charmap_open (const char *directory, const char *name)
212 {
213   size_t dlen = strlen (directory);
214   int add_slash = (dlen == 0 || directory[dlen - 1] != '/');
215   size_t nlen = strlen (name);
216   char *pathname;
217   char *p;
218   FILE *stream;
219
220   pathname = alloca (dlen + add_slash + nlen + 5);
221   p = stpcpy (pathname, directory);
222   if (add_slash)
223     *p++ = '/';
224   p = stpcpy (p, name);
225
226   stream = fopen (pathname, "rm");
227   if (stream != NULL)
228     return stream;
229
230   memcpy (p, ".gz", 4);
231   stream = fopen_uncompressed (pathname, "gzip");
232   if (stream != NULL)
233     return stream;
234
235   memcpy (p, ".bz2", 5);
236   stream = fopen_uncompressed (pathname, "bzip2");
237   if (stream != NULL)
238     return stream;
239
240   return NULL;
241 }
242
243 /* An empty alias list.  Avoids the need to return NULL from
244    charmap_aliases.  */
245 static char *empty[1];
246
247 /* Returns a NULL terminated list of alias names of a charmap.  */
248 char **
249 charmap_aliases (const char *directory, const char *name)
250 {
251   FILE *stream;
252   char **aliases;
253   size_t naliases;
254
255   stream = charmap_open (directory, name);
256   if (stream == NULL)
257     return empty;
258
259   aliases = NULL;
260   naliases = 0;
261
262   while (!feof (stream))
263     {
264       char *alias = NULL;
265       char junk[BUFSIZ];
266
267       if (fscanf (stream, " <code_set_name> %as", &alias) == 1
268           || fscanf (stream, "%% alias %as", &alias) == 1)
269         {
270           aliases = (char **) xrealloc (aliases,
271                                         (naliases + 2) * sizeof (char *));
272           aliases[naliases++] = alias;
273         }
274
275       /* Read the rest of the line.  */
276       if (fgets (junk, sizeof junk, stream) != NULL)
277         {
278           if (strstr (junk, "CHARMAP") != NULL)
279             /* We cannot expect more aliases from now on.  */
280             break;
281
282           while (strchr (junk, '\n') == NULL
283                  && fgets (junk, sizeof junk, stream) != NULL)
284             continue;
285         }
286     }
287
288   fclose (stream);
289
290   if (naliases == 0)
291     return empty;
292
293   aliases[naliases] = NULL;
294   return aliases;
295 }
296
297 /* Frees an alias list returned by charmap_aliases.  */
298 void
299 charmap_free_aliases (char **aliases)
300 {
301   if (aliases != empty)
302     {
303       char **p;
304
305       for (p = aliases; *p; p++)
306         free (*p);
307
308       free (aliases);
309     }
310 }