Implement a flag to enable fallback always

On some machines we want our watchdog running as long as the
phosphor-watchdog daemon is alive. This patch adds an option to enter
fallback mode any time the watchdog expires or is set to be disabled.

Change-Id: Ic96d2f15c761aeb4e25158c5bd861076cca6497d
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/argument.cpp b/argument.cpp
index c2d99ce..770db87 100644
--- a/argument.cpp
+++ b/argument.cpp
@@ -28,7 +28,7 @@
 const std::vector<std::string> emptyArg;
 const std::string ArgumentParser::trueString = "true"s;
 
-const char* ArgumentParser::optionStr = "p:s:t:a:f:i:ch";
+const char* ArgumentParser::optionStr = "p:s:t:a:f:i:ech";
 const option ArgumentParser::options[] =
 {
     { "path",                    required_argument,  nullptr,   'p' },
@@ -37,6 +37,7 @@
     { "action_target",           required_argument,  nullptr,   'a' },
     { "fallback_action",         required_argument,  nullptr,   'f' },
     { "fallback_interval",       required_argument,  nullptr,   'i' },
+    { "fallback_always",         no_argument,        nullptr,   'e' },
     { "continue",                no_argument,        nullptr,   'c' },
     { "help",                    no_argument,        nullptr,   'h' },
     { 0, 0, 0, 0},
@@ -111,6 +112,9 @@
                      "watchdog even when disabled via the dbus interface. "
                      "Waits for this interval before performing the fallback "
                      "action.\n";
+    std::cerr << " [--fallback_always]                       Enables the "
+                     "watchdog even when disabled by the dbus interface. "
+                     "This option is only valid with a fallback specified.\n";
     std::cerr << " [--continue]                              Continue daemon "
                      "after watchdog timeout.\n";
 }
diff --git a/mainapp.cpp b/mainapp.cpp
index 365fd50..de09ba3 100644
--- a/mainapp.cpp
+++ b/mainapp.cpp
@@ -172,9 +172,21 @@
         fallback = Watchdog::Fallback{
             .action = action,
             .interval = interval,
+            .always = false,
         };
     }
 
+    auto fallbackAlwaysParam = (options)["fallback_always"];
+    if (!fallbackAlwaysParam.empty())
+    {
+        if (!fallback)
+        {
+            exitWithError("Specified the fallback should always be enabled but "
+                    "no fallback provided.", argv);
+        }
+        fallback->always = true;
+    }
+
     sd_event* event = nullptr;
     auto r = sd_event_default(&event);
     if (r < 0)
diff --git a/test/watchdog_test.cpp b/test/watchdog_test.cpp
index af7c3ed..34d1d24 100644
--- a/test/watchdog_test.cpp
+++ b/test/watchdog_test.cpp
@@ -273,6 +273,7 @@
     Watchdog::Fallback fallback{
         .action = Watchdog::Action::PowerOff,
         .interval = static_cast<uint64_t>(fallbackIntervalMs),
+        .always = false,
     };
     std::map<Watchdog::Action, Watchdog::TargetName> emptyActionTargets;
     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, eventP,
@@ -305,3 +306,63 @@
     EXPECT_FALSE(wdog->timerExpired());
     EXPECT_TRUE(wdog->timerEnabled());
 }
+
+/** @brief Make sure the watchdog is started and with a fallback without
+ *         sending an enable
+ *         Then enable the watchdog
+ *         Wait through the initial trip and ensure the fallback is observed
+ *         Make sure that fallback runs to completion and ensure the watchdog
+ *         is in the fallback state again
+ */
+TEST_F(WdogTest, enableWdogWithFallbackAlways)
+{
+    auto primaryInterval = 5s;
+    auto primaryIntervalMs = milliseconds(primaryInterval).count();
+    auto fallbackInterval = primaryInterval * 2;
+    auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
+
+    // We need to make a wdog with the right fallback options
+    // The interval is set to be noticeably different from the default
+    // so we can always tell the difference
+    Watchdog::Fallback fallback{
+        .action = Watchdog::Action::PowerOff,
+        .interval = static_cast<uint64_t>(fallbackIntervalMs),
+        .always = true,
+    };
+    std::map<Watchdog::Action, Watchdog::TargetName> emptyActionTargets;
+    wdog = std::make_unique<Watchdog>(bus, TEST_PATH, eventP,
+                    std::move(emptyActionTargets), std::move(fallback));
+    EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
+    EXPECT_FALSE(wdog->enabled());
+    auto remaining = milliseconds(wdog->timeRemaining());
+    EXPECT_GE(fallbackInterval, remaining);
+    EXPECT_LT(primaryInterval, remaining);
+    EXPECT_FALSE(wdog->timerExpired());
+    EXPECT_TRUE(wdog->timerEnabled());
+
+    // Enable and then verify
+    EXPECT_TRUE(wdog->enabled(true));
+    EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
+
+    // Waiting default expiration
+    EXPECT_EQ(primaryInterval - 1s, waitForWatchdog(primaryInterval));
+
+    // We should now have entered the fallback once the primary expires
+    EXPECT_FALSE(wdog->enabled());
+    remaining = milliseconds(wdog->timeRemaining());
+    EXPECT_GE(fallbackInterval, remaining);
+    EXPECT_LT(primaryInterval, remaining);
+    EXPECT_FALSE(wdog->timerExpired());
+    EXPECT_TRUE(wdog->timerEnabled());
+
+    // Waiting fallback expiration
+    EXPECT_EQ(fallbackInterval - 1s, waitForWatchdog(fallbackInterval));
+
+    // We should now enter the fallback again
+    EXPECT_FALSE(wdog->enabled());
+    remaining = milliseconds(wdog->timeRemaining());
+    EXPECT_GE(fallbackInterval, remaining);
+    EXPECT_LT(primaryInterval, remaining);
+    EXPECT_FALSE(wdog->timerExpired());
+    EXPECT_TRUE(wdog->timerEnabled());
+}
diff --git a/watchdog.cpp b/watchdog.cpp
index b0c7b6f..2bfd9b7 100644
--- a/watchdog.cpp
+++ b/watchdog.cpp
@@ -134,7 +134,7 @@
 {
     // We only re-arm the watchdog if we were already enabled and have
     // a possible fallback
-    if (fallback && this->enabled())
+    if (fallback && (fallback->always || this->enabled()))
     {
         auto interval_ms = fallback->interval;
         auto interval_us = duration_cast<microseconds>(milliseconds(interval_ms));
diff --git a/watchdog.hpp b/watchdog.hpp
index 60bad4e..7603689 100644
--- a/watchdog.hpp
+++ b/watchdog.hpp
@@ -37,6 +37,7 @@
         struct Fallback {
             Action action;
             uint64_t interval;
+            bool always;
         };
 
         /** @brief Constructs the Watchdog object
@@ -60,7 +61,9 @@
             fallback(std::move(fallback)),
             timer(event, std::bind(&Watchdog::timeOutHandler, this))
         {
-            // Nothing
+            // We need to poke the enable mechanism to make sure that the timer
+            // enters the fallback state if the fallback is always enabled.
+            tryFallbackOrDisable();
         }
 
         /** @brief Since we are overriding the setter-enabled but not the