1 /*
2  * Copyright (C) 2011-2019 Daniel Scharrer
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the author(s) be held liable for any damages
6  * arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must not
13  *    claim that you wrote the original software. If you use this software
14  *    in a product, an acknowledgment in the product documentation would be
15  *    appreciated but is not required.
16  * 2. Altered source versions must be plainly marked as such, and must not be
17  *    misrepresented as being the original software.
18  * 3. This notice may not be removed or altered from any source distribution.
19  */
20 
21 #include "stream/slice.hpp"
22 
23 #include <sstream>
24 #include <cstring>
25 #include <limits>
26 
27 #include <boost/cstdint.hpp>
28 #include <boost/algorithm/string/predicate.hpp>
29 #include <boost/filesystem/operations.hpp>
30 #include <boost/range/size.hpp>
31 
32 #include "util/console.hpp"
33 #include "util/load.hpp"
34 #include "util/log.hpp"
35 
36 namespace stream {
37 
38 namespace {
39 
40 const char slice_ids[][8] = {
41 	{ 'i', 'd', 's', 'k', 'a', '1', '6', 0x1a },
42 	{ 'i', 'd', 's', 'k', 'a', '3', '2', 0x1a },
43 };
44 
45 } // anonymous namespace
46 
slice_reader(std::istream * istream,boost::uint32_t offset)47 slice_reader::slice_reader(std::istream * istream, boost::uint32_t offset)
48 	: data_offset(offset),
49 	  slices_per_disk(1), current_slice(0), slice_size(0),
50 	  is(istream) {
51 
52 	std::streampos max_size = std::streampos(std::numeric_limits<boost::int32_t>::max());
53 
54 	std::streampos file_size = is->seekg(0, std::ios_base::end).tellg();
55 
56 	slice_size = boost::uint32_t(std::min(file_size, max_size));
57 	if(is->seekg(data_offset).fail()) {
58 		throw slice_error("could not seek to data");
59 	}
60 }
61 
slice_reader(const path_type & dirname,const std::string & basename,const std::string & basename2,size_t disk_slice_count)62 slice_reader::slice_reader(const path_type & dirname, const std::string & basename,
63                            const std::string & basename2, size_t disk_slice_count)
64 	: data_offset(0),
65 	  dir(dirname), base_file(basename), base_file2(basename2),
66 	  slices_per_disk(disk_slice_count), current_slice(0), slice_size(0),
67 	  is(&ifs) { }
68 
seek(size_t slice)69 void slice_reader::seek(size_t slice) {
70 
71 	if(slice == current_slice && is_open()) {
72 		return;
73 	}
74 
75 	if(data_offset != 0) {
76 		throw slice_error("cannot change slices in single-file setup");
77 	}
78 
79 	open(slice);
80 }
81 
open_file(const path_type & file)82 bool slice_reader::open_file(const path_type & file) {
83 
84 	if(!boost::filesystem::exists(file)) {
85 		return false;
86 	}
87 
88 	log_info << "Opening \"" << color::cyan << file.string() << color::reset << '"';
89 
90 	ifs.close();
91 	ifs.clear();
92 
93 	ifs.open(file, std::ios_base::in | std::ios_base::binary | std::ios_base::ate);
94 	if(ifs.fail()) {
95 		return false;
96 	}
97 
98 	std::streampos file_size = ifs.tellg();
99 	ifs.seekg(0);
100 
101 	char magic[8];
102 	if(ifs.read(magic, 8).fail()) {
103 		ifs.close();
104 		throw slice_error("could not read slice magic number in \"" + file.string() + "\"");
105 	}
106 	bool found = false;
107 	for(size_t i = 0; boost::size(slice_ids); i++) {
108 		if(!std::memcmp(magic, slice_ids[i], 8)) {
109 			found = true;
110 			break;
111 		}
112 	}
113 	if(!found) {
114 		ifs.close();
115 		throw slice_error("bad slice magic number in \"" + file.string() + "\"");
116 	}
117 
118 	slice_size = util::load<boost::uint32_t>(ifs);
119 	if(ifs.fail()) {
120 		ifs.close();
121 		throw slice_error("could not read slice size in \"" + file.string() + "\"");
122 	} else if(std::streampos(slice_size) > file_size) {
123 		ifs.close();
124 		std::ostringstream oss;
125 		oss << "bad slice size in " << file << ": " << slice_size << " > " << file_size;
126 		throw slice_error(oss.str());
127 	} else if(std::streampos(slice_size) < ifs.tellg()) {
128 		ifs.close();
129 		std::ostringstream oss;
130 		oss << "bad slice size in " << file << ": " << slice_size << " < " << ifs.tellg();
131 		throw slice_error(oss.str());
132 	}
133 
134 	return true;
135 }
136 
slice_filename(const std::string & basename,size_t slice,size_t slices_per_disk)137 std::string slice_reader::slice_filename(const std::string & basename, size_t slice,
138                                          size_t slices_per_disk) {
139 
140 	std::ostringstream oss;
141 	oss << basename << '-';
142 
143 	if(slices_per_disk == 0) {
144 		throw slice_error("slices per disk must not be zero");
145 	}
146 
147 	if(slices_per_disk == 1) {
148 		oss << (slice + 1);
149 	} else {
150 		size_t major = (slice / slices_per_disk) + 1;
151 		size_t minor = slice % slices_per_disk;
152 		oss << major << char(boost::uint8_t('a') + minor);
153 	}
154 
155 	oss << ".bin";
156 
157 	return oss.str();
158 }
159 
open_file_case_insensitive(const path_type & dirname,const path_type & filename)160 bool slice_reader::open_file_case_insensitive(const path_type & dirname, const path_type & filename) {
161 
162 	boost::filesystem::directory_iterator end;
163 	for(boost::filesystem::directory_iterator i(dirname); i != end; ++i) {
164 		path_type actual_filename = i->path().filename();
165 		if(boost::iequals(actual_filename.string(), filename.string()) && open_file(dirname / actual_filename)) {
166 			return true;
167 		}
168 	}
169 
170 	return false;
171 }
172 
open(size_t slice)173 void slice_reader::open(size_t slice) {
174 
175 	current_slice = slice;
176 	is = &ifs;
177 	ifs.close();
178 
179 	path_type slice_file = slice_filename(base_file, slice, slices_per_disk);
180 	if(open_file(dir / slice_file)) {
181 		return;
182 	}
183 
184 	path_type slice_file2 = slice_filename(base_file2, slice, slices_per_disk);
185 	if(!base_file2.empty() && slice_file2 != slice_file && open_file(dir / slice_file2)) {
186 		return;
187 	}
188 
189 	if(open_file_case_insensitive(dir, slice_file)) {
190 		return;
191 	}
192 
193 	if(!base_file2.empty() && slice_file2 != slice_file && open_file_case_insensitive(dir, slice_file2)) {
194 		return;
195 	}
196 
197 	std::ostringstream oss;
198 	oss << "could not open slice " << slice << ": " << slice_file;
199 	if(!base_file2.empty() && slice_file2 != slice_file) {
200 		oss << " or " << slice_file2;
201 	}
202 	throw slice_error(oss.str());
203 }
204 
seek(size_t slice,boost::uint32_t offset)205 bool slice_reader::seek(size_t slice, boost::uint32_t offset) {
206 
207 	seek(slice);
208 
209 	offset += data_offset;
210 
211 	if(offset > slice_size) {
212 		return false;
213 	}
214 
215 	if(is->seekg(offset).fail()) {
216 		return false;
217 	}
218 
219 	return true;
220 }
221 
read(char * buffer,std::streamsize bytes)222 std::streamsize slice_reader::read(char * buffer, std::streamsize bytes) {
223 
224 	seek(current_slice);
225 
226 	std::streamsize nread = 0;
227 
228 	while(bytes > 0) {
229 
230 		boost::uint32_t read_pos = boost::uint32_t(is->tellg());
231 		if(read_pos > slice_size) {
232 			break;
233 		}
234 		boost::uint32_t remaining = slice_size - read_pos;
235 		if(!remaining) {
236 			seek(current_slice + 1);
237 			read_pos = boost::uint32_t(is->tellg());
238 			if(read_pos > slice_size) {
239 				break;
240 			}
241 			remaining = slice_size - read_pos;
242 		}
243 
244 		boost::uint64_t toread = std::min(boost::uint64_t(remaining), boost::uint64_t(bytes));
245 		toread = std::min(toread, boost::uint64_t(std::numeric_limits<std::streamsize>::max()));
246 		if(is->read(buffer, std::streamsize(toread)).fail()) {
247 			break;
248 		}
249 
250 		std::streamsize read = is->gcount();
251 		nread += read, buffer += read, bytes -= read;
252 	}
253 
254 	return (nread != 0 || bytes == 0) ? nread : -1;
255 }
256 
257 } // namespace stream
258