LCOV - code coverage report
Current view: top level - src/foreign/zstr - strict_fstream.hpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 88.1 % 59 52
Test Date: 2024-12-21 15:45:41 Functions: 100.0 % 11 11

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <cassert>
       4              : #include <fstream>
       5              : #include <cstring>
       6              : #include <string>
       7              : #include <vector>
       8              : 
       9              : /**
      10              :  * This namespace defines wrappers for std::ifstream, std::ofstream, and
      11              :  * std::fstream objects. The wrappers perform the following steps:
      12              :  * - check the open modes make sense
      13              :  * - check that the call to open() is successful
      14              :  * - (for input streams) check that the opened file is peek-able
      15              :  * - turn on the badbit in the exception mask
      16              :  */
      17              : namespace strict_fstream
      18              : {
      19              : 
      20              : // Help people out a bit, it seems like this is a common recommenation since
      21              : // musl breaks all over the place.
      22              : #if defined(__NEED_size_t) && !defined(__MUSL__)
      23              : #warning "It seems to be recommended to patch in a define for __MUSL__ if you use musl globally: https://www.openwall.com/lists/musl/2013/02/10/5"
      24              : #define __MUSL__
      25              : #endif
      26              : 
      27              : // Workaround for broken musl implementation
      28              : // Since musl insists that they are perfectly compatible, ironically enough,
      29              : // they don't officially have a __musl__ or similar. But __NEED_size_t is defined in their
      30              : // relevant header (and not in working implementations), so we can use that.
      31              : #ifdef __MUSL__
      32              : #warning "Working around broken strerror_r() implementation in musl, remove when musl is fixed"
      33              : #endif
      34              : 
      35              : // Non-gnu variants of strerror_* don't necessarily null-terminate if
      36              : // truncating, so we have to do things manually.
      37              : inline std::string trim_to_null(const std::vector<char> &buff)
      38              : {
      39              :     std::string ret(buff.begin(), buff.end());
      40              : 
      41              :     const std::string::size_type pos = ret.find('\0');
      42              :     if (pos == std::string::npos) {
      43              :         ret += " [...]"; // it has been truncated
      44              :     } else {
      45              :         ret.resize(pos);
      46              :     }
      47              :     return ret;
      48              : }
      49              : 
      50              : /// Overload of error-reporting function, to enable use with VS and non-GNU
      51              : /// POSIX libc's
      52              : /// Ref:
      53              : ///   - http://stackoverflow.com/a/901316/717706
      54            1 : static std::string strerror()
      55              : {
      56              :     // Can't use std::string since we're pre-C++17
      57            1 :     std::vector<char> buff(256, '\0');
      58              : 
      59              : #ifdef _WIN32
      60              :     // Since strerror_s might set errno itself, we need to store it.
      61              :     const int err_num = errno;
      62              :     if (strerror_s(buff.data(), buff.size(), err_num) != 0) {
      63              :         return trim_to_null(buff);
      64              :     } else {
      65              :         return "Unknown error (" + std::to_string(err_num) + ")";
      66              :     }
      67              : #elif ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || defined(__APPLE__)) && ! _GNU_SOURCE) || defined(__MUSL__)
      68              : // XSI-compliant strerror_r()
      69              :     const int err_num = errno; // See above
      70              :     if (strerror_r(err_num, buff.data(), buff.size()) == 0) {
      71              :         return trim_to_null(buff);
      72              :     } else {
      73              :         return "Unknown error (" + std::to_string(err_num) + ")";
      74              :     }
      75              : #else
      76              : // GNU-specific strerror_r()
      77            1 :     char * p = strerror_r(errno, &buff[0], buff.size());
      78            2 :     return std::string(p, std::strlen(p));
      79              : #endif
      80            1 : }
      81              : 
      82              : /// Exception class thrown by failed operations.
      83              : class Exception
      84              :     : public std::exception
      85              : {
      86              : public:
      87            2 :     Exception(const std::string& msg) : _msg(msg) {}
      88            1 :     const char * what() const noexcept { return _msg.c_str(); }
      89              : private:
      90              :     std::string _msg;
      91              : }; // class Exception
      92              : 
      93              : namespace detail
      94              : {
      95              : 
      96              : struct static_method_holder
      97              : {
      98            1 :     static std::string mode_to_string(std::ios_base::openmode mode)
      99              :     {
     100              :         static const int n_modes = 6;
     101              :         static const std::ios_base::openmode mode_val_v[n_modes] =
     102              :             {
     103              :                 std::ios_base::in,
     104              :                 std::ios_base::out,
     105              :                 std::ios_base::app,
     106              :                 std::ios_base::ate,
     107              :                 std::ios_base::trunc,
     108              :                 std::ios_base::binary
     109              :             };
     110              : 
     111              :         static const char * mode_name_v[n_modes] =
     112              :             {
     113              :                 "in",
     114              :                 "out",
     115              :                 "app",
     116              :                 "ate",
     117              :                 "trunc",
     118              :                 "binary"
     119              :             };
     120              :         std::string res;
     121            7 :         for (int i = 0; i < n_modes; ++i)
     122              :         {
     123            6 :             if (mode & mode_val_v[i])
     124              :             {
     125            2 :                 res += (! res.empty()? "|" : "");
     126            2 :                 res += mode_name_v[i];
     127              :             }
     128              :         }
     129            1 :         if (res.empty()) res = "none";
     130            1 :         return res;
     131              :     }
     132       205302 :     static void check_mode(const std::string& filename, std::ios_base::openmode mode)
     133              :     {
     134       205302 :         if ((mode & std::ios_base::trunc) && ! (mode & std::ios_base::out))
     135              :         {
     136            0 :             throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: trunc and not out");
     137              :         }
     138       205302 :         else if ((mode & std::ios_base::app) && ! (mode & std::ios_base::out))
     139              :         {
     140            0 :             throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: app and not out");
     141              :         }
     142       205302 :         else if ((mode & std::ios_base::trunc) && (mode & std::ios_base::app))
     143              :         {
     144            0 :             throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: trunc and app");
     145              :         }
     146       205302 :      }
     147       205302 :     static void check_open(std::ios * s_p, const std::string& filename, std::ios_base::openmode mode)
     148              :     {
     149       205302 :         if (s_p->fail())
     150              :         {
     151            1 :             throw Exception(std::string("strict_fstream: open('")
     152            3 :                             + filename + "'," + mode_to_string(mode) + "): open failed: "
     153            3 :                             + strerror());
     154              :         }
     155       205301 :     }
     156       205268 :     static void check_peek(std::istream * is_p, const std::string& filename, std::ios_base::openmode mode)
     157              :     {
     158              :         bool peek_failed = true;
     159              :         try
     160              :         {
     161       205268 :             is_p->peek();
     162       205268 :             peek_failed = is_p->fail();
     163              :         }
     164            0 :         catch (const std::ios_base::failure &) {}
     165       205268 :         if (peek_failed)
     166              :         {
     167            0 :             throw Exception(std::string("strict_fstream: open('")
     168            0 :                             + filename + "'," + mode_to_string(mode) + "): peek failed: "
     169            0 :                             + strerror());
     170              :         }
     171       205268 :         is_p->clear();
     172       205268 :     }
     173              : }; // struct static_method_holder
     174              : 
     175              : } // namespace detail
     176              : 
     177              : class ifstream
     178              :     : public std::ifstream
     179              : {
     180              : public:
     181              :     ifstream() = default;
     182       205268 :     ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     183       205268 :     {
     184       205268 :         open(filename, mode);
     185       205268 :     }
     186       205268 :     void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     187              :     {
     188              :         mode |= std::ios_base::in;
     189       205268 :         exceptions(std::ios_base::badbit);
     190       205268 :         detail::static_method_holder::check_mode(filename, mode);
     191       205268 :         std::ifstream::open(filename, mode);
     192       205268 :         detail::static_method_holder::check_open(this, filename, mode);
     193       205268 :         detail::static_method_holder::check_peek(this, filename, mode);
     194       205268 :     }
     195              : }; // class ifstream
     196              : 
     197              : class ofstream
     198              :     : public std::ofstream
     199              : {
     200              : public:
     201              :     ofstream() = default;
     202           34 :     ofstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out)
     203           34 :     {
     204           34 :         open(filename, mode);
     205           34 :     }
     206           34 :     void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out)
     207              :     {
     208              :         mode |= std::ios_base::out;
     209           34 :         exceptions(std::ios_base::badbit);
     210           34 :         detail::static_method_holder::check_mode(filename, mode);
     211           34 :         std::ofstream::open(filename, mode);
     212           34 :         detail::static_method_holder::check_open(this, filename, mode);
     213           33 :     }
     214              : }; // class ofstream
     215              : 
     216              : class fstream
     217              :     : public std::fstream
     218              : {
     219              : public:
     220              :     fstream() = default;
     221              :     fstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     222              :     {
     223              :         open(filename, mode);
     224              :     }
     225              :     void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     226              :     {
     227              :         if (! (mode & std::ios_base::out)) mode |= std::ios_base::in;
     228              :         exceptions(std::ios_base::badbit);
     229              :         detail::static_method_holder::check_mode(filename, mode);
     230              :         std::fstream::open(filename, mode);
     231              :         detail::static_method_holder::check_open(this, filename, mode);
     232              :         detail::static_method_holder::check_peek(this, filename, mode);
     233              :     }
     234              : }; // class fstream
     235              : 
     236              : } // namespace strict_fstream
        

Generated by: LCOV version 2.0-1