1/* Copyright (C) 2000-2022 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 <https://www.gnu.org/licenses/>. */
16
17#include <dirent.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <libintl.h>
21#include <spawn.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26#include <sys/stat.h>
27
28#include "localedef.h"
29#include "charmap-dir.h"
30
31/* The data type of a charmap directory being traversed. */
32struct charmap_dir
33{
34 DIR *dir;
35 /* The directory pathname, ending in a slash. */
36 char *directory;
37 size_t directory_len;
38 /* Scratch area used for returning pathnames. */
39 char *pathname;
40 size_t pathname_size;
41};
42
43/* Starts a charmap directory traversal.
44 Returns a CHARMAP_DIR, or NULL if the directory doesn't exist. */
45CHARMAP_DIR *
46charmap_opendir (const char *directory)
47{
48 struct charmap_dir *cdir;
49 DIR *dir;
50 size_t len;
51 int add_slash;
52
53 dir = opendir (name: directory);
54 if (dir == NULL)
55 {
56 record_error (status: 1, errno, gettext ("\
57cannot read character map directory `%s'"),
58 directory);
59 return NULL;
60 }
61
62 cdir = (struct charmap_dir *) xmalloc (n: sizeof (struct charmap_dir));
63 cdir->dir = dir;
64
65 len = strlen (s: directory);
66 add_slash = (len == 0 || directory[len - 1] != '/');
67 cdir->directory = (char *) xmalloc (n: len + add_slash + 1);
68 memcpy (dest: cdir->directory, src: directory, n: 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 (dirp: cdir->dir);
96 if (dirent == NULL)
97 return NULL;
98 if (strcmp (s1: dirent->d_name, s2: ".") == 0)
99 continue;
100 if (strcmp (s1: dirent->d_name, s2: "..") == 0)
101 continue;
102
103 len = strlen (s: dirent->d_name);
104
105 size = cdir->directory_len + len + 1;
106 if (size > cdir->pathname_size)
107 {
108 free (ptr: cdir->pathname);
109 if (size < 2 * cdir->pathname_size)
110 size = 2 * cdir->pathname_size;
111 cdir->pathname = (char *) xmalloc (n: 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 if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
119 mode = DTTOIF (dirent->d_type);
120 else
121 {
122 struct stat64 statbuf;
123
124 if (stat64 (file: cdir->pathname, buf: &statbuf) < 0)
125 continue;
126
127 mode = statbuf.st_mode;
128 }
129
130 if (!S_ISREG (mode))
131 continue;
132
133 /* For compressed charmaps, the canonical charmap name does not
134 include the extension. */
135 if (len > 3 && memcmp (s1: &filename[len - 3], s2: ".gz", n: 3) == 0)
136 filename[len - 3] = '\0';
137 else if (len > 4 && memcmp (s1: &filename[len - 4], s2: ".bz2", n: 4) == 0)
138 filename[len - 4] = '\0';
139
140 return filename;
141 }
142}
143
144/* Finishes a charmap directory traversal, and frees the resources
145 attached to the CHARMAP_DIR. */
146int
147charmap_closedir (CHARMAP_DIR *cdir)
148{
149 DIR *dir = cdir->dir;
150
151 free (ptr: cdir->directory);
152 free (ptr: cdir->pathname);
153 free (ptr: cdir);
154 return closedir (dirp: dir);
155}
156
157/* Creates a subprocess decompressing the given pathname, and returns
158 a stream reading its output (the decompressed data). */
159static
160FILE *
161fopen_uncompressed (const char *pathname, const char *compressor)
162{
163 int pfd;
164
165 pfd = open (file: pathname, O_RDONLY);
166 if (pfd >= 0)
167 {
168 struct stat64 statbuf;
169 int fd[2];
170
171 if (fstat64 (fd: pfd, buf: &statbuf) >= 0
172 && S_ISREG (statbuf.st_mode)
173 && pipe (pipedes: fd) >= 0)
174 {
175 char *argv[4]
176 = { (char *) compressor, (char *) "-d", (char *) "-c", NULL };
177 posix_spawn_file_actions_t actions;
178
179 if (posix_spawn_file_actions_init (file_actions: &actions) == 0)
180 {
181 if (posix_spawn_file_actions_adddup2 (file_actions: &actions,
182 fd: fd[1], STDOUT_FILENO) == 0
183 && posix_spawn_file_actions_addclose (file_actions: &actions, fd: fd[1]) == 0
184 && posix_spawn_file_actions_addclose (file_actions: &actions, fd: fd[0]) == 0
185 && posix_spawn_file_actions_adddup2 (file_actions: &actions,
186 fd: pfd, STDIN_FILENO) == 0
187 && posix_spawn_file_actions_addclose (file_actions: &actions, fd: pfd) == 0
188 && posix_spawnp (NULL, file: compressor, file_actions: &actions, NULL,
189 argv: argv, envp: environ) == 0)
190 {
191 posix_spawn_file_actions_destroy (file_actions: &actions);
192 close (fd: fd[1]);
193 close (fd: pfd);
194 return fdopen (fd: fd[0], modes: "r");
195 }
196 posix_spawn_file_actions_destroy (file_actions: &actions);
197 }
198 close (fd: fd[1]);
199 close (fd: fd[0]);
200 }
201 close (fd: pfd);
202 }
203 return NULL;
204}
205
206/* Opens a charmap for reading, given its name (not an alias name). */
207FILE *
208charmap_open (const char *directory, const char *name)
209{
210 size_t dlen = strlen (s: directory);
211 int add_slash = (dlen == 0 || directory[dlen - 1] != '/');
212 size_t nlen = strlen (s: name);
213 char *pathname;
214 char *p;
215 FILE *stream;
216
217 pathname = alloca (dlen + add_slash + nlen + 5);
218 p = stpcpy (pathname, directory);
219 if (add_slash)
220 *p++ = '/';
221 p = stpcpy (p, name);
222
223 stream = fopen (filename: pathname, modes: "rm");
224 if (stream != NULL)
225 return stream;
226
227 memcpy (dest: p, src: ".gz", n: 4);
228 stream = fopen_uncompressed (pathname, compressor: "gzip");
229 if (stream != NULL)
230 return stream;
231
232 memcpy (dest: p, src: ".bz2", n: 5);
233 stream = fopen_uncompressed (pathname, compressor: "bzip2");
234 if (stream != NULL)
235 return stream;
236
237 return NULL;
238}
239
240/* An empty alias list. Avoids the need to return NULL from
241 charmap_aliases. */
242static char *empty[1];
243
244/* Returns a NULL terminated list of alias names of a charmap. */
245char **
246charmap_aliases (const char *directory, const char *name)
247{
248 FILE *stream;
249 char **aliases;
250 size_t naliases;
251
252 stream = charmap_open (directory, name);
253 if (stream == NULL)
254 return empty;
255
256 aliases = NULL;
257 naliases = 0;
258
259 while (!feof (stream: stream))
260 {
261 char *alias = NULL;
262 char junk[BUFSIZ];
263
264 if (fscanf (stream: stream, format: " <code_set_name> %ms", &alias) == 1
265 || fscanf (stream: stream, format: "%% alias %ms", &alias) == 1)
266 {
267 aliases = (char **) xrealloc (o: aliases,
268 n: (naliases + 2) * sizeof (char *));
269 aliases[naliases++] = alias;
270 }
271
272 /* Read the rest of the line. */
273 if (fgets (s: junk, n: sizeof junk, stream: stream) != NULL)
274 {
275 if (strstr (haystack: junk, needle: "CHARMAP") != NULL)
276 /* We cannot expect more aliases from now on. */
277 break;
278
279 while (strchr (s: junk, c: '\n') == NULL
280 && fgets (s: junk, n: sizeof junk, stream: stream) != NULL)
281 continue;
282 }
283 }
284
285 fclose (stream: stream);
286
287 if (naliases == 0)
288 return empty;
289
290 aliases[naliases] = NULL;
291 return aliases;
292}
293
294/* Frees an alias list returned by charmap_aliases. */
295void
296charmap_free_aliases (char **aliases)
297{
298 if (aliases != empty)
299 {
300 char **p;
301
302 for (p = aliases; *p; p++)
303 free (ptr: *p);
304
305 free (ptr: aliases);
306 }
307}
308

source code of glibc/locale/programs/charmap-dir.c