ncurses 5.9 - patch 20111001
[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.18 2011/09/26 09:52: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
52 /*
53  * FIXME: need a no-leaks entrypoint.
54  */
55 static char *my_blob;           /* string-heap for my_list[] */
56 static char **my_list;          /* distinct places to look for data */
57 static int my_size;             /* length of my_list[] */
58 static time_t my_time;          /* cache last updated */
59 static struct {
60     const char *name;
61     char *value;
62 } my_vars[dbdLAST];
63
64 static void
65 add_to_blob(const char *text)
66 {
67     if (*text != '\0') {
68         char *last = my_blob + strlen(my_blob);
69         if (last != my_blob)
70             *last++ = ':';
71         strcpy(last, text);
72     }
73 }
74
75 static bool
76 check_existence(const char *name, struct stat *sb)
77 {
78     bool result = FALSE;
79
80     if (stat(name, sb) == 0 && 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         sprintf(temp, "%s%s", name, DBM_SUFFIX);
87         if (stat(temp, sb) == 0 && sb->st_size) {
88             result = TRUE;
89         }
90     }
91 #endif
92
93     return result;
94 }
95
96 /*
97  * Store the latest value of an environment variable in my_vars[] so we can
98  * detect if one changes, invalidating the cached search-list.
99  */
100 static bool
101 update_getenv(const char *name, DBDIRS which)
102 {
103     bool result = FALSE;
104     char *value = getenv(name);
105
106     if (which < dbdLAST) {
107         if (my_vars[which].name == 0 || strcmp(my_vars[which].name, name)) {
108             FreeIfNeeded(my_vars[which].value);
109             my_vars[which].name = name;
110             my_vars[which].value = value;
111             result = TRUE;
112         } else if ((my_vars[which].value != 0) ^ (value != 0)) {
113             FreeIfNeeded(my_vars[which].value);
114             my_vars[which].value = value;
115             result = TRUE;
116         } else if (value != 0 && strcmp(value, my_vars[which].value)) {
117             FreeIfNeeded(my_vars[which].value);
118             my_vars[which].value = value;
119             result = TRUE;
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 = 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 && use_terminfo_vars()) {
189             char *envp;
190             if ((envp = getenv("TERMINFO")) != 0)
191                 return _nc_tic_dir(envp);
192         }
193     }
194     return TicDirectory;
195 }
196
197 /*
198  * Special fix to prevent the terminfo directory from being moved after tic
199  * has chdir'd to it.  If we let it be changed, then if $TERMINFO has a
200  * relative path, we'll lose track of the actual directory.
201  */
202 NCURSES_EXPORT(void)
203 _nc_keep_tic_dir(const char *path)
204 {
205     _nc_tic_dir(path);
206     KeepTicDirectory = TRUE;
207 }
208
209 /*
210  * Cleanup.
211  */
212 NCURSES_EXPORT(void)
213 _nc_last_db(void)
214 {
215     if (my_blob != 0 && cache_expired()) {
216         free_cache();
217     }
218 }
219
220 /*
221  * This is a simple iterator which allows the caller to step through the
222  * possible locations for a terminfo directory.  ncurses uses this to find
223  * terminfo files to read.
224  */
225 NCURSES_EXPORT(const char *)
226 _nc_next_db(DBDIRS * state, int *offset)
227 {
228     const char *result;
229
230     (void) offset;
231     if ((int) *state < my_size
232         && my_list != 0
233         && my_list[*state] != 0) {
234         result = my_list[*state];
235         (*state)++;
236     } else {
237         result = 0;
238     }
239     if (result != 0) {
240         T(("_nc_next_db %d %s", *state, result));
241     }
242     return result;
243 }
244
245 NCURSES_EXPORT(void)
246 _nc_first_db(DBDIRS * state, int *offset)
247 {
248     *state = dbdTIC;
249     *offset = 0;
250
251     T(("_nc_first_db"));
252
253     /* build a blob containing all of the strings we will use for a lookup
254      * table.
255      */
256     if (my_blob == 0) {
257         size_t blobsize = 0;
258         const char *values[dbdLAST];
259         struct stat *my_stat;
260         int j, k;
261
262         for (j = 0; j < dbdLAST; ++j)
263             values[j] = 0;
264
265         /*
266          * This is the first item in the list, and is used only when tic is
267          * writing to the database, as a performance improvement.
268          */
269         values[dbdTIC] = TicDirectory;
270
271 #if USE_DATABASE
272 #ifdef TERMINFO_DIRS
273         values[dbdCfgList] = TERMINFO_DIRS;
274 #endif
275 #ifdef TERMINFO
276         values[dbdCfgOnce] = TERMINFO;
277 #endif
278 #endif
279
280 #if USE_TERMCAP
281         values[dbdCfgList2] = TERMPATH;
282 #endif
283
284         if (use_terminfo_vars()) {
285 #if USE_DATABASE
286             values[dbdEnvOnce] = cache_getenv("TERMINFO", dbdEnvOnce);
287             values[dbdHome] = _nc_home_terminfo();
288             (void) cache_getenv("HOME", dbdHome);
289             values[dbdEnvList] = cache_getenv("TERMINFO_DIRS", dbdEnvList);
290 #endif
291 #if USE_TERMCAP
292             values[dbdEnvOnce2] = cache_getenv("TERMCAP", dbdEnvOnce2);
293             values[dbdEnvList2] = cache_getenv("TERMPATH", dbdEnvList2);
294 #endif /* USE_TERMCAP */
295         }
296
297         for (j = 0; j < dbdLAST; ++j) {
298             if (values[j] == 0)
299                 values[j] = "";
300             blobsize += 2 + strlen(values[j]);
301         }
302
303         my_blob = malloc(blobsize);
304         if (my_blob != 0) {
305             *my_blob = '\0';
306             for (j = 0; j < dbdLAST; ++j) {
307                 add_to_blob(values[j]);
308             }
309
310             /* Now, build an array which will be pointers to the distinct
311              * strings in the blob.
312              */
313             blobsize = 2;
314             for (j = 0; my_blob[j] != '\0'; ++j) {
315                 if (my_blob[j] == ':')
316                     ++blobsize;
317             }
318             my_list = typeCalloc(char *, blobsize);
319             my_stat = typeCalloc(struct stat, blobsize);
320             if (my_list != 0 && my_stat != 0) {
321                 k = 0;
322                 my_list[k++] = my_blob;
323                 for (j = 0; my_blob[j] != '\0'; ++j) {
324                     if (my_blob[j] == ':') {
325                         my_blob[j] = '\0';
326                         my_list[k++] = &my_blob[j + 1];
327                     }
328                 }
329
330                 /*
331                  * Eliminate duplicates from the list.
332                  */
333                 for (j = 0; my_list[j] != 0; ++j) {
334 #ifdef TERMINFO
335                     if (*my_list[j] == '\0')
336                         my_list[j] = TERMINFO;
337 #endif
338                     for (k = 0; k < j; ++k) {
339                         if (!strcmp(my_list[j], my_list[k])) {
340                             k = --j;
341                             while ((my_list[j] = my_list[j + 1]) != 0) {
342                                 ++j;
343                             }
344                             j = k;
345                             break;
346                         }
347                     }
348                 }
349
350                 /*
351                  * Eliminate non-existent databases, and those that happen to
352                  * be symlinked to another location.
353                  */
354                 for (j = 0; my_list[j] != 0; ++j) {
355                     bool found = check_existence(my_list[j], &my_stat[j]);
356 #if HAVE_LINK
357                     if (found) {
358                         for (k = 0; k < j; ++k) {
359                             if (my_stat[j].st_dev == my_stat[k].st_dev
360                                 && my_stat[j].st_ino == my_stat[k].st_ino) {
361                                 found = FALSE;
362                                 break;
363                             }
364                         }
365                     }
366 #endif
367                     if (!found) {
368                         k = j;
369                         while ((my_list[k] = my_list[k + 1]) != 0) {
370                             ++k;
371                         }
372                         --j;
373                     }
374                 }
375                 my_size = j;
376                 my_time = time((time_t *) 0);
377             } else {
378                 FreeAndNull(my_blob);
379             }
380         }
381     }
382 }