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