LCOV - code coverage report
Current view: top level - snapdev - glob_to_list.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 103 171 60.2 %
Date: 2022-07-09 19:51:09 Functions: 28 36 77.8 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13