diff options
Diffstat (limited to 'include/llfio/v2.0/file_handle.hpp')
-rw-r--r-- | include/llfio/v2.0/file_handle.hpp | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/include/llfio/v2.0/file_handle.hpp b/include/llfio/v2.0/file_handle.hpp index b3a0ebc6..c70d192b 100644 --- a/include/llfio/v2.0/file_handle.hpp +++ b/include/llfio/v2.0/file_handle.hpp @@ -74,6 +74,14 @@ public: using ino_t = fs_handle::ino_t; using path_view_type = fs_handle::path_view_type; + //! The kinds of concurrent user exclusion which can be performed. + enum class lock_kind + { + unknown, + shared, //!< Exclude only those requesting an exclusive lock on the same inode. + exclusive //!< Exclude those requesting any kind of lock on the same inode. + }; + protected: io_service *_service{nullptr}; @@ -254,6 +262,164 @@ public: return std::move(ret).error(); } + /*! \class extent_guard + \brief EXTENSION: RAII holder a locked extent of bytes in a file. + */ + class extent_guard + { + friend class file_handle; + file_handle *_h{nullptr}; + extent_type _offset{0}, _length{0}; + lock_kind _kind{lock_kind::unknown}; + + protected: + constexpr extent_guard(file_handle *h, extent_type offset, extent_type length, lock_kind kind) + : _h(h) + , _offset(offset) + , _length(length) + , _kind(kind) + { + } + + public: + extent_guard(const extent_guard &) = delete; + extent_guard &operator=(const extent_guard &) = delete; + + //! Default constructor + constexpr extent_guard() {} // NOLINT + //! Move constructor + extent_guard(extent_guard &&o) noexcept + : _h(o._h) + , _offset(o._offset) + , _length(o._length) + , _kind(o._kind) + { + o.release(); + } + //! Move assign + extent_guard &operator=(extent_guard &&o) noexcept + { + unlock(); + _h = o._h; + _offset = o._offset; + _length = o._length; + _kind = o._kind; + o.release(); + return *this; + } + ~extent_guard() + { + if(_h != nullptr) + { + unlock(); + } + } + //! True if extent guard is valid + explicit operator bool() const noexcept { return _h != nullptr; } + + //! The `file_handle` to be unlocked + file_handle *handle() const noexcept { return _h; } + //! Sets the `file_handle` to be unlocked + void set_handle(file_handle *h) noexcept { _h = h; } + //! The extent to be unlocked + std::tuple<extent_type, extent_type, lock_kind> extent() const noexcept { return std::make_tuple(_offset, _length, _kind); } + + //! Unlocks the locked extent immediately + void unlock() noexcept + { + if(_h != nullptr) + { + _h->unlock_range(_offset, _length); + release(); + } + } + + //! Detach this RAII unlocker from the locked state + void release() noexcept + { + _h = nullptr; + _offset = 0; + _length = 0; + _kind = lock_kind::unknown; + } + }; + + /*! \brief EXTENSION: Tries to lock the range of bytes specified for shared or exclusive access. Be aware + this passes through the same semantics as the underlying OS call, including any POSIX insanity + present on your platform: + + - Any fd closed on an inode must release all byte range locks on that inode for all + other fds. If your OS isn't new enough to support the non-insane lock API, + `flag::byte_lock_insanity` will be set in flags() after the first call to this function. + - Threads replace each other's locks, indeed locks replace each other's locks. + + You almost cetainly should use your choice of an `algorithm::shared_fs_mutex::*` instead of this + as those are more portable and performant, or use the `SharedMutex` modelling member functions + which lock the whole inode for exclusive or shared access. + + \warning This is a low-level API which you should not use directly in portable code. Another + issue is that atomic lock upgrade/downgrade, if your platform implements that (you should assume + it does not in portable code), means that on POSIX you need to *release* the old `extent_guard` + after creating a new one over the same byte range, otherwise the old `extent_guard`'s destructor + will simply unlock the range entirely. On Windows however upgrade/downgrade locks overlay, so on + that platform you must *not* release the old `extent_guard`. Look into + `algorithm::shared_fs_mutex::safe_byte_ranges` for a portable solution. + + \return An extent guard, the destruction of which will call unlock(). + \param offset The offset to lock. Note that on POSIX the top bit is always cleared before use + as POSIX uses signed transport for offsets. If you want an advisory rather than mandatory lock + on Windows, one technique is to force top bit set so the region you lock is not the one you will + i/o - obviously this reduces maximum file size to (2^63)-1. + \param bytes The number of bytes to lock. + \param kind Whether the lock is to be shared or exclusive. + \param d An optional deadline by which the lock must complete, else it is cancelled. + \errors Any of the values POSIX fcntl() can return, `errc::timed_out`, `errc::not_supported` may be + returned if deadline i/o is not possible with this particular handle configuration (e.g. + non-overlapped HANDLE on Windows). + \mallocs The default synchronous implementation in file_handle performs no memory allocation. + The asynchronous implementation in async_file_handle performs one calloc and one free. + */ + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_guard> lock_range(extent_type offset, extent_type bytes, lock_kind kind, deadline d = deadline()) noexcept; + //! \overload + result<extent_guard> try_lock_range(extent_type offset, extent_type bytes, lock_kind kind) noexcept { return lock_range(offset, bytes, kind, deadline(std::chrono::seconds(0))); } + //! \overload EXTENSION: Locks for shared access + result<extent_guard> lock_range(io_request<buffers_type> reqs, deadline d = deadline()) noexcept + { + size_t bytes = 0; + for(auto &i : reqs.buffers) + { + if(bytes + i.size() < bytes) + { + return errc::value_too_large; + } + bytes += i.size(); + } + return lock_range(reqs.offset, bytes, lock_kind::shared, d); + } + //! \overload EXTENSION: Locks for exclusive access + result<extent_guard> lock_range(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept + { + size_t bytes = 0; + for(auto &i : reqs.buffers) + { + if(bytes + i.size() < bytes) + { + return errc::value_too_large; + } + bytes += i.size(); + } + return lock_range(reqs.offset, bytes, lock_kind::exclusive, d); + } + + /*! \brief EXTENSION: Unlocks a byte range previously locked. + + \param offset The offset to unlock. This should be an offset previously locked. + \param bytes The number of bytes to unlock. This should be a byte extent previously locked. + \errors Any of the values POSIX fcntl() can return. + \mallocs None. + */ + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock_range(extent_type offset, extent_type bytes) noexcept; + /*! Return the current maximum permitted extent of the file. \errors Any of the values POSIX fstat() or GetFileInformationByHandleEx() can return. |