GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
kpse.cc
Go to the documentation of this file.
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-2023 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-password.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
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
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
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
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
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
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
264 log_search (const std::list<std::string>& filenames)
265 {
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>
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>
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>
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 #if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
350  && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
351  else
352  {
353  /* We never want to search the whole disk. */
354  while (elt.length () > 1
355  && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
356  elt = elt.substr (1);
357  }
358 #endif
359 
360  /* Our caller (search), also tests first_search, and does
361  the resetting. */
362  if (first_search)
363  found = std::list<std::string> ();
364 
365  /* Search the filesystem. */
366 
367  if (found.empty ())
368  {
369  std::string dir = kpse_element_dir (elt);
370 
371  if (! dir.empty ())
372  found = dir_search (dir, name, all);
373  }
374 
375  /* Did we find anything anywhere? */
376  if (! found.empty ())
377  {
378  if (all)
379  ret_list.splice (ret_list.end (), found);
380  else
381  {
382  ret_list.push_back (found.front ());
383  done = true;
384  }
385  }
386  }
387 
388  return ret_list;
389 }
390 
391 /* If NAME has a leading ~ or ~user, Unix-style, expand it to the user's
392  home directory, and return a new malloced string. If no ~, or no
393  <pwd.h>, just return NAME. */
394 
395 static std::string
396 kpse_tilde_expand (const std::string& name)
397 {
398  std::string expansion;
399 
400  /* If no leading tilde, do nothing. */
401  if (name.empty () || name[0] != '~')
402  {
403  expansion = name;
404 
405  /* If a bare tilde, return the home directory or '.'. (Very
406  unlikely that the directory name will do anyone any good, but
407  ... */
408  }
409  else if (name.length () == 1)
410  {
411  expansion = octave::sys::env::get_home_directory ();
412 
413  if (expansion.empty ())
414  expansion = ".";
415 
416  /* If '~/', remove any trailing / or replace leading // in $HOME.
417  Should really check for doubled intermediate slashes, too. */
418  }
419  else if (IS_DIR_SEP (name[1]))
420  {
421  unsigned c = 1;
422  std::string home = octave::sys::env::get_home_directory ();
423 
424  if (home.empty ())
425  home = ".";
426 
427  std::size_t home_len = home.length ();
428 
429 #if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
430  && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
431  /* handle leading // */
432  if (home_len > 1 && IS_DIR_SEP (home[0]) && IS_DIR_SEP (home[1]))
433  home = home.substr (1);
434 #endif
435 
436  /* omit / after ~ */
437  if (IS_DIR_SEP (home[home_len - 1]))
438  c++;
439 
440  expansion = home + name.substr (c);
441 
442  /* If '~user' or '~user/', look up user in the passwd database (but
443  OS/2 doesn't have this concept. */
444  }
445  else
446 #if defined (HAVE_PWD_H)
447  {
448  unsigned c = 2;
449 
450  /* find user name */
451  while (name.length () > c && ! IS_DIR_SEP (name[c]))
452  c++;
453 
454  std::string user = name.substr (1, c-1);
455 
456  /* We only need the cast here for (deficient) systems
457  which do not declare 'getpwnam' in <pwd.h>. */
458  octave::sys::password p = octave::sys::password::getpwnam (user);
459 
460  /* If no such user, just use '.'. */
461  std::string home = (p ? p.dir () : ".");
462 
463  if (home.empty ())
464  home = ".";
465 
466 # if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
467  && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
468  /* handle leading // */
469  if (home.length () > 1 && IS_DIR_SEP (home[0]) && IS_DIR_SEP (home[1]))
470  home = home.substr (1);
471 # endif
472 
473  /* If HOME ends in /, omit the / after ~user. */
474  if (name.length () > c && IS_DIR_SEP (home.back ()))
475  c++;
476 
477  expansion = (name.length () > c ? home : home + name.substr (c));
478  }
479 #else /* not HAVE_PWD_H */
480  expansion = name;
481 #endif /* not HAVE_PWD_H */
482 
483  return expansion;
484 }
485 
486 /* Search PATH for ORIGINAL_NAME. If ALL is false, or ORIGINAL_NAME is
487  absolute_p, check ORIGINAL_NAME itself. Otherwise, look at each
488  element of PATH for the first readable ORIGINAL_NAME.
489 
490  Always return a list; if no files are found, the list will
491  contain just NULL. If ALL is true, the list will be
492  terminated with NULL. */
493 
494 static std::list<std::string>
495 search (const std::string& path, const std::string& original_name,
496  bool all)
497 {
498  std::list<std::string> ret_list;
499  bool absolute_p;
500 
501  /* Make a leading ~ count as an absolute filename. */
502  std::string name = kpse_tilde_expand (original_name);
503 
504  /* If the first name is absolute or explicitly relative, no need to
505  consider PATH at all. */
506  absolute_p = kpse_absolute_p (name, true);
507 
509  std::cerr << "kdebug: start search (file=" << name
510  << ", find_all=" << all << ", path=" << path << ")."
511  << std::endl;
512 
513  /* Find the file(s). */
514  ret_list = (absolute_p
515  ? absolute_search (name)
516  : path_search (path, name, all));
517 
518  /* The very first search is for texmf.cnf. We can't log that, since
519  we want to allow setting TEXMFLOG in texmf.cnf. */
520  if (first_search)
521  {
522  first_search = false;
523  }
524  else
525  {
526  /* Record the filenames we found, if desired. And wrap them in a
527  debugging line if we're doing that. */
528 
530  std::cerr << "kdebug: search (" << original_name << ") =>";
531 
532  log_search (ret_list);
533 
535  std::cerr << std::endl;
536  }
537 
538  return ret_list;
539 }
540 
541 /* Search PATH for the first NAME. */
542 
543 /* Perform tilde expansion on NAME. If the result is an absolute or
544  explicitly relative filename, check whether it is a readable
545  (regular) file.
546 
547  Otherwise, look in each of the directories specified in PATH (also do
548  tilde and variable expansion on elements in PATH).
549 
550  The caller must expand PATH. This is because it makes more sense to
551  do this once, in advance, instead of for every search using it.
552 
553  In any case, return the complete filename if found, otherwise NULL. */
554 
555 std::string
556 kpse_path_search (const std::string& path, const std::string& name)
557 {
558  std::list<std::string> ret_list = search (path, name, false);
559 
560  return ret_list.empty () ? "" : ret_list.front ();
561 }
562 
563 /* Like 'kpse_path_search' with MUST_EXIST true, but return a list of
564  all the filenames (or NULL if none), instead of taking the first. */
565 
566 std::list<std::string>
567 kpse_all_path_search (const std::string& path, const std::string& name)
568 {
569  return search (path, name, true);
570 }
571 
572 /* This is the hard case -- look in each element of PATH for each
573  element of NAMES. If ALL is false, return the first file found.
574  Otherwise, search all elements of PATH. */
575 
576 std::list<std::string>
577 path_find_first_of (const std::string& path,
578  const std::list<std::string>& names, bool all)
579 {
580  std::list<std::string> ret_list;
581  bool done = false;
582 
583  for (kpse_path_iterator pi (path); ! done && pi != std::string::npos; pi++)
584  {
585  std::string elt = *pi;
586 
587  std::string dir;
588  std::list<std::string> found;
589 
590  /* Do not touch the device if present */
591 
592  if (NAME_BEGINS_WITH_DEVICE (elt))
593  {
594  while (elt.length () > 3
595  && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
596  {
597  elt[2] = elt[1];
598  elt[1] = elt[0];
599  elt = elt.substr (1);
600  }
601  }
602 #if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
603  && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
604  else
605  {
606  /* We never want to search the whole disk. */
607  while (elt.length () > 1
608  && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
609  elt = elt.substr (1);
610  }
611 #endif
612 
613  /* We have to search one directory at a time. */
614  dir = kpse_element_dir (elt);
615 
616  if (! dir.empty ())
617  {
618  for (auto it = names.cbegin (); it != names.cend () && ! done; it++)
619  {
620  std::string name = *it;
621 
622  /* Our caller (find_first_of), also tests first_search,
623  and does the resetting. */
624  if (first_search)
625  found = std::list<std::string> ();
626 
627  /* Search the filesystem. */
628 
629  if (found.empty ())
630  found = dir_search (dir, name, all);
631 
632  /* Did we find anything anywhere? */
633  if (! found.empty ())
634  {
635  if (all)
636  ret_list.splice (ret_list.end (), found);
637  else
638  {
639  ret_list.push_back (found.front ());
640  done = true;
641  }
642  }
643  }
644  }
645  }
646 
647  return ret_list;
648 }
649 
650 static std::list<std::string>
651 find_first_of (const std::string& path, const std::list<std::string>& names,
652  bool all)
653 {
654  std::list<std::string> ret_list;
655 
657  {
658  std::cerr << "kdebug: start find_first_of (";
659 
660  for (auto p = names.cbegin (); p != names.cend (); p++)
661  {
662  if (p == names.cbegin ())
663  std::cerr << *p;
664  else
665  std::cerr << ", " << *p;
666  }
667 
668  std::cerr << "), path=" << path << '.' << std::endl;
669  }
670 
671  for (const auto& name : names)
672  {
673  if (kpse_absolute_p (name, true))
674  {
675  /* If the name is absolute or explicitly relative, no need
676  to consider PATH at all. If we find something, then we
677  are done. */
678 
679  ret_list = absolute_search (name);
680 
681  if (! ret_list.empty ())
682  return ret_list;
683  }
684  }
685 
686  /* Find the file. */
687  ret_list = path_find_first_of (path, names, all);
688 
689  /* The very first search is for texmf.cnf. We can't log that, since
690  we want to allow setting TEXMFLOG in texmf.cnf. */
691  if (first_search)
692  {
693  first_search = false;
694  }
695  else
696  {
697  /* Record the filenames we found, if desired. And wrap them in a
698  debugging line if we're doing that. */
699 
701  {
702  std::cerr << "kdebug: find_first_of (";
703 
704  for (auto p = names.cbegin (); p != names.cend (); p++)
705  {
706  if (p == names.cbegin ())
707  std::cerr << *p;
708  else
709  std::cerr << ", " << *p;
710  }
711 
712  std::cerr << ") =>";
713  }
714 
715  log_search (ret_list);
716 
718  std::cerr << std::endl;
719  }
720 
721  return ret_list;
722 }
723 
724 /* Search each element of PATH for each element of NAMES. Return the
725  first one found. */
726 
727 /* Search each element of PATH for each element in the list of NAMES.
728  Return the first one found. */
729 
730 std::string
731 kpse_path_find_first_of (const std::string& path,
732  const std::list<std::string>& names)
733 {
734  std::list<std::string> ret_list = find_first_of (path, names, false);
735 
736  return ret_list.empty () ? "" : ret_list.front ();
737 }
738 
739 /* Search each element of PATH for each element of NAMES and return a
740  list containing everything found, in the order found. */
741 
742 /* Like 'kpse_path_find_first_of' with MUST_EXIST true, but return a
743  list of all the filenames (or NULL if none), instead of taking the
744  first. */
745 
746 std::list<std::string>
747 kpse_all_path_find_first_of (const std::string& path,
748  const std::list<std::string>& names)
749 {
750  return find_first_of (path, names, true);
751 }
752 
753 /* Perform tilde expansion on each element of the path, and include
754  canonical directory names for only the the actually existing
755  directories in the result. */
756 
757 std::string
758 kpse_path_expand (const std::string& path)
759 {
760  std::string ret;
761  unsigned len = 0;
762 
763  /* Now expand each of the path elements, printing the results */
764  for (kpse_path_iterator pi (path); pi != std::string::npos; pi++)
765  {
766  std::string elt = kpse_tilde_expand (*pi);
767 
768  std::string dir;
769 
770  /* Do not touch the device if present */
771  if (NAME_BEGINS_WITH_DEVICE (elt))
772  {
773  while (elt.length () > 3
774  && IS_DIR_SEP (elt[2]) && IS_DIR_SEP (elt[3]))
775  {
776  elt[2] = elt[1];
777  elt[1] = elt[0];
778  elt = elt.substr (1);
779  }
780  }
781 #if (! defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) \
782  && ! defined (DOUBLE_SLASH_IS_DISTINCT_ROOT))
783  else
784  {
785  /* We never want to search the whole disk. */
786  while (elt.length () > 1
787  && IS_DIR_SEP (elt[0]) && IS_DIR_SEP (elt[1]))
788  elt = elt.substr (1);
789  }
790 #endif
791 
792  /* Search the disk for all dirs in the component specified.
793  Be faster to check the database, but this is more reliable. */
794  dir = kpse_element_dir (elt);
795 
796  std::size_t dirlen = dir.length ();
797 
798  if (dirlen > 0)
799  {
800  ret += dir;
801  len += dirlen;
802 
803  /* Retain trailing slash if that's the root directory. */
804  if (dirlen == 1
805  || (dirlen == 3 && NAME_BEGINS_WITH_DEVICE (dir)
806  && IS_DIR_SEP (dir[2])))
807  {
808  ret += ENV_SEP_STRING;
809  len++;
810  }
811 
812  ret[len-1] = ENV_SEP;
813  }
814  }
815 
816  if (! ret.empty ())
817  ret.pop_back ();
818 
819  return ret;
820 }
821 
822 /* braces.c -- code for doing word expansion in curly braces. Taken from
823  bash 1.14.5. [And subsequently modified for kpatshea.]
824 
825  Copyright (C) 1987,1991 Free Software Foundation, Inc. */
826 
827 #define brace_whitespace(c) (! (c) || (c) == ' ' || (c) == '\t' || (c) == '\n')
828 
829 /* Basic idea:
830 
831  Segregate the text into 3 sections: preamble (stuff before an open brace),
832  postamble (stuff after the matching close brace) and amble (stuff after
833  preamble, and before postamble). Expand amble, and then tack on the
834  expansions to preamble. Expand postamble, and tack on the expansions to
835  the result so far. */
836 
837 /* Return a new array of strings which is the result of appending each
838  string in ARR2 to each string in ARR1. The resultant array is
839  len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents)
840  are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2
841  is returned. */
842 
843 static std::list<std::string>
844 array_concat (const std::list<std::string>& arr1,
845  const std::list<std::string>& arr2)
846 {
847  std::list<std::string> result;
848 
849  if (arr1.empty ())
850  result = arr2;
851  else if (arr2.empty ())
852  result = arr1;
853  else
854  {
855  for (const auto& elt_2 : arr2)
856  for (const auto& elt_1 : arr1)
857  result.push_back (elt_1 + elt_2);
858  }
859 
860  return result;
861 }
862 
863 static int brace_gobbler (const std::string&, int&, int);
864 static std::list<std::string> expand_amble (const std::string&);
865 
866 /* Return an array of strings; the brace expansion of TEXT. */
867 static std::list<std::string>
868 brace_expand (const std::string& text)
869 {
870  /* Find the text of the preamble. */
871  int i = 0;
872  int c = brace_gobbler (text, i, '{');
873 
874  std::string preamble = text.substr (0, i);
875 
876  std::list<std::string> result (1, preamble);
877 
878  if (c == '{')
879  {
880  /* Find the amble. This is the stuff inside this set of braces. */
881  int start = ++i;
882  c = brace_gobbler (text, i, '}');
883 
884  /* What if there isn't a matching close brace? */
885  if (! c)
886  {
887  (*current_liboctave_warning_with_id_handler)
888  ("Octave:pathsearch-syntax",
889  "%s: Unmatched {", text.c_str ());
890 
891  result = std::list<std::string> (1, text);
892  }
893  else
894  {
895  std::string amble = text.substr (start, i-start);
896  result = array_concat (result, expand_amble (amble));
897 
898  std::string postamble = text.substr (i+1);
899  result = array_concat (result, brace_expand (postamble));
900  }
901  }
902 
903  return result;
904 }
905 
906 /* The character which is used to separate arguments. */
907 static int brace_arg_separator = ',';
908 
909 /* Expand the text found inside of braces. We simply try to split the
910  text at BRACE_ARG_SEPARATORs into separate strings. We then brace
911  expand each slot which needs it, until there are no more slots which
912  need it. */
913 static std::list<std::string>
914 expand_amble (const std::string& text)
915 {
916  std::list<std::string> result;
917 
918  std::size_t text_len = text.length ();
919  std::size_t start;
920  int i, c;
921 
922  for (start = 0, i = 0, c = 1; c && start < text_len; start = ++i)
923  {
924  int i0 = i;
925  int c0 = brace_gobbler (text, i0, brace_arg_separator);
926  int i1 = i;
927  int c1 = brace_gobbler (text, i1, ENV_SEP);
928  c = c0 | c1;
929  i = (i0 < i1 ? i0 : i1);
930 
931  std::string tem = text.substr (start, i-start);
932 
933  std::list<std::string> partial = brace_expand (tem);
934 
935  if (result.empty ())
936  result = partial;
937  else
938  result.splice (result.end (), partial);
939  }
940 
941  return result;
942 }
943 
944 /* Start at INDEX, and skip characters in TEXT. Set INDEX to the
945  index of the character matching SATISFY. This understands about
946  quoting. Return the character that caused us to stop searching;
947  this is either the same as SATISFY, or 0. */
948 static int
949 brace_gobbler (const std::string& text, int& indx, int satisfy)
950 {
951  int c = 0;
952  int level = 0;
953  int quoted = 0;
954  int pass_next = 0;
955 
956  std::size_t text_len = text.length ();
957 
958  std::size_t i = indx;
959 
960  for (; i < text_len; i++)
961  {
962  c = text[i];
963 
964  if (pass_next)
965  {
966  pass_next = 0;
967  continue;
968  }
969 
970  /* A backslash escapes the next character. This allows backslash to
971  escape the quote character in a double-quoted string. */
972  if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`'))
973  {
974  pass_next = 1;
975  continue;
976  }
977 
978  if (quoted)
979  {
980  if (c == quoted)
981  quoted = 0;
982  continue;
983  }
984 
985  if (c == '"' || c == '\'' || c == '`')
986  {
987  quoted = c;
988  continue;
989  }
990 
991  if (c == satisfy && ! level && ! quoted)
992  {
993  /* We ignore an open brace surrounded by whitespace, and also
994  an open brace followed immediately by a close brace, that
995  was preceded with whitespace. */
996  if (c == '{'
997  && ((i == 0 || brace_whitespace (text[i-1]))
998  && (i+1 < text_len
999  && (brace_whitespace (text[i+1]) || text[i+1] == '}'))))
1000  continue;
1001  /* If this is being compiled as part of bash, ignore the '{'
1002  in a '${ }' construct */
1003  if ((c != '{') || i == 0 || (text[i-1] != '$'))
1004  break;
1005  }
1006 
1007  if (c == '{')
1008  level++;
1009  else if (c == '}' && level)
1010  level--;
1011  }
1012 
1013  indx = i;
1014  c = (c == satisfy) ? c : 0;
1015  return c;
1016 }
1017 
1018 /* Return true if FN is a directory or a symlink to a directory,
1019  false if not. */
1020 
1021 static bool
1022 dir_p (const std::string& fn)
1023 {
1024  octave::sys::file_stat fs (fn);
1025 
1026  return (fs && fs.is_dir ());
1027 }
1028 
1029 /* Given a path element ELT, return a the element with a trailing slash
1030  or an empty string if the element is not a directory.
1031 
1032  It's up to the caller to expand ELT. This is because this routine is
1033  most likely only useful to be called from 'kpse_path_search', which
1034  has already assumed expansion has been done. */
1035 
1036 std::string
1037 kpse_element_dir (const std::string& elt)
1038 {
1039  std::string ret;
1040 
1041  /* If given nothing, return nothing. */
1042  if (elt.empty ())
1043  return ret;
1044 
1045  if (dir_p (elt))
1046  {
1047  ret = elt;
1048 
1049  char last_char = ret.back ();
1050 
1051  if (! (IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)))
1052  ret += DIR_SEP_STRING;
1053  }
1054 
1055  return ret;
1056 }
const std::string & m_path
Definition: kpse.h:68
std::size_t m_e
Definition: kpse.h:70
void next(void)
Definition: kpse.cc:114
void set_end(void)
Definition: kpse.cc:95
std::size_t m_len
Definition: kpse.h:71
std::size_t m_b
Definition: kpse.h:69
std::string kpse_path_find_first_of(const std::string &path, const std::list< std::string > &names)
Definition: kpse.cc:731
#define DIR_SEP_STRING
Definition: kpse.cc:68
#define NAME_BEGINS_WITH_DEVICE(name)
Definition: kpse.cc:65
static std::list< std::string > brace_expand(const std::string &text)
Definition: kpse.cc:868
static void log_search(const std::list< std::string > &filenames)
Definition: kpse.cc:264
#define KPSE_DEBUG_SEARCH
Definition: kpse.cc:86
static std::string kpse_tilde_expand(const std::string &name)
Definition: kpse.cc:396
std::string kpse_element_dir(const std::string &elt)
Definition: kpse.cc:1037
static std::list< std::string > dir_search(const std::string &dir, const std::string &name, bool search_all)
Definition: kpse.cc:287
static int brace_arg_separator
Definition: kpse.cc:907
static std::list< std::string > absolute_search(const std::string &name)
Definition: kpse.cc:311
static std::list< std::string > expand_amble(const std::string &)
Definition: kpse.cc:914
#define ENV_SEP_STRING
Definition: kpse.cc:72
static int brace_gobbler(const std::string &, int &, int)
Definition: kpse.cc:949
std::string kpse_path_expand(const std::string &path)
Definition: kpse.cc:758
std::list< std::string > kpse_all_path_find_first_of(const std::string &path, const std::list< std::string > &names)
Definition: kpse.cc:747
static bool kpse_absolute_p(const std::string &filename, int relative_ok)
Definition: kpse.cc:246
static std::list< std::string > path_search(const std::string &path, const std::string &name, bool all)
Definition: kpse.cc:327
static std::list< std::string > find_first_of(const std::string &path, const std::list< std::string > &names, bool all)
Definition: kpse.cc:651
static std::string kpse_readable_file(const std::string &name)
Definition: kpse.cc:205
unsigned int kpse_debug
Definition: kpse.cc:92
std::list< std::string > kpse_all_path_search(const std::string &path, const std::string &name)
Definition: kpse.cc:567
static bool first_search
Definition: kpse.cc:259
#define KPSE_DEBUG_P(bit)
Definition: kpse.cc:82
#define IS_DIR_SEP(ch)
Definition: kpse.cc:69
#define brace_whitespace(c)
Definition: kpse.cc:827
static bool dir_p(const std::string &fn)
Definition: kpse.cc:1022
std::list< std::string > path_find_first_of(const std::string &path, const std::list< std::string > &names, bool all)
Definition: kpse.cc:577
#define IS_DEVICE_SEP(ch)
Definition: kpse.cc:64
static std::string kpse_truncate_filename(const std::string &name)
Definition: kpse.cc:132
static bool READABLE(const std::string &fn)
Definition: kpse.cc:170
static std::list< std::string > search(const std::string &path, const std::string &original_name, bool all)
Definition: kpse.cc:495
#define ENV_SEP
Definition: kpse.cc:71
std::string kpse_path_search(const std::string &path, const std::string &name)
Definition: kpse.cc:556
static std::list< std::string > array_concat(const std::list< std::string > &arr1, const std::list< std::string > &arr2)
Definition: kpse.cc:844
static const double pi
Definition: lo-specfun.cc:1944
std::wstring u8_to_wstring(const std::string &utf8_string)
Definition: lo-sysdep.cc:514
static bool absolute_pathname(const std::string &s)
Definition: shared-fcns.h:148
int octave_access_r_ok(void)
int octave_access_wrapper(const char *nm, int mode)
F77_RET_T len
Definition: xerbla.cc:61