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