LCOV - code coverage report
Current view: top level - snapdev - file_contents.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 70 72 97.2 %
Date: 2022-03-01 21:10:20 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-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             : // self
      22             : //
      23             : #include    <snapdev/mkdir_p.h>
      24             : 
      25             : 
      26             : // C++ lib
      27             : //
      28             : #include    <fstream>
      29             : #include    <iostream>
      30             : #include    <ios>
      31             : 
      32             : 
      33             : // C lib
      34             : //
      35             : #include    <unistd.h>
      36             : 
      37             : 
      38             : 
      39             : namespace snapdev
      40             : {
      41             : 
      42             : 
      43             : 
      44             : class file_contents
      45             : {
      46             : public:
      47             :     /** \brief Define the way to determine the file size.
      48             :      *
      49             :      * The size of some files can't be determined ahead of time
      50             :      * (sockets, FIFO, /proc files, etc.) and thus attempting to
      51             :      * get the size of such files is not going to work.
      52             :      *
      53             :      * Before calling read_all(), you can change the size mode
      54             :      * to one where the size will be determined once the file
      55             :      * is read.
      56             :      */
      57             :     enum size_mode_t
      58             :     {
      59             :         /** \brief Seek to the end to determine the size of the file.
      60             :          *
      61             :          * This mode is the default. It tells the read_all() command
      62             :          * to seek to the end of the file, get the position, which
      63             :          * represents the size, then seek back to the start of the
      64             :          * file. This works with all regular files.
      65             :          */
      66             :         SIZE_MODE_SEEK,
      67             : 
      68             :         /** \brief Read the file, avoid the seek().
      69             :          *
      70             :          * This mode has to be used with any file which size can't be
      71             :          * determine with a seek() function. It will read as much as it
      72             :          * can and then return that contents. There is no real way to
      73             :          * know whether the entire file was read in this mode.
      74             :          *
      75             :          * Although it is possible ot use this mode to read a regular
      76             :          * file, it is not a good idea since (1) it will be slower and
      77             :          * (2) it may re-allocate the contents buffer multiple times
      78             :          * so the data fits in there.
      79             :          */
      80             :         SIZE_MODE_READ,
      81             :     };
      82             : 
      83             : 
      84             :     /** \brief Initialize a content file.
      85             :      *
      86             :      * The constructor initialize the file content object with a filename.
      87             :      * The filename is used by the read_all() and write_all() functions.
      88             :      *
      89             :      * If the file_content is setup to be a temporary file, then the
      90             :      * destructor also makes use of the filename to delete the file
      91             :      * at that time. By default a file is not marked as temporary.
      92             :      *
      93             :      * \exception std::invalid_argument
      94             :      * The \p filename parameter cannot be an empty string.
      95             :      *
      96             :      * \exception std::ios::failure
      97             :      * The function checks whether all the folders exist. If not then the
      98             :      * file can't be create or read so there is no valid file_content()
      99             :      * possible with that path. This exception only occurs if the
     100             :      * \p create_missing_directories is true and the creation of any
     101             :      * of the directories fails.
     102             :      *
     103             :      * \param[in] filename  The name of the file to read and write.
     104             :      * \param[in] create_missing_directories  Whether to create missing directories
     105             :      *            as found in the path (see mkdir_p()).
     106             :      * \param[in] temporary  Whether the file is temporary.
     107             :      *
     108             :      * \sa snap::mkdir_p()
     109             :      */
     110          15 :     file_contents(
     111             :               std::string const & filename
     112             :             , bool create_missing_directories = false
     113             :             , bool temporary = false)
     114          15 :         : f_filename(filename)
     115          17 :         , f_temporary(temporary)
     116             :     {
     117          15 :         if(f_filename.empty())
     118             :         {
     119           1 :             throw std::invalid_argument("snapdev::file_contents: the filename of a file_contents object cannot be the empty string.");
     120             :         }
     121             : 
     122          14 :         if(create_missing_directories)
     123             :         {
     124           3 :             int const r(mkdir_p(f_filename, true));
     125           3 :             if(r != 0)
     126             :             {
     127           1 :                 throw std::ios::failure("snapdev::file_contents: the full path to filename for a file_contents object could not be created");
     128             :             }
     129             :         }
     130          13 :     }
     131             : 
     132             : 
     133             :     /** \brief Clean up as required.
     134             :      *
     135             :      * If the file_content was marked as temporary, then the destructor
     136             :      * deletes the file on disk before returning.
     137             :      *
     138             :      * If the file does not exist and it was marked as temporary, the
     139             :      * deletion will fail silently. Otherwise you get a warning in the
     140             :      * logs with more details about the error (i.e. permission, bad
     141             :      * filename, etc.)
     142             :      */
     143          13 :     ~file_contents()
     144          13 :     {
     145          13 :         if(f_temporary)
     146             :         {
     147           2 :             if(unlink(f_filename.c_str()) != 0)
     148             :             {
     149           1 :                 if(errno != ENOENT)
     150             :                 {
     151             :                     // TODO: what should we do here?!
     152             :                 }
     153             :             }
     154             :         }
     155          13 :     }
     156             : 
     157             : 
     158             :     /** \brief Retrieve the filename.
     159             :      *
     160             :      * This function returns a refrence to the filename used by this
     161             :      * object. Note that the filename cannot be modified.
     162             :      *
     163             :      * \return A reference to this file content filename.
     164             :      */
     165             :     std::string const & filename() const
     166             :     {
     167             :         return f_filename;
     168             :     }
     169             : 
     170             : 
     171             :     /** \brief Check whether this file exists.
     172             :      *
     173             :      * This function checks whether the file exists on disk. If the
     174             :      * read_all() function returns false and this function returns true,
     175             :      * then you probably do not have permissions to read the file or it
     176             :      * is a directory.
     177             :      *
     178             :      * \return true if the file exists and can be read.
     179             :      *
     180             :      * \sa read_all()
     181             :      */
     182             :     bool exists() const
     183             :     {
     184             :         return access(f_filename.c_str(), R_OK) == 0;
     185             :     }
     186             : 
     187             : 
     188             :     /** \brief Change the size mode.
     189             :      *
     190             :      * Certain files do not have a size that can be read using the seek()
     191             :      * function and tell(). This function can be used to change the mode
     192             :      * to instead read the entire file little by little until the EOF is
     193             :      * reached.
     194             :      *
     195             :      * By default, we expect you to be using a regular file so the mode
     196             :      * is set to size_mode_t::SIZE_MODE_SEEK.
     197             :      *
     198             :      * \param[in] mode  The new size mode.
     199             :      */
     200             :     void size_mode(size_mode_t mode)
     201             :     {
     202             :         f_size_mode = mode;
     203             :     }
     204             : 
     205             : 
     206             :     /** \brief Retrieve the current size mode.
     207             :      *
     208             :      * This function returns the current size mode for this file. The
     209             :      * mode is used to know whether we can use the seek() function or
     210             :      * not. If not, we just read the file up to EOF and return from
     211             :      * the read_all() function.
     212             :      *
     213             :      * \return The current size mode of this file_contents object
     214             :      * This function returns the current size mode for this file. The
     215             :      * mode is used to know whether we can use the seek() function or
     216             :      * not. If not, we just read the file up to EOF and return from
     217             :      * the read_all() function.
     218             :      *
     219             :      * \return The current size mode of this file_contents object.
     220             :      */
     221           4 :     size_mode_t size_mode() const
     222             :     {
     223           4 :         return f_size_mode;
     224             :     }
     225             : 
     226             : 
     227             :     /** \brief Read the entire file in a buffer.
     228             :      *
     229             :      * This function reads the entire file in memory. It saves the data
     230             :      * in a buffer which you can get with the contents() function.
     231             :      * The file can be binary in which case remember that c_str()
     232             :      * and other similar function will not work right.
     233             :      *
     234             :      * The function may return false if the file could not be opened or
     235             :      * read in full.
     236             :      *
     237             :      * \return true if the file could be read, false otherwise.
     238             :      *
     239             :      * \sa write_all()
     240             :      * \sa contents() const
     241             :      * \sa last_error()
     242             :      */
     243           6 :     bool read_all()
     244             :     {
     245             :         // try to open the file
     246             :         //
     247          12 :         std::ifstream in;
     248           6 :         in.open(f_filename, std::ios::in | std::ios::binary);
     249           6 :         if(!in.is_open())
     250             :         {
     251           4 :             f_error = "could not open file \""
     252           4 :                 + f_filename
     253           6 :                 + "\" for reading.";
     254           2 :             return false;
     255             :         }
     256             : 
     257             :         // get size
     258             :         //
     259           4 :         std::ifstream::pos_type size(0);
     260           4 :         size_mode_t mode(size_mode());
     261           4 :         if(mode == size_mode_t::SIZE_MODE_SEEK)
     262             :         {
     263           4 :             in.seekg(0, std::ios::end);
     264           4 :             size = in.tellg();
     265           4 :             in.seekg(0, std::ios::beg);
     266           4 :             if(std::ifstream::pos_type(-1) == size)
     267             :             {
     268             :                 // try again in READ mode
     269             :                 //
     270           1 :                 mode = size_mode_t::SIZE_MODE_READ;
     271           1 :                 in.clear();
     272             :             }
     273             :         }
     274             : 
     275           4 :         if(mode == size_mode_t::SIZE_MODE_READ)
     276             :         {
     277             :             // on certain files, the "get size" fails (i.e. "/proc/...",
     278             :             // FIFO, socket, etc.)
     279             :             //
     280           1 :             f_contents.clear();
     281           1 :             char buf[1024 * 16];
     282           0 :             do
     283             :             {
     284           1 :                 in.read(buf, sizeof(buf));
     285           1 :                 ssize_t sz(in.gcount());
     286           1 :                 if(sz <= 0)
     287             :                 {
     288           0 :                     break;
     289             :                 }
     290           1 :                 f_contents.insert(f_contents.end(), buf, buf + sz);
     291             :             }
     292           1 :             while(in.good());
     293             :         }
     294             :         else
     295             :         {
     296             :             // resize the buffer accordingly
     297             :             //
     298             :             try
     299             :             {
     300           3 :                 f_contents.resize(size, '\0');
     301             :             }
     302             :             catch(std::bad_alloc const & e)                         // LCOV_EXCL_LINE
     303             :             {
     304             :                 f_error = "cannot allocate buffer of "              // LCOV_EXCL_LINE
     305             :                     + std::to_string(size)                          // LCOV_EXCL_LINE
     306             :                     + " bytes to read file.";                       // LCOV_EXCL_LINE
     307             :                 f_contents.clear();                                 // LCOV_EXCL_LINE
     308             :                 return false;                                       // LCOV_EXCL_LINE
     309             :             }
     310             : 
     311             :             // read the data
     312             :             //
     313           3 :             in.read(f_contents.data(), size);
     314             :         }
     315             : 
     316           4 :         if(in.bad()) // eof() always true, fail() may be true too (in case we can't gather the size() above)
     317             :         {
     318             :             f_error = "an I/O error occurred reading \""        // LCOV_EXCL_LINE
     319             :                 + f_filename                                    // LCOV_EXCL_LINE
     320             :                 + "\".";                                        // LCOV_EXCL_LINE
     321             :             return false;                                       // LCOV_EXCL_LINE
     322             :         }
     323             : 
     324           4 :         f_error.clear();
     325             : 
     326           4 :         return true;
     327             :     }
     328             : 
     329             : 
     330             :     /** \brief Write the contents to the file.
     331             :      *
     332             :      * This function writes the file contents data to the file. If a new
     333             :      * filename is specified, the contents is saved there instead. Note
     334             :      * that the filename does not get modified, but it allows for creating
     335             :      * a backup before making changes and save the new file:
     336             :      *
     337             :      * \code
     338             :      *      file_content fc("filename.here");
     339             :      *      fc.read_all();
     340             :      *      fc.write_all("filename.bak");  // create backup
     341             :      *      std::string content(fc.contents());
     342             :      *      ... // make changes on 'contents'
     343             :      *      fc.contents(contents);
     344             :      *      fc.write_all();
     345             :      * \endcode
     346             :      *
     347             :      * \warning
     348             :      * If you marked the file_contents object as managing a temporary file
     349             :      * and specify a filename here which is not exactly equal to the
     350             :      * filename passed to the constructor, then the file you are writing
     351             :      * now will not be deleted automatically.
     352             :      *
     353             :      * \param[in] filename  The name to use or an empty string (or nothing)
     354             :      *                      to use the filename defined in the constructor.
     355             :      *
     356             :      * \return true if the file was saved successfully.
     357             :      *
     358             :      * \sa contents()
     359             :      * \sa contents(std::string const & new_contents)
     360             :      * \sa read_all()
     361             :      * \sa last_error()
     362             :      */
     363           6 :     bool write_all(std::string const & filename = std::string())
     364             :     {
     365             :         // select filename
     366             :         //
     367          12 :         std::string const name(filename.empty() ? f_filename : filename);
     368             : 
     369             :         // try to open the file
     370             :         //
     371          12 :         std::ofstream out;
     372           6 :         out.open(name, std::ios::trunc | std::ios::out | std::ios::binary);
     373           6 :         if(!out.is_open())
     374             :         {
     375           2 :             f_error = "could not open file \""
     376           2 :                 + name
     377           3 :                 + "\" for writing.";
     378           1 :             return false;
     379             :         }
     380             : 
     381             :         // write the data
     382             :         //
     383           5 :         out.write(f_contents.data(), f_contents.length());
     384             : 
     385           5 :         if(out.fail())
     386             :         {
     387             :             f_error = "could not write "                    // LCOV_EXCL_LINE
     388             :                 + std::to_string(f_contents.length())       // LCOV_EXCL_LINE
     389             :                 + " bytes to \"" + name + "\".";            // LCOV_EXCL_LINE
     390             :             return false;                                   // LCOV_EXCL_LINE
     391             :         }
     392             : 
     393           5 :         f_error.clear();
     394             : 
     395           5 :         return true;
     396             :     }
     397             : 
     398             : 
     399             :     /** \brief Change the contents with \p new_contents.
     400             :      *
     401             :      * This function replaces the current file contents with
     402             :      * \p new_contents.
     403             :      *
     404             :      * If \p new_contents is empty, then the file will become empty on a
     405             :      * write_all(). If you instead wanted to delete the file on
     406             :      * destruction of the function, set the temporary flag of the
     407             :      * construction to true to do that.
     408             :      *
     409             :      * \param[in] new_contents  The new contents of the file.
     410             :      *
     411             :      * \sa contents()
     412             :      * \sa write_all()
     413             :      */
     414           6 :     void contents(std::string const & new_contents)
     415             :     {
     416           6 :         f_contents = new_contents;
     417           6 :     }
     418             : 
     419             : 
     420             :     /** \brief Get a constant reference to the contents.
     421             :      *
     422             :      * This function gives you read access to the existing contents of the
     423             :      * file.
     424             :      *
     425             :      * The string represents the contents of the file only if you called
     426             :      * the read_all() function first. It is not mandatory to do so in case
     427             :      * you are creating a new file.
     428             :      *
     429             :      * \return A constant reference to this file contents.
     430             :      *
     431             :      * \sa contents(std::string const & new_contents)
     432             :      * \sa read_all()
     433             :      */
     434           2 :     std::string const & contents() const
     435             :     {
     436           2 :         return f_contents;
     437             :     }
     438             : 
     439             : 
     440             :     /** \brief Get a reference to the contents.
     441             :      *
     442             :      * This function gives you direct read/write access to the existing
     443             :      * contents of the file.
     444             :      *
     445             :      * The string represents the contents of the file only if you called
     446             :      * the read_all() function first. It is not mandatory to do so in case
     447             :      * you are creating a new file.
     448             :      *
     449             :      * \return A constant reference to this file contents.
     450             :      *
     451             :      * \sa contents() const
     452             :      * \sa contents(std::string const & new_contents)
     453             :      * \sa read_all()
     454             :      */
     455           4 :     std::string & contents()
     456             :     {
     457           4 :         return f_contents;
     458             :     }
     459             : 
     460             : 
     461             :     /** \brief Retrieve the last error message.
     462             :      *
     463             :      * This function returns a copy of the last error message generated
     464             :      * by one of read_all() or write_all().
     465             :      *
     466             :      * \return The last error generated.
     467             :      */
     468           1 :     std::string const & last_error() const
     469             :     {
     470           1 :         return f_error;
     471             :     }
     472             : 
     473             : private:
     474             :     std::string         f_filename = std::string();
     475             :     std::string         f_contents = std::string();
     476             :     std::string         f_error = std::string();
     477             :     size_mode_t         f_size_mode = size_mode_t::SIZE_MODE_SEEK;
     478             :     bool                f_temporary = false;
     479             : };
     480             : 
     481             : 
     482             : 
     483             : } // namespace snapdev
     484             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13