LCOV - code coverage report
Current view: top level - snapdev - glob_to_list.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 118 158 74.7 %
Date: 2023-05-29 16:11:08 Functions: 35 42 83.3 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14