diff options
Diffstat (limited to 'Build/source/texk/kpathsea/pathsearch.c')
-rw-r--r-- | Build/source/texk/kpathsea/pathsearch.c | 932 |
1 files changed, 932 insertions, 0 deletions
diff --git a/Build/source/texk/kpathsea/pathsearch.c b/Build/source/texk/kpathsea/pathsearch.c new file mode 100644 index 00000000000..778b952a6b3 --- /dev/null +++ b/Build/source/texk/kpathsea/pathsearch.c @@ -0,0 +1,932 @@ +/* pathsearch.c: look up a filename in a path. + + Copyright 1993, 1994, 1995, 1997, 2007, 2009-2012, 2018, 2020 Karl Berry. + Copyright 1997-2005 Olaf Weber. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, see <http://www.gnu.org/licenses/>. */ + +#include <kpathsea/config.h> +#include <kpathsea/c-pathch.h> +#include <kpathsea/c-fopen.h> +#include <kpathsea/absolute.h> +#include <kpathsea/expand.h> +#include <kpathsea/db.h> +#include <kpathsea/pathsearch.h> +#include <kpathsea/readable.h> +#include <kpathsea/str-list.h> +#include <kpathsea/str-llist.h> +#include <kpathsea/variable.h> +#include <kpathsea/xopendir.h> + +#include <time.h> /* for `time' */ + +#ifdef __DJGPP__ +#include <sys/stat.h> /* for stat bits */ +#endif + +#ifdef WIN32 +#undef fputs +#undef puts +#define fputs win32_fputs +#define puts win32_puts +#endif + +/* The very first search is for texmf.cnf, called when someone tries to + initialize the TFM path or whatever. init_path calls kpse_cnf_get + which calls kpse_all_path_search to find all the texmf.cnf's. We + need to do various special things in this case, since we obviously + don't yet have the configuration files when we're searching for the + configuration files. Therefore we have a followup_search member in + kpathsea_instance to distinguish the first search from all others. */ + + + +#ifdef KPSE_DEBUG +/* Print on FH elements of L surrounded by brackets, separated by spaces. */ + +static void +print_space_list (FILE *fh, string *l) +{ + fputs ("[", fh); + while (l && *l) { + fputs (*l, fh); + l++; + if (*l) + fputs (" ", fh); + } + fputs ("]", fh); +} +#endif /* KPSE_DEBUG */ + +/* This function is called after every search (except the first, since + we definitely want to allow enabling the logging in texmf.cnf) to + record the filename(s) found in $TEXMFLOG. */ + +static void +log_search (kpathsea kpse, str_list_type filenames) +{ + if (kpse->log_opened == false) { + /* Get name from either envvar or config file. Thus, the first time + is called, for the first search, we will be opening texmf.cnf + and ls-R just to resolve the variable. */ + string log_name = kpathsea_var_value (kpse, "TEXMFLOG"); + kpse->log_opened = true; + if (log_name) { + kpse->log_file = fopen (log_name, FOPEN_A_MODE); + if (!kpse->log_file) + perror (log_name); + free (log_name); + } + } + + if ( +#ifdef KPSE_DEBUG + KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH) || +#endif /* KPSE_DEBUG */ + kpse->log_file) { + unsigned e; + + /* FILENAMES should never be null, but safety doesn't hurt. */ + for (e = 0; e < STR_LIST_LENGTH (filenames) && STR_LIST_ELT (filenames, e); + e++) { + string filename = STR_LIST_ELT (filenames, e); + + /* Only record absolute filenames, for privacy. */ + if (kpse->log_file && kpathsea_absolute_p (kpse, filename, false)) { + fprintf (kpse->log_file, "%lu %s\n", (long unsigned) time (NULL), + filename); + } + +#ifdef KPSE_DEBUG + /* And show them online, if debugging. We've already started the + debugging line in `search' and + `kpathsea_path_search_list_generic', where this is called, so + just print the filename here, don't use DEBUGF. */ + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + putc (' ', stderr); + fputs (filename, stderr); + } +#endif /* KPSE_DEBUG */ + } + } +} + +/* Either casefold_readable_file or kpathsea_readable_file. We need the + type even if we don't have casefolding enabled at compile-time. */ +typedef string (*readable_file_fn_type) (kpathsea, string); + +#ifdef MONOCASE_FILENAMES +/* Don't do any of this fallback casefolding stuff on Windows. */ +#undef KPSE_CASEFOLDING_SEARCH +#else +#define KPSE_CASEFOLDING_SEARCH 1 +#endif + +#ifdef KPSE_CASEFOLDING_SEARCH + +/* Same as kpathsea_readable_file (readable.c), but check + case-insensitively on the last file name component. We return the + first match (in new memory), or NULL. In practice this is always + called after checking the filename as-is, so there's no use in + continuing to look for an exact match. The KPSE arg is only passed + to kpathsea_readable_file if we have a candidate match. + + Clearly we could do some caching here, but let's see if it's actually + necessary, given all the other levels of caching (disk, memory, cpu) + that are going on these days. */ + +static string +casefold_readable_file (kpathsea kpse, string name) +{ + string ret = NULL; + const_string this_base_name = xbasename (name); + string this_dir_name = xdirname (name); + DIR *thisdir = opendir (this_dir_name); + +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + DEBUGF2 (" casefold_readable_file(%s) in %s => ", + this_base_name, this_dir_name); + } +#endif + + /* We can be called with a name like `subdir/somefile', where subdir/ + does not exist. So the opendir might fail, and that's ok. */ + if (thisdir) { + struct dirent *e; + + while ((e = readdir (thisdir)) != NULL) { + /* The standard strcasecmp seems like enough for the comparison? */ + if (strcasecmp (e->d_name, this_base_name) == 0) { + ret = concat3 (this_dir_name, DIR_SEP_STRING, e->d_name); + if (kpathsea_readable_file (kpse, ret)) { + break; /* success */ + } else { + /* This only happens when the name matches, but the potential file is + not actually readable, e.g., a broken symlink. It seems + sufficiently unusual to be worth logging. */ + #ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + fprintf (stderr, "{casefolded candidate %s not readable, continuing}",ret); + } + #endif + free (ret); /* not readable, keep looking */ + ret = NULL; + } + } + } /* end of readdir loop */ + xclosedir (thisdir); + } + free (this_dir_name); + + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + fputs (ret ? ret : "(nil)", stderr); + fputc ('\n', stderr); + } + return ret; +} +#endif /* KPSE_CASEFOLDING_SEARCH */ + +/* Return a str_list of matches in DIRS of NAME. + + Concatenate each element in DIRS with NAME, assuming each ends with /. + If SEARCH_ALL is false, return a list containing just the first + readable (according to the READABLE_FILE_P function) regular file. + Else continue to search and return them all. In any case, if none, + return a list containing just NULL. + + DIRS is modified only in that the directory where a search matches is + floated toward the top of the list. + + We keep a single buffer for the potential filenames and reallocate + only when necessary. I'm not sure it's noticeably faster, but it + does seem cleaner. (We do waste a bit of space in the return + value, since we don't shrink it to the final size returned.) */ + +#define INIT_ALLOC 75 /* Doesn't much matter what this number is. */ + +static str_list_type +dir_list_search (kpathsea kpse, str_llist_type *dirs, const_string name, + boolean search_all, readable_file_fn_type readable_file_p) +{ + str_llist_elt_type *elt; + str_llist_elt_type *next_elt; + str_list_type ret; + unsigned name_len = strlen (name); + unsigned allocated = INIT_ALLOC; + string potential = (string) xmalloc (allocated); + +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + const_string casefold = +#ifdef KPSE_CASEFOLDING_SEARCH + (readable_file_p == casefold_readable_file) ? "yes" : "no"; +#else + "No"; +#endif + DEBUGF3 (" dir_list_search(file=%s, find_all=%d, casefold=%s)\n", + name, search_all, casefold); + } +#endif + + ret = str_list_init (); + + for (elt = *dirs; elt; elt = next_elt) { + string readable_name; + const_string dir = STR_LLIST (*elt); + unsigned dir_len = strlen (dir); + + next_elt = STR_LLIST_NEXT (*elt); /* in case elt floats */ + + while (dir_len + name_len + 1 > allocated) { + allocated += allocated; + XRETALLOC (potential, allocated, char); + } + strcpy (potential, dir); + strcat (potential, name); + + readable_name = readable_file_p (kpse, potential); + if (readable_name) { + str_list_add (&ret, readable_name); + + /* Move this element towards the top of the list. */ + str_llist_float (dirs, elt); + + /* If caller only wanted one file returned, no need to + terminate the list with NULL; the caller knows to only look + at the first element. */ + if (!search_all) + return ret; + + /* Start new filename. */ + allocated = INIT_ALLOC; + potential = (string) xmalloc (allocated); + } + } + + /* If we get here, either we didn't find any files, or we were finding + all the files. But we're done with the last filename, anyway. */ + free (potential); + + return ret; +} + +/* This is analogous to dir_list_search above, except we search for + multiple NAMES instead of one (unfortunately the code is duplicated). + Absolute or explicitly relative items in NAMES are ignored; the + caller (kpathsea_path_search_list_generic) deals with those + separately. NAMES[i] is not modified. */ + + +static str_list_type +dir_list_search_list (kpathsea kpse, str_llist_type *dirs, string* names, + boolean search_all, readable_file_fn_type readable_file_p) +{ + str_llist_elt_type *elt; + str_llist_elt_type *next_elt; + str_list_type ret; + unsigned allocated = INIT_ALLOC; + string potential = XTALLOC (allocated, char); + +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + const_string casefold = +#ifdef KPSE_CASEFOLDING_SEARCH + (readable_file_p == casefold_readable_file) ? "yes" : "no"; +#else + "No"; +#endif + DEBUGF (" dir_list_search_list(files="); + print_space_list (stderr, names); + fprintf (stderr, ", find_all=%d, casefold=%s)\n", search_all, casefold); + } +#endif + + ret = str_list_init (); + + for (elt = *dirs; elt; elt = next_elt) { + int i; + string readable_name; + const_string dir = STR_LLIST (*elt); + unsigned dir_len = strlen (dir); + + next_elt = STR_LLIST_NEXT (*elt); /* in case elt floats */ + + for (i = 0; names[i]; i++) { + const_string name = names[i]; + unsigned name_len; + + /* Don't bother with absolute & explicit relative. */ + if (kpathsea_absolute_p (kpse, name, true)) + continue; + + name_len = strlen (name); + + while (dir_len + name_len + 1 > allocated) { + allocated += allocated; + XRETALLOC (potential, allocated, char); + } + + strcpy (potential, dir); + strcat (potential + dir_len, name); + readable_name = readable_file_p (kpse, potential); + if (readable_name) { + str_list_add (&ret, readable_name); + + /* Move this element towards the top of the list. */ + str_llist_float (dirs, elt); + + /* If caller only wanted one file returned, no need to + terminate the list with NULL; the caller knows to only look + at the first element. */ + if (!search_all) + return ret; + + /* Start new filename. */ + allocated = INIT_ALLOC; + potential = XTALLOC (allocated, char); + } + } + } + + /* If we get here, either we didn't find any files, or we were finding + all the files. But we're done with the last filename, anyway. */ + free (potential); + + return ret; +} + +/* This is called when NAME is absolute or explicitly relative; if it's + readable, return a one-element str_list containing it (in new + memory); otherwise, return an empty list. (We return a list so as to + have the same return value as the path_search function.) We also + check case-insensitively if enabled and needed. */ + +static str_list_type +absolute_search (kpathsea kpse, string name) +{ + str_list_type ret_list; + string found; + + /* Some old compilers can't initialize structs. */ + ret_list = str_list_init (); + + /* Do the first check. */ + found = kpathsea_readable_file (kpse, name); +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + DEBUGF2 (" absolute_search(%s) => %s\n", name, found ? found : "(nil)"); + } +#endif + if (found) { + found = xstrdup (found); /* Return new memory. */ + } + +#ifdef KPSE_CASEFOLDING_SEARCH + /* Do the casefolding search only if both needed and enabled. */ + if (!found) { + if (KPSE_CNF_P (kpathsea_var_value (kpse, "texmf_casefold_search"))) { + found = casefold_readable_file (kpse, name); +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + DEBUGF2 (" casefold search(%s) => %s\n",name,found ? found : "(nil)"); + } +#endif /* KPSE_DEBUG */ + } + } +#endif /* KPSE_CASEFOLDING_SEARCH */ + + if (found) { + /* If we didn't find anything, we'll return an empty list. */ + str_list_add (&ret_list, found); + } + + return ret_list; +} + +/* This is the hard case -- look for NAME in PATH. If ALL is false, + return the first file found. Otherwise, search all elements of PATH. + We also check case-insensitively if needed and requested. */ + +static str_list_type +path_search (kpathsea kpse, const_string path, string name, + boolean must_exist, boolean all) +{ + string elt; + str_list_type ret_list; + boolean done = false; + ret_list = str_list_init (); /* some compilers lack struct initialization */ + +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + DEBUGF4 (" path_search(file=%s, must_exist=%d, find_all=%d, path=%s)\n", + name, all, must_exist, path); + } +#endif + + for (elt = kpathsea_path_element (kpse, path); !done && elt; + elt = kpathsea_path_element (kpse, NULL)) { + str_list_type *found; + boolean allow_disk_search = true; + + if (*elt == '!' && *(elt + 1) == '!') { + /* Those magic leading chars in a path element means don't search the + disk for this elt. And move past the magic to get to the name. */ + allow_disk_search = false; + elt += 2; + } + + /* See elt-dirs.c for side effects of this function. */ + kpathsea_normalize_path (kpse, elt); + + /* Try ls-R, unless we're searching for texmf.cnf. Our caller + (search), also tests followup_search, and does the resetting. */ + found = kpse->followup_search ? kpathsea_db_search (kpse, name, elt, all) + : NULL; + + /* Search the filesystem if (1) the path spec allows it, and either + (2a) we are searching for texmf.cnf; or + (2b) no db exists; or + (2c) no db's are relevant to this elt; or + (3) MUST_EXIST && NAME was not in the db. + In (2*), `found' will be NULL. + In (3), `found' will be an empty list. */ + if (allow_disk_search + && (!found || (must_exist && !STR_LIST (*found)))) { + /* Determine the directories in which to search: */ + str_llist_type *dirs = kpathsea_element_dirs (kpse, elt); + if (dirs && *dirs) { + if (!found) { + found = XTALLOC1 (str_list_type); + } + /* Search in the directories: */ + *found = dir_list_search (kpse, dirs, name, all, + kpathsea_readable_file); +#ifdef KPSE_CASEFOLDING_SEARCH + if (!STR_LIST (*found) + && KPSE_CNF_P (kpathsea_var_value (kpse, + "texmf_casefold_search"))) { + /* Nothing found; search again, case-insensitively: */ + *found = dir_list_search (kpse, dirs, name, all, + casefold_readable_file); + } +#endif /* KPSE_CASEFOLDING_SEARCH */ + } + } + + /* Did we find anything? */ + if (found && STR_LIST (*found)) { + if (all) { + str_list_concat (&ret_list, *found); + } else { + str_list_add (&ret_list, STR_LIST_FIRST_ELT (*found)); + done = true; + } + } + + /* Free the list space, if any (but not the elements). */ + if (found) { + str_list_free (found); + free (found); + } + } + + return ret_list; +} + +/* Search PATH for ORIGINAL_NAME. If ALL is false, or ORIGINAL_NAME is + absolute_p, check ORIGINAL_NAME itself. Otherwise, look at each + element of PATH for the first readable ORIGINAL_NAME. + + Always return a list; if no files are found, the list will + contain just NULL. If ALL is true, the list will be + terminated with NULL. + + This function is a special case of kpathsea_path_search_list_generic + below (which takes a list of names, instead of a single name, to + search for), and so should be rewritten to call it. But this + function came first, and life is short, so the code duplication + is here. Sorry. Please fix. */ + +static string * +search (kpathsea kpse, const_string path, const_string original_name, + boolean must_exist, boolean all) +{ + str_list_type ret_list; + string name; + boolean absolute_p; +#ifdef __DJGPP__ + /* We will use `stat' heavily, so let's request for + the fastest possible version of `stat', by telling + it what members of struct stat do we really need. + + We need to set this on each call because this is a + library function; the caller might need other options + from `stat'. Thus save the flags and restore them + before exit. + + This call tells `stat' that we do NOT need to recognize + executable files (neither by an extension nor by a magic + signature); that we do NOT need time stamp of root directories; + and that we do NOT need the write access bit in st_mode. + + Note that `kpse_set_program_name' needs the EXEC bits, + but it was already called by the time we get here. */ + unsigned short save_djgpp_flags = _djstat_flags; + + _djstat_flags = _STAT_EXEC_MAGIC | _STAT_EXEC_EXT + | _STAT_ROOT_TIME | _STAT_WRITEBIT; +#endif + + /* Make a leading ~ count as an absolute filename, and expand $FOO's. */ + name = kpathsea_expand (kpse, original_name); + +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) + DEBUGF4 ("start search(xname=%s, must_exist=%d, find_all=%d, path=%s).\n", + name, must_exist, all, path); +#endif /* KPSE_DEBUG */ + + /* If the first name is absolute or explicitly relative, no need to + consider PATH at all. */ + absolute_p = kpathsea_absolute_p (kpse, name, true); + + /* Find the file(s). */ + ret_list = absolute_p ? absolute_search (kpse, name) + : path_search (kpse, path, name, must_exist, all); + + /* Append NULL terminator if we didn't find anything at all, or we're + supposed to find ALL and the list doesn't end in NULL now. */ + if (STR_LIST_EMPTY (ret_list) + || (all && STR_LIST_LAST_ELT (ret_list) != NULL)) + str_list_add (&ret_list, NULL); + + /* The very first search is for texmf.cnf. We can't log that, since + we want to allow setting TEXMFLOG in texmf.cnf. */ + if (kpse->followup_search == false) { + kpse->followup_search = true; + } else { + /* Record the filenames we found, if desired. And wrap them in a + debugging line if we're doing that. */ +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) + DEBUGF1 ("returning from search(%s) =>", original_name); +#endif /* KPSE_DEBUG */ + log_search (kpse, ret_list); +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) + putc ('\n', stderr); +#endif /* KPSE_DEBUG */ + } + +#ifdef __DJGPP__ + /* Undo any side effects. */ + _djstat_flags = save_djgpp_flags; +#endif + + /* Free the expanded name we were passed. It can't be in the return + list, since the path directories got unconditionally prepended. */ + free (name); + + return STR_LIST (ret_list); +} + +/* Search PATH for null-terminated array of NAMES. Always return a list; + if no files are found, the list will contain just NULL. If ALL is + true, the list will be terminated with NULL (but no NULL terminator + if ALL is false). This is a generalization of the `search' fn above. */ + +string * +kpathsea_path_search_list_generic (kpathsea kpse, + const_string path, string* names, + boolean must_exist, boolean all) +{ + str_list_type ret_list; + string* namep; + string elt; + boolean done = false; + boolean all_absolute = true; +#ifdef __DJGPP__ + /* See DJGPP comments above. */ + unsigned short save_djgpp_flags = _djstat_flags; + + _djstat_flags = _STAT_EXEC_MAGIC | _STAT_EXEC_EXT + | _STAT_ROOT_TIME | _STAT_WRITEBIT; +#endif + + ret_list = str_list_init (); + +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + DEBUGF ("start generic search(files="); + print_space_list (stderr, names); + fprintf (stderr, ", must_exist=%d, find_all=%d, path=%s)\n", + must_exist, all, path); + } +#endif /* KPSE_DEBUG */ + + /* kpathsea_find_file_generic in tex-file.c does the variable and + tilde expansion, so don't redo that here. Maybe we should have + done it differently, but we certainly don't want to create an + incompatibility now. */ + + /* First catch any absolute or explicit relative names. */ + for (namep = names; *namep; namep++) { + if (kpathsea_absolute_p (kpse, *namep, true)) { + str_list_type abs_ret_list = absolute_search (kpse, *namep); + /* That search can only return a zero- or one-element list, so: */ + if (!STR_LIST_EMPTY (abs_ret_list)) { + str_list_add (&ret_list, STR_LIST_FIRST_ELT (abs_ret_list)); + if (!all) { /* if they only wanted one, we're done */ + goto out; + } + } + } else { + all_absolute = false; + } + } + /* Shortcut: if we were only given absolute/explicit relative names, + we can skip the rest. Typically, if one name is absolute, they + all are, because our caller derived them from each other. */ + if (all_absolute) { +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + unsigned i; + DEBUGF (" generic search: all absolute, candidates are:"); + /* List might not be NULL-terminated, so can't use print_space_list. */ + for (i = 0; i < STR_LIST_LENGTH (ret_list); i++) { + fprintf (stderr, " %s", STR_LIST_ELT (ret_list, i)); + } + fputs (".\n", stderr); + } +#endif + goto out; + } + + /* Look at each path element in turn. This is essentially the same + code as `path_search' above, unfortunately. */ + for (elt = kpathsea_path_element (kpse, path); !done && elt; + elt = kpathsea_path_element (kpse, NULL)) { + str_list_type *found; + boolean allow_disk_search = true; + + if (elt[0] == '!' && elt[1] == '!') { + /* !! magic string -> disallow disk searches. */ + allow_disk_search = false; + elt += 2; + } + + /* See elt-dirs.c for side effects of this function. */ + kpathsea_normalize_path (kpse, elt); + + /* Try ls-R, unless we're searching for texmf.cnf. */ + found = kpse->followup_search + ? kpathsea_db_search_list (kpse, names, elt, all) : NULL; + + /* Search the filesystem in the same cases as `path_search' above. */ + if (allow_disk_search + && (!found || (must_exist && !STR_LIST (*found)))) { + str_llist_type *dirs = kpathsea_element_dirs (kpse, elt); + if (dirs && *dirs) { + if (!found) { + found = XTALLOC1 (str_list_type); + } + /* Search in the directories: */ + *found = dir_list_search_list (kpse, dirs, names, all, + kpathsea_readable_file); +#ifdef KPSE_CASEFOLDING_SEARCH + if (!STR_LIST (*found) && KPSE_CNF_P (kpathsea_var_value (kpse, + "texmf_casefold_search"))) { + /* Still nothing; search again, case-insensitively: */ + *found = dir_list_search_list (kpse, dirs, names, all, + casefold_readable_file); + } +#endif + } + } + + /* Did we find anything? */ + if (found && STR_LIST (*found)) { + if (all) { + str_list_concat (&ret_list, *found); + } else { + str_list_add (&ret_list, STR_LIST_FIRST_ELT (*found)); + done = true; + } + } + } + + out: + /* Uniqify, since our paths can often end up finding the same file + more than once. */ + str_list_uniqify (&ret_list); + + /* Add NULL element to terminate return list if empty or multiple. */ + if (STR_LIST_EMPTY (ret_list) + || (all && STR_LIST_LAST_ELT (ret_list) != NULL)) + str_list_add (&ret_list, NULL); + + if (kpse->followup_search == false) { + kpse->followup_search = true; + } else { + /* Record the filenames we found, if desired. And wrap them in a + debugging line if we're doing that. */ +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) { + DEBUGF ("returning from generic search("); + print_space_list (stderr, names); + fputs (") =>", stderr); + } +#endif /* KPSE_DEBUG */ + log_search (kpse, ret_list); +#ifdef KPSE_DEBUG + if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) + putc ('\n', stderr); +#endif /* KPSE_DEBUG */ + } + +#ifdef __DJGPP__ + /* Undo any side effects. */ + _djstat_flags = save_djgpp_flags; +#endif + + return STR_LIST (ret_list); +} + +/* Search PATH for the first NAME according to MUST_EXIST. */ + +string +kpathsea_path_search (kpathsea kpse, const_string path, const_string name, + boolean must_exist) +{ + string *ret_list = search (kpse, path, name, must_exist, false); + string ret = *ret_list; + free (ret_list); + return ret; +} + +/* Search PATH for all files named NAME. Might have been better not + to assert `must_exist' here, but it's too late to change. */ + +string * +kpathsea_all_path_search (kpathsea kpse, const_string path, const_string name) +{ + string *ret = search (kpse, path, name, true, true); + return ret; +} + +#if defined (KPSE_COMPAT_API) +string +kpse_path_search (const_string path, const_string name, boolean must_exist) +{ + return kpathsea_path_search (kpse_def, path, name, must_exist); +} + +string * +kpse_all_path_search (const_string path, const_string name) +{ + return kpathsea_all_path_search (kpse_def, path, name); +} +#endif /* KPSE_COMPAT_API */ + + +#ifdef TEST + +/* Each element of L on its own line, prefixed by a tab. */ + +static void +print_tab_list (string *l) +{ + while (l && *l) { + if (*l) + putchar ('\t'); + printf ("%s\n", *l); + l++; + } +} + +static void +test_path_search (const_string path, const_string file) +{ + string answer; + string *answer_list; + + printf ("\nSearch %s for %s:\t", path, file); + answer = kpse_path_search (path, file, 0); + puts (answer ? answer : "(nil)"); + + printf ("Search %s for all %s:\t", path, file); + answer_list = kpse_all_path_search (path, file); + putchar ('\n'); + print_tab_list (answer_list); +} + +static void +test_path_search_list_generic (void) +{ + const_string path = "/u/karl/.fonts"; + /* absolute: should return just console/both, because no case + sensitive match */ + // string names[] = { "/u/karl/.fonts/lucidaConsoleDK.otf", + // "/u/karl/.fonts/lucidaGrandeMonoDK.otf", NULL }; + + /* dirs: should return just grande, because case sensitive wins */ + // string names[] = { "lucidaConsoleDK.otf", "LucidaGrandeMonoDK.otf", NULL }; + + /* dirs: should return just console/both, because no case sensitive match */ + string names[] = { "lucidaConsoleDK.otf", "lucidaGrandeMonoDK.otf", NULL }; + + boolean all; + string *answer_list; + + printf ("\nGeneric search %s for ", path); + print_space_list (stdout, names); + puts (":\t"); + + all = false; answer_list + = kpathsea_path_search_list_generic (kpse_def, path, names, false, all); + puts (answer_list && *answer_list ? *answer_list : "(nil)"); + + printf ("\nGeneric search %s for all ", path); + print_space_list (stdout, names); + puts (":"); + + all = true; answer_list + = kpathsea_path_search_list_generic (kpse_def, path, names, false, all); + print_tab_list (answer_list); +} + +#define TEXFONTS "/usr/local/texlive-rel/texmf-dist/fonts" + +int +main (int argc, char **argv) +{ + xputenv ("KPATHSEA_DEBUG", "-1"); /* must be before setting progname */ + kpse_set_program_name (argv[0], NULL); + + xputenv ("MALLOC_CHECK_", "3"); + xputenv ("MALLOC_PERTURB_", "75"); + + xputenv ("texmf_casefold_search", "1"); + test_path_search (".:/k", "readme"); + exit (0); + test_path_search_list_generic (); + exit (0); + + xputenv ("TEXMFCNF", "/nc"); + /* casefolding with absolute search: */ + test_path_search ("/k", "/u/karl/.fonts/lucidaConsoleDK.otf"); + /* casefolding with directory search: */ + test_path_search ("/u/karl/.fonts", "Lucidaconsoledk.otf"); + /* exit (0); */ + xputenv ("texmf_casefold_search", "0"); + /* should fail since no casefolding: */ + test_path_search ("/u/karl/.fonts", "lucidaconsoledk.otf"); + + /* All lists end with NULL. */ + test_path_search (".", "nonexistent"); + test_path_search (".", "/nonexistent"); + test_path_search ("/k" ENV_SEP_STRING ".", "README"); + test_path_search ("/k" ENV_SEP_STRING ".", "/etc/fstab"); + test_path_search ("." ENV_SEP_STRING TEXFONTS "//", "cmr10.tfm"); + test_path_search ("." ENV_SEP_STRING TEXFONTS "//", "logo10.tfm"); + test_path_search (TEXFONTS "//times" ENV_SEP_STRING "." + ENV_SEP_STRING ENV_SEP_STRING, "ptmr.vf"); + test_path_search (TEXFONTS ENV_SEP_STRING + "/u/karl/.fonts", "LucidaConsoleDK.otf"); + + test_path_search ("~karl", ".profile"); // nil, no ~ expansion on path + test_path_search ("/k", "~karl/.profile"); + + xputenv ("NONEXIST", "nonexistent"); + test_path_search (".", "$NONEXIST"); + xputenv ("KPATHSEA", "kpathsea"); + test_path_search ("/k" ENV_SEP_STRING "./doc", "$KPATHSEA.texi"); + test_path_search ("/k" ENV_SEP_STRING "./doc", "${KPATHSEA}.texi"); + test_path_search ("$KPATHSEA" ENV_SEP_STRING ".", "README"); + test_path_search ("." ENV_SEP_STRING "$KPATHSEA", "README"); + + return 0; +} + +#endif /* TEST */ + + +/* ${wc} is the corresponding build directory. + -DMAKE_KPSE_DLL for inlined str_list_init etc. + Memory checking: -fsanitize=address or -lefence. +Local variables: +standalone-compile-command: "make --no-print-dir -C ${wc} && gcc -g -I. -I.. -I${wc}/.. -DMAKE_KPSE_DLL -DTEST pathsearch.c ${wc}/.libs/libkpathsea.a && ./a.out" +End: +*/ |