--- a/src/download/download_wrapper.cc 2017-04-30 21:25:15.511379804 +0100 +++ a/src/download/download_wrapper.cc 2017-04-30 22:10:12.910100074 +0100 @@ -46,6 +46,7 @@ #include "protocol/handshake_manager.h" #include "protocol/peer_connection_base.h" #include "torrent/exceptions.h" +#include "torrent/download.h" #include "torrent/object.h" #include "torrent/tracker_list.h" #include "torrent/data/file.h" @@ -103,7 +104,7 @@ DownloadWrapper::~DownloadWrapper() { } void -DownloadWrapper::initialize(const std::string& hash, const std::string& id) { +DownloadWrapper::initialize(const std::string& hash, const std::string& id, int flags) { char hashObfuscated[20]; sha1_salt("req2", 4, hash.c_str(), hash.length(), hashObfuscated); @@ -124,7 +125,7 @@ DownloadWrapper::initialize(const std::s // Connect various signals and slots. m_hashChecker->slot_check_chunk() = std::bind(&DownloadWrapper::check_chunk_hash, this, std::placeholders::_1); - m_hashChecker->delay_checked().slot() = std::bind(&DownloadWrapper::receive_initial_hash, this); + m_hashChecker->delay_checked().slot() = std::bind(&DownloadWrapper::receive_initial_hash, this, flags); } void @@ -156,7 +157,7 @@ DownloadWrapper::is_stopped() const { } void -DownloadWrapper::receive_initial_hash() { +DownloadWrapper::receive_initial_hash(int flags) { if (info()->is_active()) throw internal_error("DownloadWrapper::receive_initial_hash() but we're in a bad state."); @@ -172,7 +173,7 @@ DownloadWrapper::receive_initial_hash() // Initialize the ChunkSelector here so that no chunks will be // marked by HashTorrent that are not accounted for. m_main->chunk_selector()->initialize(m_main->chunk_statistics()); - receive_update_priorities(); + receive_update_priorities(flags); } if (data()->slot_initial_hash()) @@ -325,7 +326,7 @@ DownloadWrapper::receive_tick(uint32_t t } void -DownloadWrapper::receive_update_priorities() { +DownloadWrapper::receive_update_priorities(int flags) { if (m_main->chunk_selector()->empty()) { file_list()->set_selected_size_bytes(); return; @@ -335,9 +336,15 @@ DownloadWrapper::receive_update_prioriti data()->mutable_normal_priority()->clear(); for (FileList::iterator itr = m_main->file_list()->begin(); itr != m_main->file_list()->end(); ++itr) { + // Unset fallocate flag by default. + (*itr)->unset_flags(File::flag_fallocate); + switch ((*itr)->priority()) { case PRIORITY_NORMAL: { + if (flags & torrent::Download::open_enable_fallocate) + (*itr)->set_flags(File::flag_fallocate); + File::range_type range = (*itr)->range(); if ((*itr)->has_flags(File::flag_prioritize_first) && range.first != range.second) { @@ -354,6 +361,9 @@ DownloadWrapper::receive_update_prioriti break; } case PRIORITY_HIGH: + if (flags & torrent::Download::open_enable_fallocate) + (*itr)->set_flags(File::flag_fallocate); + data()->mutable_high_priority()->insert((*itr)->range().first, (*itr)->range().second); break; default: --- a/src/download/download_wrapper.h 2016-12-12 08:49:18.000000000 +0000 +++ a/src/download/download_wrapper.h 2017-04-30 22:06:19.000000000 +0100 @@ -64,7 +64,7 @@ public: ChunkList* chunk_list() { return m_main->chunk_list(); } // Initialize hash checker and various download stuff. - void initialize(const std::string& hash, const std::string& id); + void initialize(const std::string& hash, const std::string& id, int flags = 0); void close(); @@ -91,7 +91,7 @@ public: // Internal: // - void receive_initial_hash(); + void receive_initial_hash(int flags = 0); void receive_hash_done(ChunkHandle handle, const char* hash); void check_chunk_hash(ChunkHandle handle); @@ -102,7 +102,7 @@ public: void receive_tick(uint32_t ticks); - void receive_update_priorities(); + void receive_update_priorities(int flags = 0); private: DownloadWrapper(const DownloadWrapper&); --- a/src/torrent/data/file.cc 2016-12-12 08:49:18.000000000 +0000 +++ a/src/torrent/data/file.cc 2017-04-30 22:06:19.000000000 +0100 @@ -184,6 +184,7 @@ File::resize_file() { if (m_flags & flag_fallocate) { flags |= SocketFile::flag_fallocate; flags |= SocketFile::flag_fallocate_blocking; + m_flags &= ~flag_fallocate; } return SocketFile(m_fd).set_size(m_size, flags); --- a/src/torrent/data/file.h 2016-12-12 08:49:18.000000000 +0000 +++ a/src/torrent/data/file.h 2017-04-30 22:06:19.000000000 +0100 @@ -68,8 +68,11 @@ public: bool is_create_queued() const { return m_flags & flag_create_queued; } bool is_resize_queued() const { return m_flags & flag_resize_queued; } + bool is_fallocatable() const { return m_flags & flag_fallocate; } bool is_previously_created() const { return m_flags & flag_previously_created; } + bool is_fallocatable_file() { return has_flags(flag_resize_queued) && has_flags(flag_fallocate); } + bool has_flags(int flags) { return m_flags & flags; } void set_flags(int flags); --- a/src/torrent/data/file_list.cc 2017-04-30 21:15:43.000000000 +0100 +++ a/src/torrent/data/file_list.cc 2017-04-30 22:06:19.000000000 +0100 @@ -259,9 +259,9 @@ FileList::set_selected_size_bytes() { uint64_t FileList::free_diskspace() const { uint64_t freeDiskspace = std::numeric_limits::max(); + rak::fs_stat stat; for (path_list::const_iterator itr = m_indirectLinks.begin(), last = m_indirectLinks.end(); itr != last; ++itr) { - rak::fs_stat stat; if (!stat.update(*itr)) continue; @@ -269,9 +269,74 @@ FileList::free_diskspace() const { freeDiskspace = std::min(freeDiskspace, stat.bytes_avail()); } + // Check the base directory of download if files haven't been created yet + if (freeDiskspace == std::numeric_limits::max()) { + std::string dirPath = is_multi_file() ? m_rootDir.substr(0, m_rootDir.find_last_of("\\/")) : m_rootDir; + + if (stat.update(dirPath)) + freeDiskspace = std::min(freeDiskspace, stat.bytes_avail()); + } + return freeDiskspace != std::numeric_limits::max() ? freeDiskspace : 0; } +uint64_t +FileList::allocatable_size_bytes() const { + uint64_t allocatableSizeBytes = 0; + + if (data()->is_partially_done()) + return allocatableSizeBytes; + + uint32_t allocatableSizeChunks = 0; + uint32_t prevChunk = -1; + bool areAllFilesAllocatable = true; + bool isLastFileAllocatable = false; + + for (FileList::const_iterator itr = begin(), last = end(); itr != last; itr++) { + + // Checks flag_fallocate and flag_resize_queued as well, it will take care of restarting client. + if ((*itr)->is_fallocatable_file()) { + allocatableSizeChunks += (*itr)->size_chunks() - ((*itr)->range_first() == prevChunk ? 1 : 0); + prevChunk = (*itr)->range_second() - 1; + + if (itr == end() - 1) + isLastFileAllocatable = true; + } else { + areAllFilesAllocatable = false; + } + + } + + if (areAllFilesAllocatable) + return m_torrentSize; + + allocatableSizeBytes = (uint64_t)allocatableSizeChunks * (uint64_t)m_chunkSize; + + // Dealing with size of last chunk as it's usually smaller than the rest. + uint64_t reminder = m_torrentSize % (uint64_t)m_chunkSize; + + if (isLastFileAllocatable && reminder != 0) + allocatableSizeBytes = allocatableSizeBytes - (uint64_t)m_chunkSize + reminder; + + return allocatableSizeBytes; +} + +bool +FileList::is_enough_diskspace() const { + uint64_t allocatable_size = allocatable_size_bytes(); + + if (allocatable_size > 0) { + uint64_t free_disk_space = free_diskspace(); + + if (free_disk_space < allocatable_size) { + LT_LOG_FL(INFO, "File allocation is set and not enough disk space to start torrent: allocatable size:%i free space:%i", allocatable_size, free_disk_space); + return false; + } + } + + return true; +} + FileList::iterator_range FileList::split(iterator position, split_type* first, split_type* last) { if (is_open()) @@ -642,7 +707,12 @@ FileList::open_file(File* node, const Pa return false; } - return node->prepare(MemoryChunk::prot_read, 0); + // File allocation will be done if fallocate flag is set, + // create zero-length file otherwise. + if (node->has_flags(File::flag_fallocate)) + return node->prepare(MemoryChunk::prot_write, 0); + else + return node->prepare(MemoryChunk::prot_read, 0); } MemoryChunk --- a/src/torrent/data/file_list.h 2017-04-30 21:15:43.000000000 +0100 +++ a/src/torrent/data/file_list.h 2017-04-30 22:06:19.000000000 +0100 @@ -107,6 +107,7 @@ public: size_t size_files() const { return base_type::size(); } uint64_t size_bytes() const { return m_torrentSize; } + uint64_t allocatable_size_bytes() const; uint32_t size_chunks() const { return bitfield()->size_bits(); } uint64_t selected_size_bytes() const { return m_selectedSize; } @@ -135,6 +136,10 @@ public: // of free diskspace will be returned. uint64_t free_diskspace() const; + // Determines whether there is enough disk space for fallocating + // selected files. + bool is_enough_diskspace() const; + // List of directories in the torrent that might be on different // volumes as they are links, including the root directory. Used by // 'free_diskspace()'. --- a/src/torrent/download.cc 2017-04-30 21:15:43.000000000 +0100 +++ a/src/torrent/download.cc 2017-05-14 21:28:05.138916583 +0100 @@ -137,7 +137,13 @@ Download::start(int flags) { throw internal_error("Tried to start a download with empty bitfield."); if (info->is_active()) - return; + throw internal_error("Tried to start an already started download."); + + // Don't start the download if there's not enough disk space for it + // when the open_enable_fallocate was set and at least one of the + // files has fallocate flag (libtorrent would crash otherwise) + if (flags & open_enable_fallocate && !m_ptr->main()->file_list()->is_enough_diskspace()) + throw internal_error("Tried to start a download with not enough disk space for it."); LT_LOG_THIS(INFO, "Starting torrent: flags:%0x.", flags); @@ -145,9 +151,9 @@ Download::start(int flags) { // file_list()->open(flags); - // If the FileList::open_no_create flag was not set, our new - // behavior is to create all zero-length files with - // flag_queued_create set. + // If the open_enable_fallocate or the FileList::open_no_create + // flag was not set, then create all zero-length files with + // flag_create_queued set. file_list()->open(flags & ~FileList::open_no_create); if (m_ptr->connection_type() == CONNECTION_INITIAL_SEED) { @@ -566,8 +572,8 @@ Download::set_connection_type(Connection } void -Download::update_priorities() { - m_ptr->receive_update_priorities(); +Download::update_priorities(int flags) { + m_ptr->receive_update_priorities(flags); } void --- a/src/torrent/download.h 2017-04-30 21:15:43.000000000 +0100 +++ a/src/torrent/download.h 2017-05-14 21:25:52.249636506 +0100 @@ -186,7 +186,7 @@ public: // Call this when you want the modifications of the download priorities // in the entries to take effect. It is slightly expensive as it rechecks // all the peer bitfields to see if we are still interested. - void update_priorities(); + void update_priorities(int flags = 0); void add_peer(const sockaddr* addr, int port); --- a/src/torrent/torrent.cc 2016-12-12 08:49:18.000000000 +0000 +++ a/src/torrent/torrent.cc 2017-04-30 22:06:19.000000000 +0100 @@ -164,7 +164,7 @@ encoding_list() { } Download -download_add(Object* object) { +download_add(Object* object, int flags) { std::auto_ptr download(new DownloadWrapper); DownloadConstructor ctor; @@ -190,7 +190,7 @@ download_add(Object* object) { } download->set_hash_queue(manager->hash_queue()); - download->initialize(infoHash, PEER_NAME + rak::generate_random(20 - std::string(PEER_NAME).size())); + download->initialize(infoHash, PEER_NAME + rak::generate_random(20 - std::string(PEER_NAME).size()), flags); // Add trackers, etc, after setting the info hash so that log // entries look sane. --- a/src/torrent/torrent.h 2016-12-12 08:49:18.000000000 +0000 +++ a/src/torrent/torrent.h 2017-04-30 22:06:19.000000000 +0100 @@ -93,7 +93,7 @@ EncodingList* encoding_list() LIBT // is done by 'download_remove'. // // Might consider redesigning that... -Download download_add(Object* s) LIBTORRENT_EXPORT; +Download download_add(Object* s, int flags = 0) LIBTORRENT_EXPORT; void download_remove(Download d) LIBTORRENT_EXPORT; // Add all downloads to dlist. The client is responsible for clearing