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/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