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

Generated by: LCOV version 1.13