LCOV - code coverage report
Current view: top level - src/foreign/zstr - strict_fstream.hpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 51 58 87.9 %
Date: 2024-05-01 15:34:42 Functions: 11 11 100.0 %

          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             : }
      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      134788 :     static void check_mode(const std::string& filename, std::ios_base::openmode mode)
     133             :     {
     134      134788 :         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      134788 :         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      134788 :         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      134788 :      }
     147      134788 :     static void check_open(std::ios * s_p, const std::string& filename, std::ios_base::openmode mode)
     148             :     {
     149      134788 :         if (s_p->fail())
     150             :         {
     151           3 :             throw Exception(std::string("strict_fstream: open('")
     152           4 :                             + filename + "'," + mode_to_string(mode) + "): open failed: "
     153           4 :                             + strerror());
     154             :         }
     155      134787 :     }
     156      134724 :     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      134724 :             is_p->peek();
     162      134724 :             peek_failed = is_p->fail();
     163             :         }
     164           0 :         catch (const std::ios_base::failure &) {}
     165      134724 :         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      134724 :         is_p->clear();
     172      134724 :     }
     173             : }; // struct static_method_holder
     174             : 
     175             : } // namespace detail
     176             : 
     177             : class ifstream
     178             :     : public std::ifstream
     179             : {
     180             : public:
     181             :     ifstream() = default;
     182      134724 :     ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     183      134724 :     {
     184      134724 :         open(filename, mode);
     185      134724 :     }
     186      134724 :     void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     187             :     {
     188             :         mode |= std::ios_base::in;
     189      134724 :         exceptions(std::ios_base::badbit);
     190      134724 :         detail::static_method_holder::check_mode(filename, mode);
     191      134724 :         std::ifstream::open(filename, mode);
     192      134724 :         detail::static_method_holder::check_open(this, filename, mode);
     193      134724 :         detail::static_method_holder::check_peek(this, filename, mode);
     194      134724 :     }
     195             : }; // class ifstream
     196             : 
     197             : class ofstream
     198             :     : public std::ofstream
     199             : {
     200             : public:
     201             :     ofstream() = default;
     202          64 :     ofstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out)
     203          64 :     {
     204          64 :         open(filename, mode);
     205          64 :     }
     206          64 :     void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out)
     207             :     {
     208             :         mode |= std::ios_base::out;
     209          64 :         exceptions(std::ios_base::badbit);
     210          64 :         detail::static_method_holder::check_mode(filename, mode);
     211          64 :         std::ofstream::open(filename, mode);
     212          64 :         detail::static_method_holder::check_open(this, filename, mode);
     213          63 :     }
     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 1.14