xref: /openbmc/ibm-logging/policy_find.cpp (revision c57aa4b9)
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