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