util/cexec: Add lazy evaluated error checking functions

This inhibits things like error string construction until an error
actually occurs to speed up the good path.

Change-Id: I9a9350259466a2284536d50074c2c42b008da33b
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/stdplus/signal.cpp b/src/stdplus/signal.cpp
index 85b9f1e..525f2c0 100644
--- a/src/stdplus/signal.cpp
+++ b/src/stdplus/signal.cpp
@@ -8,14 +8,12 @@
 namespace signal
 {
 
-using util::callCheckErrno;
-
 void block(int signum)
 {
     sigset_t set;
-    callCheckErrno("sigprocmask get", sigprocmask, SIG_BLOCK, nullptr, &set);
-    callCheckErrno("sigaddset", sigaddset, &set, signum);
-    callCheckErrno("sigprocmask set", sigprocmask, SIG_BLOCK, &set, nullptr);
+    CHECK_ERRNO(sigprocmask(SIG_BLOCK, nullptr, &set), "sigprocmask get");
+    CHECK_ERRNO(sigaddset(&set, signum), "sigaddset");
+    CHECK_ERRNO(sigprocmask(SIG_BLOCK, &set, nullptr), "sigprocmask set");
 }
 
 } // namespace signal
diff --git a/src/stdplus/util/cexec.hpp b/src/stdplus/util/cexec.hpp
index 822067a..8a1a3f7 100644
--- a/src/stdplus/util/cexec.hpp
+++ b/src/stdplus/util/cexec.hpp
@@ -5,6 +5,80 @@
 #include <type_traits>
 #include <utility>
 
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to set errno on error.
+ *  @details We often have a pattern in our code for checking errors and
+ *           propagating up exceptions:
+ *
+ *           int c_call(const char* path);
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               int r = c_call(path);
+ *               if (r < 0)
+ *               {
+ *                   throw std::system_error(errno, std::generic_category(),
+ *                                           "our msg");
+ *               }
+ *               return r;
+ *           }
+ *
+ *           To make that more succinct, we can use CHECK_ERRNO:
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               return CHECK_ERRNO(c_call(path), "our msg");
+ *           }
+ *
+ *  @param[in] expr          - The expression returning an errno value
+ *  @param[in] error_handler - Passed to the doError function
+ *  @throws std::system_error (depends on error_handler) for an error case
+ *  @return A successful return value based on the function type
+ */
+#define CHECK_ERRNO(expr, error_handler)                                       \
+    [&](auto check_errno_ret) {                                                \
+        if constexpr (::std::is_pointer_v<decltype(check_errno_ret)>)          \
+        {                                                                      \
+            if (check_errno_ret == nullptr)                                    \
+                ::stdplus::util::doError(errno, (error_handler));              \
+        }                                                                      \
+        else if constexpr (::std::is_signed_v<decltype(check_errno_ret)>)      \
+        {                                                                      \
+            if (check_errno_ret < 0)                                           \
+                ::stdplus::util::doError(errno, (error_handler));              \
+        }                                                                      \
+        else                                                                   \
+        {                                                                      \
+            static_assert(::std::is_same_v<decltype(check_errno_ret), int>,    \
+                          "Unimplemented check routine");                      \
+        }                                                                      \
+        return check_errno_ret;                                                \
+    }((expr))
+
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to provide error information in -r.
+ *           See CHECK_ERRNO() for details.
+ *
+ *  @param[in] expr          - The expression returning an negative error value
+ *  @param[in] error_handler - Passed to the doError function
+ *  @throws std::system_error (depends on error_handler) for an error case
+ *  @return A successful return value based on the function type
+ */
+#define CHECK_RET(expr, error_handler)                                         \
+    [&](auto check_ret_ret) {                                                  \
+        if constexpr (::std::is_signed_v<decltype(check_ret_ret)>)             \
+        {                                                                      \
+            if (check_ret_ret < 0)                                             \
+                ::stdplus::util::doError(-check_ret_ret, (error_handler));     \
+        }                                                                      \
+        else                                                                   \
+        {                                                                      \
+            static_assert(::std::is_same_v<decltype(check_ret_ret), int>,      \
+                          "Unimplemented check routine");                      \
+        }                                                                      \
+        return check_ret_ret;                                                  \
+    }((expr))
+
 namespace stdplus
 {
 namespace util
@@ -24,6 +98,33 @@
     return std::system_error(error, std::generic_category(), msg);
 }
 
+/** @brief Turns errors into exceptions for the given error handler
+ *
+ *  @param[in] error - The numeric system error code
+ *  @param[in] msg   - The string used to describe the error
+ *  @throws A system exception with the given error and msg
+ */
+inline void doError(int error, const char* msg)
+{
+    throw makeSystemError(error, msg);
+}
+inline void doError(int error, const std::string& msg)
+{
+    throw makeSystemError(error, msg.c_str());
+}
+
+/** @brief Turns errors into exceptions for the given error handler
+ *
+ *  @param[in] error - The numeric system error code
+ *  @param[in] fun   - the function used to throw the error
+ */
+template <typename Fun>
+inline std::enable_if_t<std::is_invocable_v<Fun, int>, void> doError(int error,
+                                                                     Fun&& fun)
+{
+    fun(error);
+}
+
 /** @brief   Wraps common c style error handling for exception throwing
  *           This requires the callee to set errno on error.
  *  @details We often have a pattern in our code for checking errors and
diff --git a/test/util/cexec.cpp b/test/util/cexec.cpp
index c41c2d4..f5b7a5e 100644
--- a/test/util/cexec.cpp
+++ b/test/util/cexec.cpp
@@ -68,6 +68,9 @@
 
 TEST(Cexec, CallCheckErrnoInt)
 {
+    EXPECT_EQ(1, CHECK_ERRNO(sample1(), [](int) { throw 0; }));
+    EXPECT_EQ(1, CHECK_ERRNO(sample1(), "sample1"));
+    EXPECT_EQ(1, CHECK_ERRNO(sample1(), std::string("sample1")));
     EXPECT_EQ(1, callCheckErrno("sample1", sample1));
     EXPECT_EQ(2, callCheckErrno(std::string("sample2"), &sample2, 2));
     EXPECT_EQ(4, callCheckErrno("sample::s", sample::s, 4));
@@ -87,6 +90,19 @@
                   std::string_view(e.what(), strlen(error)));
         EXPECT_EQ(EBADF, e.code().value());
     }
+
+    try
+    {
+        errno = EBADF;
+        CHECK_ERRNO(sample2(-1), error);
+        EXPECT_TRUE(false);
+    }
+    catch (const std::system_error& e)
+    {
+        EXPECT_EQ(std::string_view(error),
+                  std::string_view(e.what(), strlen(error)));
+        EXPECT_EQ(EBADF, e.code().value());
+    }
 }
 
 TEST(Cexec, CallCheckErrnoIntMem)
@@ -164,11 +180,23 @@
     {
         EXPECT_EQ(errno, error);
     }
+
+    try
+    {
+        errno = EBADF;
+        CHECK_ERRNO(sample2(-1), [](int r) { throw r; });
+        EXPECT_TRUE(false);
+    }
+    catch (int i)
+    {
+        EXPECT_EQ(EBADF, i);
+    }
 }
 
 TEST(Cexec, CallCheckRetInt)
 {
     EXPECT_EQ(1, callCheckRet("sample1", sample1));
+    EXPECT_EQ(1, CHECK_RET(sample1(), "sample1"));
     EXPECT_EQ(2, callCheckRet(std::string("sample2"), &sample2, 2));
     EXPECT_EQ(4, callCheckRet("sample::s", sample::s, 4));
     ssize_t v = 10;
@@ -187,6 +215,19 @@
                   std::string_view(e.what(), strlen(error)));
         EXPECT_EQ(EINTR, e.code().value());
     }
+
+    try
+    {
+        errno = EBADF;
+        CHECK_RET(sample2(-EINTR), error);
+        EXPECT_TRUE(false);
+    }
+    catch (const std::system_error& e)
+    {
+        EXPECT_EQ(std::string_view(error),
+                  std::string_view(e.what(), strlen(error)));
+        EXPECT_EQ(EINTR, e.code().value());
+    }
 }
 
 TEST(Cexec, CallCheckRetIntMem)