ncurses 6.2 - patch 20210501
[ncurses.git] / ncurses / tinfo / db_iterator.c
1 /****************************************************************************
2  * Copyright 2018,2020 Thomas E. Dickey                                     *
3  * Copyright 2006-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Thomas E. Dickey                                                *
32  ****************************************************************************/
33
34 /*
35  * Iterators for terminal databases.
36  */
37
38 #include <curses.priv.h>
39
40 #include <time.h>
41 #include <tic.h>
42
43 #if USE_HASHED_DB
44 #include <hashed_db.h>
45 #endif
46
47 MODULE_ID("$Id: db_iterator.c,v 1.48 2020/02/02 23:34:34 tom Exp $")
48
49 #define HaveTicDirectory _nc_globals.have_tic_directory
50 #define KeepTicDirectory _nc_globals.keep_tic_directory
51 #define TicDirectory     _nc_globals.tic_directory
52 #define my_blob          _nc_globals.dbd_blob
53 #define my_list          _nc_globals.dbd_list
54 #define my_size          _nc_globals.dbd_size
55 #define my_time          _nc_globals.dbd_time
56 #define my_vars          _nc_globals.dbd_vars
57
58 static void
59 add_to_blob(const char *text, size_t limit)
60 {
61     (void) limit;
62
63     if (*text != '\0') {
64         char *last = my_blob + strlen(my_blob);
65         if (last != my_blob)
66             *last++ = NCURSES_PATHSEP;
67         _nc_STRCPY(last, text, limit);
68     }
69 }
70
71 static bool
72 check_existence(const char *name, struct stat *sb)
73 {
74     bool result = FALSE;
75
76     if (quick_prefix(name)) {
77         result = TRUE;
78     } else if (stat(name, sb) == 0
79                && (S_ISDIR(sb->st_mode)
80                    || (S_ISREG(sb->st_mode) && 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         _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) "%s%s", name, DBM_SUFFIX);
87         if (stat(temp, sb) == 0 && S_ISREG(sb->st_mode) && sb->st_size) {
88             result = TRUE;
89         }
90     }
91 #endif
92     return result;
93 }
94
95 /*
96  * Trim newlines (and backslashes preceding those) and tab characters to
97  * help simplify scripting of the quick-dump feature.  Leave spaces and
98  * other backslashes alone.
99  */
100 static void
101 trim_formatting(char *source)
102 {
103     char *target = source;
104     char ch;
105
106     while ((ch = *source++) != '\0') {
107         if (ch == '\\' && *source == '\n')
108             continue;
109         if (ch == '\n' || ch == '\t')
110             continue;
111         *target++ = ch;
112     }
113     *target = '\0';
114 }
115
116 /*
117  * Store the latest value of an environment variable in my_vars[] so we can
118  * detect if one changes, invalidating the cached search-list.
119  */
120 static bool
121 update_getenv(const char *name, DBDIRS which)
122 {
123     bool result = FALSE;
124
125     if (which < dbdLAST) {
126         char *value;
127         char *cached_value = my_vars[which].value;
128         bool same_value;
129
130         if ((value = getenv(name)) != 0) {
131             value = strdup(value);
132         }
133         same_value = ((value == 0 && cached_value == 0) ||
134                       (value != 0 &&
135                        cached_value != 0 &&
136                        strcmp(value, cached_value) == 0));
137
138         /* Set variable name to enable checks in cache_expired(). */
139         my_vars[which].name = name;
140
141         if (!same_value) {
142             FreeIfNeeded(my_vars[which].value);
143             my_vars[which].value = value;
144             result = TRUE;
145         } else {
146             free(value);
147         }
148     }
149     return result;
150 }
151
152 #if NCURSES_USE_DATABASE || NCURSES_USE_TERMCAP
153 static char *
154 cache_getenv(const char *name, DBDIRS which)
155 {
156     char *result = 0;
157
158     (void) update_getenv(name, which);
159     if (which < dbdLAST) {
160         result = my_vars[which].value;
161     }
162     return result;
163 }
164 #endif
165
166 /*
167  * The cache expires if at least a second has passed since the initial lookup,
168  * or if one of the environment variables changed.
169  *
170  * Only a few applications use multiple lookups of terminal entries, seems that
171  * aside from bulk I/O such as tic and toe, that leaves interactive programs
172  * which should not be modifying the terminal databases in a way that would
173  * invalidate the search-list.
174  *
175  * The "1-second" is to allow for user-directed changes outside the program.
176  */
177 static bool
178 cache_expired(void)
179 {
180     bool result = FALSE;
181     time_t now = time((time_t *) 0);
182
183     if (now > my_time) {
184         result = TRUE;
185     } else {
186         DBDIRS n;
187         for (n = (DBDIRS) 0; n < dbdLAST; ++n) {
188             if (my_vars[n].name != 0
189                 && update_getenv(my_vars[n].name, n)) {
190                 result = TRUE;
191                 break;
192             }
193         }
194     }
195     return result;
196 }
197
198 static void
199 free_cache(void)
200 {
201     FreeAndNull(my_blob);
202     FreeAndNull(my_list);
203 }
204
205 /*
206  * Record the "official" location of the terminfo directory, according to
207  * the place where we're writing to, or the normal default, if not.
208  */
209 NCURSES_EXPORT(const char *)
210 _nc_tic_dir(const char *path)
211 {
212     T(("_nc_tic_dir %s", NonNull(path)));
213     if (!KeepTicDirectory) {
214         if (path != 0) {
215             TicDirectory = path;
216             HaveTicDirectory = TRUE;
217         } else if (HaveTicDirectory == 0) {
218             if (use_terminfo_vars()) {
219                 const char *envp;
220                 if ((envp = getenv("TERMINFO")) != 0)
221                     return _nc_tic_dir(envp);
222             }
223         }
224     }
225     return TicDirectory ? TicDirectory : TERMINFO;
226 }
227
228 /*
229  * Special fix to prevent the terminfo directory from being moved after tic
230  * has chdir'd to it.  If we let it be changed, then if $TERMINFO has a
231  * relative path, we'll lose track of the actual directory.
232  */
233 NCURSES_EXPORT(void)
234 _nc_keep_tic_dir(const char *path)
235 {
236     _nc_tic_dir(path);
237     KeepTicDirectory = TRUE;
238 }
239
240 /*
241  * Cleanup.
242  */
243 NCURSES_EXPORT(void)
244 _nc_last_db(void)
245 {
246     if (my_blob != 0 && cache_expired()) {
247         free_cache();
248     }
249 }
250
251 /*
252  * This is a simple iterator which allows the caller to step through the
253  * possible locations for a terminfo directory.  ncurses uses this to find
254  * terminfo files to read.
255  */
256 NCURSES_EXPORT(const char *)
257 _nc_next_db(DBDIRS * state, int *offset)
258 {
259     const char *result;
260
261     (void) offset;
262     if ((int) *state < my_size
263         && my_list != 0
264         && my_list[*state] != 0) {
265         result = my_list[*state];
266         (*state)++;
267     } else {
268         result = 0;
269     }
270     if (result != 0) {
271         T(("_nc_next_db %d %s", *state, result));
272     }
273     return result;
274 }
275
276 NCURSES_EXPORT(void)
277 _nc_first_db(DBDIRS * state, int *offset)
278 {
279     bool cache_has_expired = FALSE;
280     *state = dbdTIC;
281     *offset = 0;
282
283     T((T_CALLED("_nc_first_db")));
284
285     /* build a blob containing all of the strings we will use for a lookup
286      * table.
287      */
288     if (my_blob == 0 || (cache_has_expired = cache_expired())) {
289         size_t blobsize = 0;
290         const char *values[dbdLAST];
291         struct stat *my_stat;
292         int j;
293
294         if (cache_has_expired)
295             free_cache();
296
297         for (j = 0; j < dbdLAST; ++j)
298             values[j] = 0;
299
300         /*
301          * This is the first item in the list, and is used only when tic is
302          * writing to the database, as a performance improvement.
303          */
304         values[dbdTIC] = TicDirectory;
305
306 #if NCURSES_USE_DATABASE
307 #ifdef TERMINFO_DIRS
308         values[dbdCfgList] = TERMINFO_DIRS;
309 #endif
310 #ifdef TERMINFO
311         values[dbdCfgOnce] = TERMINFO;
312 #endif
313 #endif
314
315 #if NCURSES_USE_TERMCAP
316         values[dbdCfgList2] = TERMPATH;
317 #endif
318
319         if (use_terminfo_vars()) {
320 #if NCURSES_USE_DATABASE
321             values[dbdEnvOnce] = cache_getenv("TERMINFO", dbdEnvOnce);
322             values[dbdHome] = _nc_home_terminfo();
323             (void) cache_getenv("HOME", dbdHome);
324             values[dbdEnvList] = cache_getenv("TERMINFO_DIRS", dbdEnvList);
325
326 #endif
327 #if NCURSES_USE_TERMCAP
328             values[dbdEnvOnce2] = cache_getenv("TERMCAP", dbdEnvOnce2);
329             /* only use $TERMCAP if it is an absolute path */
330             if (values[dbdEnvOnce2] != 0
331                 && *values[dbdEnvOnce2] != '/') {
332                 values[dbdEnvOnce2] = 0;
333             }
334             values[dbdEnvList2] = cache_getenv("TERMPATH", dbdEnvList2);
335 #endif /* NCURSES_USE_TERMCAP */
336         }
337
338         for (j = 0; j < dbdLAST; ++j) {
339             if (values[j] == 0)
340                 values[j] = "";
341             blobsize += 2 + strlen(values[j]);
342         }
343
344         my_blob = malloc(blobsize);
345         if (my_blob != 0) {
346             *my_blob = '\0';
347             for (j = 0; j < dbdLAST; ++j) {
348                 add_to_blob(values[j], blobsize);
349             }
350
351             /* Now, build an array which will be pointers to the distinct
352              * strings in the blob.
353              */
354             blobsize = 2;
355             for (j = 0; my_blob[j] != '\0'; ++j) {
356                 if (my_blob[j] == NCURSES_PATHSEP)
357                     ++blobsize;
358             }
359             my_list = typeCalloc(char *, blobsize);
360             my_stat = typeCalloc(struct stat, blobsize);
361             if (my_list != 0 && my_stat != 0) {
362                 int k = 0;
363                 my_list[k++] = my_blob;
364                 for (j = 0; my_blob[j] != '\0'; ++j) {
365                     if (my_blob[j] == NCURSES_PATHSEP
366                         && ((&my_blob[j] - my_list[k - 1]) != 3
367                             || !quick_prefix(my_list[k - 1]))) {
368                         my_blob[j] = '\0';
369                         my_list[k++] = &my_blob[j + 1];
370                     }
371                 }
372
373                 /*
374                  * Eliminate duplicates from the list.
375                  */
376                 for (j = 0; my_list[j] != 0; ++j) {
377 #ifdef TERMINFO
378                     if (*my_list[j] == '\0')
379                         my_list[j] = strdup(TERMINFO);
380 #endif
381                     trim_formatting(my_list[j]);
382                     for (k = 0; k < j; ++k) {
383                         if (!strcmp(my_list[j], my_list[k])) {
384                             T(("duplicate %s", my_list[j]));
385                             k = j - 1;
386                             while ((my_list[j] = my_list[j + 1]) != 0) {
387                                 ++j;
388                             }
389                             j = k;
390                             break;
391                         }
392                     }
393                 }
394
395                 /*
396                  * Eliminate non-existent databases, and those that happen to
397                  * be symlinked to another location.
398                  */
399                 for (j = 0; my_list[j] != 0; ++j) {
400                     bool found = check_existence(my_list[j], &my_stat[j]);
401 #if HAVE_LINK
402                     if (found) {
403                         for (k = 0; k < j; ++k) {
404                             if (my_stat[j].st_dev == my_stat[k].st_dev
405                                 && my_stat[j].st_ino == my_stat[k].st_ino) {
406                                 found = FALSE;
407                                 break;
408                             }
409                         }
410                     }
411 #endif
412                     if (!found) {
413                         T(("not found %s", my_list[j]));
414                         k = j;
415                         while ((my_list[k] = my_list[k + 1]) != 0) {
416                             ++k;
417                         }
418                         --j;
419                     }
420                 }
421                 my_size = j;
422                 my_time = time((time_t *) 0);
423             } else {
424                 FreeAndNull(my_blob);
425             }
426             free(my_stat);
427         }
428     }
429     returnVoid;
430 }
431
432 #if NO_LEAKS
433 void
434 _nc_db_iterator_leaks(void)
435 {
436     DBDIRS which;
437
438     if (my_blob != 0)
439         FreeAndNull(my_blob);
440     if (my_list != 0)
441         FreeAndNull(my_list);
442     for (which = 0; (int) which < dbdLAST; ++which) {
443         my_vars[which].name = 0;
444         FreeIfNeeded(my_vars[which].value);
445         my_vars[which].value = 0;
446     }
447 }
448 #endif