1 /*
2 * Copyright (C) 2012, 2015 Andrew Ayer
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 *
22 * Except as contained in this notice, the name(s) of the above copyright
23 * holders shall not be used in advertising or otherwise to promote the
24 * sale, use or other dealings in this Software without prior written
25 * authorization.
26 */
27
28 #include <cstring>
29 #include <algorithm> // for std::min
30
31 #include "fhstream.hpp"
32
33 /*
34 * ofhstream
35 */
36
ofhbuf(void * arg_handle,size_t (* arg_write_fun)(void *,const void *,size_t))37 ofhbuf::ofhbuf (void* arg_handle, size_t (*arg_write_fun)(void*, const void*, size_t))
38 : handle(arg_handle),
39 write_fun(arg_write_fun),
40 buffer(new char[default_buffer_size]),
41 buffer_size(default_buffer_size)
42 {
43 reset_buffer();
44 }
45
~ofhbuf()46 ofhbuf::~ofhbuf ()
47 {
48 if (handle) {
49 try {
50 sync();
51 } catch (...) {
52 // Ignore exception since we're in the destructor.
53 // To catch write errors, call sync() explicitly.
54 }
55 }
56 delete[] buffer;
57 }
58
overflow(ofhbuf::int_type c)59 ofhbuf::int_type ofhbuf::overflow (ofhbuf::int_type c)
60 {
61 const char* p = pbase();
62 std::streamsize bytes_to_write = pptr() - p;
63
64 if (!is_eof(c)) {
65 *pptr() = c;
66 ++bytes_to_write;
67 }
68
69 while (bytes_to_write > 0) {
70 const size_t bytes_written = write_fun(handle, p, bytes_to_write);
71 bytes_to_write -= bytes_written;
72 p += bytes_written;
73 }
74
75 reset_buffer();
76
77 return traits_type::to_int_type(0);
78 }
79
sync()80 int ofhbuf::sync ()
81 {
82 return !is_eof(overflow(traits_type::eof())) ? 0 : -1;
83 }
84
xsputn(const char * s,std::streamsize n)85 std::streamsize ofhbuf::xsputn (const char* s, std::streamsize n)
86 {
87 // Use heuristic to decide whether to write directly or just use buffer
88 // Write directly only if n >= MIN(4096, available buffer capacity)
89 // (this is similar to what basic_filebuf does)
90
91 if (n < std::min<std::streamsize>(4096, epptr() - pptr())) {
92 // Not worth it to do a direct write
93 return std::streambuf::xsputn(s, n);
94 }
95
96 // Before we can do a direct write of this string, we need to flush
97 // out the current contents of the buffer.
98 if (pbase() != pptr()) {
99 overflow(traits_type::eof()); // throws an exception or it succeeds
100 }
101
102 // Now we can go ahead and write out the string.
103 size_t bytes_to_write = n;
104
105 while (bytes_to_write > 0) {
106 const size_t bytes_written = write_fun(handle, s, bytes_to_write);
107 bytes_to_write -= bytes_written;
108 s += bytes_written;
109 }
110
111 return n; // Return the total bytes written
112 }
113
setbuf(char * s,std::streamsize n)114 std::streambuf* ofhbuf::setbuf (char* s, std::streamsize n)
115 {
116 if (s == 0 && n == 0) {
117 // Switch to unbuffered
118 // This won't take effect until the next overflow or sync
119 // (We defer it taking effect so that write errors can be properly reported)
120 // To cause it to take effect as soon as possible, we artificially reduce the
121 // size of the buffer so it has no space left. This will trigger an overflow
122 // on the next put.
123 std::streambuf::setp(pbase(), pptr());
124 std::streambuf::pbump(pptr() - pbase());
125 buffer_size = 1;
126 }
127 return this;
128 }
129
130
131
132 /*
133 * ifhstream
134 */
135
ifhbuf(void * arg_handle,size_t (* arg_read_fun)(void *,void *,size_t))136 ifhbuf::ifhbuf (void* arg_handle, size_t (*arg_read_fun)(void*, void*, size_t))
137 : handle(arg_handle),
138 read_fun(arg_read_fun),
139 buffer(new char[default_buffer_size + putback_size]),
140 buffer_size(default_buffer_size)
141 {
142 reset_buffer(0, 0);
143 }
144
~ifhbuf()145 ifhbuf::~ifhbuf ()
146 {
147 delete[] buffer;
148 }
149
underflow()150 ifhbuf::int_type ifhbuf::underflow ()
151 {
152 if (gptr() >= egptr()) { // A true underflow (no bytes in buffer left to read)
153
154 // Move the putback_size most-recently-read characters into the putback area
155 size_t nputback = std::min<size_t>(gptr() - eback(), putback_size);
156 std::memmove(buffer + (putback_size - nputback), gptr() - nputback, nputback);
157
158 // Now read new characters from the file descriptor
159 const size_t nread = read_fun(handle, buffer + putback_size, buffer_size);
160 if (nread == 0) {
161 // EOF
162 return traits_type::eof();
163 }
164
165 // Reset the buffer
166 reset_buffer(nputback, nread);
167 }
168
169 // Return the next character
170 return traits_type::to_int_type(*gptr());
171 }
172
xsgetn(char * s,std::streamsize n)173 std::streamsize ifhbuf::xsgetn (char* s, std::streamsize n)
174 {
175 // Use heuristic to decide whether to read directly
176 // Read directly only if n >= bytes_available + 4096
177
178 std::streamsize bytes_available = egptr() - gptr();
179
180 if (n < bytes_available + 4096) {
181 // Not worth it to do a direct read
182 return std::streambuf::xsgetn(s, n);
183 }
184
185 std::streamsize total_bytes_read = 0;
186
187 // First, copy out the bytes currently in the buffer
188 std::memcpy(s, gptr(), bytes_available);
189
190 s += bytes_available;
191 n -= bytes_available;
192 total_bytes_read += bytes_available;
193
194 // Now do the direct read
195 while (n > 0) {
196 const size_t bytes_read = read_fun(handle, s, n);
197 if (bytes_read == 0) {
198 // EOF
199 break;
200 }
201
202 s += bytes_read;
203 n -= bytes_read;
204 total_bytes_read += bytes_read;
205 }
206
207 // Fill up the putback area with the most recently read characters
208 size_t nputback = std::min<size_t>(total_bytes_read, putback_size);
209 std::memcpy(buffer + (putback_size - nputback), s - nputback, nputback);
210
211 // Reset the buffer with no bytes available for reading, but with some putback characters
212 reset_buffer(nputback, 0);
213
214 // Return the total number of bytes read
215 return total_bytes_read;
216 }
217
setbuf(char * s,std::streamsize n)218 std::streambuf* ifhbuf::setbuf (char* s, std::streamsize n)
219 {
220 if (s == 0 && n == 0) {
221 // Switch to unbuffered
222 // This won't take effect until the next underflow (we don't want to
223 // lose what's currently in the buffer!)
224 buffer_size = 1;
225 }
226 return this;
227 }
228