summaryrefslogtreecommitdiff
path: root/Build/source/texk/kpathsea/db.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/kpathsea/db.c')
-rw-r--r--Build/source/texk/kpathsea/db.c807
1 files changed, 807 insertions, 0 deletions
diff --git a/Build/source/texk/kpathsea/db.c b/Build/source/texk/kpathsea/db.c
new file mode 100644
index 00000000000..fb4658c1968
--- /dev/null
+++ b/Build/source/texk/kpathsea/db.c
@@ -0,0 +1,807 @@
+/* db.c: an external database to avoid filesystem lookups.
+
+ Copyright 1994, 1995, 1996, 1997, 2008, 2009, 2011, 2012, 2014, 2016,
+ 2017 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/absolute.h>
+#include <kpathsea/c-stat.h>
+#include <kpathsea/c-fopen.h>
+#include <kpathsea/c-pathch.h>
+#include <kpathsea/db.h>
+#include <kpathsea/hash.h>
+#include <kpathsea/line.h>
+#include <kpathsea/pathsearch.h>
+#include <kpathsea/readable.h>
+#include <kpathsea/str-list.h>
+#include <kpathsea/tex-file.h>
+#include <kpathsea/variable.h>
+
+#ifndef DB_HASH_SIZE
+/* Based on the size of 2014 texmf-dist/ls-R, about 130,000 entries. */
+#define DB_HASH_SIZE 64007
+#endif
+#ifndef DB_NAME
+#define DB_NAME "ls-R"
+#endif
+#ifndef DB_NAME_LC
+#define DB_NAME_LC "ls-r"
+#endif
+
+/* In the loop in init() below where we check for DB_NAME_LC and DB_NAME
+ being the same file, it's convenient to ignore the first of a pair.
+ So put the canonical name second. Sure wish I hadn't used a capital
+ letter in the name in the first place. */
+/* We use non-const strings initialized with string constants in order
+ to avoid some compiler warnings. */
+static char db_name_lc[] = DB_NAME_LC;
+static char db_name[] = DB_NAME;
+static string db_names[] = {
+ db_name_lc,
+ db_name,
+ NULL
+};
+
+#ifndef ALIAS_NAME
+#define ALIAS_NAME "aliases"
+#endif
+#ifndef ALIAS_HASH_SIZE
+#define ALIAS_HASH_SIZE 1009
+#endif
+
+
+/* If DIRNAME contains any element beginning with a `.' (that is more
+ than just `./'), return true. This is to allow ``hidden''
+ directories -- ones that don't get searched. */
+
+static boolean
+ignore_dir_p (const_string dirname)
+{
+ const_string dot_pos = dirname;
+
+ while ((dot_pos = strchr (dot_pos + 1, '.'))) {
+ /* If / before and no / after, skip it. */
+ if (IS_DIR_SEP_CH (dot_pos[-1]) && dot_pos[1] && !IS_DIR_SEP_CH (dot_pos[1]))
+ return true;
+ }
+
+ return false;
+}
+
+/* If no DB_FILENAME, return false (maybe they aren't using this feature).
+ Otherwise, add entries from DB_FILENAME to TABLE, and return true. */
+
+static boolean
+db_build (kpathsea kpse, hash_table_type *table, const_string db_filename)
+{
+ string line;
+ unsigned dir_count = 0, file_count = 0, ignore_dir_count = 0;
+ unsigned len = strlen (db_filename) - sizeof (DB_NAME) + 1; /* Keep the /. */
+ string top_dir = (string)xmalloc (len + 1);
+ string cur_dir = NULL; /* First thing in ls-R might be a filename. */
+ FILE *db_file = fopen (db_filename, FOPEN_R_MODE);
+#if defined(MONOCASE_FILENAMES)
+ string pp;
+#endif /* MONOCASE_FILENAMES */
+
+ strncpy (top_dir, db_filename, len);
+ top_dir[len] = 0;
+
+ if (db_file) {
+ while ((line = read_line (db_file)) != NULL) {
+ len = strlen (line);
+
+#if defined(MONOCASE_FILENAMES)
+ for (pp = line; *pp; pp++) {
+#if defined(_WIN32)
+ if (kpathsea_IS_KANJI(kpse, pp))
+ pp++;
+ else
+#endif /* _WIN32 */
+ *pp = TRANSFORM(*pp);
+ }
+#endif /* MONOCASE_FILENAMES */
+
+ /* A line like `/foo:' = new dir foo. Allow both absolute (/...)
+ and explicitly relative (./...) names here. It's a kludge to
+ pass in the directory name with the trailing : still attached,
+ but it doesn't actually hurt. */
+ if (len > 0 && line[len - 1] == ':'
+ && kpathsea_absolute_p (kpse, line, true)) {
+ /* New directory line. */
+ if (!ignore_dir_p (line)) {
+ /* If they gave a relative name, prepend full directory name now. */
+ line[len - 1] = DIR_SEP;
+ /* Skip over leading `./', it confuses `match' and is just a
+ waste of space, anyway. This will lose on `../', but `match'
+ won't work there, either, so it doesn't matter. */
+ cur_dir = *line == '.' ? concat (top_dir, line + 2) : xstrdup (line);
+ dir_count++;
+ } else {
+ cur_dir = NULL;
+ ignore_dir_count++;
+ }
+
+ /* Ignore blank, `.' and `..' lines. */
+ } else if (*line != 0 && cur_dir /* a file line? */
+ && !(*line == '.'
+ && (line[1] == 0 || (line[1] == '.' && line[2] == 0))))
+ {
+ /* Make a new hash table entry with a key of `line' and a data
+ of `cur_dir'. An already-existing identical key is ok, since
+ a file named `foo' can be in more than one directory. Share
+ `cur_dir' among all its files (and hence never free it).
+
+ Note that we assume that all names in the ls-R file have already
+ been case-smashed to lowercase where appropriate.
+ */
+ hash_insert_normalized (table, xstrdup (line), cur_dir);
+ file_count++;
+
+ } /* else ignore blank lines or top-level files
+ or files in ignored directories*/
+
+ free (line);
+ }
+
+ xfclose (db_file, db_filename);
+
+ if (file_count == 0) {
+ WARNING1 ("kpathsea: %s: No usable entries in ls-R", db_filename);
+ WARNING ("kpathsea: See the manual for how to generate ls-R");
+ db_file = NULL;
+ } else {
+ str_list_add (&(kpse->db_dir_list), xstrdup (top_dir));
+ }
+
+#ifdef KPSE_DEBUG
+ if (KPATHSEA_DEBUG_P (KPSE_DEBUG_HASH)) {
+ /* Don't make this a debugging bit, since the output is so
+ voluminous, and being able to specify -1 is too useful.
+ Instead, let people who want it run the program under
+ a debugger and change the variable that way. */
+ boolean hash_summary_only = true;
+
+ DEBUGF4 ("%s: %u entries in %d directories (%d hidden).\n",
+ db_filename, file_count, dir_count, ignore_dir_count);
+ DEBUGF ("ls-R hash table:");
+ hash_print (*table, hash_summary_only);
+ fflush (stderr);
+ }
+#endif /* KPSE_DEBUG */
+ }
+
+ free (top_dir);
+
+ return db_file != NULL;
+}
+
+
+/* Insert FNAME into the hash table. This is for files that get built
+ during a run. We wouldn't want to reread all of ls-R, even if it got
+ rebuilt. */
+
+void
+kpathsea_db_insert (kpathsea kpse, const_string passed_fname)
+{
+ /* We might not have found ls-R, or even had occasion to look for it
+ yet, so do nothing if we have no hash table. */
+ if (kpse->db.buckets) {
+ const_string dir_part;
+ string fname = xstrdup (passed_fname);
+ string baseptr = fname + (xbasename (fname) - fname);
+ const_string file_part = xstrdup (baseptr);
+
+ *baseptr = '\0'; /* Chop off the filename. */
+ dir_part = fname; /* That leaves the dir, with the trailing /. */
+
+ /* Note that we do not assuse that these names have been normalized. */
+ hash_insert (&(kpse->db), file_part, dir_part);
+ }
+}
+
+/* Return true if FILENAME could be in PATH_ELT, i.e., if the directory
+ part of FILENAME matches PATH_ELT. Have to consider // wildcards, but
+ $ and ~ expansion have already been done. */
+
+static boolean
+match (const_string filename, const_string path_elt)
+{
+ const_string original_filename = filename;
+ boolean matched = false;
+
+ for (; *filename && *path_elt; filename++, path_elt++) {
+ if (FILECHARCASEEQ (*filename, *path_elt)) /* normal character match */
+ ;
+
+ else if (IS_DIR_SEP_CH (*path_elt) /* at // */
+ && original_filename < filename && IS_DIR_SEP_CH (path_elt[-1])) {
+ while (IS_DIR_SEP_CH (*path_elt))
+ path_elt++; /* get past second and any subsequent /'s */
+ if (*path_elt == 0) {
+ /* Trailing //, matches anything. We could make this part of the
+ other case, but it seems pointless to do the extra work. */
+ matched = true;
+ break;
+ } else {
+ /* Intermediate //, have to match rest of PATH_ELT. */
+ for (; !matched && *filename; filename++) {
+ /* Try matching at each possible character. */
+ if (IS_DIR_SEP_CH (filename[-1])
+ && FILECHARCASEEQ (*filename, *path_elt))
+ matched = match (filename, path_elt);
+ }
+ /* Prevent filename++ when *filename='\0'. */
+ break;
+ }
+ }
+
+ else /* normal character nonmatch, quit */
+ break;
+ }
+
+ /* If we've reached the end of PATH_ELT, check that we're at the last
+ component of FILENAME (that is, no directory separators remaining);
+ only then have we matched. */
+ if (!matched && *path_elt == 0) {
+ /* Typically PATH_ELT ends with, say, `vf', and FILENAME ends with
+ `vf/ptmr.vf'. In that case, we'll be at the /. On the other
+ hand, if PATH_ELT ended with a / (as in `vf/'), FILENAME being
+ the same `vf/ptmr.vf', we'll be at the `p'.
+ Upshot: if we're at a dir sep in FILENAME, skip it. */
+ if (IS_DIR_SEP_CH (*filename))
+ filename++;
+
+ /* Here are the basic possibilities for the check on being at the
+ last component:
+ 1) PATH_ELT is empty and FILENAME is `ptmr.vf' => match.
+ (we now have original_filename == filename)
+ 2) PATH_ELT is empty and FILENAME is `foo/ptmr.vf' => no match.
+ (we now have original_filename == filename)
+ 3) PATH_ELT is `vf/' and FILENAME is `vf/ptmr.vf'
+ (we are now after the / in each) => match.
+ 4) PATH_ELT is `vf' and FILENAME is `vfoo.ext'
+ (we are now after the f in each) => no match.
+
+ When (the original) PATH_ELT was the empty string, we want to match
+ a FILENAME without dir seps. (This could be argued, and may never
+ happen in practice, but is the historical behavior.) */
+ /* if original_filename != filename then original_filename < filename */
+ if (original_filename == filename || IS_DIR_SEP_CH (filename[-1])) {
+ while (*filename && !IS_DIR_SEP_CH (*filename))
+ filename++;
+ matched = *filename == 0;
+ }
+ }
+
+ return matched;
+}
+
+
+/* If DB_DIR is a prefix of PATH_ELT, return true; otherwise false.
+ That is, the question is whether to try the db for a file looked up
+ in PATH_ELT. If PATH_ELT == ".", for example, the answer is no. If
+ PATH_ELT == "/usr/local/lib/texmf/fonts//tfm", the answer is yes.
+ If either string is NULL or empty, return false.
+
+ In practice, ls-R is only needed for lengthy subdirectory
+ comparisons, but there's no gain to checking PATH_ELT to see if it is
+ a subdir match, since the only way to do that is to do a string
+ search in it, which is all we do anyway. */
+
+static boolean
+elt_in_db (const_string db_dir, const_string path_elt)
+{
+ boolean found = false;
+
+ /* If both strings are empty or null return false on the grounds that
+ it's useless to do anything further with such a strange case (which
+ likely never happens). In theory one could argue that the empty
+ string is a prefix of any other string, but let's just declare the
+ result otherwise. */
+ if (db_dir == NULL || *db_dir == 0
+ || path_elt == NULL || *path_elt == 0)
+ return false;
+
+ while (!found && FILECHARCASEEQ (*db_dir++, *path_elt++)) {
+ /* If we've matched the entire db directory, it's good. */
+ if (*db_dir == 0)
+ found = true;
+
+ /* If we've reached the end of PATH_ELT, but not the end of the db
+ directory, it's no good. */
+ else if (*path_elt == 0)
+ break;
+ }
+
+ return found;
+}
+
+/* If ALIAS_FILENAME exists, read it into TABLE. */
+
+static boolean
+alias_build (kpathsea kpse, hash_table_type *table,
+ const_string alias_filename)
+{
+ string line, real, alias;
+ unsigned count = 0;
+ FILE *alias_file = fopen (alias_filename, FOPEN_R_MODE);
+
+ if (alias_file) {
+ while ((line = read_line (alias_file)) != NULL) {
+ /* comments or empty */
+ if (*line == 0 || *line == '%' || *line == '#') {
+ ;
+ } else {
+ /* Each line should have two fields: realname aliasname. */
+ real = line;
+ while (*real && ISSPACE (*real))
+ real++;
+ alias = real;
+ while (*alias && !ISSPACE (*alias))
+ alias++;
+ *alias++ = 0;
+ while (*alias && ISSPACE (*alias))
+ alias++;
+ /* Is the check for errors strong enough? Should we warn the user
+ for potential errors? */
+ if (strlen (real) != 0 && strlen (alias) != 0) {
+ /* Stuff in the alias file should be normalized. */
+ hash_insert_normalized (table, xstrdup (alias), xstrdup (real));
+ count++;
+ }
+ }
+ free (line);
+ }
+
+#ifdef KPSE_DEBUG
+ if (KPATHSEA_DEBUG_P (KPSE_DEBUG_HASH)) {
+ /* As with ls-R above ... */
+ boolean hash_summary_only = true;
+ DEBUGF2 ("%s: %u aliases.\n", alias_filename, count);
+ DEBUGF ("alias hash table:");
+ hash_print (*table, hash_summary_only);
+ fflush (stderr);
+ }
+#endif /* KPSE_DEBUG */
+
+ xfclose (alias_file, alias_filename);
+ }
+
+ return alias_file != NULL;
+}
+
+/* Initialize the path for ls-R files, and read them all into the hash
+ table `db'. If no usable ls-R's found, set kpse->db.buckets to NULL. */
+
+void
+kpathsea_init_db (kpathsea kpse)
+{
+ const_string db_path;
+ string *db_files;
+ string *orig_db_files;
+ str_list_type unique_list;
+ int dbi;
+ boolean ok = false;
+
+ assert (sizeof(DB_NAME) == sizeof(DB_NAME_LC));
+
+ db_path = kpathsea_init_format (kpse, kpse_db_format);
+ db_files = kpathsea_path_search_list_generic (kpse, db_path, db_names,
+ true, true);
+ orig_db_files = db_files;
+
+ /* Mac OS X and others can use a case-insensitive, case-preserving
+ filesystem by default, in which case ls-R and ls-r point to the
+ same file. Also, Windows is case-insensitive. In these cases,
+ we want to avoid reading the same file multiple times. */
+ dbi = 0;
+ unique_list = str_list_init ();
+
+ while (db_files[dbi] != NULL) {
+ string path1 = db_files[dbi];
+ string path2 = db_files[dbi + 1];
+
+ /* first-pass check in case path1/path2 aren't even
+ potentially equal; mainly in case the order from
+ kpathsea_path_search_list_generic changes. */
+ if (path2
+ && strcasecmp (path1, path2) == 0
+ && same_file_p (path1, path2)) {
+ /* they are the same, skip over path1, we'll add path2
+ on the next iteration (when it's path1). */
+#ifdef KPSE_DEBUG
+ if (KPATHSEA_DEBUG_P (KPSE_DEBUG_HASH)) {
+ DEBUGF2 ("db:init(): skipping db same_file_p %s, will add %s.\n",
+ path1, path2);
+ }
+#endif
+ free (path1);
+
+ } else {
+ /* they are not the same, add path1. */
+#ifdef KPSE_DEBUG
+ if (KPATHSEA_DEBUG_P (KPSE_DEBUG_HASH)) {
+ DEBUGF1 ("db:init(): using db file %s.\n", path1);
+ }
+#endif
+ str_list_add (&unique_list, path1);
+ }
+
+ /* could be more clever and increment by two, but then would
+ have to avoid jumping off the end of db_files */
+ dbi++;
+ }
+
+ /* always add a NULL terminator. */
+ str_list_add (&unique_list, NULL);
+
+ free (orig_db_files);
+ db_files = STR_LIST (unique_list);
+ orig_db_files = db_files;
+
+ /* Must do this after the path searching (which ends up calling
+ kpse_db_search recursively), so kpse->db.buckets stays NULL. */
+ kpse->db = hash_create (DB_HASH_SIZE);
+
+ while (db_files && *db_files) {
+ if (db_build (kpse, &(kpse->db), *db_files))
+ ok = true;
+ free (*db_files);
+ db_files++;
+ }
+
+ if (!ok) {
+ /* If db can't be built, leave `size' nonzero (so we don't
+ rebuild it), but clear `buckets' (so we don't look in it). */
+ free (kpse->db.buckets);
+ kpse->db.buckets = NULL;
+ }
+
+ free (orig_db_files);
+
+ /* Add the content of any alias databases. There may exist more than
+ one alias file along DB_NAME files. This duplicates the above code
+ -- should be a function. */
+ ok = false;
+ db_files = kpathsea_all_path_search (kpse, db_path, ALIAS_NAME);
+ orig_db_files = db_files;
+
+ kpse->alias_db = hash_create (ALIAS_HASH_SIZE);
+
+ while (db_files && *db_files) {
+ if (alias_build (kpse, &(kpse->alias_db), *db_files))
+ ok = true;
+ free (*db_files);
+ db_files++;
+ }
+
+ if (!ok) {
+ free (kpse->alias_db.buckets);
+ kpse->alias_db.buckets = NULL;
+ }
+
+ free (orig_db_files);
+}
+
+/* Avoid doing anything if this PATH_ELT is irrelevant to the databases. */
+str_list_type *
+kpathsea_db_search (kpathsea kpse, const_string name,
+ const_string orig_path_elt, boolean all)
+{
+ const_string *db_dirs, *orig_dirs;
+ const_string last_slash, path_elt;
+ string temp_str = NULL;
+ boolean done;
+ unsigned e;
+ str_list_type *ret = NULL;
+ const_string *aliases, *r;
+ boolean relevant = false;
+
+ /* If we failed to build the database (or if this is the recursive
+ call to build the db path), quit. */
+ if (kpse->db.buckets == NULL)
+ return NULL;
+
+ /* When tex-glyph.c calls us looking for, e.g., dpi600/cmr10.pk, we
+ won't find it unless we change NAME to just `cmr10.pk' and append
+ `/dpi600' to PATH_ELT. We are justified in using a literal `/'
+ here, since that's what tex-glyph.c unconditionally uses in
+ DPI_BITMAP_SPEC. But don't do anything if the / begins NAME; that
+ should never happen. */
+ last_slash = strrchr (name, '/');
+ if (last_slash && last_slash != name) {
+ unsigned len = last_slash - name + 1;
+ string dir_part = (string)xmalloc (len);
+ strncpy (dir_part, name, len - 1);
+ dir_part[len - 1] = 0;
+ path_elt = temp_str = concat3 (orig_path_elt, "/", dir_part);
+ name = last_slash + 1;
+ free (dir_part);
+ } else
+ path_elt = orig_path_elt;
+
+ /* Don't bother doing any lookups if this `path_elt' isn't covered by
+ any of database directories. We do this not so much because the
+ extra couple of hash lookups matter -- they don't -- but rather
+ because we want to return NULL in this case, so path_search can
+ know to do a disk search. */
+ for (e = 0; !relevant && e < STR_LIST_LENGTH (kpse->db_dir_list); e++) {
+ relevant = elt_in_db (STR_LIST_ELT (kpse->db_dir_list, e), path_elt);
+ }
+ if (!relevant)
+ return NULL;
+
+ /* If we have aliases for this name, use them. */
+ if (kpse->alias_db.buckets)
+ aliases = hash_lookup (kpse->alias_db, name);
+ else
+ aliases = NULL;
+
+ if (!aliases) {
+ aliases = XTALLOC1 (const_string);
+ aliases[0] = NULL;
+ }
+ { /* Push aliases up by one and insert the original name at the front. */
+ unsigned i;
+ unsigned len = 1; /* Have NULL element already allocated. */
+ for (r = aliases; *r; r++)
+ len++;
+ /* This is essentially
+ XRETALLOC (aliases, len + 1, const_string);
+ except that MSVC warns without the cast to `void *'. */
+ aliases = (const_string *) xrealloc ((void *) aliases,
+ (len + 1) * sizeof(const_string));
+ for (i = len; i > 0; i--) {
+ aliases[i] = aliases[i - 1];
+ }
+ aliases[0] = name;
+ }
+
+ done = false;
+ for (r = aliases; !done && *r; r++) {
+ const_string ctry = *r;
+
+ /* We have an ls-R db. Look up `try'. */
+ orig_dirs = db_dirs = hash_lookup (kpse->db, ctry);
+
+ ret = XTALLOC1 (str_list_type);
+ *ret = str_list_init ();
+
+ /* For each filename found, see if it matches the path element. For
+ example, if we have .../cx/cmr10.300pk and .../ricoh/cmr10.300pk,
+ and the path looks like .../cx, we don't want the ricoh file. */
+ while (!done && db_dirs && *db_dirs) {
+ string db_file = concat (*db_dirs, ctry);
+ boolean matched = match (db_file, path_elt);
+
+#ifdef KPSE_DEBUG
+ if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH))
+ DEBUGF3 ("db:match(%s,%s) = %d\n", db_file, path_elt, matched);
+#endif
+
+ /* We got a hit in the database. Now see if the file actually
+ exists, possibly under an alias. */
+ if (matched) {
+ string found = NULL;
+ if (kpathsea_readable_file (kpse, db_file)) {
+ found = db_file;
+
+ } else {
+ const_string *a;
+
+ free (db_file); /* `db_file' wasn't on disk. */
+
+ /* The hit in the DB doesn't exist in disk. Now try all its
+ aliases. For example, suppose we have a hierarchy on CD,
+ thus `mf.bas', but ls-R contains `mf.base'. Find it anyway.
+ Could probably work around this with aliases, but
+ this is pretty easy and shouldn't hurt. The upshot is that
+ if one of the aliases actually exists, we use that. */
+ for (a = aliases + 1; *a && !found; a++) {
+ string atry = concat (*db_dirs, *a);
+ if (kpathsea_readable_file (kpse, atry))
+ found = atry;
+ else
+ free (atry);
+ }
+ }
+
+ /* If we have a real file, add it to the list, maybe done. */
+ if (found) {
+ str_list_add (ret, found);
+ if (!all && found)
+ done = true;
+ }
+ } else { /* no match in the db */
+ free (db_file);
+ }
+
+
+ /* On to the next directory, if any. */
+ db_dirs++;
+ }
+
+ /* This is just the space for the pointers, not the strings. */
+ if (orig_dirs && *orig_dirs)
+ free (orig_dirs);
+ }
+
+ free ((void *) aliases);
+
+ /* If we had to break up NAME, free the TEMP_STR. */
+ if (temp_str)
+ free (temp_str);
+
+ return ret;
+}
+
+str_list_type *
+kpathsea_db_search_list (kpathsea kpse, string* names,
+ const_string path_elt, boolean all)
+{
+ const_string *db_dirs, *orig_dirs;
+ const_string last_slash, name, path;
+ string temp_str = NULL;
+ boolean done;
+ unsigned e;
+ const_string *aliases, *r;
+ int n;
+ str_list_type *ret = NULL;
+ boolean relevant = false;
+
+ /* If we failed to build the database (or if this is the recursive
+ call to build the db path), quit. */
+ if (kpse->db.buckets == NULL)
+ return NULL;
+
+ /* Don't bother doing any lookups if this `path_elt' isn't covered by
+ any of database directories. We do this not so much because the
+ extra couple of hash lookups matter -- they don't -- but rather
+ because we want to return NULL in this case, so path_search can
+ know to do a disk search. */
+ for (e = 0; !relevant && e < STR_LIST_LENGTH (kpse->db_dir_list); e++) {
+ relevant = elt_in_db (STR_LIST_ELT (kpse->db_dir_list, e), path_elt);
+ }
+ if (!relevant)
+ return NULL;
+
+ done = false;
+ ret = XTALLOC1 (str_list_type);
+ *ret = str_list_init ();
+
+ /* Handle each name. */
+ for (n = 0; !done && names[n]; n++) {
+ name = names[n];
+
+ /* Absolute names should have been caught in our caller. */
+ if (kpathsea_absolute_p(kpse, name, true))
+ continue;
+
+ /* When tex-glyph.c calls us looking for, e.g., dpi600/cmr10.pk, we
+ won't find it unless we change NAME to just `cmr10.pk' and append
+ `/dpi600' to PATH_ELT. We are justified in using a literal `/'
+ here, since that's what tex-glyph.c unconditionally uses in
+ DPI_BITMAP_SPEC. But don't do anything if the / begins NAME; that
+ should never happen. */
+ last_slash = strrchr (name, '/');
+ if (last_slash && last_slash != name) {
+ unsigned len = last_slash - name + 1;
+ string dir_part = (string)xmalloc (len);
+ strncpy (dir_part, name, len - 1);
+ dir_part[len - 1] = 0;
+ path = temp_str = concat3 (path_elt, "/", dir_part);
+ name = last_slash + 1;
+ free (dir_part);
+ } else {
+ path = path_elt;
+ }
+
+ /* If we have aliases for this name, use them. */
+ if (kpse->alias_db.buckets)
+ aliases = hash_lookup (kpse->alias_db, name);
+ else
+ aliases = NULL;
+
+ if (!aliases) {
+ aliases = XTALLOC1 (const_string);
+ aliases[0] = NULL;
+ }
+ { /* Push aliases up by one and insert the original name at front. */
+ unsigned i;
+ unsigned len = 1; /* Have NULL element already allocated. */
+ for (r = aliases; *r; r++)
+ len++;
+ aliases = (const_string *) xrealloc ((void *) aliases,
+ (len + 1) * sizeof(const_string));
+ for (i = len; i > 0; i--) {
+ aliases[i] = aliases[i - 1];
+ }
+ aliases[0] = name;
+ }
+
+ for (r = aliases; !done && *r; r++) {
+ const_string ctry = *r;
+
+ /* We have an ls-R db. Look up `try'. */
+ orig_dirs = db_dirs = hash_lookup (kpse->db, ctry);
+
+ /* For each filename found, see if it matches the path element. For
+ example, if we have .../cx/cmr10.300pk and .../ricoh/cmr10.300pk,
+ and the path looks like .../cx, we don't want the ricoh file. */
+ while (!done && db_dirs && *db_dirs) {
+ string db_file = concat (*db_dirs, ctry);
+ boolean matched = match (db_file, path);
+
+#ifdef KPSE_DEBUG
+ if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH))
+ DEBUGF3 ("db:match(%s,%s) = %d\n", db_file, path, matched);
+#endif
+
+ /* We got a hit in the database. Now see if the file actually
+ exists, possibly under an alias. */
+ if (matched) {
+ string found = NULL;
+ if (kpathsea_readable_file (kpse, db_file)) {
+ found = db_file;
+
+ } else {
+ const_string *a;
+
+ free (db_file); /* `db_file' wasn't on disk. */
+
+ /* The hit in the DB doesn't exist in disk. Now try all its
+ aliases. For example, suppose we have a hierarchy on CD,
+ thus `mf.bas', but ls-R contains `mf.base'. Find it anyway.
+ Could probably work around this with aliases, but
+ this is pretty easy and shouldn't hurt. The upshot is that
+ if one of the aliases actually exists, we use that. */
+ for (a = aliases + 1; *a && !found; a++) {
+ string atry = concat (*db_dirs, *a);
+ if (kpathsea_readable_file (kpse, atry))
+ found = atry;
+ else
+ free (atry);
+ }
+ }
+
+ /* If we have a real file, add it to the list, maybe done. */
+ if (found) {
+ str_list_add (ret, found);
+ if (!all)
+ done = true;
+ }
+ } else { /* no match in the db */
+ free (db_file);
+ }
+
+ /* On to the next directory, if any. */
+ db_dirs++;
+ }
+
+ /* This is just the space for the pointers, not the strings. */
+ if (orig_dirs && *orig_dirs)
+ free (orig_dirs);
+ }
+
+ free ((void *) aliases);
+ if (temp_str)
+ free (temp_str);
+ }
+
+ return ret;
+}