1 // This file is not compiled to a separate object file.
2 // It is included in pathsearch.cc.
3 
4 //////////////////////////////////////////////////////////////////////////
5 //
6 // Copyright (C) 1991-2021 The Octave Project Developers
7 //
8 // See the file COPYRIGHT.md in the top-level directory of this
9 // distribution or <https://octave.org/copyright/>.
10 //
11 // This file is part of Octave.
12 //
13 // Octave is free software: you can redistribute it and/or modify it
14 // under the terms of the GNU General Public License as published by
15 // the Free Software Foundation, either version 3 of the License, or
16 // (at your option) any later version.
17 //
18 // Octave is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 // GNU General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with Octave; see the file COPYING.  If not, see
25 // <https://www.gnu.org/licenses/>.
26 //
27 ////////////////////////////////////////////////////////////////////////
28 
29 //  Look up a filename in a path.
30 
31 #if defined (HAVE_CONFIG_H)
32 #  include "config.h"
33 #endif
34 
35 #include <cctype>
36 #include <cerrno>
37 #include <cstdlib>
38 
39 #include <map>
40 #include <fstream>
41 #include <iostream>
42 #include <string>
43 
44 #include "dir-ops.h"
45 #include "file-ops.h"
46 #include "file-stat.h"
47 #include "kpse.h"
48 #include "oct-env.h"
49 #include "oct-passwd.h"
50 #include "oct-time.h"
51 #include "pathsearch.h"
52 #include "unistd-wrappers.h"
53 
54 #if defined (OCTAVE_USE_WINDOWS_API)
55 #  define WIN32_LEAN_AND_MEAN 1
56 #  include <windows.h>
57 
58 #  include "lo-sysdep.h"
59 #endif
60 
61 // Define the characters which separate components of filenames and
62 // environment variable paths.
63 
64 #define IS_DEVICE_SEP(ch) octave::sys::file_ops::is_dev_sep (ch)
65 #define NAME_BEGINS_WITH_DEVICE(name)                   \
66    (name.length () > 0 && IS_DEVICE_SEP ((name)[1]))
67 
68 #define DIR_SEP_STRING octave::sys::file_ops::dir_sep_str ()
69 #define IS_DIR_SEP(ch) octave::sys::file_ops::is_dir_sep (ch)
70 
71 #define ENV_SEP octave::directory_path::path_sep_char ()
72 #define ENV_SEP_STRING octave::directory_path::path_sep_str ()
73 #define IS_ENV_SEP(ch) octave::directory_path::is_path_sep (ch)
74 
75 // If NO_DEBUG is defined (not recommended), skip all this.
76 #if ! defined (NO_DEBUG)
77 
78 // OK, we'll have tracing support.
79 #  define KPSE_DEBUG
80 
81 // Test if a bit is on.
82 #  define KPSE_DEBUG_P(bit) (kpse_debug & (1 << (bit)))
83 
84 #  define KPSE_DEBUG_STAT 0               // stat calls
85 #  define KPSE_DEBUG_EXPAND 1             // path element expansion
86 #  define KPSE_DEBUG_SEARCH 2             // searches
87 #  define KPSE_DEBUG_VARS 3               // variable values
88 #  define KPSE_LAST_DEBUG KPSE_DEBUG_VARS
89 
90 #endif
91 
92 unsigned int kpse_debug = 0;
93 
94 void
set_end(void)95 kpse_path_iterator::set_end (void)
96 {
97   m_e = m_b + 1;
98 
99   if (m_e == m_len)
100     ; // OK, we have found the last element.
101   else if (m_e > m_len)
102     m_b = m_e = std::string::npos;
103   else
104     {
105       // Find the next colon not enclosed by braces (or the end of the
106       // path).
107 
108       while (m_e < m_len && ! octave::directory_path::is_path_sep (m_path[m_e]))
109         m_e++;
110     }
111 }
112 
113 void
next(void)114 kpse_path_iterator::next (void)
115 {
116   m_b = m_e + 1;
117 
118   // Skip any consecutive colons.
119   while (m_b < m_len && octave::directory_path::is_path_sep (m_path[m_b]))
120     m_b++;
121 
122   if (m_b >= m_len)
123     m_b = m_e = std::string::npos;
124   else
125     set_end ();
126 }
127 
128 /* Truncate any too-long components in NAME, returning the result.  It's
129    too bad this is necessary.  See comments in readable.c for why.  */
130 
131 static std::string
kpse_truncate_filename(const std::string & name)132 kpse_truncate_filename (const std::string& name)
133 {
134   unsigned c_len = 0;        /* Length of current component.  */
135   unsigned ret_len = 0;      /* Length of constructed result.  */
136 
137   std::string ret = name;
138 
139   std::size_t m_len = name.length ();
140 
141   for (std::size_t i = 0; i < m_len; i++)
142     {
143       if (IS_DIR_SEP (name[i]) || IS_DEVICE_SEP (name[i]))
144         {
145           /* At a directory delimiter, reset component length.  */
146           c_len = 0;
147         }
148       else if (c_len > octave::sys::dir_entry::max_name_length ())
149         {
150           /* If past the max for a component, ignore this character.  */
151           continue;
152         }
153 
154       /* Copy this character.  */
155       ret[ret_len++] = name[i];
156       c_len++;
157     }
158 
159   ret.resize (ret_len);
160 
161   return ret;
162 }
163 
164 /* If access can read FN, run stat (assigning to stat buffer ST) and
165    check that fn is not a directory.  Don't check for just being a
166    regular file, as it is potentially useful to read fifo's or some
167    kinds of devices.  */
168 
169 static inline bool
READABLE(const std::string & fn)170 READABLE (const std::string& fn)
171 {
172 #if defined (OCTAVE_USE_WINDOWS_API)
173 
174   std::wstring w_fn = octave::sys::u8_to_wstring (fn);
175 
176   DWORD f_attr = GetFileAttributesW (w_fn.c_str ());
177 
178   return (f_attr != 0xFFFFFFFF && ! (f_attr & FILE_ATTRIBUTE_DIRECTORY));
179 
180 #else
181 
182   bool retval = false;
183 
184   const char *t = fn.c_str ();
185 
186   if (octave_access_wrapper (t, octave_access_r_ok ()) == 0)
187     {
188       octave::sys::file_stat fs (fn);
189 
190       retval = fs && ! fs.is_dir ();
191     }
192 
193   return retval;
194 
195 #endif
196 }
197 
198 /* POSIX invented the brain-damage of not necessarily truncating
199    filename components; the system's behavior is defined by the value of
200    the symbol _POSIX_NO_TRUNC, but you can't change it dynamically!
201 
202    Generic const return warning.  See extend-fname.c.  */
203 
204 static std::string
kpse_readable_file(const std::string & name)205 kpse_readable_file (const std::string& name)
206 {
207   std::string ret;
208 
209   if (READABLE (name))
210     {
211       ret = name;
212 
213 #if defined (ENAMETOOLONG)
214     }
215   else if (errno == ENAMETOOLONG)
216     {
217       ret = kpse_truncate_filename (name);
218 
219       /* Perhaps some other error will occur with the truncated name,
220          so let's call access again.  */
221 
222       if (! READABLE (ret))
223         {
224           /* Failed.  */
225           ret = "";
226         }
227 #endif /* ENAMETOOLONG */
228 
229     }
230   else
231     {
232       /* Some other error.  */
233       if (errno == EACCES)
234         {
235           /* Maybe warn them if permissions are bad.  */
236           perror (name.c_str ());
237         }
238 
239       ret = "";
240     }
241 
242   return ret;
243 }
244 
245 static bool
kpse_absolute_p(const std::string & filename,int relative_ok)246 kpse_absolute_p (const std::string& filename, int relative_ok)
247 {
248   return (octave::sys::env::absolute_pathname (filename)
249           || (relative_ok
250               && octave::sys::env::rooted_relative_pathname (filename)));
251 }
252 
253 /* The very first search is for texmf.cnf, called when someone tries to
254    initialize the TFM path or whatever.  init_path calls kpse_cnf_get
255    which calls kpse_all_path_search to find all the texmf.cnf's.  We
256    need to do various special things in this case, since we obviously
257    don't yet have the configuration files when we're searching for the
258    configuration files.  */
259 static bool first_search = true;
260 
261 /* This function is called after every search.  */
262 
263 static void
log_search(const std::list<std::string> & filenames)264 log_search (const std::list<std::string>& filenames)
265 {
266   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
267     {
268       for (const auto& filename : filenames)
269         {
270           octave::sys::time now;
271           std::cerr << now.unix_time () << ' ' << filename << std::endl;
272         }
273     }
274 }
275 
276 /* Concatenate each element in DIRS with NAME (assume each ends with a
277    /, to save time).  If SEARCH_ALL is false, return the first readable
278    regular file.  Else continue to search for more.  In any case, if
279    none, return a list containing just NULL.
280 
281    We keep a single buffer for the potential filenames and reallocate
282    only when necessary.  I'm not sure it's noticeably faster, but it
283    does seem cleaner.  (We do waste a bit of space in the return
284    value, though, since we don't shrink it to the final size returned.)  */
285 
286 static std::list<std::string>
dir_search(const std::string & dir,const std::string & name,bool search_all)287 dir_search (const std::string& dir, const std::string& name,
288             bool search_all)
289 {
290   std::list<std::string> ret;
291 
292   std::string potential = dir + name;
293 
294   std::string tmp = kpse_readable_file (potential);
295 
296   if (! tmp.empty ())
297     {
298       ret.push_back (potential);
299 
300       if (! search_all)
301         return ret;
302     }
303 
304   return ret;
305 }
306 
307 /* This is called when NAME is absolute or explicitly relative; if it's
308    readable, return (a list containing) it; otherwise, return NULL.  */
309 
310 static std::list<std::string>
absolute_search(const std::string & name)311 absolute_search (const std::string& name)
312 {
313   std::list<std::string> ret_list;
314   std::string found = kpse_readable_file (name);
315 
316   /* Add 'found' to the return list even if it's null; that tells
317      the caller we didn't find anything.  */
318   ret_list.push_back (found);
319 
320   return ret_list;
321 }
322 
323 /* This is the hard case -- look for NAME in PATH.  If ALL is false,
324    return the first file found.  Otherwise, search all elements of PATH.  */
325 
326 static std::list<std::string>
path_search(const std::string & path,const std::string & name,bool all)327 path_search (const std::string& path, const std::string& name, bool all)
328 {
329   std::list<std::string> ret_list;
330   bool done = false;
331 
332   for (kpse_path_iterator pi (path); ! done && pi != std::string::npos; pi++)
333     {
334       std::string elt = *pi;
335 
336       std::list<std::string> found;
337 
338       /* Do not touch the device if present */
339       if (NAME_BEGINS_WITH_DEVICE (elt))
340         {
341           while (elt.length () > 3
342                  && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
343             {
344               elt[2] = elt[1];
345               elt[1] = elt[0];
346               elt = elt.substr (1);
347             }
348         }
349       else
350         {
351           /* We never want to search the whole disk.  */
352           while (elt.length () > 1
353                  && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
354             elt = elt.substr (1);
355         }
356 
357       /* Our caller (search), also tests first_search, and does
358          the resetting.  */
359       if (first_search)
360         found = std::list<std::string> ();
361 
362       /* Search the filesystem.  */
363 
364       if (found.empty ())
365         {
366           std::string dir = kpse_element_dir (elt);
367 
368           if (! dir.empty ())
369             found = dir_search (dir, name, all);
370         }
371 
372       /* Did we find anything anywhere?  */
373       if (! found.empty ())
374         {
375           if (all)
376             ret_list.splice (ret_list.end (), found);
377           else
378             {
379               ret_list.push_back (found.front ());
380               done = true;
381             }
382         }
383     }
384 
385   return ret_list;
386 }
387 
388 /* If NAME has a leading ~ or ~user, Unix-style, expand it to the user's
389    home directory, and return a new malloced string.  If no ~, or no
390    <pwd.h>, just return NAME.  */
391 
392 static std::string
kpse_tilde_expand(const std::string & name)393 kpse_tilde_expand (const std::string& name)
394 {
395   std::string expansion;
396 
397   /* If no leading tilde, do nothing.  */
398   if (name.empty () || name[0] != '~')
399     {
400       expansion = name;
401 
402       /* If a bare tilde, return the home directory or '.'.  (Very
403          unlikely that the directory name will do anyone any good, but
404          ...  */
405     }
406   else if (name.length () == 1)
407     {
408       expansion = octave::sys::env::get_home_directory ();
409 
410       if (expansion.empty ())
411         expansion = ".";
412 
413       /* If '~/', remove any trailing / or replace leading // in $HOME.
414          Should really check for doubled intermediate slashes, too.  */
415     }
416   else if (IS_DIR_SEP (name[1]))
417     {
418       unsigned c = 1;
419       std::string home = octave::sys::env::get_home_directory ();
420 
421       if (home.empty ())
422         home = ".";
423 
424       std::size_t home_len = home.length ();
425 
426       /* handle leading // */
427       if (home_len > 1 && IS_DIR_SEP (home[0]) && IS_DIR_SEP (home[1]))
428         home = home.substr (1);
429 
430       /* omit / after ~ */
431       if (IS_DIR_SEP (home[home_len - 1]))
432         c++;
433 
434       expansion = home + name.substr (c);
435 
436       /* If '~user' or '~user/', look up user in the passwd database (but
437          OS/2 doesn't have this concept.  */
438     }
439   else
440 #if defined (HAVE_PWD_H)
441     {
442       unsigned c = 2;
443 
444       /* find user name */
445       while (name.length () > c && ! IS_DIR_SEP (name[c]))
446         c++;
447 
448       std::string user = name.substr (1, c-1);
449 
450       /* We only need the cast here for (deficient) systems
451          which do not declare 'getpwnam' in <pwd.h>.  */
452       octave::sys::password p = octave::sys::password::getpwnam (user);
453 
454       /* If no such user, just use '.'.  */
455       std::string home = (p ? p.dir () : ".");
456 
457       if (home.empty ())
458         home = ".";
459 
460       /* handle leading // */
461       if (home.length () > 1 && IS_DIR_SEP (home[0]) && IS_DIR_SEP (home[1]))
462         home = home.substr (1);
463 
464       /* If HOME ends in /, omit the / after ~user. */
465       if (name.length () > c && IS_DIR_SEP (home.back ()))
466         c++;
467 
468       expansion = (name.length () > c ? home : home + name.substr (c));
469     }
470 #else /* not HAVE_PWD_H */
471   expansion = name;
472 #endif /* not HAVE_PWD_H */
473 
474   return expansion;
475 }
476 
477 /* Search PATH for ORIGINAL_NAME.  If ALL is false, or ORIGINAL_NAME is
478    absolute_p, check ORIGINAL_NAME itself.  Otherwise, look at each
479    element of PATH for the first readable ORIGINAL_NAME.
480 
481    Always return a list; if no files are found, the list will
482    contain just NULL.  If ALL is true, the list will be
483    terminated with NULL.  */
484 
485 static std::list<std::string>
search(const std::string & path,const std::string & original_name,bool all)486 search (const std::string& path, const std::string& original_name,
487         bool all)
488 {
489   std::list<std::string> ret_list;
490   bool absolute_p;
491 
492   /* Make a leading ~ count as an absolute filename.  */
493   std::string name = kpse_tilde_expand (original_name);
494 
495   /* If the first name is absolute or explicitly relative, no need to
496      consider PATH at all.  */
497   absolute_p = kpse_absolute_p (name, true);
498 
499   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
500     std::cerr << "kdebug: start search (file=" << name
501               << ", find_all=" << all << ", path=" << path << ")."
502               << std::endl;
503 
504   /* Find the file(s). */
505   ret_list = (absolute_p
506               ? absolute_search (name)
507               : path_search (path, name, all));
508 
509   /* The very first search is for texmf.cnf.  We can't log that, since
510      we want to allow setting TEXMFLOG in texmf.cnf.  */
511   if (first_search)
512     {
513       first_search = false;
514     }
515   else
516     {
517       /* Record the filenames we found, if desired.  And wrap them in a
518          debugging line if we're doing that.  */
519 
520       if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
521         std::cerr << "kdebug: search (" << original_name << ") =>";
522 
523       log_search (ret_list);
524 
525       if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
526         std::cerr << std::endl;
527     }
528 
529   return ret_list;
530 }
531 
532 /* Search PATH for the first NAME.  */
533 
534 /* Perform tilde expansion on NAME.  If the result is an absolute or
535    explicitly relative filename, check whether it is a readable
536    (regular) file.
537 
538    Otherwise, look in each of the directories specified in PATH (also do
539    tilde and variable expansion on elements in PATH).
540 
541    The caller must expand PATH.  This is because it makes more sense to
542    do this once, in advance, instead of for every search using it.
543 
544    In any case, return the complete filename if found, otherwise NULL.  */
545 
546 std::string
kpse_path_search(const std::string & path,const std::string & name)547 kpse_path_search (const std::string& path, const std::string& name)
548 {
549   std::list<std::string> ret_list = search (path, name, false);
550 
551   return ret_list.empty () ? "" : ret_list.front ();
552 }
553 
554 /* Like 'kpse_path_search' with MUST_EXIST true, but return a list of
555    all the filenames (or NULL if none), instead of taking the first.  */
556 
557 std::list<std::string>
kpse_all_path_search(const std::string & path,const std::string & name)558 kpse_all_path_search (const std::string& path, const std::string& name)
559 {
560   return search (path, name, true);
561 }
562 
563 /* This is the hard case -- look in each element of PATH for each
564    element of NAMES.  If ALL is false, return the first file found.
565    Otherwise, search all elements of PATH.  */
566 
567 std::list<std::string>
path_find_first_of(const std::string & path,const std::list<std::string> & names,bool all)568 path_find_first_of (const std::string& path,
569                     const std::list<std::string>& names, bool all)
570 {
571   std::list<std::string> ret_list;
572   bool done = false;
573 
574   for (kpse_path_iterator pi (path); ! done && pi != std::string::npos; pi++)
575     {
576       std::string elt = *pi;
577 
578       std::string dir;
579       std::list<std::string> found;
580 
581       /* Do not touch the device if present */
582 
583       if (NAME_BEGINS_WITH_DEVICE (elt))
584         {
585           while (elt.length () > 3
586                  && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
587             {
588               elt[2] = elt[1];
589               elt[1] = elt[0];
590               elt = elt.substr (1);
591             }
592         }
593       else
594         {
595           /* We never want to search the whole disk.  */
596           while (elt.length () > 1
597                  && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
598             elt = elt.substr (1);
599         }
600 
601       /* We have to search one directory at a time.  */
602       dir = kpse_element_dir (elt);
603 
604       if (! dir.empty ())
605         {
606           for (auto it = names.cbegin (); it != names.cend () && ! done; it++)
607             {
608               std::string name = *it;
609 
610               /* Our caller (find_first_of), also tests first_search,
611                  and does the resetting.  */
612               if (first_search)
613                 found = std::list<std::string> ();
614 
615               /* Search the filesystem.  */
616 
617               if (found.empty ())
618                 found = dir_search (dir, name, all);
619 
620               /* Did we find anything anywhere?  */
621               if (! found.empty ())
622                 {
623                   if (all)
624                     ret_list.splice (ret_list.end (), found);
625                   else
626                     {
627                       ret_list.push_back (found.front ());
628                       done = true;
629                     }
630                 }
631             }
632         }
633     }
634 
635   return ret_list;
636 }
637 
638 static std::list<std::string>
find_first_of(const std::string & path,const std::list<std::string> & names,bool all)639 find_first_of (const std::string& path, const std::list<std::string>& names,
640                bool all)
641 {
642   std::list<std::string> ret_list;
643 
644   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
645     {
646       std::cerr << "kdebug: start find_first_of (";
647 
648       for (auto p = names.cbegin (); p != names.cend (); p++)
649         {
650           if (p == names.cbegin ())
651             std::cerr << *p;
652           else
653             std::cerr << ", " << *p;
654         }
655 
656       std::cerr << "), path=" << path << '.' << std::endl;
657     }
658 
659   for (const auto& name : names)
660     {
661       if (kpse_absolute_p (name, true))
662         {
663           /* If the name is absolute or explicitly relative, no need
664              to consider PATH at all.  If we find something, then we
665              are done.  */
666 
667           ret_list = absolute_search (name);
668 
669           if (! ret_list.empty ())
670             return ret_list;
671         }
672     }
673 
674   /* Find the file. */
675   ret_list = path_find_first_of (path, names, all);
676 
677   /* The very first search is for texmf.cnf.  We can't log that, since
678      we want to allow setting TEXMFLOG in texmf.cnf.  */
679   if (first_search)
680     {
681       first_search = false;
682     }
683   else
684     {
685       /* Record the filenames we found, if desired.  And wrap them in a
686          debugging line if we're doing that.  */
687 
688       if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
689         {
690           std::cerr << "kdebug: find_first_of (";
691 
692           for (auto p = names.cbegin (); p != names.cend (); p++)
693             {
694               if (p == names.cbegin ())
695                 std::cerr << *p;
696               else
697                 std::cerr << ", " << *p;
698             }
699 
700           std::cerr << ") =>";
701         }
702 
703       log_search (ret_list);
704 
705       if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
706         std::cerr << std::endl;
707     }
708 
709   return ret_list;
710 }
711 
712 /* Search each element of PATH for each element of NAMES.  Return the
713    first one found.  */
714 
715 /* Search each element of PATH for each element in the list of NAMES.
716    Return the first one found.  */
717 
718 std::string
kpse_path_find_first_of(const std::string & path,const std::list<std::string> & names)719 kpse_path_find_first_of (const std::string& path,
720                          const std::list<std::string>& names)
721 {
722   std::list<std::string> ret_list = find_first_of (path, names, false);
723 
724   return ret_list.empty () ? "" : ret_list.front ();
725 }
726 
727 /* Search each element of PATH for each element of NAMES and return a
728    list containing everything found, in the order found.  */
729 
730 /* Like 'kpse_path_find_first_of' with MUST_EXIST true, but return a
731    list of all the filenames (or NULL if none), instead of taking the
732    first.  */
733 
734 std::list<std::string>
kpse_all_path_find_first_of(const std::string & path,const std::list<std::string> & names)735 kpse_all_path_find_first_of (const std::string& path,
736                              const std::list<std::string>& names)
737 {
738   return find_first_of (path, names, true);
739 }
740 
741 /* Perform tilde expansion on each element of the path, and include
742    canonical directory names for only the the actually existing
743    directories in the result. */
744 
745 std::string
kpse_path_expand(const std::string & path)746 kpse_path_expand (const std::string& path)
747 {
748   std::string ret;
749   unsigned len = 0;
750 
751   /* Now expand each of the path elements, printing the results */
752   for (kpse_path_iterator pi (path); pi != std::string::npos; pi++)
753     {
754       std::string elt = kpse_tilde_expand (*pi);
755 
756       std::string dir;
757 
758       /* Do not touch the device if present */
759       if (NAME_BEGINS_WITH_DEVICE (elt))
760         {
761           while (elt.length () > 3
762                  && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
763             {
764               elt[2] = elt[1];
765               elt[1] = elt[0];
766               elt = elt.substr (1);
767             }
768         }
769       else
770         {
771           /* We never want to search the whole disk.  */
772           while (elt.length () > 1
773                  && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
774             elt = elt.substr (1);
775         }
776 
777       /* Search the disk for all dirs in the component specified.
778          Be faster to check the database, but this is more reliable.  */
779       dir = kpse_element_dir (elt);
780 
781       std::size_t dirlen = dir.length ();
782 
783       if (dirlen > 0)
784         {
785           ret += dir;
786           len += dirlen;
787 
788           /* Retain trailing slash if that's the root directory.  */
789           if (dirlen == 1
790               || (dirlen == 3 && NAME_BEGINS_WITH_DEVICE (dir)
791                   && IS_DIR_SEP (dir[2])))
792             {
793               ret += ENV_SEP_STRING;
794               len++;
795             }
796 
797           ret[len-1] = ENV_SEP;
798         }
799     }
800 
801   if (! ret.empty ())
802     ret.pop_back ();
803 
804   return ret;
805 }
806 
807 /* braces.c -- code for doing word expansion in curly braces.  Taken from
808    bash 1.14.5.  [And subsequently modified for kpatshea.]
809 
810    Copyright (C) 1987,1991 Free Software Foundation, Inc.  */
811 
812 #define brace_whitespace(c) (! (c) || (c) == ' ' || (c) == '\t' || (c) == '\n')
813 
814 /* Basic idea:
815 
816    Segregate the text into 3 sections: preamble (stuff before an open brace),
817    postamble (stuff after the matching close brace) and amble (stuff after
818    preamble, and before postamble).  Expand amble, and then tack on the
819    expansions to preamble.  Expand postamble, and tack on the expansions to
820    the result so far.  */
821 
822 /* Return a new array of strings which is the result of appending each
823    string in ARR2 to each string in ARR1.  The resultant array is
824    len (arr1) * len (arr2) long.  For convenience, ARR1 (and its contents)
825    are free ()'ed.  ARR1 can be NULL, in that case, a new version of ARR2
826    is returned. */
827 
828 static std::list<std::string>
array_concat(const std::list<std::string> & arr1,const std::list<std::string> & arr2)829 array_concat (const std::list<std::string>& arr1,
830               const std::list<std::string>& arr2)
831 {
832   std::list<std::string> result;
833 
834   if (arr1.empty ())
835     result = arr2;
836   else if (arr2.empty ())
837     result = arr1;
838   else
839     {
840       for (const auto& elt_2 : arr2)
841         for (const auto& elt_1 : arr1)
842           result.push_back (elt_1 + elt_2);
843     }
844 
845   return result;
846 }
847 
848 static int brace_gobbler (const std::string&, int&, int);
849 static std::list<std::string> expand_amble (const std::string&);
850 
851 /* Return an array of strings; the brace expansion of TEXT. */
852 static std::list<std::string>
brace_expand(const std::string & text)853 brace_expand (const std::string& text)
854 {
855   /* Find the text of the preamble. */
856   int i = 0;
857   int c = brace_gobbler (text, i, '{');
858 
859   std::string preamble = text.substr (0, i);
860 
861   std::list<std::string> result (1, preamble);
862 
863   if (c == '{')
864     {
865       /* Find the amble.  This is the stuff inside this set of braces. */
866       int start = ++i;
867       c = brace_gobbler (text, i, '}');
868 
869       /* What if there isn't a matching close brace? */
870       if (! c)
871         {
872           (*current_liboctave_warning_with_id_handler)
873             ("Octave:pathsearch-syntax",
874              "%s: Unmatched {", text.c_str ());
875 
876           result = std::list<std::string> (1, text);
877         }
878       else
879         {
880           std::string amble = text.substr (start, i-start);
881           result = array_concat (result, expand_amble (amble));
882 
883           std::string postamble = text.substr (i+1);
884           result = array_concat (result, brace_expand (postamble));
885         }
886     }
887 
888   return result;
889 }
890 
891 /* The character which is used to separate arguments. */
892 static int brace_arg_separator = ',';
893 
894 /* Expand the text found inside of braces.  We simply try to split the
895    text at BRACE_ARG_SEPARATORs into separate strings.  We then brace
896    expand each slot which needs it, until there are no more slots which
897    need it. */
898 static std::list<std::string>
expand_amble(const std::string & text)899 expand_amble (const std::string& text)
900 {
901   std::list<std::string> result;
902 
903   std::size_t text_len = text.length ();
904   std::size_t start;
905   int i, c;
906 
907   for (start = 0, i = 0, c = 1; c && start < text_len; start = ++i)
908     {
909       int i0 = i;
910       int c0 = brace_gobbler (text, i0, brace_arg_separator);
911       int i1 = i;
912       int c1 = brace_gobbler (text, i1, ENV_SEP);
913       c = c0 | c1;
914       i = (i0 < i1 ? i0 : i1);
915 
916       std::string tem = text.substr (start, i-start);
917 
918       std::list<std::string> partial = brace_expand (tem);
919 
920       if (result.empty ())
921         result = partial;
922       else
923         result.splice (result.end (), partial);
924     }
925 
926   return result;
927 }
928 
929 /* Start at INDEX, and skip characters in TEXT.  Set INDEX to the
930    index of the character matching SATISFY.  This understands about
931    quoting.  Return the character that caused us to stop searching;
932    this is either the same as SATISFY, or 0. */
933 static int
brace_gobbler(const std::string & text,int & indx,int satisfy)934 brace_gobbler (const std::string& text, int& indx, int satisfy)
935 {
936   int c = 0;
937   int level = 0;
938   int quoted = 0;
939   int pass_next = 0;
940 
941   std::size_t text_len = text.length ();
942 
943   std::size_t i = indx;
944 
945   for (; i < text_len; i++)
946     {
947       c = text[i];
948 
949       if (pass_next)
950         {
951           pass_next = 0;
952           continue;
953         }
954 
955       /* A backslash escapes the next character.  This allows backslash to
956          escape the quote character in a double-quoted string. */
957       if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`'))
958         {
959           pass_next = 1;
960           continue;
961         }
962 
963       if (quoted)
964         {
965           if (c == quoted)
966             quoted = 0;
967           continue;
968         }
969 
970       if (c == '"' || c == '\'' || c == '`')
971         {
972           quoted = c;
973           continue;
974         }
975 
976       if (c == satisfy && ! level && ! quoted)
977         {
978           /* We ignore an open brace surrounded by whitespace, and also
979              an open brace followed immediately by a close brace, that
980              was preceded with whitespace.  */
981           if (c == '{'
982               && ((i == 0 || brace_whitespace (text[i-1]))
983                   && (i+1 < text_len
984                       && (brace_whitespace (text[i+1]) || text[i+1] == '}'))))
985             continue;
986           /* If this is being compiled as part of bash, ignore the '{'
987              in a '${ }' construct */
988           if ((c != '{') || i == 0 || (text[i-1] != '$'))
989             break;
990         }
991 
992       if (c == '{')
993         level++;
994       else if (c == '}' && level)
995         level--;
996     }
997 
998   indx = i;
999   c = (c == satisfy) ? c : 0;
1000   return c;
1001 }
1002 
1003 /* Return true if FN is a directory or a symlink to a directory,
1004    false if not. */
1005 
1006 static bool
dir_p(const std::string & fn)1007 dir_p (const std::string& fn)
1008 {
1009   octave::sys::file_stat fs (fn);
1010 
1011   return (fs && fs.is_dir ());
1012 }
1013 
1014 /* Given a path element ELT, return a the element with a trailing slash
1015    or an empty string if the element is not a directory.
1016 
1017    It's up to the caller to expand ELT.  This is because this routine is
1018    most likely only useful to be called from 'kpse_path_search', which
1019    has already assumed expansion has been done.  */
1020 
1021 std::string
kpse_element_dir(const std::string & elt)1022 kpse_element_dir (const std::string& elt)
1023 {
1024   std::string ret;
1025 
1026   /* If given nothing, return nothing.  */
1027   if (elt.empty ())
1028     return ret;
1029 
1030   if (dir_p (elt))
1031     {
1032       ret = elt;
1033 
1034       char last_char = ret.back ();
1035 
1036       if (! (IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)))
1037         ret += DIR_SEP_STRING;
1038     }
1039 
1040   return ret;
1041 }
1042