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