summaryrefslogtreecommitdiff
path: root/Build/source/texk/kpathsea/pathsearch.c
blob: ad6f0cd0199cc7ffe0c8ff3f8cdc4c1abf5c2a61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
/* pathsearch.c: look up a filename in a path.

   Copyright 1993, 1994, 1995, 1997, 2007, 2009-2012, 2018 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 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:
*/