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