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