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