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