1 /** 2 * Copyright © 2017 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "watchdog.hpp" 18 19 #include <CLI/CLI.hpp> 20 #include <phosphor-logging/elog-errors.hpp> 21 #include <phosphor-logging/elog.hpp> 22 #include <phosphor-logging/log.hpp> 23 #include <sdbusplus/bus.hpp> 24 #include <sdbusplus/exception.hpp> 25 #include <sdbusplus/server/manager.hpp> 26 #include <sdeventplus/event.hpp> 27 #include <sdeventplus/source/signal.hpp> 28 #include <sdeventplus/utility/sdbus.hpp> 29 #include <stdplus/signal.hpp> 30 #include <xyz/openbmc_project/Common/error.hpp> 31 32 #include <functional> 33 #include <iostream> 34 #include <optional> 35 #include <string> 36 37 using phosphor::watchdog::Watchdog; 38 using sdbusplus::xyz::openbmc_project::State::server::convertForMessage; 39 40 void printActionTargetMap(const Watchdog::ActionTargetMap& actionTargetMap) 41 { 42 std::cerr << "Action Targets:\n"; 43 for (const auto& [action, target] : actionTargetMap) 44 { 45 std::cerr << " " << convertForMessage(action) << " -> " << target 46 << "\n"; 47 } 48 std::cerr << std::flush; 49 } 50 51 void printFallback(const Watchdog::Fallback& fallback) 52 { 53 std::cerr << "Fallback Options:\n"; 54 std::cerr << " Action: " << convertForMessage(fallback.action) << "\n"; 55 std::cerr << " Interval(ms): " << std::dec << fallback.interval << "\n"; 56 std::cerr << " Always re-execute: " << std::boolalpha << fallback.always 57 << "\n"; 58 std::cerr << std::flush; 59 } 60 61 int main(int argc, char* argv[]) 62 { 63 using namespace phosphor::logging; 64 using InternalFailure = 65 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 66 67 CLI::App app{"Canonical openbmc host watchdog daemon"}; 68 69 // Service related options 70 const std::string serviceGroup = "Service Options"; 71 std::string path; 72 app.add_option("-p,--path", path, 73 "DBus Object Path. " 74 "Ex: /xyz/openbmc_project/state/watchdog/host0") 75 ->required() 76 ->group(serviceGroup); 77 std::string service; 78 app.add_option("-s,--service", service, 79 "DBus Service Name. " 80 "Ex: xyz.openbmc_project.State.Watchdog.Host") 81 ->required() 82 ->group(serviceGroup); 83 bool continueAfterTimeout{false}; 84 app.add_flag("-c,--continue", continueAfterTimeout, 85 "Continue daemon after watchdog timeout") 86 ->group(serviceGroup); 87 88 // Target related options 89 const std::string targetGroup = "Target Options"; 90 std::optional<std::string> target; 91 app.add_option("-t,--target", target, 92 "Systemd unit to be called on " 93 "timeout for all actions but NONE. " 94 "Deprecated, use --action_target instead.") 95 ->group(targetGroup); 96 std::vector<std::string> actionTargets; 97 app.add_option("-a,--action_target", actionTargets, 98 "Map of action to " 99 "systemd unit to be called on timeout if that action is " 100 "set for ExpireAction when the timer expires.") 101 ->group(targetGroup); 102 103 // Fallback related options 104 const std::string fallbackGroup = "Fallback Options"; 105 std::optional<std::string> fallbackAction; 106 auto fallbackActionOpt = 107 app.add_option("-f,--fallback_action", fallbackAction, 108 "Enables the " 109 "watchdog even when disabled via the dbus interface. " 110 "Perform this action when the fallback expires.") 111 ->group(fallbackGroup); 112 std::optional<unsigned> fallbackIntervalMs; 113 auto fallbackIntervalOpt = 114 app.add_option("-i,--fallback_interval", fallbackIntervalMs, 115 "Enables the " 116 "watchdog even when disabled via the dbus interface. " 117 "Waits for this interval before performing the fallback " 118 "action.") 119 ->group(fallbackGroup); 120 fallbackIntervalOpt->needs(fallbackActionOpt); 121 fallbackActionOpt->needs(fallbackIntervalOpt); 122 bool fallbackAlways{false}; 123 app.add_flag("-e,--fallback_always", fallbackAlways, 124 "Enables the " 125 "watchdog even when disabled by the dbus interface. " 126 "This option is only valid with a fallback specified") 127 ->group(fallbackGroup) 128 ->needs(fallbackActionOpt) 129 ->needs(fallbackIntervalOpt); 130 131 // Should we watch for postcodes 132 bool watchPostcodes{false}; 133 app.add_flag("-w,--watch_postcodes", watchPostcodes, 134 "Should we reset the time remaining any time a postcode " 135 "is signaled."); 136 137 // Interval related options 138 uint64_t minInterval = phosphor::watchdog::DEFAULT_MIN_INTERVAL_MS; 139 app.add_option("-m,--min_interval", minInterval, 140 "Set minimum interval for watchdog in milliseconds"); 141 142 // 0 to indicate to use default from PDI if not passed in 143 uint64_t defaultInterval = 0; 144 app.add_option("-d,--default_interval", defaultInterval, 145 "Set default interval for watchdog in milliseconds"); 146 147 CLI11_PARSE(app, argc, argv); 148 149 // Put together a list of actions and associated systemd targets 150 // The new --action_target options take precedence over the legacy 151 // --target 152 Watchdog::ActionTargetMap actionTargetMap; 153 if (target) 154 { 155 actionTargetMap[Watchdog::Action::HardReset] = *target; 156 actionTargetMap[Watchdog::Action::PowerOff] = *target; 157 actionTargetMap[Watchdog::Action::PowerCycle] = *target; 158 } 159 for (const auto& actionTarget : actionTargets) 160 { 161 size_t keyValueSplit = actionTarget.find("="); 162 if (keyValueSplit == std::string::npos) 163 { 164 std::cerr << "Invalid action_target format, " 165 "expect <action>=<target>." 166 << std::endl; 167 return 1; 168 } 169 170 std::string key = actionTarget.substr(0, keyValueSplit); 171 std::string value = actionTarget.substr(keyValueSplit + 1); 172 173 // Convert an action from a fully namespaced value 174 Watchdog::Action action; 175 try 176 { 177 action = Watchdog::convertActionFromString(key); 178 } 179 catch (const sdbusplus::exception::InvalidEnumString&) 180 { 181 std::cerr << "Bad action specified: " << key << std::endl; 182 return 1; 183 } 184 185 // Detect duplicate action target arguments 186 if (actionTargetMap.find(action) != actionTargetMap.end()) 187 { 188 std::cerr << "Got duplicate action: " << key << std::endl; 189 return 1; 190 } 191 192 actionTargetMap[action] = std::move(value); 193 } 194 printActionTargetMap(actionTargetMap); 195 196 // Build the fallback option used for the Watchdog 197 std::optional<Watchdog::Fallback> maybeFallback; 198 if (fallbackAction) 199 { 200 Watchdog::Fallback fallback; 201 try 202 { 203 fallback.action = 204 Watchdog::convertActionFromString(*fallbackAction); 205 } 206 catch (const sdbusplus::exception::InvalidEnumString&) 207 { 208 std::cerr << "Bad fallback action specified: " << *fallbackAction 209 << std::endl; 210 return 1; 211 } 212 fallback.interval = *fallbackIntervalMs; 213 fallback.always = fallbackAlways; 214 215 printFallback(fallback); 216 maybeFallback = fallback; 217 } 218 219 try 220 { 221 // Get a default event loop 222 auto event = sdeventplus::Event::get_default(); 223 224 // Get a handle to system dbus. 225 auto bus = sdbusplus::bus::new_default(); 226 227 // Add systemd object manager. 228 sdbusplus::server::manager_t watchdogManager(bus, path.c_str()); 229 230 // Create a watchdog object 231 Watchdog watchdog(bus, path.c_str(), event, std::move(actionTargetMap), 232 std::move(maybeFallback), minInterval, 233 defaultInterval, 234 /*exitAfterTimeout=*/!continueAfterTimeout); 235 236 std::optional<sdbusplus::bus::match_t> watchPostcodeMatch; 237 if (watchPostcodes) 238 { 239 watchPostcodeMatch.emplace( 240 bus, 241 sdbusplus::bus::match::rules::propertiesChanged( 242 "/xyz/openbmc_project/state/boot/raw0", 243 "xyz.openbmc_project.State.Boot.Raw"), 244 std::bind(&Watchdog::resetTimeRemaining, std::ref(watchdog), 245 false)); 246 } 247 248 // Claim the bus 249 bus.request_name(service.c_str()); 250 251 auto intCb = [](sdeventplus::source::Signal& s, 252 const struct signalfd_siginfo*) { 253 s.get_event().exit(0); 254 }; 255 stdplus::signal::block(SIGINT); 256 sdeventplus::source::Signal sigint(event, SIGINT, intCb); 257 stdplus::signal::block(SIGTERM); 258 sdeventplus::source::Signal sigterm(event, SIGTERM, std::move(intCb)); 259 return sdeventplus::utility::loopWithBus(event, bus); 260 } 261 catch (const InternalFailure& e) 262 { 263 phosphor::logging::commit<InternalFailure>(); 264 265 // Need a coredump in the error cases. 266 std::terminate(); 267 } 268 return 1; 269 } 270