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 : * The Go-like pattern "..." is understood by this class. What happens
479 : * when the filename is set to "..." is that the pattern is replaced with
480 : * "*" and the GLOB_FLAG_RECURSIVE flag is set. This means all the files
481 : * from the directory specified before the "..." patterns are returned.
482 : * This means you will be responsible for checking the filenames if you
483 : * need to have a more constraining patterns than "*". Otherwise, you
484 : * may want to consider using a usual glob pattern and set the recursive
485 : * flag _manually_. Note that the "./" introducer is not required. It
486 : * is assumed if not specified.
487 : *
488 : * \warning
489 : * The class is not multithread safe. The glob() function makes use of a
490 : * global variable to report errors so there is no way at this point to
491 : * make it safe (without a \em service like implementation).
492 : *
493 : * \tparam C The type of the container where to add the filenames.
494 : * \tparam T The type used for the filenames (C<T>).
495 : */
496 : template<typename C>
497 40 : class glob_to_list
498 : : public C
499 : {
500 : private:
501 : typedef std::set<std::string> directories_t;
502 :
503 : public:
504 : typedef C container_t;
505 :
506 : /** \brief Read files at given path.
507 : *
508 : * This function runs the glob() function with the given path.
509 : *
510 : * The \p path variable is expected to include a path with glob() like
511 : * patterns (i.e. `*.txt`, `sound/0?.mp3`, etc.)
512 : *
513 : * Note that the glob()-ing is done on the entire path. However, only
514 : * the last part (after the last slash) is used in case you use the
515 : * GLOB_FLAG_RECURSIVE. Note that in recursive mode, the directories
516 : * will always be read since we have to recurse through them.
517 : *
518 : * \remarks
519 : * This implementation is not multithread safe. Make sure to use this
520 : * function in a part of your code which is locked.
521 : *
522 : * \todo
523 : * Add a read_path() which supports dynamic flags.
524 : *
525 : * \tparam args A list of one or more glob to list flags.
526 : * \param[in] path The path with glob patterns.
527 : *
528 : * \return true if no error occurred.
529 : */
530 : template<glob_to_list_flag_t ...args>
531 20 : bool read_path(std::string const & path)
532 : {
533 20 : f_recursive = false;
534 20 : f_follow_symlinks = false;
535 20 : int const flags = GLOB_NOSORT | flags_merge<args...>();
536 :
537 20 : if(!f_recursive)
538 : {
539 16 : return read_directory(path, flags);
540 : }
541 :
542 : // in recursive mode we want to collect the list of directories
543 : // along whatever the user wants to collect and then process
544 : // those directories as well; this also requires us to retrieve
545 : // the pattern (last segment) first as the actual pattern
546 : //
547 8 : directories_t visited;
548 8 : std::string pattern;
549 8 : std::string directory;
550 8 : std::string real_path;
551 4 : std::string::size_type const pos(path.rfind('/'));
552 4 : if(pos == std::string::npos)
553 : {
554 0 : directory = ".";
555 0 : pattern = path;
556 : }
557 : else
558 : {
559 4 : directory = path.substr(0, pos);
560 4 : pattern = path.substr(pos + 1);
561 : }
562 :
563 4 : if(pattern == "...")
564 : {
565 0 : pattern = "*";
566 0 : f_recursive = true;
567 : }
568 :
569 8 : return recursive_read_path(
570 : get_real_path(directory)
571 : , pattern
572 : , flags
573 8 : , visited);
574 : }
575 :
576 : /** \brief Convert the input \p path in a canonicalized path.
577 : *
578 : * This function goes through the specified \p path and canonicalize
579 : * it. This means:
580 : *
581 : * * removing any "/./"
582 : * * removing any "/../"
583 : * * replacing softlinks with the target path
584 : *
585 : * The resulting path is likely going to be a full path.
586 : *
587 : * \note
588 : * If the input path is an empty string (equivalent to ".") then the
589 : * result may also be the empty string even though no errors would have
590 : * happened.
591 : *
592 : * \param[in] path The path to canonicalize.
593 : *
594 : * \return The canonicalized version of \p path or an empty string on error.
595 : */
596 42 : std::string get_real_path(std::string const & path)
597 : {
598 42 : char buf[PATH_MAX + 1];
599 42 : buf[PATH_MAX] = '\0';
600 42 : if(realpath(path.c_str(), buf) != buf)
601 : {
602 : // it failed
603 : //
604 0 : f_last_error_errno = errno;
605 0 : f_last_error_path = path;
606 0 : switch(f_last_error_errno)
607 : {
608 0 : case EACCES:
609 0 : f_last_error_message = "realpath() is missing permission to read or search a component of the path.";
610 0 : break;
611 :
612 0 : case EIO:
613 0 : f_last_error_message = "realpath() had I/O issues while searching.";
614 0 : break;
615 :
616 0 : case ELOOP:
617 0 : f_last_error_message = "realpath() found too many symbolic links.";
618 0 : break;
619 :
620 0 : case ENAMETOOLONG:
621 0 : f_last_error_message = "realpath() output buffer too small for path.";
622 0 : break;
623 :
624 0 : case ENOENT:
625 0 : f_last_error_message = "realpath() could not find the specified file.";
626 0 : break;
627 :
628 0 : case ENOMEM:
629 0 : f_last_error_message = "realpath() could not allocate necessary memory.";
630 0 : break;
631 :
632 0 : case ENOTDIR:
633 0 : f_last_error_message = "realpath() found a file instead of a directory within the path.";
634 0 : break;
635 :
636 0 : default:
637 0 : f_last_error_message = "realpath() failed.";
638 0 : break;
639 :
640 : }
641 0 : return std::string();
642 : }
643 42 : return buf;
644 : }
645 :
646 : /** \brief The last error message.
647 : *
648 : * Whenever a call fails, it saves an error message here.
649 : *
650 : * The error message is whatever represents the error best from our
651 : * point of view.
652 : *
653 : * \note
654 : * Error messages get overwritten so you must call this function
655 : * before calling another function to not lose intermediate messages.
656 : *
657 : * \return The last error message.
658 : */
659 0 : std::string get_last_error_message() const
660 : {
661 0 : return f_last_error_message;
662 : }
663 :
664 : /** \brief The last error path.
665 : *
666 : * The path that generated the error. In most cases, this is the input
667 : * path you specified, with the pattern still intact. When using the
668 : * recursive feature, the path will be the path of the directory that
669 : * is currently being handled.
670 : *
671 : * \return The path that generated the error.
672 : */
673 0 : std::string get_last_error_path() const
674 : {
675 0 : return f_last_error_path;
676 : }
677 :
678 : /** \brief Retrieve the last error number.
679 : *
680 : * Whenever an error occurs with a system function, the errno value
681 : * gets saved in the "last error errno" variable which can then be
682 : * retrieved using this function. This should be used instead of
683 : * trying to understand the error message which is expected to only
684 : * be used for human consumption.
685 : *
686 : * \return The last error errno value.
687 : */
688 2 : int get_last_error_errno() const
689 : {
690 2 : return f_last_error_errno;
691 : }
692 :
693 : private:
694 : template<class none = void>
695 20 : constexpr int flags_merge()
696 : {
697 20 : return 0;
698 : }
699 :
700 : template<glob_to_list_flag_t flag, glob_to_list_flag_t ...args>
701 21 : constexpr int flags_merge()
702 : {
703 : switch(flag)
704 : {
705 : case glob_to_list_flag_t::GLOB_FLAG_NONE:
706 : return flags_merge<args...>();
707 :
708 : case glob_to_list_flag_t::GLOB_FLAG_BRACE:
709 : return GLOB_BRACE | flags_merge<args...>();
710 :
711 : case glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS:
712 0 : return GLOB_ERR | flags_merge<args...>();
713 :
714 : case glob_to_list_flag_t::GLOB_FLAG_MARK_DIRECTORY:
715 : return GLOB_MARK | flags_merge<args...>();
716 :
717 : case glob_to_list_flag_t::GLOB_FLAG_NO_ESCAPE:
718 : return GLOB_NOESCAPE | flags_merge<args...>();
719 :
720 : case glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES:
721 16 : return GLOB_ONLYDIR | flags_merge<args...>();
722 :
723 : case glob_to_list_flag_t::GLOB_FLAG_PERIOD:
724 : return GLOB_PERIOD | flags_merge<args...>();
725 :
726 : case glob_to_list_flag_t::GLOB_FLAG_TILDE:
727 : return GLOB_TILDE_CHECK | flags_merge<args...>();
728 :
729 : case glob_to_list_flag_t::GLOB_FLAG_RECURSIVE:
730 4 : f_recursive = true;
731 4 : return flags_merge<args...>();
732 :
733 : case glob_to_list_flag_t::GLOB_FLAG_FOLLOW_SYMLINK:
734 1 : f_follow_symlinks = true;
735 1 : return flags_merge<args...>();
736 :
737 : case glob_to_list_flag_t::GLOB_FLAG_EMPTY:
738 : f_empty = true;
739 : return flags_merge<args...>();
740 :
741 : }
742 :
743 : throw std::logic_error("unimplemented GLOB_FLAG_... in flags_merge()");
744 : }
745 :
746 0 : static int glob_err_callback(char const * p, int e)
747 : {
748 0 : g_self->f_last_error_message = "caught an error while reading a directory.";
749 0 : g_self->f_last_error_path = p;
750 0 : g_self->f_last_error_errno = e;
751 :
752 0 : return 0;
753 : }
754 :
755 32 : bool read_directory(std::string const & path, int const flags)
756 : {
757 32 : glob_t dir = glob_t();
758 32 : g_self = this;
759 32 : int const r(glob(path.c_str(), flags, glob_err_callback, &dir));
760 32 : g_self = nullptr; // to detect if glob_err_callback gets called improperly
761 32 : if(r == 0)
762 : {
763 52 : glob_pointer_t auto_release_dir(&dir);
764 79 : for(size_t idx(0); idx < dir.gl_pathc; ++idx)
765 : {
766 53 : C::insert(C::end(), typename glob_to_list::value_type(std::string(dir.gl_pathv[idx])));
767 : }
768 : }
769 : else
770 : {
771 : // do nothing when errors occur
772 : //
773 12 : std::string err_msg;
774 6 : switch(r)
775 : {
776 0 : case GLOB_NOSPACE:
777 0 : f_last_error_message =
778 : "glob(\""
779 : + path
780 : + "\") did not have enough memory to allocate its buffers.";
781 0 : break;
782 :
783 0 : case GLOB_ABORTED:
784 0 : f_last_error_message =
785 : "glob(\""
786 : + path
787 : + "\") was aborted after a read error.";
788 0 : break;
789 :
790 6 : case GLOB_NOMATCH:
791 6 : if(f_empty)
792 : {
793 0 : return true;
794 : }
795 6 : f_last_error_message = "glob(\""
796 : + path
797 : + "\") could not find any files matching the pattern.";
798 6 : f_last_error_errno = ENOENT;
799 6 : break;
800 :
801 0 : default:
802 0 : f_last_error_message = "unknown glob(\""
803 : + path
804 : + "\") error code: "
805 : + std::to_string(r)
806 : + ".";
807 0 : break;
808 :
809 : }
810 :
811 6 : return false;
812 : }
813 :
814 26 : return true;
815 : }
816 :
817 16 : bool recursive_read_path(
818 : std::string const & path
819 : , std::string const & pattern
820 : , int flags
821 : , directories_t & visited)
822 : {
823 : // (1) read client's files in path
824 : //
825 16 : if(!read_directory(path + '/' + pattern, flags))
826 : {
827 4 : if(f_last_error_errno != ENOENT)
828 : {
829 0 : return false;
830 : }
831 : }
832 :
833 : // (2) find child directories
834 : //
835 : typedef std::list<file> dir_list_t;
836 32 : glob_to_list<dir_list_t> sub_dirs;
837 16 : bool success(false);
838 16 : if((flags & GLOB_ERR) != 0)
839 : {
840 0 : success = sub_dirs.read_path<
841 : glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS
842 0 : , glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES>(path + "/*");
843 : }
844 : else
845 : {
846 32 : success = sub_dirs.read_path<
847 32 : glob_to_list_flag_t::GLOB_FLAG_ONLY_DIRECTORIES>(path + "/*");
848 : }
849 32 : if(!success
850 16 : && sub_dirs.get_last_error_errno() != ENOENT)
851 : {
852 0 : f_last_error_message = sub_dirs.get_last_error_message();
853 0 : f_last_error_path = sub_dirs.get_last_error_path();
854 0 : f_last_error_errno = sub_dirs.get_last_error_errno();
855 0 : return f_last_error_errno != ENOENT;
856 : }
857 :
858 : // (3) read the sub-directories
859 : //
860 42 : for(auto const & d : sub_dirs)
861 : {
862 26 : if(!d.exists())
863 : {
864 0 : continue;
865 : }
866 26 : if(d.is_symbolic_link())
867 : {
868 28 : if(!f_follow_symlinks
869 14 : || !d.is_target_directory())
870 : {
871 11 : continue;
872 : }
873 : }
874 : else
875 : {
876 12 : if(!d.is_directory())
877 : {
878 0 : continue;
879 : }
880 : }
881 :
882 : // because we use a real-path, we may find that some paths are
883 : // duplicates (most certainly because of a softlink)
884 : //
885 30 : std::string const p(get_real_path(d.filename()));
886 15 : if(visited.insert(p).second)
887 : {
888 12 : if(!recursive_read_path(
889 : p
890 : , pattern
891 : , flags
892 : , visited))
893 : {
894 0 : return false;
895 : }
896 : }
897 : }
898 :
899 16 : return true;
900 : }
901 :
902 : static thread_local glob_to_list *
903 : g_self;
904 :
905 : std::string f_last_error_message = std::string();
906 : std::string f_last_error_path = std::string();
907 : int f_last_error_errno = 0;
908 : bool f_recursive = false;
909 : bool f_follow_symlinks = false;
910 : bool f_empty = false;
911 : };
912 :
913 :
914 : template <typename C>
915 : thread_local glob_to_list<C> * glob_to_list<C>::g_self = nullptr;
916 :
917 :
918 : } // namespace snapdev
919 : // vim: ts=4 sw=4 et
|