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