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