# 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;
    };

View this code on Github

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.

1. 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]

Last revised: February 08, 2019 at 22:18:08 UTC