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

Generated by: LCOV version 1.13