user507077
user507077

Reputation:

Construct std::error_code from errno on POSIX and GetLastError() on Windows

My question: What is the correct way to construct std::error_code instances from errno values on POSIX and GetLastError() on Windows so that the instances can be compared to the well-known values from std::errc?

The longer explanation: My goal is to add an std::error_code instance to a self-made exception object that works on POSIX and Windows systems in a C++11ish way.

In my cross-platform application I'm using a self-made I/O class hierarchy that uses the POSIX fopen() and Windows' CreateFile() calls for opening/creating files. If that fails a generic, self-made open_error exception is thrown (it is derived from std::exception, yes, but it's not one of C++'s predefined exception classes). I'm trying to extend this rather bare-bones exception with an error code; to be more precise with C++11's std::error_code if I understood correctly.

My problem is how to construct such an object from errno (in the POSIX case) or GetLastError() (in the Windows case). For POSIX, as far as I've understood things, I can simply use errno in std::error_code's constructor, e.g. like this:

std::error_code ec(errno, std::generic_category());

And that ec should be comparable to the well-known values from std::errc.

For Windows a similar call can be made, of course:

std::error_code ec(::GetLastError(), std::generic_category());

But I'm not sure whether or not the values returned by GetLastError() map nicely to the well-known constants from std::errc. I've read in Boost's system library that they do for Boost's implementation of error_code, but I'm asking about the std implementation, not about Boost's.

Please don't advice to switch to using C++ streams for file access. I'd love to, but refactoring half of my code is not something I'd like to do right at this very moment.

Upvotes: 36

Views: 11083

Answers (3)

MHebes
MHebes

Reputation: 3246

It looks like you should use system_category() for GetLastError()/errno and it will do the right thing on both platforms.

If you already have an errc, use generic_category() (or make_error_code) instead.

Here's some testing with an "address already in use" error.

#include <iostream>
#include <system_error>

#ifdef _WIN32
#include <WinError.h>
#define LAST_ERROR WSAEADDRINUSE
#else
#include <errno.h>
#define LAST_ERROR EADDRINUSE
#endif

#define ERRC std::errc::address_in_use

#define TRY(...)                                                              \
  {                                                                           \
    std::error_code ec = {__VA_ARGS__};                                       \
    std::cout << std::boolalpha << (ec == ERRC) << "\t" << ec.value() << "\t" \
              << ec.message() << "\n";                                        \
  }

int main() {
  TRY(static_cast<int>(ERRC), std::system_category())
  TRY(static_cast<int>(ERRC), std::generic_category()) // note: same as make_error_code
  TRY(static_cast<int>(LAST_ERROR), std::system_category())
  TRY(static_cast<int>(LAST_ERROR), std::generic_category()) // note: same as make_error_code
  return 0;
}

On Windows:

false   100 Cannot create another system semaphore.
true    100 address in use
true    10048   Only one usage of each socket address (protocol/network address/port) is normally permitted.
false   10048   unknown error

On POSIX:

true    98  Address already in use
true    98  Address already in use
true    98  Address already in use
true    98  Address already in use

I get similar results testing with these triplets of equivalent error codes:

equivalent errc               Windows                 POSIX

errc::broken_pipe             ERROR_BROKEN_PIPE       EPIPE
errc::filename_too_long       ERROR_BUFFER_OVERFLOW   ENAMETOOLONG
errc::not_supported           ERROR_NOT_SUPPORTED     ENOTSUP
errc::operation_would_block   WSAEWOULDBLOCK          EWOULDBLOCK

If anyone's interested, here's a list of std::errcs mapped to == WinError.h constants. This is checking if (std::error_code(static_cast<int>(win_error_constant), std::system_category()) == errc).

address_family_not_supported:
        WSAEAFNOSUPPORT
address_in_use:
        WSAEADDRINUSE
address_not_available:
        WSAEADDRNOTAVAIL
already_connected:
        WSAEISCONN
argument_list_too_long:
argument_out_of_domain:
bad_address:
        WSAEFAULT
bad_file_descriptor:
        WSAEBADF
bad_message:
broken_pipe:
        ERROR_BROKEN_PIPE
connection_aborted:
        WSAECONNABORTED
connection_already_in_progress:
        WSAEALREADY
connection_refused:
        WSAECONNREFUSED
connection_reset:
        WSAECONNRESET
cross_device_link:
        ERROR_NOT_SAME_DEVICE
destination_address_required:
        WSAEDESTADDRREQ
device_or_resource_busy:
        ERROR_BUSY_DRIVE
        ERROR_BUSY
        ERROR_OPEN_FILES
        ERROR_DEVICE_IN_USE
directory_not_empty:
        ERROR_DIR_NOT_EMPTY
executable_format_error:
file_exists:
        ERROR_FILE_EXISTS
        ERROR_ALREADY_EXISTS
file_too_large:
filename_too_long:
        ERROR_BUFFER_OVERFLOW
        WSAENAMETOOLONG
function_not_supported:
        ERROR_INVALID_FUNCTION
host_unreachable:
        WSAEHOSTUNREACH
identifier_removed:
illegal_byte_sequence:
inappropriate_io_control_operation:
interrupted:
        WSAEINTR
invalid_argument:
        ERROR_INVALID_HANDLE
        ERROR_INVALID_PARAMETER
        ERROR_NEGATIVE_SEEK
        ERROR_DIRECTORY
        ERROR_REPARSE_TAG_INVALID
        WSAEINVAL
invalid_seek:
io_error:
        ERROR_SEEK
        ERROR_WRITE_FAULT
        ERROR_READ_FAULT
        ERROR_OPEN_FAILED
        ERROR_CANTOPEN
        ERROR_CANTREAD
        ERROR_CANTWRITE
is_a_directory:
message_size:
        WSAEMSGSIZE
network_down:
        WSAENETDOWN
network_reset:
        WSAENETRESET
network_unreachable:
        WSAENETUNREACH
no_buffer_space:
        WSAENOBUFS
no_child_process:
no_link:
no_lock_available:
        ERROR_LOCK_VIOLATION
        ERROR_LOCKED
no_message_available:
no_message:
no_protocol_option:
        WSAENOPROTOOPT
no_space_on_device:
        ERROR_HANDLE_DISK_FULL
        ERROR_DISK_FULL
no_stream_resources:
no_such_device_or_address:
no_such_device:
        ERROR_INVALID_DRIVE
        ERROR_BAD_UNIT
        ERROR_DEV_NOT_EXIST
no_such_file_or_directory:
        ERROR_FILE_NOT_FOUND
        ERROR_PATH_NOT_FOUND
        ERROR_BAD_NETPATH
        ERROR_INVALID_NAME
no_such_process:
not_a_directory:
not_a_socket:
        WSAENOTSOCK
not_a_stream:
not_connected:
        WSAENOTCONN
not_enough_memory:
        ERROR_NOT_ENOUGH_MEMORY
        ERROR_OUTOFMEMORY
not_supported:
        ERROR_NOT_SUPPORTED
operation_canceled:
        ERROR_OPERATION_ABORTED
operation_in_progress:
        WSAEINPROGRESS
operation_not_permitted:
operation_not_supported:
        WSAEOPNOTSUPP
operation_would_block:
        WSAEWOULDBLOCK
owner_dead:
permission_denied:
        ERROR_ACCESS_DENIED
        ERROR_INVALID_ACCESS
        ERROR_CURRENT_DIRECTORY
        ERROR_WRITE_PROTECT
        ERROR_SHARING_VIOLATION
        ERROR_CANNOT_MAKE
        ERROR_NOACCESS
        WSAEACCES
protocol_error:
protocol_not_supported:
        WSAEPROTONOSUPPORT
read_only_file_system:
resource_deadlock_would_occur:
resource_unavailable_try_again:
        ERROR_NOT_READY
        ERROR_RETRY
result_out_of_range:
state_not_recoverable:
stream_timeout:
text_file_busy:
timed_out:
        WSAETIMEDOUT
too_many_files_open_in_system:
too_many_files_open:
        ERROR_TOO_MANY_OPEN_FILES
        WSAEMFILE
too_many_links:
too_many_symbolic_link_levels:
value_too_large:
wrong_protocol_type:
        WSAEPROTOTYPE.0

Upvotes: 2

John Gordon
John Gordon

Reputation: 2704

This is an old question, but I haven't really found a good answer on SO. The accepted answer confuses me a little as it seems to give an error_condition rather than an error_code. I have settled on the following for myself on POSIX:

std::error_code error_code_from_errno(int errno_code) {
  return std::make_error_code(static_cast<std::errc>(errno_code));
}

This always gives me the correct category (generic or system). I've had problems in the past where error codes with the same errno code compared as not equal because one had generic_category and the other had system_category.

Upvotes: 7

ecatmur
ecatmur

Reputation: 157514

That's a quality of implementation issue. The const static object returned by std::system_category() is relied upon to perform the mapping from the platform-native error code enumeration to the standard std::error_condition enumeration. Under 17.6.5.14 Value of error codes [value.error.codes]:

Implementations for operating systems that are not based on POSIX are encouraged to define values identical to the operating system’s values.

You can see in http://www.boost.org/doc/libs/1_46_1/libs/system/src/error_code.cpp how Boost performs the mapping; any standard library supplied by your compiler vendor for use on Windows should do something similar.

The intended behaviour is covered in 19.5.1.5p4, describing system_category().default_error_condition(int ev):

If the argument ev corresponds to a POSIX errno value posv, the function shall return error_condition(posv, generic_category()). Otherwise, the function shall return error_condition(ev, system_category()).

So, for example, error_code(ERROR_FILE_NOT_FOUND, std::system_category()).default_error_condition() will invoke std::system_category().default_error_condition(ERROR_FILE_NOT_FOUND), which should return std::error_condition(std::no_such_file_or_directory, std::generic_category()).

Upvotes: 12

Related Questions