/* $OpenBSD: db_iterator.c,v 1.2 2023/10/17 09:52:09 nicm Exp $ */ /**************************************************************************** * Copyright 2018-2022,2023 Thomas E. Dickey * * Copyright 2006-2016,2017 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * * copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, distribute with modifications, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ /**************************************************************************** * Author: Thomas E. Dickey * ****************************************************************************/ /* * Iterators for terminal databases. */ #include #include #include #if USE_HASHED_DB #include #endif MODULE_ID("$Id: db_iterator.c,v 1.2 2023/10/17 09:52:09 nicm Exp $") #define HaveTicDirectory _nc_globals.have_tic_directory #define KeepTicDirectory _nc_globals.keep_tic_directory #define TicDirectory _nc_globals.tic_directory #define my_blob _nc_globals.dbd_blob #define my_list _nc_globals.dbd_list #define my_size _nc_globals.dbd_size #define my_time _nc_globals.dbd_time #define my_vars _nc_globals.dbd_vars static void add_to_blob(const char *text, size_t limit) { (void) limit; if (*text != '\0') { char *last = my_blob + strlen(my_blob); if (last != my_blob) *last++ = NCURSES_PATHSEP; _nc_STRCPY(last, text, limit); } } static bool check_existence(const char *name, struct stat *sb) { bool result = FALSE; if (quick_prefix(name)) { result = TRUE; } else if (stat(name, sb) == 0 && (S_ISDIR(sb->st_mode) || (S_ISREG(sb->st_mode) && sb->st_size))) { result = TRUE; } #if USE_HASHED_DB else if (strlen(name) < PATH_MAX - sizeof(DBM_SUFFIX)) { char temp[PATH_MAX]; _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) "%s%s", name, DBM_SUFFIX); if (stat(temp, sb) == 0 && S_ISREG(sb->st_mode) && sb->st_size) { result = TRUE; } } #endif return result; } /* * Trim newlines (and backslashes preceding those) and tab characters to * help simplify scripting of the quick-dump feature. Leave spaces and * other backslashes alone. */ static void trim_formatting(char *source) { char *target = source; char ch; while ((ch = *source++) != '\0') { if (ch == '\\' && *source == '\n') continue; if (ch == '\n' || ch == '\t') continue; *target++ = ch; } *target = '\0'; } /* * Store the latest value of an environment variable in my_vars[] so we can * detect if one changes, invalidating the cached search-list. */ static bool update_getenv(const char *name, DBDIRS which) { bool result = FALSE; if (which < dbdLAST) { char *value; char *cached_value = my_vars[which].value; bool same_value; if ((value = getenv(name)) != 0) { value = strdup(value); } same_value = ((value == 0 && cached_value == 0) || (value != 0 && cached_value != 0 && strcmp(value, cached_value) == 0)); /* Set variable name to enable checks in cache_expired(). */ my_vars[which].name = name; if (!same_value) { FreeIfNeeded(my_vars[which].value); my_vars[which].value = value; result = TRUE; } else { free(value); } } return result; } #if NCURSES_USE_DATABASE || NCURSES_USE_TERMCAP static char * cache_getenv(const char *name, DBDIRS which) { char *result = 0; (void) update_getenv(name, which); if (which < dbdLAST) { result = my_vars[which].value; } return result; } #endif /* * The cache expires if at least a second has passed since the initial lookup, * or if one of the environment variables changed. * * Only a few applications use multiple lookups of terminal entries, seems that * aside from bulk I/O such as tic and toe, that leaves interactive programs * which should not be modifying the terminal databases in a way that would * invalidate the search-list. * * The "1-second" is to allow for user-directed changes outside the program. */ static bool cache_expired(void) { bool result = FALSE; time_t now = time((time_t *) 0); if (now > my_time) { result = TRUE; } else { DBDIRS n; for (n = (DBDIRS) 0; n < dbdLAST; ++n) { if (my_vars[n].name != 0 && update_getenv(my_vars[n].name, n)) { result = TRUE; break; } } } return result; } static void free_cache(void) { FreeAndNull(my_blob); FreeAndNull(my_list); } static void update_tic_dir(const char *update) { free((char *) TicDirectory); TicDirectory = update; } /* * Record the "official" location of the terminfo directory, according to * the place where we're writing to, or the normal default, if not. */ NCURSES_EXPORT(const char *) _nc_tic_dir(const char *path) { T(("_nc_tic_dir %s", NonNull(path))); if (!KeepTicDirectory) { if (path != NULL) { if (path != TicDirectory) update_tic_dir(strdup(path)); HaveTicDirectory = TRUE; } else if (HaveTicDirectory == 0) { if (use_terminfo_vars()) { const char *envp; if ((envp = getenv("TERMINFO")) != 0) return _nc_tic_dir(envp); } } } return TicDirectory ? TicDirectory : TERMINFO; } /* * Special fix to prevent the terminfo directory from being moved after tic * has chdir'd to it. If we let it be changed, then if $TERMINFO has a * relative path, we'll lose track of the actual directory. */ NCURSES_EXPORT(void) _nc_keep_tic_dir(const char *path) { _nc_tic_dir(path); KeepTicDirectory = TRUE; } /* * Cleanup. */ NCURSES_EXPORT(void) _nc_last_db(void) { if (my_blob != 0 && cache_expired()) { free_cache(); } } /* * This is a simple iterator which allows the caller to step through the * possible locations for a terminfo directory. ncurses uses this to find * terminfo files to read. */ NCURSES_EXPORT(const char *) _nc_next_db(DBDIRS * state, int *offset) { const char *result; (void) offset; if ((int) *state < my_size && my_list != 0 && my_list[*state] != 0) { result = my_list[*state]; (*state)++; } else { result = 0; } if (result != 0) { T(("_nc_next_db %d %s", *state, result)); } return result; } NCURSES_EXPORT(void) _nc_first_db(DBDIRS * state, int *offset) { bool cache_has_expired = FALSE; *state = dbdTIC; *offset = 0; T((T_CALLED("_nc_first_db"))); /* build a blob containing all of the strings we will use for a lookup * table. */ if (my_blob == 0 || (cache_has_expired = cache_expired())) { size_t blobsize = 0; const char *values[dbdLAST]; struct stat *my_stat; int j; if (cache_has_expired) free_cache(); for (j = 0; j < dbdLAST; ++j) values[j] = 0; /* * This is the first item in the list, and is used only when tic is * writing to the database, as a performance improvement. */ values[dbdTIC] = TicDirectory; #if NCURSES_USE_DATABASE #ifdef TERMINFO_DIRS values[dbdCfgList] = TERMINFO_DIRS; #endif #ifdef TERMINFO values[dbdCfgOnce] = TERMINFO; #endif #endif #if NCURSES_USE_TERMCAP values[dbdCfgList2] = TERMPATH; #endif if (use_terminfo_vars()) { #if NCURSES_USE_DATABASE values[dbdEnvOnce] = cache_getenv("TERMINFO", dbdEnvOnce); values[dbdHome] = _nc_home_terminfo(); (void) cache_getenv("HOME", dbdHome); values[dbdEnvList] = cache_getenv("TERMINFO_DIRS", dbdEnvList); #endif #if NCURSES_USE_TERMCAP values[dbdEnvOnce2] = cache_getenv("TERMCAP", dbdEnvOnce2); /* only use $TERMCAP if it is an absolute path */ if (values[dbdEnvOnce2] != 0 && *values[dbdEnvOnce2] != '/') { values[dbdEnvOnce2] = 0; } values[dbdEnvList2] = cache_getenv("TERMPATH", dbdEnvList2); #endif /* NCURSES_USE_TERMCAP */ } for (j = 0; j < dbdLAST; ++j) { if (values[j] == 0) values[j] = ""; blobsize += 2 + strlen(values[j]); } my_blob = malloc(blobsize); if (my_blob != 0) { *my_blob = '\0'; for (j = 0; j < dbdLAST; ++j) { add_to_blob(values[j], blobsize); } /* Now, build an array which will be pointers to the distinct * strings in the blob. */ blobsize = 2; for (j = 0; my_blob[j] != '\0'; ++j) { if (my_blob[j] == NCURSES_PATHSEP) ++blobsize; } my_list = typeCalloc(char *, blobsize); my_stat = typeCalloc(struct stat, blobsize); if (my_list != 0 && my_stat != 0) { int k = 0; my_list[k++] = my_blob; for (j = 0; my_blob[j] != '\0'; ++j) { if (my_blob[j] == NCURSES_PATHSEP && ((&my_blob[j] - my_list[k - 1]) != 3 || !quick_prefix(my_list[k - 1]))) { my_blob[j] = '\0'; my_list[k++] = &my_blob[j + 1]; } } /* * Eliminate duplicates from the list. */ for (j = 0; my_list[j] != 0; ++j) { #ifdef TERMINFO if (*my_list[j] == '\0') { char *my_copy = strdup(TERMINFO); if (my_copy != 0) my_list[j] = my_copy; } #endif trim_formatting(my_list[j]); for (k = 0; k < j; ++k) { if (!strcmp(my_list[j], my_list[k])) { T(("duplicate %s", my_list[j])); k = j - 1; while ((my_list[j] = my_list[j + 1]) != 0) { ++j; } j = k; break; } } } /* * Eliminate non-existent databases, and those that happen to * be symlinked to another location. */ for (j = 0; my_list[j] != 0; ++j) { bool found = check_existence(my_list[j], &my_stat[j]); #if HAVE_LINK if (found) { for (k = 0; k < j; ++k) { if (my_stat[j].st_dev == my_stat[k].st_dev && my_stat[j].st_ino == my_stat[k].st_ino) { found = FALSE; break; } } } #endif if (!found) { T(("not found %s", my_list[j])); k = j; while ((my_list[k] = my_list[k + 1]) != 0) { ++k; } --j; } } my_size = j; my_time = time((time_t *) 0); } else { FreeAndNull(my_blob); } free(my_stat); } } returnVoid; } #if NO_LEAKS void _nc_db_iterator_leaks(void) { DBDIRS which; if (my_blob != 0) FreeAndNull(my_blob); if (my_list != 0) FreeAndNull(my_list); for (which = 0; (int) which < dbdLAST; ++which) { my_vars[which].name = 0; FreeIfNeeded(my_vars[which].value); my_vars[which].value = 0; } update_tic_dir(NULL); } #endif