carb/extras/Errors.h

File members: carb/extras/Errors.h

// Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include "../Defines.h"

#include "../logging/Log.h"

#if CARB_PLATFORM_WINDOWS
#    include "../CarbWindows.h"
#    include "Unicode.h"

#    include <memory>
#elif CARB_POSIX
// Nothing needed for now
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
#include <cerrno>
#include <string.h>
#include <string>

namespace carb
{
namespace extras
{

using ErrnoType = std::decay_t<decltype(errno)>;

#if defined(DOXYGEN_BUILD) || CARB_PLATFORM_WINDOWS
using WinApiErrorType = unsigned long;
#endif

inline ErrnoType getLastErrno() noexcept
{
    return errno;
}

inline std::string convertErrnoToMessage(ErrnoType errorCode)
{
    if (errorCode == 0)
        return {};

    char buffer[1024];
    constexpr size_t bufferSize = carb::countOf(buffer);

#if CARB_PLATFORM_WINDOWS
    if (CARB_LIKELY(strerror_s(buffer, bufferSize, errorCode) == 0))
        return buffer;

    return ("Error code " + std::to_string(errorCode)) + " failed to format";
#elif CARB_PLATFORM_MACOS || CARB_PLATFORM_LINUX

    // strerror_r implementation switch
#    if CARB_PLATFORM_MACOS || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE)
    // XSI-compliant strerror_r
    if (CARB_LIKELY(strerror_r(errorCode, buffer, bufferSize) == 0))
        return buffer;

    return ("Error code " + std::to_string(errorCode)) + " failed to format";
#    else
    // GNU-specific strerror_r
    // We always get some result in this implementation for a valid buffer
    return strerror_r(errorCode, buffer, bufferSize);

#    endif // end of strerror_r implementation switch
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif // end of platform switch
}

inline std::string getLastErrnoMessage(ErrnoType* out = nullptr)
{
    const ErrnoType errorCode = getLastErrno();
    if (out)
        *out = errorCode;
    auto str = convertErrnoToMessage(errorCode);
    errno = errorCode;
    return str;
}

#if CARB_PLATFORM_WINDOWS || defined(DOXYGEN_BUILD)

inline WinApiErrorType getLastWinApiErrorCode() noexcept
{
    return ::GetLastError();
}

inline std::string convertWinApiErrorCodeToMessage(WinApiErrorType errorCode)
{
    if (errorCode == CARBWIN_ERROR_SUCCESS)
    {
        return {};
    }

    LPWSTR resultMessageBuffer = nullptr;
    const DWORD kFormatFlags = CARBWIN_FORMAT_MESSAGE_ALLOCATE_BUFFER | CARBWIN_FORMAT_MESSAGE_FROM_SYSTEM |
                               CARBWIN_FORMAT_MESSAGE_IGNORE_INSERTS;

    const DWORD dwFormatResultCode = FormatMessageW(kFormatFlags, nullptr, errorCode,
                                                    CARBWIN_MAKELANGID(CARBWIN_LANG_NEUTRAL, CARBWIN_SUBLANG_DEFAULT),
                                                    reinterpret_cast<LPWSTR>(&resultMessageBuffer), 0, nullptr);
    if (dwFormatResultCode == 0 || !resultMessageBuffer)
    {
        return ("Error code " + std::to_string(errorCode)) + " failed to format";
    }

    struct Deleter
    {
        void operator()(LPWSTR str)
        {
            ::LocalFree(str);
        }
    };
    std::unique_ptr<WCHAR, Deleter> systemBuffKeeper(resultMessageBuffer);
    return carb::extras::convertWideToUtf8(resultMessageBuffer);
}

inline std::string getLastWinApiErrorMessage()
{
    const WinApiErrorType errorCode = getLastWinApiErrorCode();
    auto str = convertWinApiErrorCodeToMessage(errorCode);
    SetLastError(errorCode);
    return str;
}

#endif // #if CARB_PLATFORM_WINDOWS

} // namespace extras
} // namespace carb