1166124Srafan/****************************************************************************
2262685Sdelphij * Copyright (c) 2006-2012,2013 Free Software Foundation, Inc.              *
3166124Srafan *                                                                          *
4166124Srafan * Permission is hereby granted, free of charge, to any person obtaining a  *
5166124Srafan * copy of this software and associated documentation files (the            *
6166124Srafan * "Software"), to deal in the Software without restriction, including      *
7166124Srafan * without limitation the rights to use, copy, modify, merge, publish,      *
8166124Srafan * distribute, distribute with modifications, sublicense, and/or sell       *
9166124Srafan * copies of the Software, and to permit persons to whom the Software is    *
10166124Srafan * furnished to do so, subject to the following conditions:                 *
11166124Srafan *                                                                          *
12166124Srafan * The above copyright notice and this permission notice shall be included  *
13166124Srafan * in all copies or substantial portions of the Software.                   *
14166124Srafan *                                                                          *
15166124Srafan * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16166124Srafan * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17166124Srafan * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18166124Srafan * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19166124Srafan * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20166124Srafan * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21166124Srafan * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22166124Srafan *                                                                          *
23166124Srafan * Except as contained in this notice, the name(s) of the above copyright   *
24166124Srafan * holders shall not be used in advertising or otherwise to promote the     *
25166124Srafan * sale, use or other dealings in this Software without prior written       *
26166124Srafan * authorization.                                                           *
27166124Srafan ****************************************************************************/
28166124Srafan
29166124Srafan/****************************************************************************
30174993Srafan *  Author: Thomas E. Dickey                                                *
31166124Srafan ****************************************************************************/
32166124Srafan
33166124Srafan/*
34166124Srafan * Iterators for terminal databases.
35166124Srafan */
36166124Srafan
37166124Srafan#include <curses.priv.h>
38166124Srafan
39262685Sdelphij#include <time.h>
40166124Srafan#include <tic.h>
41166124Srafan
42262685Sdelphij#if USE_HASHED_DB
43262685Sdelphij#include <hashed_db.h>
44262685Sdelphij#endif
45166124Srafan
46262685SdelphijMODULE_ID("$Id: db_iterator.c,v 1.38 2013/12/14 21:23:20 tom Exp $")
47262685Sdelphij
48174993Srafan#define HaveTicDirectory _nc_globals.have_tic_directory
49174993Srafan#define KeepTicDirectory _nc_globals.keep_tic_directory
50174993Srafan#define TicDirectory     _nc_globals.tic_directory
51262685Sdelphij#define my_blob          _nc_globals.dbd_blob
52262685Sdelphij#define my_list          _nc_globals.dbd_list
53262685Sdelphij#define my_size          _nc_globals.dbd_size
54262685Sdelphij#define my_time          _nc_globals.dbd_time
55262685Sdelphij#define my_vars          _nc_globals.dbd_vars
56166124Srafan
57262685Sdelphijstatic void
58262685Sdelphijadd_to_blob(const char *text, size_t limit)
59262685Sdelphij{
60262685Sdelphij    (void) limit;
61262685Sdelphij
62262685Sdelphij    if (*text != '\0') {
63262685Sdelphij	char *last = my_blob + strlen(my_blob);
64262685Sdelphij	if (last != my_blob)
65262685Sdelphij	    *last++ = NCURSES_PATHSEP;
66262685Sdelphij	_nc_STRCPY(last, text, limit);
67262685Sdelphij    }
68262685Sdelphij}
69262685Sdelphij
70262685Sdelphijstatic bool
71262685Sdelphijcheck_existence(const char *name, struct stat *sb)
72262685Sdelphij{
73262685Sdelphij    bool result = FALSE;
74262685Sdelphij
75262685Sdelphij    if (stat(name, sb) == 0
76262685Sdelphij	&& (S_ISDIR(sb->st_mode) || S_ISREG(sb->st_mode))) {
77262685Sdelphij	result = TRUE;
78262685Sdelphij    }
79262685Sdelphij#if USE_HASHED_DB
80262685Sdelphij    else if (strlen(name) < PATH_MAX - sizeof(DBM_SUFFIX)) {
81262685Sdelphij	char temp[PATH_MAX];
82262685Sdelphij	_nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) "%s%s", name, DBM_SUFFIX);
83262685Sdelphij	if (stat(temp, sb) == 0 && S_ISREG(sb->st_mode)) {
84262685Sdelphij	    result = TRUE;
85262685Sdelphij	}
86262685Sdelphij    }
87262685Sdelphij#endif
88262685Sdelphij    return result;
89262685Sdelphij}
90262685Sdelphij
91166124Srafan/*
92262685Sdelphij * Store the latest value of an environment variable in my_vars[] so we can
93262685Sdelphij * detect if one changes, invalidating the cached search-list.
94262685Sdelphij */
95262685Sdelphijstatic bool
96262685Sdelphijupdate_getenv(const char *name, DBDIRS which)
97262685Sdelphij{
98262685Sdelphij    bool result = FALSE;
99262685Sdelphij
100262685Sdelphij    if (which < dbdLAST) {
101262685Sdelphij	char *value;
102262685Sdelphij
103262685Sdelphij	if ((value = getenv(name)) == 0 || (value = strdup(value)) == 0) {
104262685Sdelphij	    ;
105262685Sdelphij	} else if (my_vars[which].name == 0 || strcmp(my_vars[which].name, name)) {
106262685Sdelphij	    FreeIfNeeded(my_vars[which].value);
107262685Sdelphij	    my_vars[which].name = name;
108262685Sdelphij	    my_vars[which].value = value;
109262685Sdelphij	    result = TRUE;
110262685Sdelphij	} else if ((my_vars[which].value != 0) ^ (value != 0)) {
111262685Sdelphij	    FreeIfNeeded(my_vars[which].value);
112262685Sdelphij	    my_vars[which].value = value;
113262685Sdelphij	    result = TRUE;
114262685Sdelphij	} else if (value != 0 && strcmp(value, my_vars[which].value)) {
115262685Sdelphij	    FreeIfNeeded(my_vars[which].value);
116262685Sdelphij	    my_vars[which].value = value;
117262685Sdelphij	    result = TRUE;
118262685Sdelphij	} else {
119262685Sdelphij	    free(value);
120262685Sdelphij	}
121262685Sdelphij    }
122262685Sdelphij    return result;
123262685Sdelphij}
124262685Sdelphij
125262685Sdelphijstatic char *
126262685Sdelphijcache_getenv(const char *name, DBDIRS which)
127262685Sdelphij{
128262685Sdelphij    char *result = 0;
129262685Sdelphij
130262685Sdelphij    (void) update_getenv(name, which);
131262685Sdelphij    if (which < dbdLAST) {
132262685Sdelphij	result = my_vars[which].value;
133262685Sdelphij    }
134262685Sdelphij    return result;
135262685Sdelphij}
136262685Sdelphij
137262685Sdelphij/*
138262685Sdelphij * The cache expires if at least a second has passed since the initial lookup,
139262685Sdelphij * or if one of the environment variables changed.
140262685Sdelphij *
141262685Sdelphij * Only a few applications use multiple lookups of terminal entries, seems that
142262685Sdelphij * aside from bulk I/O such as tic and toe, that leaves interactive programs
143262685Sdelphij * which should not be modifying the terminal databases in a way that would
144262685Sdelphij * invalidate the search-list.
145262685Sdelphij *
146262685Sdelphij * The "1-second" is to allow for user-directed changes outside the program.
147262685Sdelphij */
148262685Sdelphijstatic bool
149262685Sdelphijcache_expired(void)
150262685Sdelphij{
151262685Sdelphij    bool result = FALSE;
152262685Sdelphij    time_t now = time((time_t *) 0);
153262685Sdelphij
154262685Sdelphij    if (now > my_time) {
155262685Sdelphij	result = TRUE;
156262685Sdelphij    } else {
157262685Sdelphij	DBDIRS n;
158262685Sdelphij	for (n = (DBDIRS) 0; n < dbdLAST; ++n) {
159262685Sdelphij	    if (my_vars[n].name != 0
160262685Sdelphij		&& update_getenv(my_vars[n].name, n)) {
161262685Sdelphij		result = TRUE;
162262685Sdelphij		break;
163262685Sdelphij	    }
164262685Sdelphij	}
165262685Sdelphij    }
166262685Sdelphij    return result;
167262685Sdelphij}
168262685Sdelphij
169262685Sdelphijstatic void
170262685Sdelphijfree_cache(void)
171262685Sdelphij{
172262685Sdelphij    FreeAndNull(my_blob);
173262685Sdelphij    FreeAndNull(my_list);
174262685Sdelphij}
175262685Sdelphij
176262685Sdelphij/*
177166124Srafan * Record the "official" location of the terminfo directory, according to
178166124Srafan * the place where we're writing to, or the normal default, if not.
179166124Srafan */
180166124SrafanNCURSES_EXPORT(const char *)
181166124Srafan_nc_tic_dir(const char *path)
182166124Srafan{
183262685Sdelphij    T(("_nc_tic_dir %s", NonNull(path)));
184174993Srafan    if (!KeepTicDirectory) {
185166124Srafan	if (path != 0) {
186174993Srafan	    TicDirectory = path;
187174993Srafan	    HaveTicDirectory = TRUE;
188262685Sdelphij	} else if (HaveTicDirectory == 0) {
189262685Sdelphij	    if (use_terminfo_vars()) {
190262685Sdelphij		char *envp;
191262685Sdelphij		if ((envp = getenv("TERMINFO")) != 0)
192262685Sdelphij		    return _nc_tic_dir(envp);
193262685Sdelphij	    }
194166124Srafan	}
195166124Srafan    }
196262685Sdelphij    return TicDirectory ? TicDirectory : TERMINFO;
197166124Srafan}
198166124Srafan
199166124Srafan/*
200166124Srafan * Special fix to prevent the terminfo directory from being moved after tic
201166124Srafan * has chdir'd to it.  If we let it be changed, then if $TERMINFO has a
202166124Srafan * relative path, we'll lose track of the actual directory.
203166124Srafan */
204166124SrafanNCURSES_EXPORT(void)
205166124Srafan_nc_keep_tic_dir(const char *path)
206166124Srafan{
207166124Srafan    _nc_tic_dir(path);
208174993Srafan    KeepTicDirectory = TRUE;
209166124Srafan}
210166124Srafan
211166124Srafan/*
212166124Srafan * Cleanup.
213166124Srafan */
214166124SrafanNCURSES_EXPORT(void)
215166124Srafan_nc_last_db(void)
216166124Srafan{
217262685Sdelphij    if (my_blob != 0 && cache_expired()) {
218262685Sdelphij	free_cache();
219166124Srafan    }
220166124Srafan}
221166124Srafan
222166124Srafan/*
223166124Srafan * This is a simple iterator which allows the caller to step through the
224166124Srafan * possible locations for a terminfo directory.  ncurses uses this to find
225166124Srafan * terminfo files to read.
226166124Srafan */
227166124SrafanNCURSES_EXPORT(const char *)
228166124Srafan_nc_next_db(DBDIRS * state, int *offset)
229166124Srafan{
230166124Srafan    const char *result;
231166124Srafan
232262685Sdelphij    (void) offset;
233262685Sdelphij    if ((int) *state < my_size
234262685Sdelphij	&& my_list != 0
235262685Sdelphij	&& my_list[*state] != 0) {
236262685Sdelphij	result = my_list[*state];
237262685Sdelphij	(*state)++;
238262685Sdelphij    } else {
239166124Srafan	result = 0;
240262685Sdelphij    }
241262685Sdelphij    if (result != 0) {
242262685Sdelphij	T(("_nc_next_db %d %s", *state, result));
243262685Sdelphij    }
244262685Sdelphij    return result;
245262685Sdelphij}
246166124Srafan
247262685SdelphijNCURSES_EXPORT(void)
248262685Sdelphij_nc_first_db(DBDIRS * state, int *offset)
249262685Sdelphij{
250262685Sdelphij    bool cache_has_expired = FALSE;
251262685Sdelphij    *state = dbdTIC;
252262685Sdelphij    *offset = 0;
253262685Sdelphij
254262685Sdelphij    T(("_nc_first_db"));
255262685Sdelphij
256262685Sdelphij    /* build a blob containing all of the strings we will use for a lookup
257262685Sdelphij     * table.
258262685Sdelphij     */
259262685Sdelphij    if (my_blob == 0 || (cache_has_expired = cache_expired())) {
260262685Sdelphij	size_t blobsize = 0;
261262685Sdelphij	const char *values[dbdLAST];
262262685Sdelphij	struct stat *my_stat;
263262685Sdelphij	int j, k;
264262685Sdelphij
265262685Sdelphij	if (cache_has_expired)
266262685Sdelphij	    free_cache();
267262685Sdelphij
268262685Sdelphij	for (j = 0; j < dbdLAST; ++j)
269262685Sdelphij	    values[j] = 0;
270262685Sdelphij
271262685Sdelphij	/*
272262685Sdelphij	 * This is the first item in the list, and is used only when tic is
273262685Sdelphij	 * writing to the database, as a performance improvement.
274262685Sdelphij	 */
275262685Sdelphij	values[dbdTIC] = TicDirectory;
276262685Sdelphij
277262685Sdelphij#if NCURSES_USE_DATABASE
278262685Sdelphij#ifdef TERMINFO_DIRS
279262685Sdelphij	values[dbdCfgList] = TERMINFO_DIRS;
280262685Sdelphij#endif
281262685Sdelphij#ifdef TERMINFO
282262685Sdelphij	values[dbdCfgOnce] = TERMINFO;
283262685Sdelphij#endif
284262685Sdelphij#endif
285262685Sdelphij
286262685Sdelphij#if NCURSES_USE_TERMCAP
287262685Sdelphij	values[dbdCfgList2] = TERMPATH;
288262685Sdelphij#endif
289262685Sdelphij
290262685Sdelphij	if (use_terminfo_vars()) {
291262685Sdelphij#if NCURSES_USE_DATABASE
292262685Sdelphij	    values[dbdEnvOnce] = cache_getenv("TERMINFO", dbdEnvOnce);
293262685Sdelphij	    values[dbdHome] = _nc_home_terminfo();
294262685Sdelphij	    (void) cache_getenv("HOME", dbdHome);
295262685Sdelphij	    values[dbdEnvList] = cache_getenv("TERMINFO_DIRS", dbdEnvList);
296262685Sdelphij
297262685Sdelphij#endif
298262685Sdelphij#if NCURSES_USE_TERMCAP
299262685Sdelphij	    values[dbdEnvOnce2] = cache_getenv("TERMCAP", dbdEnvOnce2);
300262685Sdelphij	    /* only use $TERMCAP if it is an absolute path */
301262685Sdelphij	    if (values[dbdEnvOnce2] != 0
302262685Sdelphij		&& *values[dbdEnvOnce2] != '/') {
303262685Sdelphij		values[dbdEnvOnce2] = 0;
304166124Srafan	    }
305262685Sdelphij	    values[dbdEnvList2] = cache_getenv("TERMPATH", dbdEnvList2);
306262685Sdelphij#endif /* NCURSES_USE_TERMCAP */
307262685Sdelphij	}
308262685Sdelphij
309262685Sdelphij	for (j = 0; j < dbdLAST; ++j) {
310262685Sdelphij	    if (values[j] == 0)
311262685Sdelphij		values[j] = "";
312262685Sdelphij	    blobsize += 2 + strlen(values[j]);
313262685Sdelphij	}
314262685Sdelphij
315262685Sdelphij	my_blob = malloc(blobsize);
316262685Sdelphij	if (my_blob != 0) {
317262685Sdelphij	    *my_blob = '\0';
318262685Sdelphij	    for (j = 0; j < dbdLAST; ++j) {
319262685Sdelphij		add_to_blob(values[j], blobsize);
320166124Srafan	    }
321262685Sdelphij
322262685Sdelphij	    /* Now, build an array which will be pointers to the distinct
323262685Sdelphij	     * strings in the blob.
324262685Sdelphij	     */
325262685Sdelphij	    blobsize = 2;
326262685Sdelphij	    for (j = 0; my_blob[j] != '\0'; ++j) {
327262685Sdelphij		if (my_blob[j] == NCURSES_PATHSEP)
328262685Sdelphij		    ++blobsize;
329166124Srafan	    }
330262685Sdelphij	    my_list = typeCalloc(char *, blobsize);
331262685Sdelphij	    my_stat = typeCalloc(struct stat, blobsize);
332262685Sdelphij	    if (my_list != 0 && my_stat != 0) {
333262685Sdelphij		k = 0;
334262685Sdelphij		my_list[k++] = my_blob;
335262685Sdelphij		for (j = 0; my_blob[j] != '\0'; ++j) {
336262685Sdelphij		    if (my_blob[j] == NCURSES_PATHSEP) {
337262685Sdelphij			my_blob[j] = '\0';
338262685Sdelphij			my_list[k++] = &my_blob[j + 1];
339262685Sdelphij		    }
340262685Sdelphij		}
341262685Sdelphij
342262685Sdelphij		/*
343262685Sdelphij		 * Eliminate duplicates from the list.
344262685Sdelphij		 */
345262685Sdelphij		for (j = 0; my_list[j] != 0; ++j) {
346262685Sdelphij#ifdef TERMINFO
347262685Sdelphij		    if (*my_list[j] == '\0')
348262685Sdelphij			my_list[j] = strdup(TERMINFO);
349166124Srafan#endif
350262685Sdelphij		    for (k = 0; k < j; ++k) {
351262685Sdelphij			if (!strcmp(my_list[j], my_list[k])) {
352262685Sdelphij			    k = j - 1;
353262685Sdelphij			    while ((my_list[j] = my_list[j + 1]) != 0) {
354262685Sdelphij				++j;
355262685Sdelphij			    }
356262685Sdelphij			    j = k;
357262685Sdelphij			    break;
358262685Sdelphij			}
359262685Sdelphij		    }
360262685Sdelphij		}
361262685Sdelphij
362262685Sdelphij		/*
363262685Sdelphij		 * Eliminate non-existent databases, and those that happen to
364262685Sdelphij		 * be symlinked to another location.
365262685Sdelphij		 */
366262685Sdelphij		for (j = 0; my_list[j] != 0; ++j) {
367262685Sdelphij		    bool found = check_existence(my_list[j], &my_stat[j]);
368262685Sdelphij#if HAVE_LINK
369262685Sdelphij		    if (found) {
370262685Sdelphij			for (k = 0; k < j; ++k) {
371262685Sdelphij			    if (my_stat[j].st_dev == my_stat[k].st_dev
372262685Sdelphij				&& my_stat[j].st_ino == my_stat[k].st_ino) {
373262685Sdelphij				found = FALSE;
374262685Sdelphij				break;
375262685Sdelphij			    }
376262685Sdelphij			}
377262685Sdelphij		    }
378166124Srafan#endif
379262685Sdelphij		    if (!found) {
380262685Sdelphij			k = j;
381262685Sdelphij			while ((my_list[k] = my_list[k + 1]) != 0) {
382262685Sdelphij			    ++k;
383262685Sdelphij			}
384262685Sdelphij			--j;
385262685Sdelphij		    }
386262685Sdelphij		}
387262685Sdelphij		my_size = j;
388262685Sdelphij		my_time = time((time_t *) 0);
389262685Sdelphij	    } else {
390262685Sdelphij		FreeAndNull(my_blob);
391166124Srafan	    }
392262685Sdelphij	    free(my_stat);
393166124Srafan	}
394166124Srafan    }
395166124Srafan}
396166124Srafan
397262685Sdelphij#if NO_LEAKS
398262685Sdelphijvoid
399262685Sdelphij_nc_db_iterator_leaks(void)
400166124Srafan{
401262685Sdelphij    DBDIRS which;
402262685Sdelphij
403262685Sdelphij    if (my_blob != 0)
404262685Sdelphij	FreeAndNull(my_blob);
405262685Sdelphij    if (my_list != 0)
406262685Sdelphij	FreeAndNull(my_list);
407262685Sdelphij    for (which = 0; (int) which < dbdLAST; ++which) {
408262685Sdelphij	my_vars[which].name = 0;
409262685Sdelphij	FreeIfNeeded(my_vars[which].value);
410262685Sdelphij	my_vars[which].value = 0;
411262685Sdelphij    }
412166124Srafan}
413262685Sdelphij#endif
414