Mercurial > hg > octave-max
comparison kpathsea/db.c @ 2999:faa5d0421460
[project @ 1997-05-23 03:02:09 by jwe]
author | jwe |
---|---|
date | Fri, 23 May 1997 03:02:36 +0000 |
parents | |
children | 1f0b06020e36 |
comparison
equal
deleted
inserted
replaced
2998:692ba9d441ec | 2999:faa5d0421460 |
---|---|
1 /* db.c: an external database to avoid filesystem lookups. | |
2 | |
3 Copyright (C) 1994, 95, 96, 97 Karl Berry. | |
4 | |
5 This library is free software; you can redistribute it and/or | |
6 modify it under the terms of the GNU Library General Public | |
7 License as published by the Free Software Foundation; either | |
8 version 2 of the License, or (at your option) any later version. | |
9 | |
10 This library is distributed in the hope that it will be useful, | |
11 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 Library General Public License for more details. | |
14 | |
15 You should have received a copy of the GNU Library General Public | |
16 License along with this library; if not, write to the Free Software | |
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |
18 | |
19 #include <kpathsea/config.h> | |
20 #include <kpathsea/absolute.h> | |
21 #include <kpathsea/c-fopen.h> | |
22 #include <kpathsea/c-pathch.h> | |
23 #include <kpathsea/db.h> | |
24 #include <kpathsea/hash.h> | |
25 #include <kpathsea/line.h> | |
26 #include <kpathsea/pathsearch.h> | |
27 #include <kpathsea/readable.h> | |
28 #include <kpathsea/str-list.h> | |
29 #include <kpathsea/tex-file.h> | |
30 #include <kpathsea/variable.h> | |
31 | |
32 static hash_table_type db; /* The hash table for all the ls-R's. */ | |
33 #ifndef DB_HASH_SIZE | |
34 #define DB_HASH_SIZE 7603 /* A minimal ls-R has about 3500 entries. */ | |
35 #endif | |
36 #ifndef DB_NAME | |
37 #define DB_NAME "ls-R" | |
38 #endif | |
39 | |
40 static hash_table_type alias_db; | |
41 #ifndef ALIAS_NAME | |
42 #define ALIAS_NAME "aliases" | |
43 #endif | |
44 #ifndef ALIAS_HASH_SIZE | |
45 #define ALIAS_HASH_SIZE 1009 | |
46 #endif | |
47 | |
48 static str_list_type db_dir_list; | |
49 | |
50 /* If DIRNAME contains any element beginning with a `.' (that is more | |
51 than just `./'), return true. This is to allow ``hidden'' | |
52 directories -- ones that don't get searched. */ | |
53 | |
54 static boolean | |
55 ignore_dir_p P1C(const_string, dirname) | |
56 { | |
57 const_string dot_pos = dirname; | |
58 | |
59 while ((dot_pos = strchr (dot_pos + 1, '.'))) { | |
60 /* If / before and no / after, skip it. */ | |
61 if (IS_DIR_SEP (dot_pos[-1]) && dot_pos[1] && !IS_DIR_SEP (dot_pos[1])) | |
62 return true; | |
63 } | |
64 | |
65 return false; | |
66 } | |
67 | |
68 /* If no DB_FILENAME, return false (maybe they aren't using this feature). | |
69 Otherwise, add entries from DB_FILENAME to TABLE, and return true. */ | |
70 | |
71 static boolean | |
72 db_build P2C(hash_table_type *, table, const_string, db_filename) | |
73 { | |
74 string line; | |
75 unsigned dir_count = 0, file_count = 0, ignore_dir_count = 0; | |
76 unsigned len = strlen (db_filename) - sizeof (DB_NAME) + 1; /* Keep the /. */ | |
77 string top_dir = xmalloc (len + 1); | |
78 string cur_dir = NULL; /* First thing in ls-R might be a filename. */ | |
79 FILE *db_file = fopen (db_filename, FOPEN_R_MODE); | |
80 | |
81 strncpy (top_dir, db_filename, len); | |
82 top_dir[len] = 0; | |
83 | |
84 if (db_file) { | |
85 while ((line = read_line (db_file)) != NULL) { | |
86 len = strlen (line); | |
87 | |
88 /* A line like `/foo:' = new dir foo. Allow both absolute (/...) | |
89 and explicitly relative (./...) names here. It's a kludge to | |
90 pass in the directory name with the trailing : still attached, | |
91 but it doesn't actually hurt. */ | |
92 if (len > 0 && line[len - 1] == ':' && kpse_absolute_p (line, true)) { | |
93 /* New directory line. */ | |
94 if (!ignore_dir_p (line)) { | |
95 /* If they gave a relative name, prepend full directory name now. */ | |
96 line[len - 1] = DIR_SEP; | |
97 /* Skip over leading `./', it confuses `match' and is just a | |
98 waste of space, anyway. This will lose on `../', but `match' | |
99 won't work there, either, so it doesn't matter. */ | |
100 cur_dir = *line == '.' ? concat (top_dir, line + 2) : xstrdup (line); | |
101 dir_count++; | |
102 } else { | |
103 cur_dir = NULL; | |
104 ignore_dir_count++; | |
105 } | |
106 | |
107 /* Ignore blank, `.' and `..' lines. */ | |
108 } else if (*line != 0 && cur_dir /* a file line? */ | |
109 && !(*line == '.' | |
110 && (line[1] == '0' || (line[1] == '.' && line[2] == 0)))) | |
111 {/* Make a new hash table entry with a key of `line' and a data | |
112 of `cur_dir'. An already-existing identical key is ok, since | |
113 a file named `foo' can be in more than one directory. Share | |
114 `cur_dir' among all its files (and hence never free it). */ | |
115 hash_insert (table, xstrdup (line), cur_dir); | |
116 file_count++; | |
117 | |
118 } /* else ignore blank lines or top-level files | |
119 or files in ignored directories*/ | |
120 | |
121 free (line); | |
122 } | |
123 | |
124 xfclose (db_file, db_filename); | |
125 | |
126 if (file_count == 0) { | |
127 WARNING1 ("kpathsea: No usable entries in %s", db_filename); | |
128 WARNING ("kpathsea: See the manual for how to generate ls-R"); | |
129 db_file = NULL; | |
130 } else { | |
131 str_list_add (&db_dir_list, xstrdup (top_dir)); | |
132 } | |
133 | |
134 #ifdef KPSE_DEBUG | |
135 if (KPSE_DEBUG_P (KPSE_DEBUG_HASH)) { | |
136 /* Don't make this a debugging bit, since the output is so | |
137 voluminous, and being able to specify -1 is too useful. | |
138 Instead, let people who want it run the program under | |
139 a debugger and change the variable that way. */ | |
140 boolean hash_summary_only = true; | |
141 | |
142 DEBUGF4 ("%s: %u entries in %d directories (%d hidden).\n", | |
143 db_filename, file_count, dir_count, ignore_dir_count); | |
144 DEBUGF ("ls-R hash table:"); | |
145 hash_print (*table, hash_summary_only); | |
146 fflush (stderr); | |
147 } | |
148 #endif /* KPSE_DEBUG */ | |
149 } | |
150 | |
151 free (top_dir); | |
152 | |
153 return db_file != NULL; | |
154 } | |
155 | |
156 | |
157 /* Insert FNAME into the hash table. This is for files that get built | |
158 during a run. We wouldn't want to reread all of ls-R, even if it got | |
159 rebuilt. */ | |
160 | |
161 void | |
162 kpse_db_insert P1C(const_string, passed_fname) | |
163 { | |
164 /* We might not have found ls-R, or even had occasion to look for it | |
165 yet, so do nothing if we have no hash table. */ | |
166 if (db.buckets) { | |
167 const_string dir_part; | |
168 string fname = xstrdup (passed_fname); | |
169 string baseptr = (string) basename (fname); | |
170 const_string file_part = xstrdup (baseptr); | |
171 | |
172 *baseptr = '\0'; /* Chop off the filename. */ | |
173 dir_part = fname; /* That leaves the dir, with the trailing /. */ | |
174 | |
175 hash_insert (&db, file_part, dir_part); | |
176 } | |
177 } | |
178 | |
179 /* Return true if FILENAME could be in PATH_ELT, i.e., if the directory | |
180 part of FILENAME matches PATH_ELT. Have to consider // wildcards, but | |
181 $ and ~ expansion have already been done. */ | |
182 | |
183 static boolean | |
184 match P2C(const_string, filename, const_string, path_elt) | |
185 { | |
186 const_string original_filename = filename; | |
187 boolean matched = false; | |
188 boolean done = false; | |
189 | |
190 for (; !done && *filename && *path_elt; filename++, path_elt++) { | |
191 if (FILECHARCASEEQ (*filename, *path_elt)) /* normal character match */ | |
192 ; | |
193 | |
194 else if (IS_DIR_SEP (*path_elt) /* at // */ | |
195 && original_filename < filename && IS_DIR_SEP (path_elt[-1])) { | |
196 while (IS_DIR_SEP (*path_elt)) | |
197 path_elt++; /* get past second and any subsequent /'s */ | |
198 if (*path_elt == 0) { | |
199 /* Trailing //, matches anything. We could make this part of the | |
200 other case, but it seems pointless to do the extra work. */ | |
201 matched = true; | |
202 done = true; | |
203 } else { | |
204 /* Intermediate //, have to match rest of PATH_ELT. */ | |
205 for (; !matched && *filename; filename++) { | |
206 /* Try matching at each possible character. */ | |
207 if (IS_DIR_SEP (filename[-1]) | |
208 && FILECHARCASEEQ (*filename, *path_elt)) | |
209 matched = match (filename, path_elt); | |
210 } | |
211 } | |
212 } | |
213 | |
214 else /* normal character nonmatch, quit */ | |
215 done = true; | |
216 } | |
217 | |
218 /* If we've reached the end of PATH_ELT, check that we're at the last | |
219 component of FILENAME, we've matched. */ | |
220 if (!matched && *path_elt == 0) { | |
221 /* Probably PATH_ELT ended with `vf' or some such, and FILENAME ends | |
222 with `vf/ptmr.vf'. In that case, we'll be at a directory | |
223 separator. On the other hand, if PATH_ELT ended with a / (as in | |
224 `vf/'), FILENAME being the same `vf/ptmr.vf', we'll be at the | |
225 `p'. Upshot: if we're at a dir separator in FILENAME, skip it. | |
226 But if not, that's ok, as long as there are no more dir separators. */ | |
227 if (IS_DIR_SEP (*filename)) | |
228 filename++; | |
229 | |
230 while (*filename && !IS_DIR_SEP (*filename)) | |
231 filename++; | |
232 matched = *filename == 0; | |
233 } | |
234 | |
235 return matched; | |
236 } | |
237 | |
238 | |
239 /* If DB_DIR is a prefix of PATH_ELT, return true; otherwise false. | |
240 That is, the question is whether to try the db for a file looked up | |
241 in PATH_ELT. If PATH_ELT == ".", for example, the answer is no. If | |
242 PATH_ELT == "/usr/local/lib/texmf/fonts//tfm", the answer is yes. | |
243 | |
244 In practice, ls-R is only needed for lengthy subdirectory | |
245 comparisons, but there's no gain to checking PATH_ELT to see if it is | |
246 a subdir match, since the only way to do that is to do a string | |
247 search in it, which is all we do anyway. */ | |
248 | |
249 static boolean | |
250 elt_in_db P2C(const_string, db_dir, const_string, path_elt) | |
251 { | |
252 boolean found = false; | |
253 | |
254 while (!found && FILECHARCASEEQ (*db_dir++, *path_elt++)) { | |
255 /* If we've matched the entire db directory, it's good. */ | |
256 if (*db_dir == 0) | |
257 found = true; | |
258 | |
259 /* If we've reached the end of PATH_ELT, but not the end of the db | |
260 directory, it's no good. */ | |
261 else if (*path_elt == 0) | |
262 break; | |
263 } | |
264 | |
265 return found; | |
266 } | |
267 | |
268 /* If ALIAS_FILENAME exists, read it into TABLE. */ | |
269 | |
270 static boolean | |
271 alias_build P2C(hash_table_type *, table, const_string, alias_filename) | |
272 { | |
273 string line, real, alias; | |
274 unsigned count = 0; | |
275 FILE *alias_file = fopen (alias_filename, FOPEN_R_MODE); | |
276 | |
277 if (alias_file) { | |
278 while ((line = read_line (alias_file)) != NULL) { | |
279 /* comments or empty */ | |
280 if (*line == 0 || *line == '%' || *line == '#') { | |
281 ; | |
282 } else { | |
283 /* Each line should have two fields: realname aliasname. */ | |
284 real = line; | |
285 while (*real && ISSPACE (*real)) | |
286 real++; | |
287 alias = real; | |
288 while (*alias && !ISSPACE (*alias)) | |
289 alias++; | |
290 *alias++ = 0; | |
291 while (*alias && ISSPACE (*alias)) | |
292 alias++; | |
293 /* Is the check for errors strong enough? Should we warn the user | |
294 for potential errors? */ | |
295 if (strlen (real) != 0 && strlen (alias) != 0) { | |
296 hash_insert (table, xstrdup (alias), xstrdup (real)); | |
297 count++; | |
298 } | |
299 } | |
300 free (line); | |
301 } | |
302 | |
303 #ifdef KPSE_DEBUG | |
304 if (KPSE_DEBUG_P (KPSE_DEBUG_HASH)) { | |
305 /* As with ls-R above ... */ | |
306 boolean hash_summary_only = true; | |
307 DEBUGF2 ("%s: %u aliases.\n", alias_filename, count); | |
308 DEBUGF ("alias hash table:"); | |
309 hash_print (*table, hash_summary_only); | |
310 fflush (stderr); | |
311 } | |
312 #endif /* KPSE_DEBUG */ | |
313 | |
314 xfclose (alias_file, alias_filename); | |
315 } | |
316 | |
317 return alias_file != NULL; | |
318 } | |
319 | |
320 /* Initialize the path for ls-R files, and read them all into the hash | |
321 table `db'. If no usable ls-R's are found, set db.buckets to NULL. */ | |
322 | |
323 void | |
324 kpse_init_db P1H(void) | |
325 { | |
326 boolean ok = false; | |
327 const_string db_path = kpse_init_format (kpse_db_format); | |
328 string *db_files = kpse_all_path_search (db_path, DB_NAME); | |
329 string *orig_db_files = db_files; | |
330 | |
331 /* Must do this after the path searching (which ends up calling | |
332 kpse_db_search recursively), so db.buckets stays NULL. */ | |
333 db = hash_create (DB_HASH_SIZE); | |
334 | |
335 while (db_files && *db_files) { | |
336 if (db_build (&db, *db_files)) | |
337 ok = true; | |
338 free (*db_files); | |
339 db_files++; | |
340 } | |
341 | |
342 if (!ok) { | |
343 /* If db can't be built, leave `size' nonzero (so we don't | |
344 rebuild it), but clear `buckets' (so we don't look in it). */ | |
345 free (db.buckets); | |
346 db.buckets = NULL; | |
347 } | |
348 | |
349 free (orig_db_files); | |
350 | |
351 /* Add the content of any alias databases. There may exist more than | |
352 one alias file along DB_NAME files. This duplicates the above code | |
353 -- should be a function. */ | |
354 ok = false; | |
355 db_files = kpse_all_path_search (db_path, ALIAS_NAME); | |
356 orig_db_files = db_files; | |
357 | |
358 alias_db = hash_create (ALIAS_HASH_SIZE); | |
359 | |
360 while (db_files && *db_files) { | |
361 if (alias_build (&alias_db, *db_files)) | |
362 ok = true; | |
363 free (*db_files); | |
364 db_files++; | |
365 } | |
366 | |
367 if (!ok) { | |
368 free (alias_db.buckets); | |
369 alias_db.buckets = NULL; | |
370 } | |
371 | |
372 free (orig_db_files); | |
373 } | |
374 | |
375 /* Avoid doing anything if this PATH_ELT is irrelevant to the databases. */ | |
376 | |
377 str_list_type * | |
378 kpse_db_search P3C(const_string, name, const_string, orig_path_elt, | |
379 boolean, all) | |
380 { | |
381 string *db_dirs, *orig_dirs, *r; | |
382 const_string last_slash; | |
383 string path_elt; | |
384 boolean done; | |
385 str_list_type *ret; | |
386 unsigned e; | |
387 string *aliases = NULL; | |
388 boolean relevant = false; | |
389 | |
390 /* If we failed to build the database (or if this is the recursive | |
391 call to build the db path), quit. */ | |
392 if (db.buckets == NULL) | |
393 return NULL; | |
394 | |
395 /* When tex-glyph.c calls us looking for, e.g., dpi600/cmr10.pk, we | |
396 won't find it unless we change NAME to just `cmr10.pk' and append | |
397 `/dpi600' to PATH_ELT. We are justified in using a literal `/' | |
398 here, since that's what tex-glyph.c unconditionally uses in | |
399 DPI_BITMAP_SPEC. But don't do anything if the / begins NAME; that | |
400 should never happen. */ | |
401 last_slash = strrchr (name, '/'); | |
402 if (last_slash && last_slash != name) { | |
403 unsigned len = last_slash - name + 1; | |
404 string dir_part = xmalloc (len); | |
405 strncpy (dir_part, name, len - 1); | |
406 dir_part[len - 1] = 0; | |
407 path_elt = concat3 (orig_path_elt, "/", dir_part); | |
408 name = last_slash + 1; | |
409 } else | |
410 path_elt = (string) orig_path_elt; | |
411 | |
412 /* Don't bother doing any lookups if this `path_elt' isn't covered by | |
413 any of database directories. We do this not so much because the | |
414 extra couple of hash lookups matter -- they don't -- but rather | |
415 because we want to return NULL in this case, so path_search can | |
416 know to do a disk search. */ | |
417 for (e = 0; !relevant && e < STR_LIST_LENGTH (db_dir_list); e++) { | |
418 relevant = elt_in_db (STR_LIST_ELT (db_dir_list, e), path_elt); | |
419 } | |
420 if (!relevant) | |
421 return NULL; | |
422 | |
423 /* If we have aliases for this name, use them. */ | |
424 if (alias_db.buckets) | |
425 aliases = hash_lookup (alias_db, name); | |
426 | |
427 if (!aliases) { | |
428 aliases = XTALLOC1 (string); | |
429 aliases[0] = NULL; | |
430 } | |
431 { /* Push aliases up by one and insert the original name at the front. */ | |
432 unsigned i; | |
433 unsigned len = 1; /* Have NULL element already allocated. */ | |
434 for (r = aliases; *r; r++) | |
435 len++; | |
436 XRETALLOC (aliases, len + 1, string); | |
437 for (i = len; i > 0; i--) { | |
438 aliases[i] = aliases[i - 1]; | |
439 } | |
440 aliases[0] = (string) name; | |
441 } | |
442 | |
443 done = false; | |
444 for (r = aliases; !done && *r; r++) { | |
445 string try = *r; | |
446 | |
447 /* We have an ls-R db. Look up `try'. */ | |
448 orig_dirs = db_dirs = hash_lookup (db, try); | |
449 | |
450 ret = XTALLOC1 (str_list_type); | |
451 *ret = str_list_init (); | |
452 | |
453 /* For each filename found, see if it matches the path element. For | |
454 example, if we have .../cx/cmr10.300pk and .../ricoh/cmr10.300pk, | |
455 and the path looks like .../cx, we don't want the ricoh file. */ | |
456 while (!done && db_dirs && *db_dirs) { | |
457 string db_file = concat (*db_dirs, try); | |
458 boolean matched = match (db_file, path_elt); | |
459 | |
460 #ifdef KPSE_DEBUG | |
461 if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH)) | |
462 DEBUGF3 ("db:match(%s,%s) = %d\n", db_file, path_elt, matched); | |
463 #endif | |
464 | |
465 /* We got a hit in the database. Now see if the file actually | |
466 exists, possibly under an alias. */ | |
467 if (matched) { | |
468 string found = NULL; | |
469 if (kpse_readable_file (db_file)) { | |
470 found = db_file; | |
471 | |
472 } else { | |
473 string *a; | |
474 | |
475 free (db_file); /* `db_file' wasn't on disk. */ | |
476 | |
477 /* The hit in the DB doesn't exist in disk. Now try all its | |
478 aliases. For example, suppose we have a hierarchy on CD, | |
479 thus `mf.bas', but ls-R contains `mf.base'. Find it anyway. | |
480 Could probably work around this with aliases, but | |
481 this is pretty easy and shouldn't hurt. The upshot is that | |
482 if one of the aliases actually exists, we use that. */ | |
483 for (a = aliases + 1; *a && !found; a++) { | |
484 string atry = concat (*db_dirs, *a); | |
485 if (kpse_readable_file (atry)) | |
486 found = atry; | |
487 else | |
488 free (atry); | |
489 } | |
490 } | |
491 | |
492 /* If we have a real file, add it to the list, maybe done. */ | |
493 if (found) { | |
494 str_list_add (ret, found); | |
495 if (!all && found) | |
496 done = true; | |
497 } | |
498 } else { /* no match in the db */ | |
499 free (db_file); | |
500 } | |
501 | |
502 | |
503 /* On to the next directory, if any. */ | |
504 db_dirs++; | |
505 } | |
506 | |
507 /* This is just the space for the pointers, not the strings. */ | |
508 if (orig_dirs && *orig_dirs) | |
509 free (orig_dirs); | |
510 } | |
511 | |
512 free (aliases); | |
513 | |
514 /* If we had to break up NAME, free the temporary PATH_ELT. */ | |
515 if (path_elt != orig_path_elt) | |
516 free (path_elt); | |
517 | |
518 return ret; | |
519 } |