Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Portable way to read a file in C++ and handle possible errors

Tags:

c++

file-io

boost

I want to do a simple thing: read the first line from a file, and do a proper error reporting in case there is no such file, no permission to read the file and so on.

I considered the following options:

  • std::ifstream. Unfortunately, there is no portable way to report system errors. Some other answers suggest checking errno after reading failed, but the standard does not guarantee that errno is set by any functions in iostreams library.
  • C style fopen/fread/fclose. This works, but is not as convenient as iostreams with std::getline. I'm looking for C++ solution.

Is there any way to accomplish this using C++14 and boost?

like image 405
Oleg Andriyanov Avatar asked Aug 23 '17 13:08

Oleg Andriyanov


People also ask

How do you handle errors in file operations?

int ferror (FILE *stream); The ferror() function checks for any error in the stream. It returns a value zero if no error has occurred and a non-zero value if there is an error. The error indication will last until the file is closed unless it is cleared by the clearerr() function.

Is there exception handling in C?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

What are error handling functions during I O operations?

Error handling helps you during the processing of input-output statements by catching severe errors that might not otherwise be noticed. For input-output operations, there are several important error-handling phrases and clauses. These are as follows: AT END phrase.


2 Answers

Disclaimer: I am the author of AFIO. But exactly what you are looking for is https://ned14.github.io/afio/ which is the v2 library incorporating the feedback from its Boost peer review in August 2015. See the list of features here.

I will of course caveat that this is an alpha quality library, and you should not use it in production code. However, quite a few people already are doing so.

How to use AFIO to solve the OP's problem:

Note that AFIO is a very low level library, hence you have to type a lot more code to achieve the same as iostreams, on the other hand you get no memory allocation, no exception throwing, no unpredictable latency spikes:

  // Try to read first line from file at path, returning no string if file does not exist,
  // throwing exception for any other error
  optional<std::string> read_first_line(filesystem::path path)
  {
    using namespace AFIO_V2_NAMESPACE;
    // The result<T> is from WG21 P0762, it looks quite like an `expected<T, std::error_code>` object
    // See Outcome v2 at https://ned14.github.io/outcome/ and https://lists.boost.org/boost-announce/2017/06/0510.php

    // Open for reading the file at path using a null handle as the base
    result<file_handle> _fh = file({}, path);
    // If fh represents failure ...
    if(!_fh)
    {
      // Fetch the error code
      std::error_code ec = _fh.error();
      // Did we fail due to file not found?
      // It is *very* important to note that ec contains the *original* error code which could
      // be POSIX, or Win32 or NT kernel error code domains. However we can always compare,
      // via 100% C++ 11 STL, any error code to a generic error *condition* for equivalence
      // So this comparison will work as expected irrespective of original error code.
      if(ec == std::errc::no_such_file_or_directory)
      {
        // Return empty optional
        return {};
      }
      std::cerr << "Opening file " << path << " failed with " << ec.message() << std::endl;
    }
    // If errored, result<T>.value() throws an error code failure as if `throw std::system_error(fh.error());`
    // Otherwise unpack the value containing the valid file_handle
    file_handle fh(std::move(_fh.value()));
    // Configure the scatter buffers for the read, ideally aligned to a page boundary for DMA
    alignas(4096) char buffer[4096];
    // There is actually a faster to type shortcut for this, but I thought best to spell it out
    file_handle::buffer_type reqs[] = {{buffer, sizeof(buffer)}};
    // Do a blocking read from offset 0 possibly filling the scatter buffers passed in
    file_handle::io_result<file_handle::buffers_type> _buffers_read = read(fh, {reqs, 0});
    if(!_buffers_read)
    {
      std::error_code ec = _fh.error();
      std::cerr << "Reading the file " << path << " failed with " << ec.message() << std::endl;
    }
    // Same as before, either throw any error or unpack the value returned
    file_handle::buffers_type buffers_read(_buffers_read.value());
    // Note that buffers returned by AFIO read() may be completely different to buffers submitted
    // This lets us skip unnecessary memory copying

    // Make a string view of the first buffer returned
    string_view v(buffers_read[0].data, buffers_read[0].len);
    // Sub view that view with the first line
    string_view line(v.substr(0, v.find_first_of('\n')));
    // Return a string copying the first line from the file, or all 4096 bytes read if no newline found.
    return std::string(line);
  }
like image 78
Niall Douglas Avatar answered Sep 21 '22 08:09

Niall Douglas


People on boost-users mailing list pointed out that the boost.beast library has OS-independent API for basic file IO including proper error handling. There are three implementations of the file concept out-of-the-box: POSIX, stdio and win32. The implementations support RAII (automatic closing on destruction) and move semantics. The POSIX file model automatically handles EINTR error. Basically this is sufficient and convenient to portably read a file chunk by chunk and, for example, explicitly handle the situation of absence of a file:

using namespace boost::beast;
using namespace boost::system;

file f;
error_code ec;
f.open("/path/to/file", file_mode::read, ec);
if(ec == errc::no_such_file_or_directory) {
    // ...
} else {
    // ...
}
like image 28
Oleg Andriyanov Avatar answered Sep 22 '22 08:09

Oleg Andriyanov