3a4827df04627c8d710bd6e0177f62e98322fa88
[ncurses.git] / ncurses / tinfo / db_iterator.c
1 /****************************************************************************
2  * Copyright (c) 2006-2010,2011 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.26 2012/01/07 20:09:36 juergen 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)
59 {
60     if (*text != '\0') {
61         char *last = my_blob + strlen(my_blob);
62         if (last != my_blob)
63             *last++ = NCURSES_PATHSEP;
64         strcpy(last, text);
65     }
66 }
67
68 static bool
69 check_existence(const char *name, struct stat *sb)
70 {
71     bool result = FALSE;
72     if (stat(name, sb) == 0
73 #ifndef __MINGW32__
74         && sb->st_size
75 #endif
76         ) {
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         sprintf(temp, "%s%s", name, DBM_SUFFIX);
83         if (stat(temp, sb) == 0
84 #ifndef __MINGW32__
85             && sb->st_size
86 #endif
87             ) {
88             result = TRUE;
89         }
90     }
91 #endif
92     return result;
93 }
94
95 /*
96  * Store the latest value of an environment variable in my_vars[] so we can
97  * detect if one changes, invalidating the cached search-list.
98  */
99 static bool
100 update_getenv(const char *name, DBDIRS which)
101 {
102     bool result = FALSE;
103     char *value = getenv(name);
104
105     if (which < dbdLAST) {
106         if (my_vars[which].name == 0 || strcmp(my_vars[which].name, name)) {
107             FreeIfNeeded(my_vars[which].value);
108             my_vars[which].name = name;
109             my_vars[which].value = value;
110             result = TRUE;
111         } else if ((my_vars[which].value != 0) ^ (value != 0)) {
112             FreeIfNeeded(my_vars[which].value);
113             my_vars[which].value = value;
114             result = TRUE;
115         } else if (value != 0 && strcmp(value, my_vars[which].value)) {
116             FreeIfNeeded(my_vars[which].value);
117             my_vars[which].value = value;
118             result = TRUE;
119         }
120     }
121     return result;
122 }
123
124 static char *
125 cache_getenv(const char *name, DBDIRS which)
126 {
127     char *result = 0;
128
129     (void) update_getenv(name, which);
130     if (which < dbdLAST) {
131         result = my_vars[which].value;
132     }
133     return result;
134 }
135
136 /*
137  * The cache expires if at least a second has passed since the initial lookup,
138  * or if one of the environment variables changed.
139  *
140  * Only a few applications use multiple lookups of terminal entries, seems that
141  * aside from bulk I/O such as tic and toe, that leaves interactive programs
142  * which should not be modifying the terminal databases in a way that would
143  * invalidate the search-list.
144  *
145  * The "1-second" is to allow for user-directed changes outside the program.
146  */
147 static bool
148 cache_expired(void)
149 {
150     bool result = FALSE;
151     time_t now = time((time_t *) 0);
152
153     if (now > my_time) {
154         result = TRUE;
155     } else {
156         DBDIRS n;
157         for (n = (DBDIRS) 0; n < dbdLAST; ++n) {
158             if (my_vars[n].name != 0
159                 && update_getenv(my_vars[n].name, n)) {
160                 result = TRUE;
161                 break;
162             }
163         }
164     }
165     return result;
166 }
167
168 static void
169 free_cache(void)
170 {
171     FreeAndNull(my_blob);
172     FreeAndNull(my_list);
173 }
174
175 /*
176  * Record the "official" location of the terminfo directory, according to
177  * the place where we're writing to, or the normal default, if not.
178  */
179 NCURSES_EXPORT(const char *)
180 _nc_tic_dir(const char *path)
181 {
182     T(("_nc_tic_dir %s", NonNull(path)));
183     if (!KeepTicDirectory) {
184         if (path != 0) {
185             TicDirectory = path;
186             HaveTicDirectory = TRUE;
187         } else if (!HaveTicDirectory && use_terminfo_vars()) {
188             char *envp;
189             if ((envp = getenv("TERMINFO")) != 0)
190                 return _nc_tic_dir(envp);
191         }
192     }
193     return TicDirectory ? TicDirectory : TERMINFO;
194 }
195
196 /*
197  * Special fix to prevent the terminfo directory from being moved after tic
198  * has chdir'd to it.  If we let it be changed, then if $TERMINFO has a
199  * relative path, we'll lose track of the actual directory.
200  */
201 NCURSES_EXPORT(void)
202 _nc_keep_tic_dir(const char *path)
203 {
204     _nc_tic_dir(path);
205     KeepTicDirectory = TRUE;
206 }
207
208 /*
209  * Cleanup.
210  */
211 NCURSES_EXPORT(void)
212 _nc_last_db(void)
213 {
214     if (my_blob != 0 && cache_expired()) {
215         free_cache();
216     }
217 }
218
219 /*
220  * This is a simple iterator which allows the caller to step through the
221  * possible locations for a terminfo directory.  ncurses uses this to find
222  * terminfo files to read.
223  */
224 NCURSES_EXPORT(const char *)
225 _nc_next_db(DBDIRS * state, int *offset)
226 {
227     const char *result;
228
229     (void) offset;
230     if ((int) *state < my_size
231         && my_list != 0
232         && my_list[*state] != 0) {
233         result = my_list[*state];
234         (*state)++;
235     } else {
236         result = 0;
237     }
238     if (result != 0) {
239         T(("_nc_next_db %d %s", *state, result));
240     }
241     return result;
242 }
243
244 NCURSES_EXPORT(void)
245 _nc_first_db(DBDIRS * state, int *offset)
246 {
247     *state = dbdTIC;
248     *offset = 0;
249
250     T(("_nc_first_db"));
251
252     /* build a blob containing all of the strings we will use for a lookup
253      * table.
254      */
255     if (my_blob == 0) {
256         size_t blobsize = 0;
257         const char *values[dbdLAST];
258         struct stat *my_stat;
259         int j, k;
260
261         for (j = 0; j < dbdLAST; ++j)
262             values[j] = 0;
263
264         /*
265          * This is the first item in the list, and is used only when tic is
266          * writing to the database, as a performance improvement.
267          */
268         values[dbdTIC] = TicDirectory;
269
270 #if USE_DATABASE
271 #ifdef TERMINFO_DIRS
272         values[dbdCfgList] = TERMINFO_DIRS;
273 #endif
274 #ifdef TERMINFO
275         values[dbdCfgOnce] = TERMINFO;
276 #endif
277 #endif
278
279 #if USE_TERMCAP
280         values[dbdCfgList2] = TERMPATH;
281 #endif
282
283         if (use_terminfo_vars()) {
284 #if USE_DATABASE
285             values[dbdEnvOnce] = cache_getenv("TERMINFO", dbdEnvOnce);
286             values[dbdHome] = _nc_home_terminfo();
287             (void) cache_getenv("HOME", dbdHome);
288             values[dbdEnvList] = cache_getenv("TERMINFO_DIRS", dbdEnvList);
289
290 #endif
291 #if USE_TERMCAP
292             values[dbdEnvOnce2] = cache_getenv("TERMCAP", dbdEnvOnce2);
293             /* only use $TERMCAP if it is an absolute path */
294             if (values[dbdEnvOnce2] != 0
295                 && *values[dbdEnvOnce2] != '/') {
296                 values[dbdEnvOnce2] = 0;
297             }
298             values[dbdEnvList2] = cache_getenv("TERMPATH", dbdEnvList2);
299 #endif /* USE_TERMCAP */
300         }
301
302         for (j = 0; j < dbdLAST; ++j) {
303             if (values[j] == 0)
304                 values[j] = "";
305             blobsize += 2 + strlen(values[j]);
306         }
307
308         my_blob = malloc(blobsize);
309         if (my_blob != 0) {
310             *my_blob = '\0';
311             for (j = 0; j < dbdLAST; ++j) {
312                 add_to_blob(values[j]);
313             }
314
315             /* Now, build an array which will be pointers to the distinct
316              * strings in the blob.
317              */
318             blobsize = 2;
319             for (j = 0; my_blob[j] != '\0'; ++j) {
320                 if (my_blob[j] == NCURSES_PATHSEP)
321                     ++blobsize;
322             }
323             my_list = typeCalloc(char *, blobsize);
324             my_stat = typeCalloc(struct stat, blobsize);
325             if (my_list != 0 && my_stat != 0) {
326                 k = 0;
327                 my_list[k++] = my_blob;
328                 for (j = 0; my_blob[j] != '\0'; ++j) {
329                     if (my_blob[j] == NCURSES_PATHSEP) {
330                         my_blob[j] = '\0';
331                         my_list[k++] = &my_blob[j + 1];
332                     }
333                 }
334
335                 /*
336                  * Eliminate duplicates from the list.
337                  */
338                 for (j = 0; my_list[j] != 0; ++j) {
339 #ifdef TERMINFO
340                     if (*my_list[j] == '\0')
341                         my_list[j] = strdup(TERMINFO);
342 #endif
343                     for (k = 0; k < j; ++k) {
344                         if (!strcmp(my_list[j], my_list[k])) {
345                             k = j - 1;
346                             while ((my_list[j] = my_list[j + 1]) != 0) {
347                                 ++j;
348                             }
349                             j = k;
350                             break;
351                         }
352                     }
353                 }
354
355                 /*
356                  * Eliminate non-existent databases, and those that happen to
357                  * be symlinked to another location.
358                  */
359                 for (j = 0; my_list[j] != 0; ++j) {
360                     bool found = check_existence(my_list[j], &my_stat[j]);
361 #if HAVE_LINK
362                     if (found) {
363                         for (k = 0; k < j; ++k) {
364                             if (my_stat[j].st_dev == my_stat[k].st_dev
365                                 && my_stat[j].st_ino == my_stat[k].st_ino) {
366                                 found = FALSE;
367                                 break;
368                             }
369                         }
370                     }
371 #endif
372                     if (!found) {
373                         k = j;
374                         while ((my_list[k] = my_list[k + 1]) != 0) {
375                             ++k;
376                         }
377                         --j;
378                     }
379                 }
380                 my_size = j;
381                 my_time = time((time_t *) 0);
382             } else {
383                 FreeAndNull(my_blob);
384             }
385             free(my_stat);
386         }
387     }
388 }
389
390 #if NO_LEAKS
391 void
392 _nc_db_iterator_leaks(void)
393 {
394     if (my_blob != 0)
395         FreeAndNull(my_blob);
396     if (my_list != 0)
397         FreeAndNull(my_list);
398 }
399 #endif