1 /** 2 * Copyright © 2018 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 #include "policy_find.hpp" 17 18 #include <phosphor-logging/log.hpp> 19 #include <sstream> 20 21 namespace ibm 22 { 23 namespace logging 24 { 25 namespace policy 26 { 27 28 static constexpr auto HOST_EVENT = "org.open_power.Host.Error.Event"; 29 30 namespace optional_ns = std::experimental; 31 32 /** 33 * Returns a property value from a map of properties. 34 * 35 * @tparam - T the property data type 36 * @param[in] properties - the property map 37 * @param[in] name - the property name 38 * 39 * @return optional<T> - the property value 40 */ 41 template <typename T> 42 optional_ns::optional<T> getProperty(const DbusPropertyMap& properties, 43 const std::string& name) 44 { 45 auto prop = properties.find(name); 46 47 if (prop != properties.end()) 48 { 49 return prop->second.template get<T>(); 50 } 51 52 return {}; 53 } 54 55 /** 56 * Finds a value in the AdditionalData property, which is 57 * an array of strings in the form of: 58 * 59 * NAME=VALUE 60 * 61 * @param[in] additionalData - the AdditionalData property contents 62 * @param[in] name - the name of the value to find 63 * 64 * @return optional<std::string> - the data value. Will not be empty if found. 65 */ 66 optional_ns::optional<std::string> 67 getAdditionalDataItem(const std::vector<std::string>& additionalData, 68 const std::string& name) 69 { 70 std::string value; 71 72 for (const auto& item : additionalData) 73 { 74 if (item.find(name + "=") != std::string::npos) 75 { 76 value = item.substr(item.find('=') + 1); 77 if (!item.empty()) 78 { 79 return value; 80 } 81 } 82 } 83 84 return {}; 85 } 86 87 /** 88 * Returns a string version of the severity from the PEL 89 * log in the extended SEL data from the host, where a PEL stands 90 * for 'Platform Event Log' and is an IBM standard for error logging 91 * that OpenPower host firmware uses. 92 * 93 * The severity is the 11th byte in the 'User Header' section in a PEL 94 * that starts at byte 48. We only need the first nibble, which signifies 95 * the type - 'Recovered', 'Predictive', 'Critical', etc. 96 * 97 * type value | type | returned severity string 98 * ------------------------------------ 99 * 1 Recovered Informational 100 * 2 Predictive Warning 101 * everything else na Critical 102 * 103 * @param[in] data - the PEL string in the form of "00 11 22 33 4e ff" 104 * 105 * @return optional<std::string> - the severity string as listed above 106 */ 107 optional_ns::optional<std::string> getESELSeverity(const std::string& data) 108 { 109 // The User Header section starts at byte 48, and take into account 110 // the input data is a space separated string representation of HEX data. 111 static constexpr auto UH_OFFSET = 48 * 4; 112 113 // The eye catcher is "UH" 114 static constexpr auto UH_EYECATCHER = "55 48"; 115 116 // The severity is the 11th byte in the section, and take into 117 // account a byte is "BB " 118 static constexpr auto UH_SEV_OFFSET = 10 * 3; 119 120 std::string severity = "Critical"; 121 122 // The only values that don't map to "Critical" 123 const std::map<std::string, std::string> sevTypes{{"1", "Informational"}, 124 {"2", "Warning"}}; 125 if (data.size() <= (UH_OFFSET + UH_SEV_OFFSET)) 126 { 127 return {}; 128 } 129 130 // Sanity check that the User Header section is there. 131 auto userHeader = data.substr(UH_OFFSET, 5); 132 if (userHeader.compare(UH_EYECATCHER)) 133 { 134 return {}; 135 } 136 137 // The severity type nibble is a full byte in the string. 138 auto sevType = data.substr(UH_OFFSET + UH_SEV_OFFSET, 1); 139 140 auto sev = sevTypes.find(sevType); 141 if (sev != sevTypes.end()) 142 { 143 severity = sev->second; 144 }; 145 146 return severity; 147 } 148 149 /** 150 * Returns the search modifier to use, but if it isn't found 151 * in the table then code should then call getSearchModifier() 152 * and try again. 153 * 154 * This is to be tolerant of the policy table not having 155 * entries for every device path or FRU callout, and trying 156 * again gives code a chance to find the more generic entries 157 * for those classes of errors rather than not being found 158 * at all. 159 * 160 * e.g. If the device path is missing in the table, then it 161 * can still find the generic "Failed to read from an I2C 162 * device" entry. 163 * 164 * @param[in] message- the error message, like xyz.A.Error.B 165 * @param[in] properties - the property map for the error 166 * 167 * @return string - the search modifier 168 * may be empty if none found 169 */ 170 std::string getSearchModifierFirstTry(const std::string& message, 171 const DbusPropertyMap& properties) 172 { 173 auto data = 174 getProperty<std::vector<std::string>>(properties, "AdditionalData"); 175 176 if (!data) 177 { 178 return std::string{}; 179 } 180 181 // Try the called out device path as the search modifier 182 auto devPath = getAdditionalDataItem(*data, "CALLOUT_DEVICE_PATH"); 183 if (devPath) 184 { 185 return *devPath; 186 } 187 188 // For Host.Error.Event errors, try <callout>||<severity string> 189 // as the search modifier. 190 if (message == HOST_EVENT) 191 { 192 auto callout = getAdditionalDataItem(*data, "CALLOUT_INVENTORY_PATH"); 193 if (callout) 194 { 195 auto selData = getAdditionalDataItem(*data, "ESEL"); 196 if (selData) 197 { 198 auto severity = getESELSeverity(*selData); 199 if (severity) 200 { 201 return *callout + "||" + *severity; 202 } 203 } 204 } 205 } 206 207 return std::string{}; 208 } 209 210 /** 211 * Returns the search modifier to use. 212 * 213 * The modifier is used when the error name itself isn't granular 214 * enough to find a policy table entry. The modifier is determined 215 * using rules provided by the IBM service team. 216 * 217 * Not all errors need a modifier, so this function isn't 218 * guaranteed to find one. 219 * 220 * @param[in] properties - the property map for the error 221 * 222 * @return string - the search modifier 223 * may be empty if none found 224 */ 225 auto getSearchModifier(const DbusPropertyMap& properties) 226 { 227 // The modifier may be one of several things within the 228 // AdditionalData property. Try them all until one 229 // is found. 230 231 auto data = 232 getProperty<std::vector<std::string>>(properties, "AdditionalData"); 233 234 if (!data) 235 { 236 return std::string{}; 237 } 238 239 // AdditionalData fields where the value is the modifier 240 static const std::vector<std::string> ADFields{"CALLOUT_INVENTORY_PATH", 241 "RAIL_NAME", "INPUT_NAME"}; 242 243 optional_ns::optional<std::string> mod; 244 for (const auto& field : ADFields) 245 { 246 mod = getAdditionalDataItem(*data, field); 247 if (mod) 248 { 249 return *mod; 250 } 251 } 252 253 // Next are the AdditionalData fields where the value needs 254 // to be massaged to get the modifier. 255 256 // A device path, but we only care about the type 257 mod = getAdditionalDataItem(*data, "CALLOUT_DEVICE_PATH"); 258 if (mod) 259 { 260 // The table only handles I2C and FSI 261 if ((*mod).find("i2c") != std::string::npos) 262 { 263 return std::string{"I2C"}; 264 } 265 else if ((*mod).find("fsi") != std::string::npos) 266 { 267 return std::string{"FSI"}; 268 } 269 } 270 271 // A hostboot procedure ID 272 mod = getAdditionalDataItem(*data, "PROCEDURE"); 273 if (mod) 274 { 275 // Convert decimal (e.g. 109) to hex (e.g. 6D) 276 std::ostringstream stream; 277 try 278 { 279 stream << std::hex << std::stoul((*mod).c_str()); 280 auto value = stream.str(); 281 282 if (!value.empty()) 283 { 284 std::transform(value.begin(), value.end(), value.begin(), 285 toupper); 286 return value; 287 } 288 } 289 catch (std::exception& e) 290 { 291 using namespace phosphor::logging; 292 log<level::ERR>("Invalid PROCEDURE value found", 293 entry("PROCEDURE=%s", mod->c_str())); 294 } 295 } 296 297 return std::string{}; 298 } 299 300 PolicyProps find(const policy::Table& policy, 301 const DbusPropertyMap& errorLogProperties) 302 { 303 auto errorMsg = getProperty<std::string>(errorLogProperties, 304 "Message"); // e.g. xyz.X.Error.Y 305 if (errorMsg) 306 { 307 FindResult result; 308 309 // Try with the FirstTry modifier first, and then the regular one. 310 311 auto modifier = 312 getSearchModifierFirstTry(*errorMsg, errorLogProperties); 313 314 if (!modifier.empty()) 315 { 316 result = policy.find(*errorMsg, modifier); 317 } 318 319 if (!result) 320 { 321 modifier = getSearchModifier(errorLogProperties); 322 323 result = policy.find(*errorMsg, modifier); 324 } 325 326 if (result) 327 { 328 return {(*result).get().ceid, (*result).get().msg}; 329 } 330 } 331 else 332 { 333 using namespace phosphor::logging; 334 log<level::ERR>("No Message metadata found in an error"); 335 } 336 337 return {policy.defaultEID(), policy.defaultMsg()}; 338 } 339 } // namespace policy 340 } // namespace logging 341 } // namespace ibm 342