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