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