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