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 
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 
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 
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 
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 
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 
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 
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 
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 
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>
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 
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 
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