add posix_part_file, specific to posix_storage. It uses fopen/fclose and is not thread safe
This commit is contained in:

committed by
Arvid Norberg

parent
7f26e50bef
commit
37a165def3
@ -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
|
||||
|
||||
|
1
Jamfile
1
Jamfile
@ -787,6 +787,7 @@ SOURCES =
|
||||
mmap_disk_io
|
||||
mmap_storage
|
||||
posix_disk_io
|
||||
posix_part_file
|
||||
posix_storage
|
||||
ssl
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -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 \
|
||||
|
@ -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;
|
||||
|
139
include/libtorrent/aux_/posix_part_file.hpp
Normal file
139
include/libtorrent/aux_/posix_part_file.hpp
Normal 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
|
@ -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
473
src/posix_part_file.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user