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