1 /**
2 * Copyright © 2020 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 "error_logging.hpp"
18
19 #include "exception_utils.hpp"
20
21 #include <errno.h> // for errno
22 #include <string.h> // for strerror()
23 #include <sys/types.h> // for getpid(), lseek(), ssize_t
24 #include <unistd.h> // for getpid(), lseek(), write()
25
26 #include <sdbusplus/message.hpp>
27
28 #include <exception>
29 #include <ios>
30 #include <sstream>
31 #include <stdexcept>
32
33 namespace phosphor::power::regulators
34 {
35
logConfigFileError(Entry::Level severity,Journal & journal)36 void DBusErrorLogging::logConfigFileError(Entry::Level severity,
37 Journal& journal)
38 {
39 std::string message{
40 "xyz.openbmc_project.Power.Regulators.Error.ConfigFile"};
41 if (severity == Entry::Level::Critical)
42 {
43 // Specify a different message property for critical config file errors.
44 // These are logged when a critical operation cannot be performed due to
45 // the lack of a valid config file. These errors may require special
46 // handling, like stopping a power on attempt.
47 message =
48 "xyz.openbmc_project.Power.Regulators.Error.ConfigFile.Critical";
49 }
50
51 std::map<std::string, std::string> additionalData{};
52 logError(message, severity, additionalData, journal);
53 }
54
logDBusError(Entry::Level severity,Journal & journal)55 void DBusErrorLogging::logDBusError(Entry::Level severity, Journal& journal)
56 {
57 std::map<std::string, std::string> additionalData{};
58 logError("xyz.openbmc_project.Power.Error.DBus", severity, additionalData,
59 journal);
60 }
61
logI2CError(Entry::Level severity,Journal & journal,const std::string & bus,uint8_t addr,int errorNumber)62 void DBusErrorLogging::logI2CError(Entry::Level severity, Journal& journal,
63 const std::string& bus, uint8_t addr,
64 int errorNumber)
65 {
66 // Convert I2C address to a hex string
67 std::ostringstream ss;
68 ss << "0x" << std::hex << std::uppercase << static_cast<uint16_t>(addr);
69 std::string addrStr = ss.str();
70
71 // Convert errno value to an integer string
72 std::string errorNumberStr = std::to_string(errorNumber);
73
74 std::map<std::string, std::string> additionalData{};
75 additionalData.emplace("CALLOUT_IIC_BUS", bus);
76 additionalData.emplace("CALLOUT_IIC_ADDR", addrStr);
77 additionalData.emplace("CALLOUT_ERRNO", errorNumberStr);
78 logError("xyz.openbmc_project.Power.Error.I2C", severity, additionalData,
79 journal);
80 }
81
logInternalError(Entry::Level severity,Journal & journal)82 void DBusErrorLogging::logInternalError(Entry::Level severity, Journal& journal)
83 {
84 std::map<std::string, std::string> additionalData{};
85 logError("xyz.openbmc_project.Power.Error.Internal", severity,
86 additionalData, journal);
87 }
88
logPhaseFault(Entry::Level severity,Journal & journal,PhaseFaultType type,const std::string & inventoryPath,std::map<std::string,std::string> additionalData)89 void DBusErrorLogging::logPhaseFault(
90 Entry::Level severity, Journal& journal, PhaseFaultType type,
91 const std::string& inventoryPath,
92 std::map<std::string, std::string> additionalData)
93 {
94 std::string message =
95 (type == PhaseFaultType::n)
96 ? "xyz.openbmc_project.Power.Regulators.Error.PhaseFault.N"
97 : "xyz.openbmc_project.Power.Regulators.Error.PhaseFault.NPlus1";
98 additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
99 logError(message, severity, additionalData, journal);
100 }
101
logPMBusError(Entry::Level severity,Journal & journal,const std::string & inventoryPath)102 void DBusErrorLogging::logPMBusError(Entry::Level severity, Journal& journal,
103 const std::string& inventoryPath)
104 {
105 std::map<std::string, std::string> additionalData{};
106 additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
107 logError("xyz.openbmc_project.Power.Error.PMBus", severity, additionalData,
108 journal);
109 }
110
logWriteVerificationError(Entry::Level severity,Journal & journal,const std::string & inventoryPath)111 void DBusErrorLogging::logWriteVerificationError(
112 Entry::Level severity, Journal& journal, const std::string& inventoryPath)
113 {
114 std::map<std::string, std::string> additionalData{};
115 additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
116 logError("xyz.openbmc_project.Power.Regulators.Error.WriteVerification",
117 severity, additionalData, journal);
118 }
119
createFFDCFile(const std::vector<std::string> & lines)120 FFDCFile DBusErrorLogging::createFFDCFile(const std::vector<std::string>& lines)
121 {
122 // Create FFDC file of type Text
123 FFDCFile file{FFDCFormat::Text};
124 int fd = file.getFileDescriptor();
125
126 // Write lines to file
127 std::string buffer;
128 for (const std::string& line : lines)
129 {
130 // Copy line to buffer. Add newline if necessary.
131 buffer = line;
132 if (line.empty() || (line.back() != '\n'))
133 {
134 buffer += '\n';
135 }
136
137 // Write buffer to file
138 const char* bufPtr = buffer.c_str();
139 unsigned int count = buffer.size();
140 while (count > 0)
141 {
142 // Try to write remaining bytes; it might not write all of them
143 ssize_t bytesWritten = write(fd, bufPtr, count);
144 if (bytesWritten == -1)
145 {
146 throw std::runtime_error{
147 std::string{"Unable to write to FFDC file: "} +
148 strerror(errno)};
149 }
150 bufPtr += bytesWritten;
151 count -= bytesWritten;
152 }
153 }
154
155 // Seek to beginning of file so error logging system can read data
156 if (lseek(fd, 0, SEEK_SET) != 0)
157 {
158 throw std::runtime_error{
159 std::string{"Unable to seek within FFDC file: "} + strerror(errno)};
160 }
161
162 return file;
163 }
164
createFFDCFiles(Journal & journal)165 std::vector<FFDCFile> DBusErrorLogging::createFFDCFiles(Journal& journal)
166 {
167 std::vector<FFDCFile> files{};
168
169 // Create FFDC files containing journal messages from relevant executables.
170 // Executables in priority order in case error log cannot hold all the FFDC.
171 std::vector<std::string> executables{"phosphor-regulators", "systemd"};
172 for (const std::string& executable : executables)
173 {
174 try
175 {
176 // Get recent journal messages from the executable
177 std::vector<std::string> messages =
178 journal.getMessages("SYSLOG_IDENTIFIER", executable, 30);
179
180 // Create FFDC file containing the journal messages
181 if (!messages.empty())
182 {
183 files.emplace_back(createFFDCFile(messages));
184 }
185 }
186 catch (const std::exception& e)
187 {
188 journal.logError(exception_utils::getMessages(e));
189 }
190 }
191
192 return files;
193 }
194
195 std::vector<FFDCTuple>
createFFDCTuples(std::vector<FFDCFile> & files)196 DBusErrorLogging::createFFDCTuples(std::vector<FFDCFile>& files)
197 {
198 std::vector<FFDCTuple> ffdcTuples{};
199 for (FFDCFile& file : files)
200 {
201 ffdcTuples.emplace_back(
202 file.getFormat(), file.getSubType(), file.getVersion(),
203 sdbusplus::message::unix_fd(file.getFileDescriptor()));
204 }
205 return ffdcTuples;
206 }
207
logError(const std::string & message,Entry::Level severity,std::map<std::string,std::string> & additionalData,Journal & journal)208 void DBusErrorLogging::logError(
209 const std::string& message, Entry::Level severity,
210 std::map<std::string, std::string>& additionalData, Journal& journal)
211 {
212 try
213 {
214 // Add PID to AdditionalData
215 additionalData.emplace("_PID", std::to_string(getpid()));
216
217 // Create FFDC files containing debug data to store in error log
218 std::vector<FFDCFile> files{createFFDCFiles(journal)};
219
220 // Create FFDC tuples used to pass FFDC files to D-Bus method
221 std::vector<FFDCTuple> ffdcTuples{createFFDCTuples(files)};
222
223 // Call D-Bus method to create an error log with FFDC files
224 const char* service = "xyz.openbmc_project.Logging";
225 const char* objPath = "/xyz/openbmc_project/logging";
226 const char* interface = "xyz.openbmc_project.Logging.Create";
227 const char* method = "CreateWithFFDCFiles";
228 auto reqMsg = bus.new_method_call(service, objPath, interface, method);
229 reqMsg.append(message, severity, additionalData, ffdcTuples);
230 auto respMsg = bus.call(reqMsg);
231
232 // Remove FFDC files. If an exception occurs before this, the files
233 // will be deleted by FFDCFile desctructor but errors will be ignored.
234 removeFFDCFiles(files, journal);
235 }
236 catch (const std::exception& e)
237 {
238 journal.logError(exception_utils::getMessages(e));
239 journal.logError("Unable to log error " + message);
240 }
241 }
242
removeFFDCFiles(std::vector<FFDCFile> & files,Journal & journal)243 void DBusErrorLogging::removeFFDCFiles(std::vector<FFDCFile>& files,
244 Journal& journal)
245 {
246 // Explicitly remove FFDC files rather than relying on FFDCFile destructor.
247 // This allows any resulting errors to be written to the journal.
248 for (FFDCFile& file : files)
249 {
250 try
251 {
252 file.remove();
253 }
254 catch (const std::exception& e)
255 {
256 journal.logError(exception_utils::getMessages(e));
257 }
258 }
259
260 // Clear vector since the FFDCFile objects can no longer be used
261 files.clear();
262 }
263
264 } // namespace phosphor::power::regulators
265