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 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 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 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 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 * 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 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 201 free_cache(void) 202 { 203 FreeAndNull(my_blob); 204 FreeAndNull(my_list); 205 } 206 207 static void 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 *) 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) 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) 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 *) 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) 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 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