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