util/cexec: Implement exception helpers

See src/stdplus/util/cexec.hpp callCheckErrno for the motivation behind
this change.

Change-Id: I0225b87398b632624f2ef8ccd6c00b5dd6b7e056
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index f58f9f5..b7f492b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,4 @@
 /test/handle/copyable
 /test/handle/managed
 /test/signal
+/test/util/cexec
diff --git a/src/Makefile.am b/src/Makefile.am
index d45007e..2c1f835 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -10,3 +10,5 @@
 
 nobase_include_HEADERS += stdplus/signal.hpp
 libstdplus_la_SOURCES += stdplus/signal.cpp
+
+nobase_include_HEADERS += stdplus/util/cexec.hpp
diff --git a/src/meson.build b/src/meson.build
index a6ff714..8744d33 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -23,3 +23,7 @@
   'stdplus/handle/copyable.hpp',
   'stdplus/handle/managed.hpp',
   subdir: 'stdplus/handle')
+
+install_headers(
+  'stdplus/util/cexec.hpp',
+  subdir: 'stdplus/util')
diff --git a/src/stdplus/util/cexec.hpp b/src/stdplus/util/cexec.hpp
new file mode 100644
index 0000000..86dfe88
--- /dev/null
+++ b/src/stdplus/util/cexec.hpp
@@ -0,0 +1,113 @@
+#pragma once
+#include <functional>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+
+namespace stdplus
+{
+namespace util
+{
+
+/** @brief   Common pattern used by default for constructing a system exception
+ *  @details Most libc or system calls will want to return a generic
+ *           system_error when detecting an error in a call. This function
+ *           creates that error from the errno and message.
+ *
+ *  @param[in] error -
+ *  @param[in] msg   -
+ *  @return The exception passed to a `throw` call.
+ */
+inline auto makeSystemError(int error, const char* msg)
+{
+    return std::system_error(error, std::generic_category(), msg);
+}
+
+/** @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 callCheckErrno:
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               return callCheckErrno("our msg", c_call, path);
+ *           }
+ *
+ *  @param[in] msg     - The error message displayed when errno is set.
+ *  @param[in] func    - The wrapped function we invoke
+ *  @param[in] args... - The arguments passed to the function
+ *  @throws std::system_error for an error case.
+ *  @return A successful return value based on the function type
+ */
+template <auto (*makeError)(int, const char*) = makeSystemError,
+          typename... Args>
+inline auto callCheckErrno(const char* msg, Args&&... args)
+{
+    using Ret = typename std::invoke_result<Args...>::type;
+
+    if constexpr (std::is_integral_v<Ret> && std::is_signed_v<Ret>)
+    {
+        Ret r = std::invoke(std::forward<Args>(args)...);
+        if (r < 0)
+            throw makeError(errno, msg);
+        return r;
+    }
+    else if constexpr (std::is_pointer_v<Ret>)
+    {
+        Ret r = std::invoke(std::forward<Args>(args)...);
+        if (r == nullptr)
+            throw makeError(errno, msg);
+        return r;
+    }
+    else
+    {
+        static_assert(std::is_same_v<Ret, int>, "Unimplemented check routine");
+    }
+}
+
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to provide error information in -r.
+ *           See callCheckErrno() for details.
+ *
+ *  @param[in] msg     - The error message displayed when errno is set.
+ *  @param[in] func    - The wrapped function we invoke
+ *  @param[in] args... - The arguments passed to the function
+ *  @throws std::system_error for an error case.
+ *  @return A successful return value based on the function type
+ */
+template <auto (*makeError)(int, const char*) = makeSystemError,
+          typename... Args>
+inline auto callCheckRet(const char* msg, Args&&... args)
+{
+    using Ret = typename std::invoke_result<Args...>::type;
+
+    if constexpr (std::is_integral_v<Ret> && std::is_signed_v<Ret>)
+    {
+        Ret r = std::invoke(std::forward<Args>(args)...);
+        if (r < 0)
+            throw makeError(-r, msg);
+        return r;
+    }
+    else
+    {
+        static_assert(std::is_same_v<Ret, int>, "Unimplemented check routine");
+    }
+}
+
+} // namespace util
+} // namespace stdplus
diff --git a/test/Makefile.am b/test/Makefile.am
index 76cd56f..7698901 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -21,3 +21,8 @@
 signal_SOURCES = signal.cpp
 signal_CPPFLAGS = $(gtest_cppflags)
 signal_LDADD = $(gtest_ldadd)
+
+check_PROGRAMS += util/cexec
+util_cexec_SOURCES = util/cexec.cpp
+util_cexec_CPPFLAGS = $(gtest_cppflags)
+util_cexec_LDADD = $(gtest_ldadd)
diff --git a/test/meson.build b/test/meson.build
index cc3d12d..f11864e 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -5,6 +5,7 @@
   'signal',
   'handle/copyable',
   'handle/managed',
+  'util/cexec',
 ]
 
 foreach t : tests
diff --git a/test/util/cexec.cpp b/test/util/cexec.cpp
new file mode 100644
index 0000000..51fdb8b
--- /dev/null
+++ b/test/util/cexec.cpp
@@ -0,0 +1,230 @@
+#include <gtest/gtest.h>
+#include <stdplus/util/cexec.hpp>
+#include <string_view>
+#include <system_error>
+
+namespace stdplus
+{
+namespace util
+{
+namespace
+{
+
+int sample1()
+{
+    return 1;
+}
+
+int sample2(int val)
+{
+    return val;
+}
+
+ssize_t sample3(int val, ssize_t* val2)
+{
+    return *val2 + val;
+}
+
+const char* ptr(const char* p)
+{
+    return p;
+}
+
+struct sample
+{
+    int count = 3;
+
+    int one()
+    {
+        return count++;
+    }
+
+    int two(int val) const
+    {
+        return val;
+    }
+
+    int* ptr()
+    {
+        return &count;
+    }
+
+    int* ptr2()
+    {
+        return nullptr;
+    }
+
+    static int s(int val)
+    {
+        return val;
+    }
+};
+
+int makeTrivialError(int error, const char* msg)
+{
+    (void)msg;
+    return error;
+}
+
+TEST(Cexec, CallCheckErrnoInt)
+{
+    EXPECT_EQ(1, callCheckErrno("sample1", sample1));
+    EXPECT_EQ(2, callCheckErrno("sample2", &sample2, 2));
+    EXPECT_EQ(4, callCheckErrno("sample::s", sample::s, 4));
+    ssize_t v = 10;
+    EXPECT_EQ(12, callCheckErrno("sample3", sample3, 2, &v));
+
+    constexpr auto error = "sample2 error";
+    try
+    {
+        errno = EBADF;
+        callCheckErrno(error, sample2, -1);
+        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)
+{
+    sample s;
+    const sample* sp = &s;
+    EXPECT_EQ(3, callCheckErrno("sample::one", &sample::one, s));
+    EXPECT_EQ(4, callCheckErrno("sample::one", &sample::one, &s));
+    EXPECT_EQ(5, callCheckErrno("sample::two", &sample::two, sp, 5));
+
+    constexpr auto error = "sample error";
+    try
+    {
+        errno = EBADF;
+        callCheckErrno(error, &sample::two, sp, -1);
+        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, CallCheckErrnoPtr)
+{
+    constexpr auto sample = "sample";
+    EXPECT_EQ(sample, callCheckErrno("sample1", ptr, sample));
+
+    constexpr auto error = "sample error";
+    try
+    {
+        errno = EBADF;
+        callCheckErrno(error, &ptr, nullptr);
+        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, CallCheckErrnoPtrMem)
+{
+    sample s;
+    EXPECT_EQ(&s.count, callCheckErrno("sample1", &sample::ptr, &s));
+
+    constexpr auto error = "sample error";
+    try
+    {
+        errno = EBADF;
+        callCheckErrno(error, &sample::ptr2, s);
+        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, CallCheckErrnoErrorFunc)
+{
+    errno = EBADF;
+    try
+    {
+        callCheckErrno<makeTrivialError>("sample2", sample2, -1);
+        EXPECT_TRUE(false);
+    }
+    catch (int error)
+    {
+        EXPECT_EQ(errno, error);
+    }
+}
+
+TEST(Cexec, CallCheckRetInt)
+{
+    EXPECT_EQ(1, callCheckRet("sample1", sample1));
+    EXPECT_EQ(2, callCheckRet("sample2", &sample2, 2));
+    EXPECT_EQ(4, callCheckRet("sample::s", sample::s, 4));
+    ssize_t v = 10;
+    EXPECT_EQ(12, callCheckRet("sample3", sample3, 2, &v));
+
+    constexpr auto error = "sample2 error";
+    try
+    {
+        errno = EBADF;
+        callCheckRet(error, sample2, -EINTR);
+        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)
+{
+    sample s;
+    const sample* sp = &s;
+    EXPECT_EQ(3, callCheckRet("sample::one", &sample::one, s));
+    EXPECT_EQ(4, callCheckRet("sample::one", &sample::one, &s));
+    EXPECT_EQ(5, callCheckRet("sample::two", &sample::two, sp, 5));
+
+    constexpr auto error = "sample error";
+    try
+    {
+        errno = EBADF;
+        callCheckRet(error, &sample::two, s, -EINTR);
+        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, CallCheckRetErrorFunc)
+{
+    try
+    {
+        callCheckRet<makeTrivialError>("sample2", sample2, -EBADF);
+        EXPECT_TRUE(false);
+    }
+    catch (int error)
+    {
+        EXPECT_EQ(EBADF, error);
+    }
+}
+
+} // namespace
+} // namespace util
+} // namespace stdplus