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