LCOV - code coverage report
Current view: top level - snapdev - file_contents.h (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 76 76
Test Date: 2026-02-16 17:48:13 Functions: 100.0 % 9 9
Legend: Lines: hit not hit

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

Generated by: LCOV version 2.0-1

Snap C++ | List of projects | List of versions