1/* Copyright (C) 2000-2017 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, see <http://www.gnu.org/licenses/>. */
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. */
33struct 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. */
46CHARMAP_DIR *
47charmap_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 ("\
58cannot 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. */
84const char *
85charmap_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 stat64 statbuf;
125
126 if (stat64 (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. */
148int
149charmap_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). */
161static
162FILE *
163fopen_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 stat64 statbuf;
171 int fd[2];
172
173 if (fstat64 (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). */
209FILE *
210charmap_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. */
244static char *empty[1];
245
246/* Returns a NULL terminated list of alias names of a charmap. */
247char **
248charmap_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> %ms", &alias) == 1
267 || fscanf (stream, "%% alias %ms", &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. */
297void
298charmap_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}
310