source.cpp (10735B)
1 #include "source.hpp" 2 3 #include "sink.hpp" 4 5 #include <libshit/doctest.hpp> 6 #include <libshit/except.hpp> 7 #include <libshit/lua/function_call.hpp> 8 #include <libshit/platform.hpp> 9 #include <libshit/string_utils.hpp> 10 11 #include <fstream> 12 #include <iostream> 13 14 #if !LIBSHIT_OS_IS_WINDOWS 15 # include <unistd.h> 16 #endif 17 18 #define LIBSHIT_LOG_NAME "source" 19 #include <libshit/logger_helper.hpp> 20 21 namespace Neptools 22 { 23 TEST_SUITE_BEGIN("Neptools::Source"); 24 25 namespace 26 { 27 28 template <typename T> 29 struct UnixLike : public Source::Provider 30 { 31 UnixLike(Libshit::LowIo io, boost::filesystem::path file_name, 32 FilePosition size) 33 : Source::Provider{Libshit::Move(file_name), size}, 34 io{Libshit::Move(io)} {} 35 36 void Destroy() noexcept; 37 38 void Pread(FilePosition offs, Byte* buf, FileMemSize len) override; 39 void EnsureChunk(FilePosition i); 40 41 Libshit::LowIo io; 42 }; 43 44 struct MmapProvider final : public UnixLike<MmapProvider> 45 { 46 MmapProvider(Libshit::LowIo&& fd, boost::filesystem::path file_name, 47 FilePosition size); 48 ~MmapProvider() noexcept override { Destroy(); } 49 50 static FileMemSize CHUNK_SIZE; 51 void* ReadChunk(FilePosition offs, FileMemSize size); 52 void DeleteChunk(size_t i); 53 }; 54 55 struct UnixProvider final : public UnixLike<UnixProvider> 56 { 57 //using UnixLike::UnixLike; 58 // workaround clang bug... 59 UnixProvider(Libshit::LowIo&& io, boost::filesystem::path file_name, 60 FilePosition size) 61 : UnixLike{Libshit::Move(io), Libshit::Move(file_name), size} {} 62 ~UnixProvider() noexcept override { Destroy(); } 63 64 static FileMemSize CHUNK_SIZE; 65 void* ReadChunk(FilePosition offs, FileMemSize size); 66 void DeleteChunk(size_t i); 67 }; 68 69 struct StringProvider final : public Source::Provider 70 { 71 StringProvider(boost::filesystem::path file_name, std::string str) 72 : Source::Provider(Libshit::Move(file_name), str.size()), 73 str{Libshit::Move(str)} 74 { 75 LruPush(reinterpret_cast<const Byte*>(this->str.data()), 76 0, this->str.size()); 77 } 78 79 void Pread(FilePosition, Byte*, FileMemSize) override 80 { LIBSHIT_UNREACHABLE("StringProvider Pread"); } 81 82 std::string str; 83 }; 84 85 struct UniquePtrProvider final : public Source::Provider 86 { 87 UniquePtrProvider(boost::filesystem::path file_name, 88 std::unique_ptr<char[]> data, std::size_t len) 89 : Source::Provider(Libshit::Move(file_name), len), 90 data{Libshit::Move(data)} 91 { LruPush(reinterpret_cast<const Byte*>(this->data.get()), 0, len); } 92 93 void Pread(FilePosition, Byte*, FileMemSize) override 94 { LIBSHIT_UNREACHABLE("UniquePtrProvider Pread"); } 95 96 std::unique_ptr<char[]> data; 97 }; 98 99 } 100 101 102 Source Source::FromFile(const boost::filesystem::path& fname) 103 { 104 LIBSHIT_ADD_INFOS(return FromFile_(fname), "File name", fname.string()); 105 } 106 107 Source Source::FromFile_(const boost::filesystem::path& fname) 108 { 109 Libshit::LowIo io{fname.c_str(), Libshit::LowIo::Permission::READ_ONLY, 110 Libshit::LowIo::Mode::OPEN_ONLY}; 111 112 FilePosition size = io.GetSize(); 113 114 Libshit::SmartPtr<Provider> p; 115 try { p = Libshit::MakeSmart<MmapProvider>(Libshit::Move(io), fname, size); } 116 catch (const Libshit::SystemError& e) 117 { 118 WARN << "Mmap failed, falling back to normal reading: " 119 << Libshit::PrintException(true) << std::endl; 120 p = Libshit::MakeSmart<UnixProvider>(Libshit::Move(io), fname, size); 121 } 122 return Libshit::MakeNotNull(Libshit::Move(p)); 123 } 124 125 Source Source::FromFd( 126 boost::filesystem::path fname, Libshit::LowIo::FdType fd, bool owning) 127 { 128 Libshit::LowIo io{fd, owning}; 129 auto size = io.GetSize(); 130 return {Libshit::MakeSmart<UnixProvider>( 131 Libshit::Move(io), Libshit::Move(fname), size)}; 132 } 133 134 Source Source::FromMemory(boost::filesystem::path fname, std::string str) 135 { 136 return {Libshit::MakeSmart<StringProvider>( 137 Libshit::Move(fname), Libshit::Move(str))}; 138 } 139 140 Source Source::FromMemory(boost::filesystem::path fname, 141 std::unique_ptr<char[]> data, std::size_t len) 142 { 143 return {Libshit::MakeSmart<UniquePtrProvider>( 144 Libshit::Move(fname), Libshit::Move(data), len)}; 145 } 146 147 148 void Source::Pread_(FilePosition offs, Byte* buf, FileMemSize len) const 149 { 150 offs += offset; 151 while (len) 152 { 153 if (p->LruGet(offs)) 154 { 155 auto& x = p->lru[0]; 156 auto buf_offs = offs - x.offset; 157 auto to_cpy = std::min<FilePosition>(len, x.size - buf_offs); 158 memcpy(buf, x.ptr + buf_offs, to_cpy); 159 offs += to_cpy; 160 buf += to_cpy; 161 len -= to_cpy; 162 } 163 else 164 return p->Pread(offs, buf, len); 165 } 166 } 167 168 std::string Source::PreadCString(FilePosition offs) const 169 { 170 std::string ret; 171 Libshit::StringView e; 172 size_t len; 173 do 174 { 175 e = GetChunk(offs); 176 len = strnlen(e.data(), e.size()); 177 ret.append(e.data(), len); 178 offs += e.size(); 179 } while (len == e.size()); 180 return ret; 181 } 182 183 Source::BufEntry Source::GetTemporaryEntry(FilePosition offs) const 184 { 185 if (p->LruGet(offs)) return p->lru[0]; 186 p->Pread(offs, nullptr, 0); 187 LIBSHIT_ASSERT(p->lru[0].offset <= offs && 188 p->lru[0].offset + p->lru[0].size > offs); 189 return p->lru[0]; 190 } 191 192 Libshit::StringView Source::GetChunk(FilePosition offs) const 193 { 194 LIBSHIT_ASSERT(offs < size); 195 auto e = GetTemporaryEntry(offs + offset); 196 auto eoffs = offs + offset - e.offset; 197 auto size = std::min(e.size - eoffs, GetSize() - offs); 198 return { e.ptr + eoffs, std::size_t(size) }; 199 } 200 201 202 void Source::Provider::LruPush( 203 const Byte* ptr, FilePosition offset, FileMemSize size) 204 { 205 memmove(&lru[1], &lru[0], sizeof(BufEntry)*(lru.size()-1)); 206 lru[0].ptr = ptr; 207 lru[0].offset = offset; 208 lru[0].size = size; 209 } 210 211 bool Source::Provider::LruGet(FilePosition offs) 212 { 213 for (size_t i = 0; i < lru.size(); ++i) 214 { 215 auto x = lru[i]; 216 if (x.offset <= offs && x.offset + x.size > offs) 217 { 218 LIBSHIT_ASSERT(x.ptr); 219 memmove(&lru[1], &lru[0], sizeof(BufEntry)*i); 220 lru[0] = x; 221 return true; 222 } 223 } 224 return false; 225 } 226 227 template <typename T> 228 void UnixLike<T>::Destroy() noexcept 229 { 230 for (size_t i = 0; i < lru.size(); ++i) 231 if (lru[i].size) 232 static_cast<T*>(this)->DeleteChunk(i); 233 } 234 235 template <typename T> 236 void UnixLike<T>::Pread(FilePosition offs, Byte* buf, FileMemSize len) 237 { 238 if (len > static_cast<T*>(this)->CHUNK_SIZE) 239 return io.Pread(buf, len, offs); 240 241 if (len == 0) EnsureChunk(offs); // TODO: GetTemporaryEntry hack 242 while (len) 243 { 244 EnsureChunk(offs); 245 auto buf_offs = offs - lru[0].offset; 246 auto to_cpy = std::min<FilePosition>(len, lru[0].size - buf_offs); 247 memcpy(buf, lru[0].ptr + buf_offs, to_cpy); 248 buf += to_cpy; 249 offs += to_cpy; 250 len -= to_cpy; 251 } 252 } 253 254 template <typename T> 255 void UnixLike<T>::EnsureChunk(FilePosition offs) 256 { 257 auto const CHUNK_SIZE = static_cast<T*>(this)->CHUNK_SIZE; 258 auto ch_offs = offs/CHUNK_SIZE*CHUNK_SIZE; 259 if (LruGet(offs)) return; 260 261 auto size = std::min<FilePosition>(CHUNK_SIZE, this->size-ch_offs); 262 auto x = static_cast<T*>(this)->ReadChunk(ch_offs, size); 263 static_cast<T*>(this)->DeleteChunk(lru.size()-1); 264 LruPush(static_cast<Byte*>(x), ch_offs, size); 265 } 266 267 FileMemSize MmapProvider::CHUNK_SIZE = MMAP_CHUNK; 268 MmapProvider::MmapProvider( 269 Libshit::LowIo&& io, boost::filesystem::path file_name, FilePosition size) 270 : UnixLike{{}, Libshit::Move(file_name), size} 271 { 272 std::size_t to_map = size < MMAP_LIMIT ? size : MMAP_CHUNK; 273 274 io.PrepareMmap(false); 275 void* ptr = io.Mmap(0, to_map, false).Release(); 276 #if !LIBSHIT_OS_IS_WINDOWS 277 if (to_map == size) io.Reset(); 278 #endif 279 this->io = Libshit::Move(io); 280 281 lru[0].ptr = static_cast<Byte*>(ptr); 282 lru[0].offset = 0; 283 lru[0].size = to_map; 284 } 285 286 void* MmapProvider::ReadChunk(FilePosition offs, FileMemSize size) 287 { 288 return io.Mmap(offs, size, false).Release(); 289 } 290 291 void MmapProvider::DeleteChunk(size_t i) 292 { 293 if (lru[i].ptr) 294 Libshit::LowIo::Munmap(const_cast<Byte*>(lru[i].ptr), lru[i].size); 295 } 296 297 FileMemSize UnixProvider::CHUNK_SIZE = MEM_CHUNK; 298 299 void* UnixProvider::ReadChunk(FilePosition offs, FileMemSize size) 300 { 301 std::unique_ptr<Byte[]> x{new Byte[size]}; 302 io.Pread(x.get(), size, offs); 303 return x.release(); 304 } 305 306 void UnixProvider::DeleteChunk(size_t i) 307 { 308 delete[] lru[i].ptr; 309 } 310 311 void Source::Inspect(std::ostream& os) const 312 { 313 os << "neptools.source.from_memory(" 314 << Libshit::Quoted(GetFileName().string()) << ", " 315 << Quoted(*this) << ")"; 316 } 317 318 std::string Source::Inspect() const 319 { 320 std::stringstream ss; 321 Inspect(ss); 322 return ss.str(); 323 } 324 325 void Source::Dump(Sink& sink) const 326 { 327 FilePosition offset = 0; 328 auto size = GetSize(); 329 while (offset < size) 330 { 331 auto chunk = GetChunk(offset); 332 sink.Write(chunk); 333 offset += chunk.size(); 334 } 335 } 336 337 void DumpableSource::Inspect_(std::ostream& os, unsigned) const 338 { 339 os << "neptools.dumpable_source("; 340 src.Inspect(os); 341 os << ')'; 342 } 343 344 std::string to_string(const Source& src) 345 { 346 std::stringstream ss; 347 ss << src.GetFileName() << ", pos: " << src.Tell(); 348 return ss.str(); 349 } 350 351 #if LIBSHIT_WITH_LUA 352 353 LIBSHIT_LUAGEN(name: "read") 354 static Libshit::Lua::RetNum LuaRead( 355 Libshit::Lua::StateRef vm, Source& src, FileMemSize len) 356 { 357 std::unique_ptr<char[]> ptr{new char[len]}; 358 src.Read<Libshit::Check::Throw>(ptr.get(), len); 359 lua_pushlstring(vm, ptr.get(), len); 360 return {1}; 361 } 362 363 LIBSHIT_LUAGEN(name: "pread") 364 static Libshit::Lua::RetNum LuaPread( 365 Libshit::Lua::StateRef vm, Source& src, FilePosition offs, FileMemSize len) 366 { 367 std::unique_ptr<char[]> ptr{new char[len]}; 368 src.Pread<Libshit::Check::Throw>(offs, ptr.get(), len); 369 lua_pushlstring(vm, ptr.get(), len); 370 return {1}; 371 } 372 373 #endif 374 375 TEST_CASE("small source") 376 { 377 char buf[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; 378 std::ofstream os{"neptools_test.tmp", std::ios_base::binary}; 379 os.write(buf, 16); 380 os.close(); 381 382 auto src = Source::FromFile("neptools_test.tmp"); 383 char buf2[16]; 384 src.ReadGen(buf2); 385 REQUIRE(memcmp(buf, buf2, 16) == 0); 386 CHECK(src.Inspect() == 387 R"(neptools.source.from_memory("neptools_test.tmp", "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f"))"); 388 } 389 TEST_SUITE_END(); 390 } 391 392 #include "source.binding.hpp"