signal: Add idempotent block function

This makes it trivial to block individual signals from being handled by
a thread. Useful when tryng to set up signal handling in event loops.

Tested:
    Builds and passes unit tests.

Change-Id: I61739debe2a47ec0ec3e767cf138125c6f59165f
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index 7d1eea5..205819a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,4 +41,4 @@
 /src/stdplus.pc
 
 # Output binaries
-/test/placeholder
+/test/signal
diff --git a/src/Makefile.am b/src/Makefile.am
index de5de8d..5e935f4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,5 +4,5 @@
 libstdplus_la_SOURCES =
 libstdplus_la_LIBADD = $(COMMON_LIBS)
 
-nobase_include_HEADERS += stdplus/placeholder.hpp
-libstdplus_la_SOURCES += stdplus/placeholder.cpp
+nobase_include_HEADERS += stdplus/signal.hpp
+libstdplus_la_SOURCES += stdplus/signal.cpp
diff --git a/src/stdplus/placeholder.cpp b/src/stdplus/placeholder.cpp
deleted file mode 100644
index a4c9e2b..0000000
--- a/src/stdplus/placeholder.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include <stdplus/placeholder.hpp>
diff --git a/src/stdplus/placeholder.hpp b/src/stdplus/placeholder.hpp
deleted file mode 100644
index 6f70f09..0000000
--- a/src/stdplus/placeholder.hpp
+++ /dev/null
@@ -1 +0,0 @@
-#pragma once
diff --git a/src/stdplus/signal.cpp b/src/stdplus/signal.cpp
new file mode 100644
index 0000000..10f8e39
--- /dev/null
+++ b/src/stdplus/signal.cpp
@@ -0,0 +1,32 @@
+#include <signal.h>
+#include <stdplus/signal.hpp>
+#include <system_error>
+
+namespace stdplus
+{
+namespace signal
+{
+
+void block(int signum)
+{
+    sigset_t set;
+    if (sigprocmask(SIG_BLOCK, nullptr, &set) < 0)
+    {
+        throw std::system_error(errno, std::generic_category(),
+                                "sigprocmask get");
+    }
+
+    if (sigaddset(&set, signum) < 0)
+    {
+        throw std::system_error(errno, std::generic_category(), "sigaddset");
+    }
+
+    if (sigprocmask(SIG_BLOCK, &set, nullptr) < 0)
+    {
+        throw std::system_error(errno, std::generic_category(),
+                                "sigprocmask set");
+    }
+}
+
+} // namespace signal
+} // namespace stdplus
diff --git a/src/stdplus/signal.hpp b/src/stdplus/signal.hpp
new file mode 100644
index 0000000..465181b
--- /dev/null
+++ b/src/stdplus/signal.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace stdplus
+{
+namespace signal
+{
+
+/** @brief Blocks the signal from being handled by the designated
+ *         sigaction. If the signal is already blocked this does nothing.
+ *
+ *  @param[in] signum - The int representing the signal to block
+ *  @throws std::system_error if any underlying error occurs.
+ */
+void block(int signum);
+
+} // namespace signal
+} // namespace stdplus
diff --git a/test/Makefile.am b/test/Makefile.am
index c67e07a..1c4a5a1 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -7,7 +7,7 @@
 check_PROGRAMS =
 TESTS = $(check_PROGRAMS)
 
-check_PROGRAMS += placeholder
-placeholder_SOURCES = placeholder.cpp
-placeholder_CPPFLAGS = $(gtest_cppflags)
-placeholder_LDADD = $(gtest_ldadd)
+check_PROGRAMS += signal
+signal_SOURCES = signal.cpp
+signal_CPPFLAGS = $(gtest_cppflags)
+signal_LDADD = $(gtest_ldadd)
diff --git a/test/placeholder.cpp b/test/placeholder.cpp
deleted file mode 100644
index 782277f..0000000
--- a/test/placeholder.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include <gtest/gtest.h>
-
-TEST(PlaceholderTest, None)
-{
-}
diff --git a/test/signal.cpp b/test/signal.cpp
new file mode 100644
index 0000000..6389cd8
--- /dev/null
+++ b/test/signal.cpp
@@ -0,0 +1,62 @@
+#include <cstring>
+#include <gtest/gtest.h>
+#include <signal.h>
+#include <stdplus/signal.hpp>
+
+namespace stdplus
+{
+namespace signal
+{
+namespace
+{
+
+TEST(SignalTest, BlockSignal)
+{
+    constexpr int s = SIGINT;
+    constexpr int otherS = SIGTERM;
+    constexpr int notBlocked = SIGPROF;
+
+    sigset_t expectedSet;
+    EXPECT_EQ(0, sigprocmask(SIG_BLOCK, nullptr, &expectedSet));
+    EXPECT_EQ(0, sigaddset(&expectedSet, otherS));
+    EXPECT_EQ(0, sigprocmask(SIG_BLOCK, &expectedSet, nullptr));
+    EXPECT_EQ(0, sigismember(&expectedSet, notBlocked));
+    EXPECT_EQ(0, sigismember(&expectedSet, s));
+    EXPECT_EQ(0, sigaddset(&expectedSet, s));
+
+    block(s);
+
+    sigset_t newSet;
+    EXPECT_EQ(0, sigprocmask(SIG_BLOCK, nullptr, &newSet));
+    EXPECT_EQ(sigismember(&expectedSet, s), sigismember(&newSet, s));
+    EXPECT_EQ(sigismember(&expectedSet, otherS), sigismember(&newSet, otherS));
+    EXPECT_EQ(sigismember(&expectedSet, notBlocked),
+              sigismember(&newSet, notBlocked));
+}
+
+TEST(SignalTest, KeepBlockSignal)
+{
+    constexpr int s = SIGINT;
+    constexpr int otherS = SIGTERM;
+    constexpr int notBlocked = SIGPROF;
+
+    sigset_t expectedSet;
+    EXPECT_EQ(0, sigprocmask(SIG_BLOCK, nullptr, &expectedSet));
+    EXPECT_EQ(0, sigaddset(&expectedSet, s));
+    EXPECT_EQ(0, sigaddset(&expectedSet, otherS));
+    EXPECT_EQ(0, sigismember(&expectedSet, notBlocked));
+    EXPECT_EQ(0, sigprocmask(SIG_BLOCK, &expectedSet, nullptr));
+
+    block(s);
+
+    sigset_t newSet;
+    EXPECT_EQ(0, sigprocmask(SIG_BLOCK, nullptr, &newSet));
+    EXPECT_EQ(sigismember(&expectedSet, s), sigismember(&newSet, s));
+    EXPECT_EQ(sigismember(&expectedSet, otherS), sigismember(&newSet, otherS));
+    EXPECT_EQ(sigismember(&expectedSet, notBlocked),
+              sigismember(&newSet, notBlocked));
+}
+
+} // namespace
+} // namespace signal
+} // namespace stdplus