/* Copyright (c) 2014-2022, Arvid Norberg Copyright (c) 2017-2018, Steven Siloti Copyright (c) 2018, Alden Torres All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #ifndef TORRENT_DISK_INTERFACE_HPP #define TORRENT_DISK_INTERFACE_HPP #include "libtorrent/bdecode.hpp" #include #include #include "libtorrent/fwd.hpp" #include "libtorrent/units.hpp" #include "libtorrent/disk_buffer_holder.hpp" #include "libtorrent/aux_/vector.hpp" #include "libtorrent/aux_/export.hpp" #include "libtorrent/storage_defs.hpp" #include "libtorrent/time.hpp" #include "libtorrent/sha1_hash.hpp" #include "libtorrent/flags.hpp" #include "libtorrent/session_types.hpp" // OVERVIEW // // The disk I/O can be customized in libtorrent. In previous versions, the // customization was at the level of each torrent. Now, the customization point // is at the session level. All torrents added to a session will use the same // disk I/O subsystem, as determined by the disk_io_constructor (in // session_params). // // This allows the disk subsystem to also customize threading and disk job // management. // // To customize the disk subsystem, implement disk_interface and provide a // factory function to the session constructor (via session_params). // // Example use: // // .. include:: ../examples/custom_storage.cpp // :code: c++ // :tab-width: 2 // :start-after: -- example begin // :end-before: // -- example end namespace libtorrent { struct disk_observer; struct counters; struct storage_holder; // return values from check_fastresume, and move_storage using status_t = flags::bitfield_flag; namespace disk_status { // the disk operation could not be completed because of an // unrecoverable error (e.g. failed write). The torrent is paused // and a ``file_error_alert`` is posted. constexpr status_t fatal_disk_error = 0_bit; // returned from check_fastresume to indicate that resume data // failed validation and a full piece-hash check is required. constexpr status_t need_full_check = 1_bit; // returned from move_storage when the destination already exists // and the ``dont_replace`` flag was set. constexpr status_t file_exist = 2_bit; // returned from check_fastresume when a file on disk is larger // than the size recorded in the .torrent metadata. // This triggers an alert to let the user decide whether to truncate the // file or save the torrent in another directory. constexpr status_t oversized_file = 3_bit; // Returned by do_job(hash) when the job was hung on a cache piece. // kick_hasher will post it to completed_jobs once hashing finishes. // perform_job() checks this flag before writing j->ret. constexpr status_t job_deferred = 4_bit; } // internal // this is a bittorrent constant constexpr int default_block_size = 0x4000; using file_open_mode_t = flags::bitfield_flag; namespace file_open_mode { // open the file for reading only constexpr file_open_mode_t read_only{}; // open the file for writing only constexpr file_open_mode_t write_only = 0_bit; // open the file for reading and writing constexpr file_open_mode_t read_write = 1_bit; // the mask for the bits determining read or write mode constexpr file_open_mode_t rw_mask = read_only | write_only | read_write; // open the file in sparse mode (if supported by the // filesystem). constexpr file_open_mode_t sparse = 2_bit; // don't update the access timestamps on the file (if // supported by the operating system and filesystem). // this generally improves disk performance. constexpr file_open_mode_t no_atime = 3_bit; // When this is not set, the kernel is hinted that access to this file will // be made sequentially. constexpr file_open_mode_t random_access = 5_bit; #if TORRENT_ABI_VERSION == 1 // prevent the file from being opened by another process // while it's still being held open by this handle constexpr file_open_mode_t locked TORRENT_DEPRECATED = 6_bit; #endif // the file is memory mapped constexpr file_open_mode_t mmapped = 7_bit; } // this contains information about a file that's currently open by the // libtorrent disk I/O subsystem. It's associated with a single torrent. struct TORRENT_EXPORT open_file_state { // the index of the file this entry refers to into the ``file_storage`` // file list of this torrent. This starts indexing at 0. file_index_t file_index; // ``open_mode`` is a bitmask of the file flags this file is currently // opened with. For possible flags, see file_open_mode_t. // // Note that the read/write mode is not a bitmask. The two least significant bits are used // to represent the read/write mode. Those bits can be masked out using the ``rw_mask`` constant. file_open_mode_t open_mode; // a (high precision) timestamp of when the file was last used. time_point last_use; }; using disk_job_flags_t = flags::bitfield_flag; // The disk_interface is the customization point for disk I/O in libtorrent. // implement this interface and provide a factory function to the session constructor // use custom disk I/O. All functions on the disk subsystem (implementing // disk_interface) are called from within libtorrent's network thread. For // disk I/O to be performed in a separate thread, the disk subsystem has to // manage that itself. // // Although the functions are called ``async_*``, they do not technically // *have* to be asynchronous, but they support being asynchronous, by // expecting the result passed back into a callback. The callbacks must be // posted back onto the network thread via the io_context object passed into // the constructor. The callbacks will be run in the network thread. struct TORRENT_EXPORT disk_interface { // force making a copy of the cached block, rather than getting a // reference to a block already in the cache. This is used the block is // expected to be overwritten very soon, by async_write(), and we need // access to the previous content. static inline constexpr disk_job_flags_t force_copy = 0_bit; // hint that there may be more disk operations with sequential access to // the file static inline constexpr disk_job_flags_t sequential_access = 3_bit; // don't keep the read block in cache. This is a hint that this block is // unlikely to be read again anytime soon, and caching it would be // wasteful. static inline constexpr disk_job_flags_t volatile_read = 4_bit; // compute a v1 piece hash. This is only used by the async_hash() call. // If this flag is not set in the async_hash() call, the SHA-1 piece // hash does not need to be computed. static inline constexpr disk_job_flags_t v1_hash = 5_bit; // this flag instructs a hash job that we just completed this piece, and // it should be flushed to disk static constexpr disk_job_flags_t flush_piece = 7_bit; // this is called when a new torrent is added. The shared_ptr can be // used to hold the internal torrent object alive as long as there are // outstanding disk operations on the storage. // The returned storage_holder is an owning reference to the underlying // storage that was just created. It is fundamentally a storage_index_t virtual storage_holder new_torrent(storage_params const& p , std::shared_ptr const& torrent) = 0; // remove the storage with the specified index. This is not expected to // delete any files from disk, just to clean up any resources associated // with the specified storage. virtual void remove_torrent(storage_index_t) = 0; // perform a read or write operation from/to the specified storage // index and the specified request. When the operation completes, call // handler possibly with a disk_buffer_holder, holding the buffer with // the result. Flags may be set to affect the read operation. See // disk_job_flags_t. // // The disk_observer is a callback to indicate that // the store buffer/disk write queue is below the watermark to let peers // start writing buffers to disk again. When ``async_write()`` returns // ``true``, indicating the write queue is full, the peer will stop // further writes and wait for the passed-in ``disk_observer`` to be // notified before resuming. // // Note that for ``async_read``, the peer_request (``r``) is not // necessarily aligned to blocks (but it is most of the time). However, // all writes (passed to ``async_write``) are guaranteed to be block // aligned. virtual void async_read(storage_index_t storage, peer_request const& r , std::function handler , disk_job_flags_t flags = {}) = 0; virtual bool async_write(storage_index_t storage, peer_request const& r , char const* buf, std::shared_ptr o , std::function handler , disk_job_flags_t flags = {}) = 0; // Compute hash(es) for the specified piece. Unless the v1_hash flag is // set (in ``flags``), the SHA-1 hash of the whole piece does not need // to be computed. // // The `v2` span is optional and can be empty, which means v2 hashes // should not be computed. If v2 is non-empty it must be at least large // enough to hold all v2 blocks in the piece, and this function will // fill in the span with the SHA-256 block hashes of the piece. // // The contents of the `v2` span on entry are not read by the // implementation; it is treated as output only. Callers may safely // reuse the same buffer across pieces without clearing it. virtual void async_hash(storage_index_t storage, piece_index_t piece, span v2 , disk_job_flags_t flags , std::function handler) = 0; // computes the v2 hash (SHA-256) of a single block. The block at // ``offset`` in piece ``piece``. virtual void async_hash2(storage_index_t storage, piece_index_t piece, int offset, disk_job_flags_t flags , std::function handler) = 0; // called to request the files for the specified storage/torrent be // moved to a new location. It is the disk I/O object's responsibility // to synchronize this with any currently outstanding disk operations to // the storage. Whether files are replaced at the destination path or // not is controlled by ``flags`` (see move_flags_t). virtual void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags , std::function handler) = 0; // This is called on disk I/O objects to request they close all open // files for the specified storage/torrent. If file handles are not // pooled/cached, it can be a no-op. For truly asynchronous disk I/O, // this should provide at least one point in time when all files are // closed. It is possible that later asynchronous operations will // re-open some of the files, by the time this completion handler is // called, that's fine. virtual void async_release_files(storage_index_t storage , std::function handler = std::function()) = 0; // this is called when torrents are added to validate their resume data // against the files on disk. This function is expected to do a few things: // // if ``links`` is non-empty, it contains a string for each file in the // torrent. The string being a path to an existing identical file. The // default behavior is to create hard links of those files into the // storage of the new torrent (specified by ``storage``). An empty // string indicates that there is no known identical file. This is part // of the "mutable torrent" feature, where files can be reused from // other torrents. // // The ``resume_data`` points the resume data passed in by the client. // // If the ``resume_data->flags`` field has the seed_mode flag set, all // files/pieces are expected to be on disk already. This should be // verified. Not just the existence of the file, but also that it has // the correct size. // // Any file with a piece set in the ``resume_data->have_pieces`` bitmask // should exist on disk, this should be verified. Pad files and files // with zero priority may be skipped. virtual void async_check_files(storage_index_t storage , add_torrent_params const* resume_data , aux::vector links , std::function handler) = 0; // This is called when a torrent is stopped. It gives the disk I/O // object an opportunity to flush any data to disk that's currently kept // cached. This function should at least do the same thing as // async_release_files(). virtual void async_stop_torrent(storage_index_t storage , std::function handler = std::function()) = 0; // This function is called when the name of a file in the specified // storage has been requested to be renamed. The disk I/O object is // responsible for renaming the file without racing with other // potentially outstanding operations against the file (such as read, // write, move, etc.). virtual void async_rename_file(storage_index_t storage , file_index_t index, std::string name , std::function handler) = 0; // This function is called when some file(s) on disk have been requested // to be removed by the client. ``storage`` indicates which torrent is // referred to. See session_handle for ``remove_flags_t`` flags // indicating which files are to be removed. // e.g. session_handle::delete_files - delete all files // session_handle::delete_partfile - only delete part file. virtual void async_delete_files(storage_index_t storage, remove_flags_t options , std::function handler) = 0; // This is called to set the priority of some or all files. Changing the // priority from or to 0 may involve moving data to and from the // partfile. The disk I/O object is responsible for correctly // synchronizing this work to not race with any potentially outstanding // asynchronous operations affecting these files. // // ``prio`` is a vector of the file priority for all files. If it's // shorter than the total number of files in the torrent, they are // assumed to be set to the default priority. virtual void async_set_file_priority(storage_index_t storage , aux::vector prio , std::function)> handler) = 0; // This is called when a piece fails the hash check, to ensure there are // no outstanding disk operations to the piece before blocks are // re-requested from peers to overwrite the existing blocks. The disk I/O // object does not need to perform any action other than synchronize // with all outstanding disk operations to the specified piece before // posting the result back. virtual void async_clear_piece(storage_index_t storage, piece_index_t index , std::function handler) = 0; // update_stats_counters() is called to give the disk storage an // opportunity to update gauges in the ``c`` stats counters, that aren't // updated continuously as operations are performed. This is called // before a snapshot of the counters are passed to the client. virtual void update_stats_counters(counters& c) const = 0; // Return a list of all the files that are currently open for the // specified storage/torrent. This is is just used for the client to // query the currently open files, and which modes those files are open // in. virtual std::vector get_status(storage_index_t) const = 0; // this is called when the session is starting to shut down. The disk // I/O object is expected to flush any outstanding write jobs, cancel // hash jobs and initiate tearing down of any internal threads. If // ``wait`` is true, this should be asynchronous. i.e. this call should // not return until all threads have stopped and all jobs have either // been aborted or completed and the disk I/O object is ready to be // destructed. virtual void abort(bool wait) = 0; // This will be called after a batch of disk jobs has been issues (via // the ``async_*`` ). It gives the disk I/O object an opportunity to // notify any potential condition variables to wake up the disk // thread(s). The ``async_*`` calls can of course also notify condition // variables, but doing it in this call allows for batching jobs, by // issuing the notification once for a collection of jobs. virtual void submit_jobs() = 0; // This is called to notify the disk I/O object that the settings have // been updated. In the disk io constructor, a settings_interface // reference is passed in. Whenever these settings are updated, this // function is called to allow the disk I/O object to react to any // changed settings relevant to its operations. virtual void settings_updated() = 0; // hidden virtual ~disk_interface() {} }; // a unique, owning, reference to the storage of a torrent in a disk io // subsystem (class that implements disk_interface). This is held by the // internal libtorrent torrent object to tie the storage object allocated // for a torrent to the lifetime of the internal torrent object. When a // torrent is removed from the session, this holder is destructed and will // inform the disk object. struct TORRENT_EXPORT storage_holder { // constructs a storage holder, for storage ``idx`` held at disk // interface ``disk_io``. When the holder is destructed or reset, // ``disk_io`` will be notified that the storage can be removed. storage_holder(storage_index_t idx, disk_interface& disk_io) : m_disk_io(&disk_io) , m_idx(idx) {} storage_holder() = default; ~storage_holder() { if (m_disk_io) m_disk_io->remove_torrent(m_idx); } explicit operator bool() const { return m_disk_io != nullptr; } operator storage_index_t() const { TORRENT_ASSERT(m_disk_io); return m_idx; } // release ownership of the storage, removing the torrent from // the disk subsystem. After this call the holder is empty and // ``operator bool()`` returns false. void reset() { if (m_disk_io) m_disk_io->remove_torrent(m_idx); m_disk_io = nullptr; } storage_holder(storage_holder const&) = delete; storage_holder& operator=(storage_holder const&) = delete; storage_holder(storage_holder&& rhs) noexcept : m_disk_io(rhs.m_disk_io) , m_idx(rhs.m_idx) { rhs.m_disk_io = nullptr; } storage_holder& operator=(storage_holder&& rhs) noexcept { if (&rhs == this) return *this; if (m_disk_io) m_disk_io->remove_torrent(m_idx); m_disk_io = rhs.m_disk_io; m_idx = rhs.m_idx; rhs.m_disk_io = nullptr; return *this; } private: disk_interface* m_disk_io = nullptr; storage_index_t m_idx{0}; }; } // namespace libtorrent #endif