diff options
author | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2018-07-03 11:43:55 +0300 |
---|---|---|
committer | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2018-07-03 11:43:55 +0300 |
commit | 53d415a3b69d2680c15ae2c56a9baf9b149ad71d (patch) | |
tree | b55e046b3b7a2e504c9abebf4a15a3dddff67eba /include/llfio/v2.0/file_handle.hpp | |
parent | 69e9c42365a92bc1af57a843f48513e19aaf0cdf (diff) |
Rename afio to llfio part 1 of many
Diffstat (limited to 'include/llfio/v2.0/file_handle.hpp')
-rw-r--r-- | include/llfio/v2.0/file_handle.hpp | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/include/llfio/v2.0/file_handle.hpp b/include/llfio/v2.0/file_handle.hpp new file mode 100644 index 00000000..04634ed5 --- /dev/null +++ b/include/llfio/v2.0/file_handle.hpp @@ -0,0 +1,437 @@ +/* A handle to a file +(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits) +File Created: Dec 2015 + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License in the accompanying file +Licence.txt or at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Distributed under the Boost Software License, Version 1.0. + (See accompanying file Licence.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +#ifndef AFIO_FILE_HANDLE_H +#define AFIO_FILE_HANDLE_H + +#include "io_handle.hpp" +#include "path_discovery.hpp" +#include "utils.hpp" + +//! \file file_handle.hpp Provides file_handle + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) // dll interface +#endif + +AFIO_V2_NAMESPACE_EXPORT_BEGIN + +class io_service; + +/*! \class file_handle +\brief A handle to a regular file or device, kept data layout compatible with +async_file_handle. + +<table> +<tr><th></th><th>Cost of opening</th><th>Cost of i/o</th><th>Concurrency and Atomicity</th><th>Other remarks</th></tr> +<tr><td>`file_handle`</td><td>Least</td><td>Syscall</td><td>POSIX guarantees (usually)</td><td>Least gotcha</td></tr> +<tr><td>`async_file_handle`</td><td>More</td><td>Most (syscall + malloc/free + reactor)</td><td>POSIX guarantees (usually)</td><td>Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`</td></tr> +<tr><td>`mapped_file_handle`</td><td>Most</td><td>Least</td><td>None</td><td>Cannot be used with uncached i/o</td></tr> +</table> + +*/ +class AFIO_DECL file_handle : public io_handle, public fs_handle +{ + AFIO_HEADERS_ONLY_VIRTUAL_SPEC const handle &_get_handle() const noexcept final { return *this; } + +public: + using path_type = io_handle::path_type; + using extent_type = io_handle::extent_type; + using size_type = io_handle::size_type; + using mode = io_handle::mode; + using creation = io_handle::creation; + using caching = io_handle::caching; + using flag = io_handle::flag; + using buffer_type = io_handle::buffer_type; + using const_buffer_type = io_handle::const_buffer_type; + using buffers_type = io_handle::buffers_type; + using const_buffers_type = io_handle::const_buffers_type; + template <class T> using io_request = io_handle::io_request<T>; + template <class T> using io_result = io_handle::io_result<T>; + using dev_t = fs_handle::dev_t; + using ino_t = fs_handle::ino_t; + using path_view_type = fs_handle::path_view_type; + +protected: + io_service *_service{nullptr}; + +public: + //! Default constructor + constexpr file_handle() {} // NOLINT + //! Construct a handle from a supplied native handle + constexpr file_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none) + : io_handle(std::move(h), caching, flags) + , fs_handle(devid, inode) + , _service(nullptr) + { + } + //! No copy construction (use clone()) + file_handle(const file_handle &) = delete; + //! No copy assignment + file_handle &operator=(const file_handle &) = delete; + //! Implicit move construction of file_handle permitted + constexpr file_handle(file_handle &&o) noexcept : io_handle(std::move(o)), fs_handle(std::move(o)), _service(o._service) { o._service = nullptr; } + //! Explicit conversion from handle and io_handle permitted + explicit constexpr file_handle(handle &&o, dev_t devid, ino_t inode) noexcept : io_handle(std::move(o)), fs_handle(devid, inode), _service(nullptr) {} + //! Move assignment of file_handle permitted + file_handle &operator=(file_handle &&o) noexcept + { + this->~file_handle(); + new(this) file_handle(std::move(o)); + return *this; + } + //! Swap with another instance + AFIO_MAKE_FREE_FUNCTION + void swap(file_handle &o) noexcept + { + file_handle temp(std::move(*this)); + *this = std::move(o); + o = std::move(temp); + } + + /*! Create a file handle opening access to a file on path + \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute. + \param path The path relative to base to open. + \param _mode How to open the file. + \param _creation How to create the file. + \param _caching How to ask the kernel to cache the file. + \param flags Any additional custom behaviours. + + \errors Any of the values POSIX open() or CreateFile() can return. + */ + AFIO_MAKE_FREE_FUNCTION + static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<file_handle> file(const path_handle &base, path_view_type path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept; + /*! Create a file handle creating a randomly named file on a path. + The file is opened exclusively with `creation::only_if_not_exist` so it + will never collide with nor overwrite any existing file. Note also + that caching defaults to temporary which hints to the OS to only + flush changes to physical storage as lately as possible. + + \errors Any of the values POSIX open() or CreateFile() can return. + */ + AFIO_MAKE_FREE_FUNCTION + static inline result<file_handle> random_file(const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::temporary, flag flags = flag::none) noexcept + { + try + { + for(;;) + { + auto randomname = utils::random_string(32); + randomname.append(".random"); + result<file_handle> ret = file(dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags); + if(ret || (!ret && ret.error() != errc::file_exists)) + { + return ret; + } + } + } + catch(...) + { + return error_from_exception(); + } + } + /*! Create a file handle creating the named file on some path which + the OS declares to be suitable for temporary files. Most OSs are + very lazy about flushing changes made to these temporary files. + Note the default flags are to have the newly created file deleted + on first handle close. + Note also that an empty name is equivalent to calling + `random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation + parameter is ignored. + + \note If the temporary file you are creating is not going to have its + path sent to another process for usage, this is the WRONG function + to use. Use `temp_inode()` instead, it is far more secure. + + \errors Any of the values POSIX open() or CreateFile() can return. + */ + AFIO_MAKE_FREE_FUNCTION + static inline result<file_handle> temp_file(path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::temporary, flag flags = flag::unlink_on_first_close) noexcept + { + auto &tempdirh = path_discovery::storage_backed_temporary_files_directory(); + return name.empty() ? random_file(tempdirh, _mode, _caching, flags) : file(tempdirh, name, _mode, _creation, _caching, flags); + } + /*! \em Securely create a file handle creating a temporary anonymous inode in + the filesystem referred to by \em dirpath. The inode created has + no name nor accessible path on the filing system and ceases to + exist as soon as the last handle is closed, making it ideal for use as + a temporary file where other processes do not need to have access + to its contents via some path on the filing system (a classic use case + is for backing shared memory maps). + + \errors Any of the values POSIX open() or CreateFile() can return. + */ + AFIO_MAKE_FREE_FUNCTION + static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<file_handle> temp_inode(const path_handle &dirh = path_discovery::storage_backed_temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept; + + AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~file_handle() override + { + if(_v) + { + (void) file_handle::close(); + } + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override + { + AFIO_LOG_FUNCTION_CALL(this); + if(_flags & flag::unlink_on_first_close) + { + auto ret = unlink(); + if(!ret) + { + // File may have already been deleted, if so ignore + if(ret.error() != errc::no_such_file_or_directory) + { + return ret.error(); + } + } + } + return io_handle::close(); + } + + AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override; + + /*! Clone this handle (copy constructor is disabled to avoid accidental copying), + optionally race free reopening the handle with different access or caching. + + Microsoft Windows provides a syscall for cloning an existing handle but with new + access. On POSIX, if not changing the mode, we change caching via `fcntl()`, if + changing the mode we must loop calling `current_path()`, + trying to open the path returned and making sure it is the same inode. + + \errors Any of the values POSIX dup() or DuplicateHandle() can return. + \mallocs On POSIX if changing the mode, we must loop calling `current_path()` and + trying to open the path returned. Thus many allocations may occur. + */ + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept; + + //! The i/o service this handle is attached to, if any + io_service *service() const noexcept { return _service; } + + /*! Return the current maximum permitted extent of the file. + + \errors Any of the values POSIX fstat() or GetFileInformationByHandleEx() can return. + */ + AFIO_MAKE_FREE_FUNCTION + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> maximum_extent() const noexcept; + + /*! Resize the current maximum permitted extent of the file to the given extent, avoiding any + new allocation of physical storage where supported. Note that on extents based filing systems + this will succeed even if there is insufficient free space on the storage medium. + + \return The bytes actually truncated to. + \param newsize The bytes to truncate the file to. + \errors Any of the values POSIX ftruncate() or SetFileInformationByHandle() can return. + */ + AFIO_MAKE_FREE_FUNCTION + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> truncate(extent_type newsize) noexcept; + + /*! \brief Returns a list of currently valid extents for this open file. WARNING: racy! + \return A vector of pairs of extent offset + extent length representing the valid extents + in this file. Filing systems which do not support extents return a single extent matching + the length of the file rather than returning an error. + */ + AFIO_MAKE_FREE_FUNCTION + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<std::vector<std::pair<extent_type, extent_type>>> extents() const noexcept; + + /*! \brief Efficiently zero, and possibly deallocate, data on storage. + + On most major operating systems and with recent filing systems which are "extents based", one can + deallocate the physical storage of a file, causing the space deallocated to appear all bits zero. + This call attempts to deallocate whole pages (usually 4Kb) entirely, and memset's any excess to all + bits zero. This call works on most Linux filing systems with a recent kernel, Microsoft Windows + with NTFS, and FreeBSD with ZFS. On other systems it simply writes zeros. + + \return The bytes zeroed. + \param offset The offset to start zeroing from. + \param bytes The number of bytes to zero. + \param d An optional deadline by which the i/o must complete, else it is cancelled. + Note function may return significantly after this deadline if the i/o takes long to cancel. + \errors Any of the values POSIX write() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be + returned if deadline i/o is not possible with this particular handle configuration (e.g. + writing to regular files on POSIX or writing to a non-overlapped HANDLE on Windows). + \mallocs The default synchronous implementation in file_handle performs no memory allocation. + The asynchronous implementation in async_file_handle may perform one calloc and one free. + */ + AFIO_MAKE_FREE_FUNCTION + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> zero(extent_type offset, extent_type bytes, deadline d = deadline()) noexcept; +}; + +//! \brief Constructor for `file_handle` +template <> struct construct<file_handle> +{ + const path_handle &base; + file_handle::path_view_type _path; + file_handle::mode _mode = file_handle::mode::read; + file_handle::creation _creation = file_handle::creation::open_existing; + file_handle::caching _caching = file_handle::caching::all; + file_handle::flag flags = file_handle::flag::none; + result<file_handle> operator()() const noexcept { return file_handle::file(base, _path, _mode, _creation, _caching, flags); } +}; + +// BEGIN make_free_functions.py +//! Swap with another instance +inline void swap(file_handle &self, file_handle &o) noexcept +{ + return self.swap(std::forward<decltype(o)>(o)); +} +/*! Create a file handle opening access to a file on path +\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute. +\param path The path relative to base to open. +\param _mode How to open the file. +\param _creation How to create the file. +\param _caching How to ask the kernel to cache the file. +\param flags Any additional custom behaviours. + +\errors Any of the values POSIX open() or CreateFile() can return. +*/ +inline result<file_handle> file(const path_handle &base, file_handle::path_view_type path, file_handle::mode _mode = file_handle::mode::read, file_handle::creation _creation = file_handle::creation::open_existing, file_handle::caching _caching = file_handle::caching::all, + file_handle::flag flags = file_handle::flag::none) noexcept +{ + return file_handle::file(std::forward<decltype(base)>(base), std::forward<decltype(path)>(path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags)); +} +/*! Create a file handle creating a randomly named file on a path. +The file is opened exclusively with `creation::only_if_not_exist` so it +will never collide with nor overwrite any existing file. Note also +that caching defaults to temporary which hints to the OS to only +flush changes to physical storage as lately as possible. + +\errors Any of the values POSIX open() or CreateFile() can return. +*/ +inline result<file_handle> random_file(const path_handle &dirpath, file_handle::mode _mode = file_handle::mode::write, file_handle::caching _caching = file_handle::caching::temporary, file_handle::flag flags = file_handle::flag::none) noexcept +{ + return file_handle::random_file(std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags)); +} +/*! Create a file handle creating the named file on some path which +the OS declares to be suitable for temporary files. Most OSs are +very lazy about flushing changes made to these temporary files. +Note the default flags are to have the newly created file deleted +on first handle close. +Note also that an empty name is equivalent to calling +`random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation +parameter is ignored. + +\note If the temporary file you are creating is not going to have its +path sent to another process for usage, this is the WRONG function +to use. Use `temp_inode()` instead, it is far more secure. + +\errors Any of the values POSIX open() or CreateFile() can return. +*/ +inline result<file_handle> temp_file(file_handle::path_view_type name = file_handle::path_view_type(), file_handle::mode _mode = file_handle::mode::write, file_handle::creation _creation = file_handle::creation::if_needed, file_handle::caching _caching = file_handle::caching::temporary, + file_handle::flag flags = file_handle::flag::unlink_on_first_close) noexcept +{ + return file_handle::temp_file(std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags)); +} +/*! \em Securely create a file handle creating a temporary anonymous inode in +the filesystem referred to by \em dirpath. The inode created has +no name nor accessible path on the filing system and ceases to +exist as soon as the last handle is closed, making it ideal for use as +a temporary file where other processes do not need to have access +to its contents via some path on the filing system (a classic use case +is for backing shared memory maps). + +\errors Any of the values POSIX open() or CreateFile() can return. +*/ +inline result<file_handle> temp_inode(const path_handle &dirh = path_discovery::storage_backed_temporary_files_directory(), file_handle::mode _mode = file_handle::mode::write, file_handle::flag flags = file_handle::flag::none) noexcept +{ + return file_handle::temp_inode(std::forward<decltype(dirh)>(dirh), std::forward<decltype(_mode)>(_mode), std::forward<decltype(flags)>(flags)); +} +/*! Return the current maximum permitted extent of the file. + +\errors Any of the values POSIX fstat() or GetFileInformationByHandleEx() can return. +*/ +inline result<file_handle::extent_type> maximum_extent(const file_handle &self) noexcept +{ + return self.maximum_extent(); +} +/*! Resize the current maximum permitted extent of the file to the given extent, avoiding any +new allocation of physical storage where supported. Note that on extents based filing systems +this will succeed even if there is insufficient free space on the storage medium. + +\return The bytes actually truncated to. +\param self The object whose member function to call. +\param newsize The bytes to truncate the file to. +\errors Any of the values POSIX ftruncate() or SetFileInformationByHandle() can return. +*/ +inline result<file_handle::extent_type> truncate(file_handle &self, file_handle::extent_type newsize) noexcept +{ + return self.truncate(std::forward<decltype(newsize)>(newsize)); +} +/*! \brief Returns a list of currently valid extents for this open file. WARNING: racy! +*/ +inline result<std::vector<std::pair<file_handle::extent_type, file_handle::extent_type>>> extents(const file_handle &self) noexcept +{ + return self.extents(); +} +/*! \brief Efficiently zero, and possibly deallocate, data on storage. + +On most major operating systems and with recent filing systems which are "extents based", one can +deallocate the physical storage of a file, causing the space deallocated to appear all bits zero. +This call attempts to deallocate whole pages (usually 4Kb) entirely, and memset's any excess to all +bits zero. This call works on most Linux filing systems with a recent kernel, Microsoft Windows +with NTFS, and FreeBSD with ZFS. On other systems it simply writes zeros. + +\return The bytes zeroed. +\param self The object whose member function to call. +\param offset The offset to start zeroing from. +\param bytes The number of bytes to zero. +\param d An optional deadline by which the i/o must complete, else it is cancelled. +Note function may return significantly after this deadline if the i/o takes long to cancel. +\errors Any of the values POSIX write() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be +returned if deadline i/o is not possible with this particular handle configuration (e.g. +writing to regular files on POSIX or writing to a non-overlapped HANDLE on Windows). +\mallocs The default synchronous implementation in file_handle performs no memory allocation. +The asynchronous implementation in async_file_handle may perform one calloc and one free. +*/ +inline result<file_handle::extent_type> zero(file_handle &self, file_handle::extent_type offset, file_handle::extent_type bytes, deadline d = deadline()) noexcept +{ + return self.zero(std::forward<decltype(offset)>(offset), std::forward<decltype(bytes)>(bytes), std::forward<decltype(d)>(d)); +} +// END make_free_functions.py + +AFIO_V2_NAMESPACE_END + +#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#define AFIO_INCLUDED_BY_HEADER 1 +#ifdef _WIN32 +#include "detail/impl/windows/file_handle.ipp" +#else +#include "detail/impl/posix/file_handle.ipp" +#endif +#undef AFIO_INCLUDED_BY_HEADER +#endif + +// Needs to be here as path discovery uses file_handle +#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#define AFIO_INCLUDED_BY_HEADER 1 +#include "detail/impl/path_discovery.ipp" +#undef AFIO_INCLUDED_BY_HEADER +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif |