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 188053 : static void check_mode(const std::string& filename, std::ios_base::openmode mode)
133 : {
134 188053 : 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 188053 : 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 188053 : 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 188053 : }
147 188053 : static void check_open(std::ios * s_p, const std::string& filename, std::ios_base::openmode mode)
148 : {
149 188053 : 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 188052 : }
156 188019 : 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 188019 : is_p->peek();
162 188019 : peek_failed = is_p->fail();
163 : }
164 0 : catch (const std::ios_base::failure &) {}
165 188019 : 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 188019 : is_p->clear();
172 188019 : }
173 : }; // struct static_method_holder
174 :
175 : } // namespace detail
176 :
177 : class ifstream
178 : : public std::ifstream
179 : {
180 : public:
181 : ifstream() = default;
182 188019 : ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
183 188019 : {
184 188019 : open(filename, mode);
185 188019 : }
186 188019 : void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
187 : {
188 : mode |= std::ios_base::in;
189 188019 : exceptions(std::ios_base::badbit);
190 188019 : detail::static_method_holder::check_mode(filename, mode);
191 188019 : std::ifstream::open(filename, mode);
192 188019 : detail::static_method_holder::check_open(this, filename, mode);
193 188019 : detail::static_method_holder::check_peek(this, filename, mode);
194 188019 : }
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
|