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