A file handle
Borrowing from llfio::file_handle
which uses this design pattern1, here is a simplified file_handle
implementation:
class file_handle
{
int _fd{-1}; // file descriptor
struct stat _stat
{
0
}; // stat of the fd at open
// Phase 1 private constexpr constructor
constexpr file_handle() {}
public:
using path_type = filesystem::path;
//! The behaviour of the handle: does it read, read and write, or atomic append?
enum class mode : unsigned char // bit 0 set means writable
{
unchanged = 0,
none = 2, //!< No ability to read or write anything, but can synchronise (SYNCHRONIZE or 0)
attr_read = 4, //!< Ability to read attributes (FILE_READ_ATTRIBUTES|SYNCHRONIZE or O_RDONLY)
attr_write = 5, //!< Ability to read and write attributes (FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES|SYNCHRONIZE or O_RDONLY)
read = 6, //!< Ability to read (READ_CONTROL|FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA|SYNCHRONISE or O_RDONLY)
write = 7, //!< Ability to read and write (READ_CONTROL|FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA|FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA|FILE_APPEND_DATA|SYNCHRONISE or O_RDWR)
append = 9 //!< All mainstream OSs and CIFS guarantee this is atomic with respect to all other appenders (FILE_APPEND_DATA|SYNCHRONISE or O_APPEND)
};
// Moves but not copies permitted
file_handle(const file_handle &) = delete;
file_handle(file_handle &&o) noexcept : _fd(o._fd) { o._fd = -1; }
file_handle &operator=(const file_handle &) = delete;
file_handle &operator=(file_handle &&o) noexcept
{
this->~file_handle();
new(this) file_handle(std::move(o));
return *this;
}
// Destruction closes the handle
~file_handle()
{
if(_fd != -1)
{
if(-1 == ::close(_fd))
{
int e = errno;
std::cerr << "FATAL: Closing the fd during destruction failed due to " << strerror(e) << std::endl;
std::terminate();
}
_fd = -1;
}
}
// Phase 2 static member constructor function, which cannot throw
static inline outcome::result<file_handle> file(path_type path, mode mode = mode::read) noexcept;
};
Note the default member initialisers, these are particularly convenient for
implementing phase 1 of construction. Note also the constexpr
constructor,
which thanks to the default member initialisers is otherwise empty.
File handles are very expensive to copy as they involve a syscall to duplicate the file descriptor, so we enable moves only.
The destructor closes the file descriptor if it is not -1, and if the close fails, seeing as there is nothing else we can do without leaking the file descriptor, we fatal exit the process.
Finally we declare the phase 2 constructor which is a static member function.
- LLFIO uses Outcome “in anger”, both in Standard and Experimental configurations. If you would like a real world user of Outcome to study the source code of, it can be studied at https://github.com/ned14/llfio/. [return]