Move source into src directory

This makes the source more distinguishable from top-level metadata.

Change-Id: I5e41186d4606422937ec7d37402a3031d3f776b6
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/mainapp.cpp b/src/mainapp.cpp
new file mode 100644
index 0000000..06b22ec
--- /dev/null
+++ b/src/mainapp.cpp
@@ -0,0 +1,257 @@
+/**
+ * Copyright © 2017 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "watchdog.hpp"
+
+#include <CLI/CLI.hpp>
+#include <functional>
+#include <iostream>
+#include <optional>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/server/manager.hpp>
+#include <sdeventplus/event.hpp>
+#include <string>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+using phosphor::watchdog::Watchdog;
+using sdbusplus::xyz::openbmc_project::State::server::convertForMessage;
+
+void printActionTargetMap(const Watchdog::ActionTargetMap& actionTargetMap)
+{
+    std::cerr << "Action Targets:\n";
+    for (const auto& [action, target] : actionTargetMap)
+    {
+        std::cerr << "  " << convertForMessage(action) << " -> " << target
+                  << "\n";
+    }
+    std::cerr << std::flush;
+}
+
+void printFallback(const Watchdog::Fallback& fallback)
+{
+    std::cerr << "Fallback Options:\n";
+    std::cerr << "  Action: " << convertForMessage(fallback.action) << "\n";
+    std::cerr << "  Interval(ms): " << std::dec << fallback.interval << "\n";
+    std::cerr << "  Always re-execute: " << std::boolalpha << fallback.always
+              << "\n";
+    std::cerr << std::flush;
+}
+
+int main(int argc, char* argv[])
+{
+    using namespace phosphor::logging;
+    using InternalFailure =
+        sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+    CLI::App app{"Canonical openbmc host watchdog daemon"};
+
+    // Service related options
+    const std::string serviceGroup = "Service Options";
+    std::string path;
+    app.add_option("-p,--path", path,
+                   "DBus Object Path. "
+                   "Ex: /xyz/openbmc_project/state/watchdog/host0")
+        ->required()
+        ->group(serviceGroup);
+    std::string service;
+    app.add_option("-s,--service", service,
+                   "DBus Service Name. "
+                   "Ex: xyz.openbmc_project.State.Watchdog.Host")
+        ->required()
+        ->group(serviceGroup);
+    bool continueAfterTimeout;
+    app.add_flag("-c,--continue", continueAfterTimeout,
+                 "Continue daemon after watchdog timeout")
+        ->group(serviceGroup);
+
+    // Target related options
+    const std::string targetGroup = "Target Options";
+    std::optional<std::string> target;
+    app.add_option("-t,--target", target,
+                   "Systemd unit to be called on "
+                   "timeout for all actions but NONE. "
+                   "Deprecated, use --action_target instead.")
+        ->group(targetGroup);
+    std::vector<std::string> actionTargets;
+    app.add_option("-a,--action_target", actionTargets,
+                   "Map of action to "
+                   "systemd unit to be called on timeout if that action is "
+                   "set for ExpireAction when the timer expires.")
+        ->group(targetGroup);
+
+    // Fallback related options
+    const std::string fallbackGroup = "Fallback Options";
+    std::optional<std::string> fallbackAction;
+    auto fallbackActionOpt =
+        app.add_option("-f,--fallback_action", fallbackAction,
+                       "Enables the "
+                       "watchdog even when disabled via the dbus interface. "
+                       "Perform this action when the fallback expires.")
+            ->group(fallbackGroup);
+    std::optional<unsigned> fallbackIntervalMs;
+    auto fallbackIntervalOpt =
+        app.add_option("-i,--fallback_interval", fallbackIntervalMs,
+                       "Enables the "
+                       "watchdog even when disabled via the dbus interface. "
+                       "Waits for this interval before performing the fallback "
+                       "action.")
+            ->group(fallbackGroup);
+    fallbackIntervalOpt->needs(fallbackActionOpt);
+    fallbackActionOpt->needs(fallbackIntervalOpt);
+    bool fallbackAlways;
+    app.add_flag("-e,--fallback_always", fallbackAlways,
+                 "Enables the "
+                 "watchdog even when disabled by the dbus interface. "
+                 "This option is only valid with a fallback specified")
+        ->group(fallbackGroup)
+        ->needs(fallbackActionOpt)
+        ->needs(fallbackIntervalOpt);
+
+    // Should we watch for postcodes
+    bool watchPostcodes;
+    app.add_flag("-w,--watch_postcodes", watchPostcodes,
+                 "Should we reset the time remaining any time a postcode "
+                 "is signaled.");
+
+    uint64_t minInterval = phosphor::watchdog::DEFAULT_MIN_INTERVAL_MS;
+    app.add_option("-m,--min_interval", minInterval,
+                   "Set minimum interval for watchdog in milliseconds");
+
+    CLI11_PARSE(app, argc, argv);
+
+    // Put together a list of actions and associated systemd targets
+    // The new --action_target options take precedence over the legacy
+    // --target
+    Watchdog::ActionTargetMap actionTargetMap;
+    if (target)
+    {
+        actionTargetMap[Watchdog::Action::HardReset] = *target;
+        actionTargetMap[Watchdog::Action::PowerOff] = *target;
+        actionTargetMap[Watchdog::Action::PowerCycle] = *target;
+    }
+    for (const auto& actionTarget : actionTargets)
+    {
+        size_t keyValueSplit = actionTarget.find("=");
+        if (keyValueSplit == std::string::npos)
+        {
+            std::cerr << "Invalid action_target format, "
+                         "expect <action>=<target>."
+                      << std::endl;
+            return 1;
+        }
+
+        std::string key = actionTarget.substr(0, keyValueSplit);
+        std::string value = actionTarget.substr(keyValueSplit + 1);
+
+        // Convert an action from a fully namespaced value
+        Watchdog::Action action;
+        try
+        {
+            action = Watchdog::convertActionFromString(key);
+        }
+        catch (const sdbusplus::exception::InvalidEnumString&)
+        {
+            std::cerr << "Bad action specified: " << key << std::endl;
+            return 1;
+        }
+
+        // Detect duplicate action target arguments
+        if (actionTargetMap.find(action) != actionTargetMap.end())
+        {
+            std::cerr << "Got duplicate action: " << key << std::endl;
+            return 1;
+        }
+
+        actionTargetMap[action] = std::move(value);
+    }
+    printActionTargetMap(actionTargetMap);
+
+    // Build the fallback option used for the Watchdog
+    std::optional<Watchdog::Fallback> maybeFallback;
+    if (fallbackAction)
+    {
+        Watchdog::Fallback fallback;
+        try
+        {
+            fallback.action =
+                Watchdog::convertActionFromString(*fallbackAction);
+        }
+        catch (const sdbusplus::exception::InvalidEnumString&)
+        {
+            std::cerr << "Bad fallback action specified: " << *fallbackAction
+                      << std::endl;
+            return 1;
+        }
+        fallback.interval = *fallbackIntervalMs;
+        fallback.always = fallbackAlways;
+
+        printFallback(fallback);
+        maybeFallback = std::move(fallback);
+    }
+
+    try
+    {
+        // Get a default event loop
+        auto event = sdeventplus::Event::get_default();
+
+        // Get a handle to system dbus.
+        auto bus = sdbusplus::bus::new_default();
+
+        // Add systemd object manager.
+        sdbusplus::server::manager::manager(bus, path.c_str());
+
+        // Attach the bus to sd_event to service user requests
+        bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+        // Create a watchdog object
+        Watchdog watchdog(bus, path.c_str(), event, std::move(actionTargetMap),
+                          std::move(maybeFallback), minInterval);
+
+        std::optional<sdbusplus::bus::match::match> watchPostcodeMatch;
+        if (watchPostcodes)
+        {
+            watchPostcodeMatch.emplace(
+                bus,
+                sdbusplus::bus::match::rules::propertiesChanged(
+                    "/xyz/openbmc_project/state/boot/raw",
+                    "xyz.openbmc_project.State.Boot.Raw"),
+                std::bind(&Watchdog::resetTimeRemaining, std::ref(watchdog),
+                          false));
+        }
+
+        // Claim the bus
+        bus.request_name(service.c_str());
+
+        // Loop until our timer expires and we don't want to continue
+        while (continueAfterTimeout || !watchdog.timerExpired())
+        {
+            // Run and never timeout
+            event.run(std::nullopt);
+        }
+    }
+    catch (InternalFailure& e)
+    {
+        phosphor::logging::commit<InternalFailure>();
+
+        // Need a coredump in the error cases.
+        std::terminate();
+    }
+    return 0;
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..63fae3d
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,43 @@
+watchdog_headers = include_directories('.')
+
+# CLI11 might not have a pkg-config. It is header only so just make
+# sure we can access the needed symbols from the header.
+cli11_dep = dependency('cli11', required: false)
+has_cli11 = meson.get_compiler('cpp').has_header_symbol(
+  'CLI/CLI.hpp',
+  'CLI::App',
+  dependencies: cli11_dep,
+  required: false)
+if not has_cli11
+  cli11_proj = subproject('cli11', required: false)
+  assert(cli11_proj.found(), 'CLI11 is required')
+  cli11_dep = cli11_proj.get_variable('CLI11_dep')
+endif
+
+watchdog_deps = [
+  cli11_dep,
+  dependency('phosphor-dbus-interfaces'),
+  dependency('phosphor-logging'),
+  dependency('sdbusplus', fallback: ['sdbusplus', 'sdbusplus_dep']),
+  dependency('sdeventplus', fallback: ['sdeventplus', 'sdeventplus_dep']),
+]
+
+watchdog_lib = static_library(
+  'watchdog',
+  'watchdog.cpp',
+  implicit_include_directories: false,
+  include_directories: watchdog_headers,
+  dependencies: watchdog_deps)
+
+watchdog_dep = declare_dependency(
+  dependencies: watchdog_deps,
+  include_directories: watchdog_headers,
+  link_with: watchdog_lib)
+
+executable(
+  'phosphor-watchdog',
+  'mainapp.cpp',
+  implicit_include_directories: false,
+  dependencies: watchdog_dep,
+  install: true,
+  install_dir: get_option('bindir'))
diff --git a/src/watchdog.cpp b/src/watchdog.cpp
new file mode 100644
index 0000000..57e9050
--- /dev/null
+++ b/src/watchdog.cpp
@@ -0,0 +1,179 @@
+#include "watchdog.hpp"
+
+#include <algorithm>
+#include <chrono>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/exception.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace watchdog
+{
+using namespace std::chrono;
+using namespace std::chrono_literals;
+using namespace phosphor::logging;
+
+using sdbusplus::exception::SdBusError;
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+// systemd service to kick start a target.
+constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
+constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
+constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
+
+void Watchdog::resetTimeRemaining(bool enableWatchdog)
+{
+    timeRemaining(interval());
+    if (enableWatchdog)
+    {
+        enabled(true);
+    }
+}
+
+// Enable or disable watchdog
+bool Watchdog::enabled(bool value)
+{
+    if (!value)
+    {
+        // Make sure we accurately reflect our enabled state to the
+        // tryFallbackOrDisable() call
+        WatchdogInherits::enabled(value);
+
+        // Attempt to fallback or disable our timer if needed
+        tryFallbackOrDisable();
+
+        return false;
+    }
+    else if (!this->enabled())
+    {
+        auto interval_ms = this->interval();
+        timer.restart(milliseconds(interval_ms));
+        log<level::INFO>("watchdog: enabled and started",
+                         entry("INTERVAL=%llu", interval_ms));
+    }
+
+    return WatchdogInherits::enabled(value);
+}
+
+// Get the remaining time before timer expires.
+// If the timer is disabled, returns 0
+uint64_t Watchdog::timeRemaining() const
+{
+    // timer may have already expired and disabled
+    if (!timerEnabled())
+    {
+        return 0;
+    }
+
+    return duration_cast<milliseconds>(timer.getRemaining()).count();
+}
+
+// Reset the timer to a new expiration value
+uint64_t Watchdog::timeRemaining(uint64_t value)
+{
+    if (!timerEnabled())
+    {
+        // We don't need to update the timer because it is off
+        return 0;
+    }
+
+    if (this->enabled())
+    {
+        // Update interval to minInterval if applicable
+        value = std::max(value, minInterval);
+    }
+    else
+    {
+        // Having a timer but not displaying an enabled value means we
+        // are inside of the fallback
+        value = fallback->interval;
+    }
+
+    // Update new expiration
+    timer.setRemaining(milliseconds(value));
+
+    // Update Base class data.
+    return WatchdogInherits::timeRemaining(value);
+}
+
+// Set value of Interval
+uint64_t Watchdog::interval(uint64_t value)
+{
+    return WatchdogInherits::interval(std::max(value, minInterval));
+}
+
+// Optional callback function on timer expiration
+void Watchdog::timeOutHandler()
+{
+    Action action = expireAction();
+    if (!this->enabled())
+    {
+        action = fallback->action;
+    }
+
+    expiredTimerUse(currentTimerUse());
+
+    auto target = actionTargetMap.find(action);
+    if (target == actionTargetMap.end())
+    {
+        log<level::INFO>("watchdog: Timed out with no target",
+                         entry("ACTION=%s", convertForMessage(action).c_str()),
+                         entry("TIMER_USE=%s",
+                               convertForMessage(expiredTimerUse()).c_str()));
+    }
+    else
+    {
+        log<level::INFO>(
+            "watchdog: Timed out",
+            entry("ACTION=%s", convertForMessage(action).c_str()),
+            entry("TIMER_USE=%s", convertForMessage(expiredTimerUse()).c_str()),
+            entry("TARGET=%s", target->second.c_str()));
+
+        try
+        {
+            auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
+                                              SYSTEMD_INTERFACE, "StartUnit");
+            method.append(target->second);
+            method.append("replace");
+
+            bus.call_noreply(method);
+        }
+        catch (const SdBusError& e)
+        {
+            log<level::ERR>("watchdog: Failed to start unit",
+                            entry("TARGET=%s", target->second.c_str()),
+                            entry("ERROR=%s", e.what()));
+            commit<InternalFailure>();
+        }
+    }
+
+    tryFallbackOrDisable();
+}
+
+void Watchdog::tryFallbackOrDisable()
+{
+    // We only re-arm the watchdog if we were already enabled and have
+    // a possible fallback
+    if (fallback && (fallback->always || this->enabled()))
+    {
+        auto interval_ms = fallback->interval;
+        timer.restart(milliseconds(interval_ms));
+        log<level::INFO>("watchdog: falling back",
+                         entry("INTERVAL=%llu", interval_ms));
+    }
+    else if (timerEnabled())
+    {
+        timer.setEnabled(false);
+
+        log<level::INFO>("watchdog: disabled");
+    }
+
+    // Make sure we accurately reflect our enabled state to the
+    // dbus interface.
+    WatchdogInherits::enabled(false);
+}
+
+} // namespace watchdog
+} // namespace phosphor
diff --git a/src/watchdog.hpp b/src/watchdog.hpp
new file mode 100644
index 0000000..7de9bb3
--- /dev/null
+++ b/src/watchdog.hpp
@@ -0,0 +1,176 @@
+#pragma once
+
+#include <functional>
+#include <optional>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+#include <unordered_map>
+#include <utility>
+#include <xyz/openbmc_project/State/Watchdog/server.hpp>
+
+namespace phosphor
+{
+namespace watchdog
+{
+
+constexpr auto DEFAULT_MIN_INTERVAL_MS = 0;
+namespace Base = sdbusplus::xyz::openbmc_project::State::server;
+using WatchdogInherits = sdbusplus::server::object::object<Base::Watchdog>;
+
+/** @class Watchdog
+ *  @brief OpenBMC watchdog implementation.
+ *  @details A concrete implementation for the
+ *  xyz.openbmc_project.State.Watchdog DBus API.
+ */
+class Watchdog : public WatchdogInherits
+{
+  public:
+    Watchdog() = delete;
+    ~Watchdog() = default;
+    Watchdog(const Watchdog&) = delete;
+    Watchdog& operator=(const Watchdog&) = delete;
+    Watchdog(Watchdog&&) = delete;
+    Watchdog& operator=(Watchdog&&) = delete;
+
+    /** @brief Type used to hold the name of a systemd target.
+     */
+    using TargetName = std::string;
+
+    /** @brief Type used to store the mapping of a Watchdog timeout
+     *         action to a systemd target.
+     */
+    using ActionTargetMap = std::unordered_map<Action, TargetName>;
+
+    /** @brief Type used to specify the parameters of a fallback watchdog
+     */
+    struct Fallback
+    {
+        Action action;
+        uint64_t interval;
+        bool always;
+    };
+
+    /** @brief Constructs the Watchdog object
+     *
+     *  @param[in] bus            - DBus bus to attach to.
+     *  @param[in] objPath        - Object path to attach to.
+     *  @param[in] event          - reference to sdeventplus::Event loop
+     *  @param[in] actionTargets  - map of systemd targets called on timeout
+     *  @param[in] fallback
+     */
+    Watchdog(sdbusplus::bus::bus& bus, const char* objPath,
+             const sdeventplus::Event& event,
+             ActionTargetMap&& actionTargetMap = {},
+             std::optional<Fallback>&& fallback = std::nullopt,
+             uint64_t minInterval = DEFAULT_MIN_INTERVAL_MS) :
+        WatchdogInherits(bus, objPath),
+        bus(bus), actionTargetMap(std::move(actionTargetMap)),
+        fallback(std::move(fallback)), minInterval(minInterval),
+        timer(event, std::bind(&Watchdog::timeOutHandler, this))
+    {
+        // We set the watchdog interval with the default value.
+        interval(interval());
+        // 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 Resets the TimeRemaining to the configured Interval
+     *         Optionally enables the watchdog.
+     *
+     *  @param[in] enableWatchdog - Should the call enable the watchdog
+     */
+    void resetTimeRemaining(bool enableWatchdog) override;
+
+    /** @brief Since we are overriding the setter-enabled but not the
+     *         getter-enabled, we need to have this using in order to
+     *         allow passthrough usage of the getter-enabled.
+     */
+    using Base::Watchdog::enabled;
+
+    /** @brief Enable or disable watchdog
+     *         If a watchdog state is changed from disable to enable,
+     *         the watchdog timer is set with the default expiration
+     *         interval and it starts counting down.
+     *         If a watchdog is already enabled, setting @value to true
+     *         has no effect.
+     *
+     *  @param[in] value - 'true' to enable. 'false' to disable
+     *
+     *  @return : applied value if success, previous value otherwise
+     */
+    bool enabled(bool value) override;
+
+    /** @brief Gets the remaining time before watchdog expires.
+     *
+     *  @return 0 if watchdog is expired.
+     *          Remaining time in milliseconds otherwise.
+     */
+    uint64_t timeRemaining() const override;
+
+    /** @brief Reset timer to expire after new timeout in milliseconds.
+     *
+     *  @param[in] value - the time in milliseconds after which
+     *                     the watchdog will expire
+     *
+     *  @return: updated timeout value if watchdog is enabled.
+     *           0 otherwise.
+     */
+    uint64_t timeRemaining(uint64_t value) override;
+
+    /** @brief Get value of Interval
+     *
+     *
+     *  @return: current interval
+     *
+     */
+    using WatchdogInherits::interval;
+
+    /** @brief Set value of Interval
+     *
+     *  @param[in] value - interval time to set
+     *
+     *  @return: interval that was set
+     *
+     */
+    uint64_t interval(uint64_t value);
+
+    /** @brief Tells if the referenced timer is expired or not */
+    inline auto timerExpired() const
+    {
+        return timer.hasExpired();
+    }
+
+    /** @brief Tells if the timer is running or not */
+    inline bool timerEnabled() const
+    {
+        return timer.isEnabled();
+    }
+
+  private:
+    /** @brief sdbusplus handle */
+    sdbusplus::bus::bus& bus;
+
+    /** @brief Map of systemd units to be started when the timer expires */
+    ActionTargetMap actionTargetMap;
+
+    /** @brief Fallback timer options */
+    std::optional<Fallback> fallback;
+
+    /** @brief Minimum watchdog interval value */
+    uint64_t minInterval;
+
+    /** @brief Contained timer object */
+    sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> timer;
+
+    /** @brief Optional Callback handler on timer expirartion */
+    void timeOutHandler();
+
+    /** @brief Attempt to enter the fallback watchdog or disables it */
+    void tryFallbackOrDisable();
+};
+
+} // namespace watchdog
+} // namespace phosphor