Line data Source code
1 : // Copyright (c) 2019-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : #pragma once
20 :
21 : /** \file
22 : * \brief Create a list of files using glob().
23 : *
24 : * This template allows you to insert filenames from the output of a glob()
25 : * call to an STL container.
26 : */
27 :
28 : // snapdev lib
29 : //
30 : #include "snapdev/raii_generic_deleter.h"
31 :
32 :
33 : // C++ lib
34 : //
35 : #include <list>
36 : #include <memory>
37 : #include <set>
38 :
39 :
40 : // C lib
41 : //
42 : #include <glob.h>
43 : #include <limits.h>
44 : #include <stdlib.h>
45 : #include <sys/stat.h>
46 :
47 :
48 : namespace snapdev
49 : {
50 :
51 :
52 : /** \brief An object that holds the information about the file being loaded.
53 : *
54 : * In order to only read certain types of files (such as directories),
55 : * we have to get the lstat()'s. This object represents one file found
56 : * in the directory with it's lstat()'s.
57 : *
58 : * Note that the regular functions use `lstat()` to read the file
59 : * statistics. The `target_...()` functions read the target statistics.
60 : * In other words, if the file is a symbolic link, the both functions
61 : * will return different results.
62 : */
63 78 : class file
64 : {
65 : public:
66 26 : file(std::string const & filename)
67 26 : : f_filename(filename)
68 : {
69 26 : }
70 :
71 15 : std::string const & filename() const
72 : {
73 15 : return f_filename;
74 : }
75 :
76 64 : bool exists() const
77 : {
78 64 : load_stats();
79 64 : return f_stat_loaded;
80 : }
81 :
82 26 : bool is_symbolic_link() const
83 : {
84 26 : if(exists())
85 : {
86 26 : return S_ISLNK(f_stat.st_mode);
87 : }
88 0 : return false;
89 : }
90 :
91 : bool is_regular_file() const
92 : {
93 : if(exists())
94 : {
95 : return S_ISREG(f_stat.st_mode);
96 : }
97 : return false;
98 : }
99 :
100 12 : bool is_directory() const
101 : {
102 12 : if(exists())
103 : {
104 12 : return S_ISDIR(f_stat.st_mode);
105 : }
106 0 : return false;
107 : }
108 :
109 : bool is_character() const
110 : {
111 : if(exists())
112 : {
113 : return S_ISCHR(f_stat.st_mode);
114 : }
115 : return false;
116 : }
117 :
118 : bool is_block() const
119 : {
120 : if(exists())
121 : {
122 : return S_ISBLK(f_stat.st_mode);
123 : }
124 : return false;
125 : }
126 :
127 : bool is_fifo() const
128 : {
129 : if(exists())
130 : {
131 : return S_ISFIFO(f_stat.st_mode);
132 : }
133 : return false;
134 : }
135 :
136 : bool is_socket() const
137 : {
138 : if(exists())
139 : {
140 : return S_ISSOCK(f_stat.st_mode);
141 : }
142 : return false;
143 : }
144 :
145 : int is_uid() const
146 : {
147 : if(exists())
148 : {
149 : return (f_stat.st_mode & S_ISUID) != 0;
150 : }
151 : return 0;
152 : }
153 :
154 : int is_gid() const
155 : {
156 : if(exists())
157 : {
158 : return (f_stat.st_mode & S_ISGID) != 0;
159 : }
160 : return 0;
161 : }
162 :
163 : int is_vtx() const
164 : {
165 : if(exists())
166 : {
167 : return (f_stat.st_mode & S_ISVTX) != 0;
168 : }
169 : return 0;
170 : }
171 :
172 : int permissions() const
173 : {
174 : if(exists())
175 : {
176 : return f_stat.st_mode & 0777;
177 : }
178 : return 0;
179 : }
180 :
181 : uid_t uid() const
182 : {
183 : if(exists())
184 : {
185 : return f_stat.st_uid;
186 : }
187 : return -1;
188 : }
189 :
190 : gid_t gid() const
191 : {
192 : if(exists())
193 : {
194 : return f_stat.st_gid;
195 : }
196 : return -1;
197 : }
198 :
199 : gid_t size() const
200 : {
201 : if(exists())
202 : {
203 : return f_stat.st_size;
204 : }
205 : return -1;
206 : }
207 :
208 5 : bool target_exists() const
209 : {
210 5 : load_target_stats();
211 5 : return f_target_stat_loaded;
212 : }
213 :
214 : bool is_target_symbolic_link() const
215 : {
216 : if(target_exists())
217 : {
218 : return S_ISLNK(f_target_stat.st_mode);
219 : }
220 : return false;
221 : }
222 :
223 : bool is_target_regular_file() const
224 : {
225 : if(target_exists())
226 : {
227 : return S_ISREG(f_target_stat.st_mode);
228 : }
229 : return false;
230 : }
231 :
232 5 : bool is_target_directory() const
233 : {
234 5 : if(target_exists())
235 : {
236 5 : return S_ISDIR(f_target_stat.st_mode);
237 : }
238 0 : return false;
239 : }
240 :
241 : bool is_target_character() const
242 : {
243 : if(target_exists())
244 : {
245 : return S_ISCHR(f_target_stat.st_mode);
246 : }
247 : return false;
248 : }
249 :
250 : bool is_target_block() const
251 : {
252 : if(target_exists())
253 : {
254 : return S_ISBLK(f_target_stat.st_mode);
255 : }
256 : return false;
257 : }
258 :
259 : bool is_target_fifo() const
260 : {
261 : if(target_exists())
262 : {
263 : return S_ISFIFO(f_target_stat.st_mode);
264 : }
265 : return false;
266 : }
267 :
268 : bool is_target_socket() const
269 : {
270 : if(target_exists())
271 : {
272 : return S_ISSOCK(f_target_stat.st_mode);
273 : }
274 : return false;
275 : }
276 :
277 : int is_target_uid() const
278 : {
279 : if(target_exists())
280 : {
281 : return (f_target_stat.st_mode & S_ISUID) != 0;
282 : }
283 : return 0;
284 : }
285 :
286 : int is_target_gid() const
287 : {
288 : if(target_exists())
289 : {
290 : return (f_target_stat.st_mode & S_ISGID) != 0;
291 : }
292 : return 0;
293 : }
294 :
295 : int is_target_vtx() const
296 : {
297 : if(target_exists())
298 : {
299 : return (f_target_stat.st_mode & S_ISVTX) != 0;
300 : }
301 : return 0;
302 : }
303 :
304 : int target_permissions() const
305 : {
306 : if(target_exists())
307 : {
308 : return f_target_stat.st_mode & 0777;
309 : }
310 : return 0;
311 : }
312 :
313 : uid_t target_uid() const
314 : {
315 : if(target_exists())
316 : {
317 : return f_target_stat.st_uid;
318 : }
319 : return -1;
320 : }
321 :
322 : gid_t target_gid() const
323 : {
324 : if(target_exists())
325 : {
326 : return f_target_stat.st_gid;
327 : }
328 : return -1;
329 : }
330 :
331 : gid_t target_size() const
332 : {
333 : if(target_exists())
334 : {
335 : return f_target_stat.st_size;
336 : }
337 : return -1;
338 : }
339 :
340 : private:
341 64 : void load_stats() const
342 : {
343 64 : if(f_stat_loaded)
344 : {
345 38 : return;
346 : }
347 :
348 26 : if(lstat(f_filename.c_str(), &f_stat) != 0)
349 : {
350 0 : return;
351 : }
352 :
353 26 : f_stat_loaded = true;
354 : }
355 :
356 5 : void load_target_stats() const
357 : {
358 5 : if(f_target_stat_loaded)
359 : {
360 0 : return;
361 : }
362 :
363 5 : if(stat(f_filename.c_str(), &f_target_stat) != 0)
364 : {
365 0 : return;
366 : }
367 :
368 5 : f_target_stat_loaded = true;
369 : }
370 :
371 : std::string f_filename = std::string();
372 : mutable struct stat f_stat = {};
373 : mutable struct stat f_target_stat = {};
374 : mutable bool f_stat_loaded = false;
375 : mutable bool f_target_stat_loaded = false;
376 : };
377 :
378 :
379 : /** \brief A smart pointer to auto-delete glob results.
380 : *
381 : * This type defines a smart pointer which automatically frees all
382 : * the data allocated by glob() and this pointer too.
383 : *
384 : * Usage:
385 : *
386 : * \code
387 : * snapdev::glob_to_list<std::vector<std::string>> glob;
388 : * if(glob.read_path<
389 : * snapdev::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS,
390 : * snapdev::glob_to_list_flag_t::GLOB_FLAG_PERIOD>(pattern))
391 : * {
392 : * if(!glob.empty())
393 : * {
394 : * // handle file names
395 : *
396 : * }
397 : * else
398 : * {
399 : * // no files case
400 : * }
401 : * }
402 : * else {
403 : * // handle error
404 : * }
405 : * \endcode
406 : *
407 : * Note that the glob() function always gets called with the GLOB_NOSORT
408 : * flag set. If you want sorted results, use a container which returns
409 : * the data sorted such as the `std::set<>`.
410 : *
411 : * \warning
412 : * glob() is not thread safe and we currently do not add any additional
413 : * support to make it thread safe. Especially, the glob() function
414 : * makes use of a global function for errors and that uses global
415 : * pointers.
416 : * \warning
417 : * If you use our cppthread project, you can use a guard to lock a global
418 : * mutex. Remember that if you may get called before main() you must first
419 : * lock the `g_system_mutex`. Otherwise the initialization process may
420 : * not work correctly and your mutex may get initialized after you hit
421 : * your `cppthread::guard` statement (i.e. your g_mutex must be a pointer
422 : * that you allocate the first time you use it and to make that thread
423 : * safe you need to first lock the `g_system_mutex`).
424 : *
425 : * \code
426 : * cppthread::mutex g_mutex;
427 : * {
428 : * cppthread::guard lock(g_mutex);
429 : *
430 : * ...glob.read_path<...>(pattern);...
431 : * }
432 : * \endcode
433 : */
434 26 : typedef std::unique_ptr<glob_t, raii_pointer_deleter<glob_t, decltype(&::globfree), &::globfree>> glob_pointer_t;
435 :
436 :
437 : enum class glob_to_list_flag_t
438 : {
439 : GLOB_FLAG_NONE, // not a flag, useful in case you need a value for ?:
440 : GLOB_FLAG_BRACE, // allow {a,b,c}...
441 : GLOB_FLAG_IGNORE_ERRORS, // read as much as possible
442 : GLOB_FLAG_MARK_DIRECTORY, // add "/" to directory names
443 : GLOB_FLAG_NO_ESCAPE, // ignore '\'
444 : GLOB_FLAG_ONLY_DIRECTORIES, // only return directories
445 : GLOB_FLAG_PERIOD, // allow period at the start (i.e. pattern ".*")
446 : GLOB_FLAG_TILDE, // transform "~/..." with "$HOME/..."
447 : GLOB_FLAG_RECURSIVE, // when a directory is found, read it too
448 : GLOB_FLAG_FOLLOW_SYMLINK, // in recursive mode, do or do not follow symlinks
449 : GLOB_FLAG_EMPTY, // on a GLOB_NOMATCH error, still return true
450 : };
451 :
452 :
453 : /** \brief Manage the results of glob() calls.
454 : *
455 : * This template is able to call glob() and insert the results to your
456 : * container object. If the type of the container is std::string, then
457 : * only the filenames are returned. If the type is set to a snapdev::file,
458 : * then the function returns a set of snapdev::file objects.
459 : *
460 : * The supported flags allow for selecting which files to ignore. By
461 : * default, files that start with a period (.) are ignored.
462 : *
463 : * Here is an example of usage:
464 : *
465 : * \code
466 : * glob_to_list<std::vector<std::string>> dir;
467 : * if(!dir.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES>("/proc/[0-9]*"))
468 : * {
469 : * ...handle error...
470 : * return;
471 : * }
472 : * for(auto f : dir)
473 : * {
474 : * ...f is std::string with filename...
475 : * }
476 : * \endcode
477 : *
478 : * \warning
479 : * The class is not multithread safe. The glob() function makes use of a
480 : * global variable to report errors so there is no way at this point to
481 : * make it safe (without a \em service like implementation).
482 : *
483 : * \tparam C The type of the container where to add the filenames.
484 : * \tparam T The type used for the filenames (C<T>).
485 : */
486 : template<typename C>
487 40 : class glob_to_list
488 : : public C
489 : {
490 : private:
491 : typedef std::set<std::string> directories_t;
492 :
493 : public:
494 : typedef C container_t;
495 :
496 : /** \brief Read files at given path.
497 : *
498 : * This function runs the glob() function with the given path.
499 : *
500 : * The \p path variable is expected to include a path with glob() like
501 : * patterns (i.e. `*.txt`, `sound/0?.mp3`, etc.)
502 : *
503 : * Note that the glob()-ing is done on the entire path. However, only
504 : * the last part (after the last slash) is used in case you use the
505 : * GLOB_FLAG_RECURSIVE. Note that in recursive mode, the directories
506 : * will always be read since we have to recurse through them.
507 : *
508 : * \remarks
509 : * This implementation is not multithread safe. Make sure to use this
510 : * function in a part of your code which is locked.
511 : *
512 : * \todo
513 : * Add a read_path() which supports dynamic flags.
514 : *
515 : * \tparam args A list of one or more glob to list flags.
516 : * \param[in] path The path with glob patterns.
517 : *
518 : * \return true if no error occurred.
519 : */
520 : template<glob_to_list_flag_t ...args>
521 20 : bool read_path(std::string const & path)
522 : {
523 20 : f_recursive = false;
524 20 : f_follow_symlinks = false;
525 20 : int const flags = GLOB_NOSORT | flags_merge<args...>();
526 :
527 20 : if(!f_recursive)
528 : {
529 16 : return read_directory(path, flags);
530 : }
531 :
532 : // in recursive mode we want to collect the list of directories
533 : // along whatever the user wants to collect and then process
534 : // those directories as well; this also requires us to retrieve
535 : // the pattern (last segment) first as the actual pattern
536 : //
537 8 : directories_t visited;
538 4 : std::string::size_type const pos(path.rfind('/'));
539 4 : if(pos == std::string::npos)
540 : {
541 : // no directory in the path, only a pattern
542 : //
543 0 : return recursive_read_path(
544 : get_real_path(".")
545 : , path
546 : , flags
547 0 : , visited);
548 : }
549 : else
550 : {
551 12 : return recursive_read_path(
552 : get_real_path(path.substr(0, pos))
553 : , path.substr(pos + 1)
554 : , flags
555 12 : , visited);
556 : }
557 : }
558 :
559 : /** \brief Convert the input \p path in a canonicalized path.
560 : *
561 : * This function goes through the specified \p path and canonicalize
562 : * it. This means:
563 : *
564 : * * removing any "/./"
565 : * * removing any "/../"
566 : * * replacing softlinks with the target path
567 : *
568 : * The resulting path is likely going to be a full path.
569 : *
570 : * \note
571 : * If the input path is an empty string (equivalent to ".") then the
572 : * result may also be the empty string even though no errors would have
573 : * happened.
574 : *
575 : * \param[in] path The path to canonicalize.
576 : *
577 : * \return The canonicalized version of \p path or an empty string on error.
578 : */
579 42 : std::string get_real_path(std::string const & path)
580 : {
581 42 : char buf[PATH_MAX + 1];
582 42 : buf[PATH_MAX] = '\0';
583 42 : if(realpath(path.c_str(), buf) != buf)
584 : {
585 : // it failed
586 : //
587 0 : f_last_error_errno = errno;
588 0 : f_last_error_path = path;
589 0 : switch(f_last_error_errno)
590 : {
591 0 : case EACCES:
592 0 : f_last_error_message = "realpath() is missing permission to read or search a component of the path.";
593 0 : break;
594 :
595 0 : case EIO:
596 0 : f_last_error_message = "realpath() had I/O issues while searching.";
597 0 : break;
598 :
599 0 : case ELOOP:
600 0 : f_last_error_message = "realpath() found too many symbolic links.";
601 0 : break;
602 :
603 0 : case ENAMETOOLONG:
604 0 : f_last_error_message = "realpath() output buffer too small for path.";
605 0 : break;
606 :
607 0 : case ENOENT:
608 0 : f_last_error_message = "realpath() could not find the specified file.";
609 0 : break;
610 :
611 0 : case ENOMEM:
612 0 : f_last_error_message = "realpath() could not allocate necessary memory.";
613 0 : break;
614 :
615 0 : case ENOTDIR:
616 0 : f_last_error_message = "realpath() found a file instead of a directory within the path.";
617 0 : break;
618 :
619 0 : default:
620 0 : f_last_error_message = "realpath() failed.";
621 0 : break;
622 :
623 : }
624 0 : return std::string();
625 : }
626 42 : return buf;
627 : }
628 :
629 : /** \brief The last error message.
630 : *
631 : * Whenever a call fails, it saves an error message here.
632 : *
633 : * The error message is whatever represents the error best from our
634 : * point of view.
635 : *
636 : * \note
637 : * Error messages get overwritten so you must call this function
638 : * before calling another function to not lose intermediate messages.
639 : *
640 : * \return The last error message.
641 : */
642 0 : std::string get_last_error_message() const
643 : {
644 0 : return f_last_error_message;
645 : }
646 :
647 : /** \brief The last error path.
648 : *
649 : * The path that generated the error. In most cases, this is the input
650 : * path you specified, with the pattern still intact. When using the
651 : * recursive feature, the path will be the path of the directory that
652 : * is currently being handled.
653 : *
654 : * \return The path that generated the error.
655 : */
656 0 : std::string get_last_error_path() const
657 : {
658 0 : return f_last_error_path;
659 : }
660 :
661 : /** \brief Retrieve the last error number.
662 : *
663 : * Whenever an error occurs with a system function, the errno value
664 : * gets saved in the "last error errno" variable which can then be
665 : * retrieved using this function. This should be used instead of
666 : * trying to understand the error message which is expected to only
667 : * be used for human consumption.
668 : *
669 : * \return The last error errno value.
670 : */
671 2 : int get_last_error_errno() const
672 : {
673 2 : return f_last_error_errno;
674 : }
675 :
676 : private:
677 : template<class none = void>
678 20 : constexpr int flags_merge()
679 : {
680 20 : return 0;
681 : }
682 :
683 : template<glob_to_list_flag_t flag, glob_to_list_flag_t ...args>
684 21 : constexpr int flags_merge()
685 : {
686 : switch(flag)
687 : {
688 : case glob_to_list_flag_t::GLOB_FLAG_NONE:
689 : return GLOB_ONLYDIR | flags_merge<args...>();
690 :
691 : case glob_to_list_flag_t::GLOB_FLAG_BRACE:
692 : return GLOB_BRACE | flags_merge<args...>();
693 :
694 : case glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS:
695 0 : return GLOB_ERR | flags_merge<args...>();
696 :
697 : case glob_to_list_flag_t::GLOB_FLAG_MARK_DIRECTORY:
698 : return GLOB_MARK | flags_merge<args...>();
699 :
700 : case glob_to_list_flag_t::GLOB_FLAG_NO_ESCAPE:
701 : return GLOB_NOESCAPE | flags_merge<args...>();
702 :
703 : case glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES:
704 16 : return GLOB_ONLYDIR | flags_merge<args...>();
705 :
706 : case glob_to_list_flag_t::GLOB_FLAG_PERIOD:
707 : return GLOB_PERIOD | flags_merge<args...>();
708 :
709 : case glob_to_list_flag_t::GLOB_FLAG_TILDE:
710 : return GLOB_TILDE_CHECK | flags_merge<args...>();
711 :
712 : case glob_to_list_flag_t::GLOB_FLAG_RECURSIVE:
713 4 : f_recursive = true;
714 4 : return flags_merge<args...>();
715 :
716 : case glob_to_list_flag_t::GLOB_FLAG_FOLLOW_SYMLINK:
717 1 : f_follow_symlinks = true;
718 1 : return flags_merge<args...>();
719 :
720 : case glob_to_list_flag_t::GLOB_FLAG_EMPTY:
721 : f_empty = true;
722 : return flags_merge<args...>();
723 :
724 : }
725 :
726 : throw std::logic_error("unimplemented GLOB_FLAG_... in flags_merge()");
727 : }
728 :
729 0 : static int glob_err_callback(char const * p, int e)
730 : {
731 0 : g_self->f_last_error_message = "caught an error while reading a directory.";
732 0 : g_self->f_last_error_path = p;
733 0 : g_self->f_last_error_errno = e;
734 :
735 0 : return 0;
736 : }
737 :
738 32 : bool read_directory(std::string const & path, int const flags)
739 : {
740 32 : glob_t dir = glob_t();
741 32 : g_self = this;
742 32 : int const r(glob(path.c_str(), flags, glob_err_callback, &dir));
743 32 : g_self = nullptr; // to detect if glob_err_callback gets called improperly
744 32 : if(r == 0)
745 : {
746 52 : glob_pointer_t auto_release_dir(&dir);
747 79 : for(size_t idx(0); idx < dir.gl_pathc; ++idx)
748 : {
749 53 : C::insert(C::end(), typename glob_to_list::value_type(std::string(dir.gl_pathv[idx])));
750 : }
751 : }
752 : else
753 : {
754 : // do nothing when errors occur
755 : //
756 12 : std::string err_msg;
757 6 : switch(r)
758 : {
759 0 : case GLOB_NOSPACE:
760 0 : f_last_error_message =
761 : "glob(\""
762 : + path
763 : + "\") did not have enough memory to allocate its buffers.";
764 0 : break;
765 :
766 0 : case GLOB_ABORTED:
767 0 : f_last_error_message =
768 : "glob(\""
769 : + path
770 : + "\") was aborted after a read error.";
771 0 : break;
772 :
773 6 : case GLOB_NOMATCH:
774 6 : if(f_empty)
775 : {
776 0 : return true;
777 : }
778 6 : f_last_error_message = "glob(\""
779 : + path
780 : + "\") could not find any files matching the pattern.";
781 6 : f_last_error_errno = ENOENT;
782 6 : break;
783 :
784 0 : default:
785 0 : f_last_error_message = "unknown glob(\""
786 : + path
787 : + "\") error code: "
788 : + std::to_string(r)
789 : + ".";
790 0 : break;
791 :
792 : }
793 :
794 6 : return false;
795 : }
796 :
797 26 : return true;
798 : }
799 :
800 16 : bool recursive_read_path(
801 : std::string const & path
802 : , std::string const & pattern
803 : , int flags
804 : , directories_t & visited)
805 : {
806 : // (1) read client's files in path
807 : //
808 16 : if(!read_directory(path + '/' + pattern, flags))
809 : {
810 4 : if(f_last_error_errno != ENOENT)
811 : {
812 0 : return false;
813 : }
814 : }
815 :
816 : // (2) find child directories
817 : //
818 : typedef std::list<file> dir_list_t;
819 32 : glob_to_list<dir_list_t> sub_dirs;
820 16 : bool success(false);
821 16 : if((flags & GLOB_ERR) != 0)
822 : {
823 0 : success = sub_dirs.read_path<
824 : glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS
825 0 : , glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES>(path + "/*");
826 : }
827 : else
828 : {
829 32 : success = sub_dirs.read_path<
830 32 : glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES>(path + "/*");
831 : }
832 32 : if(!success
833 16 : && sub_dirs.get_last_error_errno() != ENOENT)
834 : {
835 0 : f_last_error_message = sub_dirs.get_last_error_message();
836 0 : f_last_error_path = sub_dirs.get_last_error_path();
837 0 : f_last_error_errno = sub_dirs.get_last_error_errno();
838 0 : return f_last_error_errno != ENOENT;
839 : }
840 :
841 : // (3) read the sub-directories
842 : //
843 42 : for(auto const & d : sub_dirs)
844 : {
845 26 : if(!d.exists())
846 : {
847 0 : continue;
848 : }
849 26 : if(d.is_symbolic_link())
850 : {
851 28 : if(!f_follow_symlinks
852 14 : || !d.is_target_directory())
853 : {
854 11 : continue;
855 : }
856 : }
857 : else
858 : {
859 12 : if(!d.is_directory())
860 : {
861 0 : continue;
862 : }
863 : }
864 :
865 : // because we use a real-path, we may find that some paths are
866 : // duplicates (most certainly because of a softlink)
867 : //
868 30 : std::string const p(get_real_path(d.filename()));
869 15 : if(visited.insert(p).second)
870 : {
871 12 : if(!recursive_read_path(
872 : p
873 : , pattern
874 : , flags
875 : , visited))
876 : {
877 0 : return false;
878 : }
879 : }
880 : }
881 :
882 16 : return true;
883 : }
884 :
885 : static thread_local glob_to_list *
886 : g_self;
887 :
888 : std::string f_last_error_message = std::string();
889 : std::string f_last_error_path = std::string();
890 : int f_last_error_errno = 0;
891 : bool f_recursive = false;
892 : bool f_follow_symlinks = false;
893 : bool f_empty = false;
894 : };
895 :
896 :
897 : template <typename C>
898 : thread_local glob_to_list<C> * glob_to_list<C>::g_self = nullptr;
899 :
900 :
901 : } // namespace snapdev
902 : // vim: ts=4 sw=4 et
|