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