add posix_part_file, specific to posix_storage. It uses fopen/fclose and is not thread safe

This commit is contained in:
Arvid Norberg
2020-11-15 15:16:42 +01:00
committed by Arvid Norberg
parent 7f26e50bef
commit 37a165def3
9 changed files with 742 additions and 11 deletions

View File

@ -235,6 +235,7 @@ set(libtorrent_aux_include_files
path
polymorphic_socket
portmap
posix_part_file
proxy_settings
range
receive_buffer
@ -398,6 +399,7 @@ set(sources
mmap_disk_io
mmap_storage
posix_disk_io
posix_part_file
posix_storage
ssl

View File

@ -787,6 +787,7 @@ SOURCES =
mmap_disk_io
mmap_storage
posix_disk_io
posix_part_file
posix_storage
ssl

View File

@ -368,6 +368,7 @@ SOURCES = \
piece_picker.cpp \
platform_util.cpp \
posix_disk_io.cpp \
posix_part_file.cpp \
posix_storage.cpp \
proxy_base.cpp \
proxy_settings.cpp \
@ -622,6 +623,7 @@ HEADERS = \
aux_/path.hpp \
aux_/polymorphic_socket.hpp \
aux_/portmap.hpp \
aux_/posix_part_file.hpp \
aux_/posix_storage.hpp \
aux_/proxy_settings.hpp \
aux_/range.hpp \

View File

@ -34,18 +34,24 @@ POSSIBILITY OF SUCH DAMAGE.
#define TORRENT_FILE_POINTER_HPP
#include <cstdio>
#include <utility> // for swap
namespace libtorrent {
namespace aux {
struct file_pointer
{
file_pointer(FILE* p) : ptr(p) {}
~file_pointer() { if (ptr != nullptr) fclose(ptr); }
file_pointer() : ptr(nullptr) {}
explicit file_pointer(FILE* p) : ptr(p) {}
~file_pointer() { if (ptr != nullptr) ::fclose(ptr); }
file_pointer(file_pointer const&) = delete;
file_pointer(file_pointer&& f) : ptr(f.ptr) { f.ptr = nullptr; }
file_pointer& operator=(file_pointer const&) = delete;
file_pointer& operator=(file_pointer&&) = delete;
file_pointer& operator=(file_pointer&& f)
{
std::swap(ptr, f.ptr);
return *this;
}
FILE* file() const { return ptr; }
private:
FILE* ptr;

View File

@ -0,0 +1,139 @@
/*
Copyright (c) 2014-2020, Arvid Norberg
Copyright (c) 2017, Steven Siloti
Copyright (c) 2018, d-komarov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_POSIX_PART_FILE_HPP_INCLUDE
#define TORRENT_POSIX_PART_FILE_HPP_INCLUDE
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <memory>
#include "libtorrent/config.hpp"
#include "libtorrent/error_code.hpp"
#include "libtorrent/units.hpp"
#include "libtorrent/hasher.hpp"
#include "libtorrent/aux_/open_mode.hpp"
#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t
#include "libtorrent/aux_/file_pointer.hpp"
namespace libtorrent {
namespace aux {
using slot_index_t = aux::strong_typedef<int, struct slot_index_tag_t>;
struct TORRENT_EXTRA_EXPORT posix_part_file
{
// create a part file at ``path``, that can hold ``num_pieces`` pieces.
// each piece being ``piece_size`` number of bytes
posix_part_file(std::string path, std::string name, int num_pieces, int piece_size);
~posix_part_file();
int writev(span<iovec_t const> bufs, piece_index_t piece, int offset, error_code& ec);
int readv(span<iovec_t const> bufs, piece_index_t piece, int offset, error_code& ec);
int hashv(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec);
int hashv2(hasher256& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec);
// free the slot the given piece is stored in. We no longer need to store this
// piece in the part file
void free_piece(piece_index_t piece);
void move_partfile(std::string const& path, error_code& ec);
// the function is called for every block of data belonging to the
// specified range that's in the posix_part_file. The first parameter is the
// offset within the range
void export_file(std::function<void(std::int64_t, span<char>)> f
, std::int64_t offset, std::int64_t size, error_code& ec);
// flush the metadata
void flush_metadata(error_code& ec);
private:
enum class open_mode : std::uint8_t
{
read_only, read_write
};
file_pointer open_file(open_mode mode, error_code& ec);
void flush_metadata_impl(error_code& ec);
std::int64_t slot_offset(slot_index_t const slot) const
{
return static_cast<int>(slot) * static_cast<std::int64_t>(m_piece_size)
+ m_header_size;
}
template <typename Hasher>
int do_hashv(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec);
std::string m_path;
std::string const m_name;
// allocate a slot and return the slot index
slot_index_t allocate_slot(piece_index_t piece);
// this is a list of unallocated slots in the part file
// within the m_num_allocated range
std::vector<slot_index_t> m_free_slots;
// this is the number of slots allocated
slot_index_t m_num_allocated{0};
// the max number of pieces in the torrent this part file is
// backing
int const m_max_pieces;
// number of bytes each piece contains
int const m_piece_size;
// this is the size of the posix_part_file header, it is added
// to offsets when calculating the offset to read and write
// payload data from
int const m_header_size;
// if this is true, the metadata in memory has changed since
// we last saved or read it from disk. It means that we
// need to flush the metadata before closing the file
bool m_dirty_metadata = false;
// maps a piece index to the part-file slot it is stored in
std::unordered_map<piece_index_t, slot_index_t> m_piece_map;
};
}
}
#endif

View File

@ -42,7 +42,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/hex.hpp" // to_hex
#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t
#include "libtorrent/aux_/file_pointer.hpp"
#include "libtorrent/part_file.hpp"
#include "libtorrent/aux_/posix_part_file.hpp"
#include <memory>
#include <string>
@ -114,7 +114,7 @@ namespace aux {
aux::vector<bool, file_index_t> m_use_partfile;
std::string m_part_file_name;
std::unique_ptr<part_file> m_part_file;
std::unique_ptr<posix_part_file> m_part_file;
};
}
}

473
src/posix_part_file.cpp Normal file
View File

@ -0,0 +1,473 @@
/*
Copyright (c) 2014-2020, Arvid Norberg
Copyright (c) 2016-2017, Steven Siloti
Copyright (c) 2016-2018, Alden Torres
Copyright (c) 2018, d-komarov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
The posix_part_file file format is an array of piece sized blocks with
a simple header. For a given number of pieces, the header has a
fixed size. The header size is rounded up to an even multiple of
1024, in an attempt at improving disk I/O performance by aligning
reads and writes to clusters on the drive. This is the file header
format. All values are stored big endian on disk.
// the size of the torrent (and can be used to calculate the size
// of the file header)
uint32_t num_pieces;
// the number of bytes in each piece. This determines the size of
// each slot in the part file. This is typically an even power of 2,
// but it is not guaranteed to be.
uint32_t piece_size;
// this is an array specifying which slots a particular piece resides in,
// A value of 0xffffffff (-1 if you will) means the piece is not in the posix_part_file
// Any other value means the piece resides in the slot with that index
uint32_t piece[num_pieces];
// unused, n is defined as the number to align the size of this
// header to an even multiple of 1024 bytes.
uint8_t padding[n];
*/
#include "libtorrent/aux_/posix_part_file.hpp"
#include "libtorrent/aux_/numeric_cast.hpp"
#include "libtorrent/io.hpp"
#include "libtorrent/assert.hpp"
#include "libtorrent/aux_/vector.hpp"
#include "libtorrent/aux_/path.hpp"
#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t
#include <functional> // for std::function
#include <cstdint>
namespace {
// round up to even kilobyte
int round_up(int n)
{ return (n + 1023) & ~0x3ff; }
}
#ifdef _MSC_VER
#define fseeko _fseeki64
#endif
namespace libtorrent {
namespace aux {
posix_part_file::posix_part_file(std::string path, std::string name
, int const num_pieces, int const piece_size)
: m_path(std::move(path))
, m_name(std::move(name))
, m_max_pieces(num_pieces)
, m_piece_size(piece_size)
, m_header_size(round_up((2 + num_pieces) * 4))
{
TORRENT_ASSERT(num_pieces > 0);
TORRENT_ASSERT(m_piece_size > 0);
error_code ec;
auto f = open_file(open_mode::read_only, ec);
if (ec) return;
// parse header
std::vector<char> header(static_cast<std::size_t>(m_header_size));
auto const n = std::fread(header.data(), 1, header.size(), f.file());
if (std::size_t(n) != header.size())
{
ec.assign(errno, generic_category());
return;
}
// we don't have a full header. consider the file empty
if (n < std::size_t(m_header_size)) return;
using namespace libtorrent::aux;
char* ptr = header.data();
// we have a header. Parse it
int const num_pieces_ = int(read_uint32(ptr));
int const piece_size_ = int(read_uint32(ptr));
// if there is a mismatch in number of pieces or piece size
// consider the file empty and overwrite anything in there
if (num_pieces != num_pieces_ || m_piece_size != piece_size_) return;
// this is used to determine which slots are free, and how many
// slots are allocated
aux::vector<bool, slot_index_t> free_slots;
free_slots.resize(num_pieces, true);
for (piece_index_t i = piece_index_t(0); i < piece_index_t(num_pieces); ++i)
{
slot_index_t const slot(read_int32(ptr));
if (static_cast<int>(slot) < 0) continue;
// invalid part-file
TORRENT_ASSERT(slot < slot_index_t(num_pieces));
if (slot >= slot_index_t(num_pieces)) continue;
if (slot >= m_num_allocated)
m_num_allocated = next(slot);
free_slots[slot] = false;
m_piece_map[i] = slot;
}
// now, populate the free_list with the "holes"
for (slot_index_t i(0); i < m_num_allocated; ++i)
{
if (free_slots[i]) m_free_slots.push_back(i);
}
}
posix_part_file::~posix_part_file()
{
error_code ec;
flush_metadata_impl(ec);
}
slot_index_t posix_part_file::allocate_slot(piece_index_t const piece)
{
TORRENT_ASSERT(m_piece_map.find(piece) == m_piece_map.end());
slot_index_t slot(-1);
if (!m_free_slots.empty())
{
slot = m_free_slots.front();
m_free_slots.erase(m_free_slots.begin());
}
else
{
slot = m_num_allocated;
++m_num_allocated;
}
m_piece_map[piece] = slot;
m_dirty_metadata = true;
return slot;
}
int posix_part_file::writev(span<iovec_t const> bufs, piece_index_t const piece
, int const offset, error_code& ec)
{
TORRENT_ASSERT(offset >= 0);
TORRENT_ASSERT(int(bufs.size()) + offset <= m_piece_size);
auto f = open_file(open_mode::read_write, ec);
if (ec) return -1;
auto const i = m_piece_map.find(piece);
slot_index_t const slot = (i == m_piece_map.end())
? allocate_slot(piece) : i->second;
if (::fseeko(f.file(), slot_offset(slot) + offset, SEEK_SET) != 0)
{
ec.assign(errno, generic_category());
return -1;
}
int ret = 0;
for (auto const& b : bufs)
{
auto const written = std::fwrite(b.data(), 1, std::size_t(b.size()), f.file());
if (written != std::size_t(b.size()))
{
ec.assign(errno, generic_category());
return -1;
}
ret += int(written);
}
return ret;
}
int posix_part_file::readv(span<iovec_t const> bufs
, piece_index_t const piece
, int const offset, error_code& ec)
{
TORRENT_ASSERT(offset >= 0);
TORRENT_ASSERT(int(bufs.size()) + offset <= m_piece_size);
auto const i = m_piece_map.find(piece);
if (i == m_piece_map.end())
{
ec = make_error_code(boost::system::errc::no_such_file_or_directory);
return -1;
}
slot_index_t const slot = i->second;
auto f = open_file(open_mode::read_only, ec);
if (ec) return -1;
if (::fseeko(f.file(), slot_offset(slot) + offset, SEEK_SET) != 0)
{
ec.assign(errno, generic_category());
return -1;
}
int ret = 0;
for (auto const& b : bufs)
{
auto const read = std::fread(b.data(), 1, std::size_t(b.size()), f.file());
if (read != std::size_t(b.size()))
{
ec.assign(errno, generic_category());
return -1;
}
ret += int(read);
}
return ret;
}
int posix_part_file::hashv(hasher& ph
, std::ptrdiff_t const len
, piece_index_t const piece
, int const offset, error_code& ec)
{
return do_hashv(ph, len, piece, offset, ec);
}
int posix_part_file::hashv2(hasher256& ph
, std::ptrdiff_t const len
, piece_index_t const piece
, int const offset, error_code& ec)
{
return do_hashv(ph, len, piece, offset, ec);
}
template <typename Hasher>
int posix_part_file::do_hashv(Hasher& ph
, std::ptrdiff_t const len
, piece_index_t const piece
, int const offset, error_code& ec)
{
TORRENT_ASSERT(offset >= 0);
TORRENT_ASSERT(len >= 0);
TORRENT_ASSERT(int(len) + offset <= m_piece_size);
auto const i = m_piece_map.find(piece);
if (i == m_piece_map.end())
{
ec = error_code(boost::system::errc::no_such_file_or_directory
, boost::system::generic_category());
return -1;
}
slot_index_t const slot = i->second;
auto f = open_file(open_mode::read_only, ec);
if (ec) return -1;
std::vector<char> buffer(static_cast<std::size_t>(len));
std::int64_t const slot_offset = std::int64_t(m_header_size) + std::int64_t(static_cast<int>(slot)) * m_piece_size;
if (::fseeko(f.file(), slot_offset + offset, SEEK_SET) != 0)
{
ec.assign(errno, generic_category());
return -1;
}
auto const ret = std::fread(buffer.data(), 1, buffer.size(), f.file());
if (ret != buffer.size())
{
ec.assign(errno, generic_category());
return -1;
}
ph.update(buffer);
return numeric_cast<int>(ret);
}
file_pointer posix_part_file::open_file(open_mode const mode, error_code& ec)
{
std::string const fn = combine_path(m_path, m_name);
char const* mode_str[] = {"rb", "rb+"};
file_pointer ret(::fopen(fn.c_str(), mode_str[static_cast<std::uint8_t>(mode)]));
if (ret.file() == nullptr)
ec.assign(errno, generic_category());
if (mode == open_mode::read_write
&& ec == boost::system::errc::no_such_file_or_directory)
{
// this means the directory the file is in doesn't exist.
// so create it
ec.clear();
create_directories(m_path, ec);
if (ec) return {};
ret = file_pointer(::fopen(fn.c_str(), "wb+"));
if (ret.file() == nullptr)
ec.assign(errno, generic_category());
}
if (ec) return {};
return ret;
}
void posix_part_file::free_piece(piece_index_t const piece)
{
auto const i = m_piece_map.find(piece);
if (i == m_piece_map.end()) return;
// TODO: what do we do if someone is currently reading from the disk
// from this piece? does it matter? Since we won't actively erase the
// data from disk, but it may be overwritten soon, it's probably not that
// big of a deal
m_free_slots.push_back(i->second);
m_piece_map.erase(i);
m_dirty_metadata = true;
}
void posix_part_file::move_partfile(std::string const& path, error_code& ec)
{
flush_metadata_impl(ec);
if (ec) return;
if (!m_piece_map.empty())
{
std::string old_path = combine_path(m_path, m_name);
std::string new_path = combine_path(path, m_name);
rename(old_path, new_path, ec);
if (ec == boost::system::errc::no_such_file_or_directory)
ec.clear();
if (ec)
{
copy_file(old_path, new_path, ec);
if (ec) return;
remove(old_path, ec);
}
}
m_path = path;
}
void posix_part_file::export_file(std::function<void(std::int64_t, span<char>)> f
, std::int64_t const offset, std::int64_t size, error_code& ec)
{
// there's nothing stored in the posix_part_file. Nothing to do
if (m_piece_map.empty()) return;
piece_index_t piece(int(offset / m_piece_size));
piece_index_t const end = piece_index_t(int(((offset + size) + m_piece_size - 1) / m_piece_size));
std::unique_ptr<char[]> buf;
std::int64_t piece_offset = offset - std::int64_t(static_cast<int>(piece))
* m_piece_size;
std::int64_t file_offset = 0;
auto file = open_file(open_mode::read_only, ec);
if (ec) return;
for (; piece < end; ++piece)
{
auto const i = m_piece_map.find(piece);
int const block_to_copy = int(std::min(m_piece_size - piece_offset, size));
if (i != m_piece_map.end())
{
slot_index_t const slot = i->second;
if (!buf) buf.reset(new char[std::size_t(m_piece_size)]);
if (::fseeko(file.file(), slot_offset(slot) + piece_offset, SEEK_SET) != 0)
{
ec.assign(errno, generic_category());
return;
}
auto bytes_read = std::fread(buf.get(), 1, std::size_t(block_to_copy), file.file());
if (int(bytes_read) != block_to_copy)
ec.assign(errno, generic_category());
TORRENT_ASSERT(!ec);
if (ec) return;
f(file_offset, {buf.get(), block_to_copy});
}
file_offset += block_to_copy;
piece_offset = 0;
size -= block_to_copy;
}
}
void posix_part_file::flush_metadata(error_code& ec)
{
flush_metadata_impl(ec);
}
// TODO: instead of rebuilding the whole file header
// and flushing it, update the slot entries as we go
void posix_part_file::flush_metadata_impl(error_code& ec)
{
// do we need to flush the metadata?
if (m_dirty_metadata == false) return;
if (m_piece_map.empty())
{
// if we don't have any pieces left in the
// part file, remove it
std::string const p = combine_path(m_path, m_name);
remove(p, ec);
if (ec == boost::system::errc::no_such_file_or_directory)
ec.clear();
return;
}
auto f = open_file(open_mode::read_write, ec);
if (ec) return;
std::vector<char> header(static_cast<std::size_t>(m_header_size));
using namespace libtorrent::aux;
char* ptr = header.data();
write_uint32(m_max_pieces, ptr);
write_uint32(m_piece_size, ptr);
for (piece_index_t piece(0); piece < piece_index_t(m_max_pieces); ++piece)
{
auto const i = m_piece_map.find(piece);
slot_index_t const slot(i == m_piece_map.end()
? slot_index_t(-1) : i->second);
write_int32(static_cast<int>(slot), ptr);
}
std::memset(ptr, 0, std::size_t(m_header_size - (ptr - header.data())));
auto const written = std::fwrite(header.data(), 1, header.size(), f.file());
if (written != header.size())
{
ec.assign(errno, generic_category());
return;
}
m_dirty_metadata = false;
}
}
}

View File

@ -71,7 +71,7 @@ namespace aux {
{
if (m_part_file) return;
m_part_file = std::make_unique<part_file>(
m_part_file = std::make_unique<posix_part_file>(
m_save_path, m_part_file_name
, files().num_pieces(), files().piece_length());
}
@ -548,7 +548,7 @@ namespace aux {
{
ec.file(idx);
ec.operation = operation_t::mkdir;
return nullptr;
return file_pointer{};
}
// now that we've created the directories, try again
@ -565,14 +565,14 @@ namespace aux {
ec.ec.assign(errno, generic_category());
ec.file(idx);
ec.operation = operation_t::file_open;
return nullptr;
return file_pointer{};
}
}
else
{
ec.file(idx);
ec.operation = operation_t::file_open;
return nullptr;
return file_pointer{};
}
}
@ -587,11 +587,11 @@ namespace aux {
ec.ec.assign(errno, generic_category());
ec.file(idx);
ec.operation = operation_t::file_seek;
return nullptr;
return file_pointer{};
}
}
return f;
return file_pointer{f};
}
bool posix_storage::use_partfile(file_index_t const index) const

View File

@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "test.hpp"
#include "libtorrent/part_file.hpp"
#include "libtorrent/aux_/posix_part_file.hpp"
#include "libtorrent/aux_/path.hpp"
#include "libtorrent/error_code.hpp"
@ -147,3 +148,110 @@ TORRENT_TEST(part_file)
if (ec) std::printf("exists: %s\n", ec.message().c_str());
}
}
TORRENT_TEST(posix_part_file)
{
error_code ec;
std::string cwd = complete(".");
remove_all(combine_path(cwd, "partfile_test_dir"), ec);
if (ec) std::printf("remove_all: %s\n", ec.message().c_str());
remove_all(combine_path(cwd, "partfile_test_dir2"), ec);
if (ec) std::printf("remove_all: %s\n", ec.message().c_str());
int piece_size = 16 * 0x4000;
std::array<char, 1024> buf;
{
create_directory(combine_path(cwd, "partfile_test_dir"), ec);
if (ec) std::printf("create_directory: %s\n", ec.message().c_str());
create_directory(combine_path(cwd, "partfile_test_dir2"), ec);
if (ec) std::printf("create_directory: %s\n", ec.message().c_str());
aux::posix_part_file pf(combine_path(cwd, "partfile_test_dir"), "partfile.parts", 100, piece_size);
pf.flush_metadata(ec);
if (ec) std::printf("flush_metadata: %s\n", ec.message().c_str());
// since we don't have anything in the part file, it will have
// not have been created yet
TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts")));
// write something to the metadata file
for (int i = 0; i < 1024; ++i) buf[std::size_t(i)] = char(i & 0xff);
iovec_t v = buf;
pf.writev(v, piece_index_t(10), 0, ec);
if (ec) std::printf("posix_part_file::writev: %s\n", ec.message().c_str());
pf.flush_metadata(ec);
if (ec) std::printf("flush_metadata: %s\n", ec.message().c_str());
// now wwe should have created the partfile
TEST_CHECK(exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts")));
pf.move_partfile(combine_path(cwd, "partfile_test_dir2"), ec);
TEST_CHECK(!ec);
if (ec) std::printf("move_partfile: %s\n", ec.message().c_str());
TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts")));
TEST_CHECK(exists(combine_path(combine_path(cwd, "partfile_test_dir2"), "partfile.parts")));
buf.fill(0);
pf.readv(v, piece_index_t(10), 0, ec);
if (ec) std::printf("posix_part_file::readv: %s\n", ec.message().c_str());
for (int i = 0; i < int(buf.size()); ++i)
TEST_CHECK(buf[std::size_t(i)] == char(i));
sha1_hash const cmp_hash = hasher(buf).final();
hasher ph;
pf.hashv(ph, sizeof(buf), piece_index_t(10), 0, ec);
if (ec) std::printf("posix_part_file::hashv: %s\n", ec.message().c_str());
TEST_CHECK(ph.final() == cmp_hash);
}
{
// load the part file back in
aux::posix_part_file pf(combine_path(cwd, "partfile_test_dir2"), "partfile.parts", 100, piece_size);
buf.fill(0);
iovec_t v = buf;
pf.readv(v, piece_index_t(10), 0, ec);
if (ec) std::printf("posix_part_file::readv: %s\n", ec.message().c_str());
for (int i = 0; i < 1024; ++i)
TEST_CHECK(buf[std::size_t(i)] == static_cast<char>(i));
// test exporting the piece to a file
std::string output_filename = combine_path(combine_path(cwd, "partfile_test_dir")
, "part_file_test_export");
pf.export_file([](std::int64_t file_offset, span<char> buf_data)
{
for (char i : buf_data)
{
// make sure we got the bytes we expected
TEST_CHECK(i == static_cast<char>(file_offset));
++file_offset;
}
}, 10 * piece_size, 1024, ec);
if (ec) std::printf("export_file: %s\n", ec.message().c_str());
pf.free_piece(piece_index_t(10));
pf.flush_metadata(ec);
if (ec) std::printf("flush_metadata: %s\n", ec.message().c_str());
// we just removed the last piece. The partfile does not
// contain anything anymore, it should have deleted itself
TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir2"), "partfile.parts"), ec));
TEST_CHECK(!ec);
if (ec) std::printf("exists: %s\n", ec.message().c_str());
}
}