1 /****************************************************************************
2 * Copyright (c) 2006-2012,2013 Free Software Foundation, Inc. *
3 * *
4 * Permission is hereby granted, free of charge, to any person obtaining a *
5 * copy of this software and associated documentation files (the *
6 * "Software"), to deal in the Software without restriction, including *
7 * without limitation the rights to use, copy, modify, merge, publish, *
8 * distribute, distribute with modifications, sublicense, and/or sell *
9 * copies of the Software, and to permit persons to whom the Software is *
10 * furnished to do so, subject to the following conditions: *
11 * *
12 * The above copyright notice and this permission notice shall be included *
13 * in all copies or substantial portions of the Software. *
14 * *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
22 * *
23 * Except as contained in this notice, the name(s) of the above copyright *
24 * holders shall not be used in advertising or otherwise to promote the *
25 * sale, use or other dealings in this Software without prior written *
26 * authorization. *
27 ****************************************************************************/
28
29 /****************************************************************************
30 * Author: Thomas E. Dickey *
31 ****************************************************************************/
32
33 /*
34 * Iterators for terminal databases.
35 */
36
37 #include <curses.priv.h>
38
39 #include <time.h>
40 #include <tic.h>
41
42 #if USE_HASHED_DB
43 #include <hashed_db.h>
44 #endif
45
46 MODULE_ID("$Id: db_iterator.c,v 1.38 2013/12/14 21:23:20 tom Exp $")
47
48 #define HaveTicDirectory _nc_globals.have_tic_directory
49 #define KeepTicDirectory _nc_globals.keep_tic_directory
50 #define TicDirectory _nc_globals.tic_directory
51 #define my_blob _nc_globals.dbd_blob
52 #define my_list _nc_globals.dbd_list
53 #define my_size _nc_globals.dbd_size
54 #define my_time _nc_globals.dbd_time
55 #define my_vars _nc_globals.dbd_vars
56
57 static void
add_to_blob(const char * text,size_t limit)58 add_to_blob(const char *text, size_t limit)
59 {
60 (void) limit;
61
62 if (*text != '\0') {
63 char *last = my_blob + strlen(my_blob);
64 if (last != my_blob)
65 *last++ = NCURSES_PATHSEP;
66 _nc_STRCPY(last, text, limit);
67 }
68 }
69
70 static bool
check_existence(const char * name,struct stat * sb)71 check_existence(const char *name, struct stat *sb)
72 {
73 bool result = FALSE;
74
75 if (stat(name, sb) == 0
76 && (S_ISDIR(sb->st_mode) || S_ISREG(sb->st_mode))) {
77 result = TRUE;
78 }
79 #if USE_HASHED_DB
80 else if (strlen(name) < PATH_MAX - sizeof(DBM_SUFFIX)) {
81 char temp[PATH_MAX];
82 _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) "%s%s", name, DBM_SUFFIX);
83 if (stat(temp, sb) == 0 && S_ISREG(sb->st_mode)) {
84 result = TRUE;
85 }
86 }
87 #endif
88 return result;
89 }
90
91 /*
92 * Store the latest value of an environment variable in my_vars[] so we can
93 * detect if one changes, invalidating the cached search-list.
94 */
95 static bool
update_getenv(const char * name,DBDIRS which)96 update_getenv(const char *name, DBDIRS which)
97 {
98 bool result = FALSE;
99
100 if (which < dbdLAST) {
101 char *value;
102
103 if ((value = getenv(name)) == 0 || (value = strdup(value)) == 0) {
104 ;
105 } else if (my_vars[which].name == 0 || strcmp(my_vars[which].name, name)) {
106 FreeIfNeeded(my_vars[which].value);
107 my_vars[which].name = name;
108 my_vars[which].value = value;
109 result = TRUE;
110 } else if ((my_vars[which].value != 0) ^ (value != 0)) {
111 FreeIfNeeded(my_vars[which].value);
112 my_vars[which].value = value;
113 result = TRUE;
114 } else if (value != 0 && strcmp(value, my_vars[which].value)) {
115 FreeIfNeeded(my_vars[which].value);
116 my_vars[which].value = value;
117 result = TRUE;
118 } else {
119 free(value);
120 }
121 }
122 return result;
123 }
124
125 static char *
cache_getenv(const char * name,DBDIRS which)126 cache_getenv(const char *name, DBDIRS which)
127 {
128 char *result = 0;
129
130 (void) update_getenv(name, which);
131 if (which < dbdLAST) {
132 result = my_vars[which].value;
133 }
134 return result;
135 }
136
137 /*
138 * The cache expires if at least a second has passed since the initial lookup,
139 * or if one of the environment variables changed.
140 *
141 * Only a few applications use multiple lookups of terminal entries, seems that
142 * aside from bulk I/O such as tic and toe, that leaves interactive programs
143 * which should not be modifying the terminal databases in a way that would
144 * invalidate the search-list.
145 *
146 * The "1-second" is to allow for user-directed changes outside the program.
147 */
148 static bool
cache_expired(void)149 cache_expired(void)
150 {
151 bool result = FALSE;
152 time_t now = time((time_t *) 0);
153
154 if (now > my_time) {
155 result = TRUE;
156 } else {
157 DBDIRS n;
158 for (n = (DBDIRS) 0; n < dbdLAST; ++n) {
159 if (my_vars[n].name != 0
160 && update_getenv(my_vars[n].name, n)) {
161 result = TRUE;
162 break;
163 }
164 }
165 }
166 return result;
167 }
168
169 static void
free_cache(void)170 free_cache(void)
171 {
172 FreeAndNull(my_blob);
173 FreeAndNull(my_list);
174 }
175
176 /*
177 * Record the "official" location of the terminfo directory, according to
178 * the place where we're writing to, or the normal default, if not.
179 */
180 NCURSES_EXPORT(const char *)
_nc_tic_dir(const char * path)181 _nc_tic_dir(const char *path)
182 {
183 T(("_nc_tic_dir %s", NonNull(path)));
184 if (!KeepTicDirectory) {
185 if (path != 0) {
186 TicDirectory = path;
187 HaveTicDirectory = TRUE;
188 } else if (HaveTicDirectory == 0) {
189 if (use_terminfo_vars()) {
190 char *envp;
191 if ((envp = getenv("TERMINFO")) != 0)
192 return _nc_tic_dir(envp);
193 }
194 }
195 }
196 return TicDirectory ? TicDirectory : TERMINFO;
197 }
198
199 /*
200 * Special fix to prevent the terminfo directory from being moved after tic
201 * has chdir'd to it. If we let it be changed, then if $TERMINFO has a
202 * relative path, we'll lose track of the actual directory.
203 */
204 NCURSES_EXPORT(void)
_nc_keep_tic_dir(const char * path)205 _nc_keep_tic_dir(const char *path)
206 {
207 _nc_tic_dir(path);
208 KeepTicDirectory = TRUE;
209 }
210
211 /*
212 * Cleanup.
213 */
214 NCURSES_EXPORT(void)
_nc_last_db(void)215 _nc_last_db(void)
216 {
217 if (my_blob != 0 && cache_expired()) {
218 free_cache();
219 }
220 }
221
222 /*
223 * This is a simple iterator which allows the caller to step through the
224 * possible locations for a terminfo directory. ncurses uses this to find
225 * terminfo files to read.
226 */
227 NCURSES_EXPORT(const char *)
_nc_next_db(DBDIRS * state,int * offset)228 _nc_next_db(DBDIRS * state, int *offset)
229 {
230 const char *result;
231
232 (void) offset;
233 if ((int) *state < my_size
234 && my_list != 0
235 && my_list[*state] != 0) {
236 result = my_list[*state];
237 (*state)++;
238 } else {
239 result = 0;
240 }
241 if (result != 0) {
242 T(("_nc_next_db %d %s", *state, result));
243 }
244 return result;
245 }
246
247 NCURSES_EXPORT(void)
_nc_first_db(DBDIRS * state,int * offset)248 _nc_first_db(DBDIRS * state, int *offset)
249 {
250 bool cache_has_expired = FALSE;
251 *state = dbdTIC;
252 *offset = 0;
253
254 T(("_nc_first_db"));
255
256 /* build a blob containing all of the strings we will use for a lookup
257 * table.
258 */
259 if (my_blob == 0 || (cache_has_expired = cache_expired())) {
260 size_t blobsize = 0;
261 const char *values[dbdLAST];
262 struct stat *my_stat;
263 int j, k;
264
265 if (cache_has_expired)
266 free_cache();
267
268 for (j = 0; j < dbdLAST; ++j)
269 values[j] = 0;
270
271 /*
272 * This is the first item in the list, and is used only when tic is
273 * writing to the database, as a performance improvement.
274 */
275 values[dbdTIC] = TicDirectory;
276
277 #if NCURSES_USE_DATABASE
278 #ifdef TERMINFO_DIRS
279 values[dbdCfgList] = TERMINFO_DIRS;
280 #endif
281 #ifdef TERMINFO
282 values[dbdCfgOnce] = TERMINFO;
283 #endif
284 #endif
285
286 #if NCURSES_USE_TERMCAP
287 values[dbdCfgList2] = TERMPATH;
288 #endif
289
290 if (use_terminfo_vars()) {
291 #if NCURSES_USE_DATABASE
292 values[dbdEnvOnce] = cache_getenv("TERMINFO", dbdEnvOnce);
293 values[dbdHome] = _nc_home_terminfo();
294 (void) cache_getenv("HOME", dbdHome);
295 values[dbdEnvList] = cache_getenv("TERMINFO_DIRS", dbdEnvList);
296
297 #endif
298 #if NCURSES_USE_TERMCAP
299 values[dbdEnvOnce2] = cache_getenv("TERMCAP", dbdEnvOnce2);
300 /* only use $TERMCAP if it is an absolute path */
301 if (values[dbdEnvOnce2] != 0
302 && *values[dbdEnvOnce2] != '/') {
303 values[dbdEnvOnce2] = 0;
304 }
305 values[dbdEnvList2] = cache_getenv("TERMPATH", dbdEnvList2);
306 #endif /* NCURSES_USE_TERMCAP */
307 }
308
309 for (j = 0; j < dbdLAST; ++j) {
310 if (values[j] == 0)
311 values[j] = "";
312 blobsize += 2 + strlen(values[j]);
313 }
314
315 my_blob = malloc(blobsize);
316 if (my_blob != 0) {
317 *my_blob = '\0';
318 for (j = 0; j < dbdLAST; ++j) {
319 add_to_blob(values[j], blobsize);
320 }
321
322 /* Now, build an array which will be pointers to the distinct
323 * strings in the blob.
324 */
325 blobsize = 2;
326 for (j = 0; my_blob[j] != '\0'; ++j) {
327 if (my_blob[j] == NCURSES_PATHSEP)
328 ++blobsize;
329 }
330 my_list = typeCalloc(char *, blobsize);
331 my_stat = typeCalloc(struct stat, blobsize);
332 if (my_list != 0 && my_stat != 0) {
333 k = 0;
334 my_list[k++] = my_blob;
335 for (j = 0; my_blob[j] != '\0'; ++j) {
336 if (my_blob[j] == NCURSES_PATHSEP) {
337 my_blob[j] = '\0';
338 my_list[k++] = &my_blob[j + 1];
339 }
340 }
341
342 /*
343 * Eliminate duplicates from the list.
344 */
345 for (j = 0; my_list[j] != 0; ++j) {
346 #ifdef TERMINFO
347 if (*my_list[j] == '\0')
348 my_list[j] = strdup(TERMINFO);
349 #endif
350 for (k = 0; k < j; ++k) {
351 if (!strcmp(my_list[j], my_list[k])) {
352 k = j - 1;
353 while ((my_list[j] = my_list[j + 1]) != 0) {
354 ++j;
355 }
356 j = k;
357 break;
358 }
359 }
360 }
361
362 /*
363 * Eliminate non-existent databases, and those that happen to
364 * be symlinked to another location.
365 */
366 for (j = 0; my_list[j] != 0; ++j) {
367 bool found = check_existence(my_list[j], &my_stat[j]);
368 #if HAVE_LINK
369 if (found) {
370 for (k = 0; k < j; ++k) {
371 if (my_stat[j].st_dev == my_stat[k].st_dev
372 && my_stat[j].st_ino == my_stat[k].st_ino) {
373 found = FALSE;
374 break;
375 }
376 }
377 }
378 #endif
379 if (!found) {
380 k = j;
381 while ((my_list[k] = my_list[k + 1]) != 0) {
382 ++k;
383 }
384 --j;
385 }
386 }
387 my_size = j;
388 my_time = time((time_t *) 0);
389 } else {
390 FreeAndNull(my_blob);
391 }
392 free(my_stat);
393 }
394 }
395 }
396
397 #if NO_LEAKS
398 void
_nc_db_iterator_leaks(void)399 _nc_db_iterator_leaks(void)
400 {
401 DBDIRS which;
402
403 if (my_blob != 0)
404 FreeAndNull(my_blob);
405 if (my_list != 0)
406 FreeAndNull(my_list);
407 for (which = 0; (int) which < dbdLAST; ++which) {
408 my_vars[which].name = 0;
409 FreeIfNeeded(my_vars[which].value);
410 my_vars[which].value = 0;
411 }
412 }
413 #endif
414