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