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::logPMBusError(Entry::Level severity, Journal& journal,
90                                      const std::string& inventoryPath)
91 {
92     std::map<std::string, std::string> additionalData{};
93     additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
94     logError("xyz.openbmc_project.Power.Error.PMBus", severity, additionalData,
95              journal);
96 }
97 
98 void DBusErrorLogging::logWriteVerificationError(
99     Entry::Level severity, Journal& journal, const std::string& inventoryPath)
100 {
101     std::map<std::string, std::string> additionalData{};
102     additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
103     logError("xyz.openbmc_project.Power.Regulators.Error.WriteVerification",
104              severity, additionalData, journal);
105 }
106 
107 FFDCFile DBusErrorLogging::createFFDCFile(const std::vector<std::string>& lines)
108 {
109     // Create FFDC file of type Text
110     FFDCFile file{FFDCFormat::Text};
111     int fd = file.getFileDescriptor();
112 
113     // Write lines to file
114     std::string buffer;
115     for (const std::string& line : lines)
116     {
117         // Copy line to buffer.  Add newline if necessary.
118         buffer = line;
119         if (line.empty() || (line.back() != '\n'))
120         {
121             buffer += '\n';
122         }
123 
124         // Write buffer to file
125         const char* bufPtr = buffer.c_str();
126         unsigned int count = buffer.size();
127         while (count > 0)
128         {
129             // Try to write remaining bytes; it might not write all of them
130             ssize_t bytesWritten = write(fd, bufPtr, count);
131             if (bytesWritten == -1)
132             {
133                 throw std::runtime_error{
134                     std::string{"Unable to write to FFDC file: "} +
135                     strerror(errno)};
136             }
137             bufPtr += bytesWritten;
138             count -= bytesWritten;
139         }
140     }
141 
142     // Seek to beginning of file so error logging system can read data
143     if (lseek(fd, 0, SEEK_SET) != 0)
144     {
145         throw std::runtime_error{
146             std::string{"Unable to seek within FFDC file: "} + strerror(errno)};
147     }
148 
149     return file;
150 }
151 
152 std::vector<FFDCFile> DBusErrorLogging::createFFDCFiles(Journal& journal)
153 {
154     std::vector<FFDCFile> files{};
155 
156     // Create FFDC files containing journal messages from relevant executables.
157     // Executables in priority order in case error log cannot hold all the FFDC.
158     std::vector<std::string> executables{"phosphor-regulators", "systemd"};
159     for (const std::string& executable : executables)
160     {
161         try
162         {
163             // Get recent journal messages from the executable
164             std::vector<std::string> messages =
165                 journal.getMessages("SYSLOG_IDENTIFIER", executable, 30);
166 
167             // Create FFDC file containing the journal messages
168             if (!messages.empty())
169             {
170                 files.emplace_back(createFFDCFile(messages));
171             }
172         }
173         catch (const std::exception& e)
174         {
175             journal.logError(exception_utils::getMessages(e));
176         }
177     }
178 
179     return files;
180 }
181 
182 std::vector<FFDCTuple>
183     DBusErrorLogging::createFFDCTuples(std::vector<FFDCFile>& files)
184 {
185     std::vector<FFDCTuple> ffdcTuples{};
186     for (FFDCFile& file : files)
187     {
188         ffdcTuples.emplace_back(
189             file.getFormat(), file.getSubType(), file.getVersion(),
190             sdbusplus::message::unix_fd(file.getFileDescriptor()));
191     }
192     return ffdcTuples;
193 }
194 
195 void DBusErrorLogging::logError(
196     const std::string& message, Entry::Level severity,
197     std::map<std::string, std::string>& additionalData, Journal& journal)
198 {
199     try
200     {
201         // Add PID to AdditionalData
202         additionalData.emplace("_PID", std::to_string(getpid()));
203 
204         // Create FFDC files containing debug data to store in error log
205         std::vector<FFDCFile> files{createFFDCFiles(journal)};
206 
207         // Create FFDC tuples used to pass FFDC files to D-Bus method
208         std::vector<FFDCTuple> ffdcTuples{createFFDCTuples(files)};
209 
210         // Call D-Bus method to create an error log with FFDC files
211         const char* service = "xyz.openbmc_project.Logging";
212         const char* objPath = "/xyz/openbmc_project/logging";
213         const char* interface = "xyz.openbmc_project.Logging.Create";
214         const char* method = "CreateWithFFDCFiles";
215         auto reqMsg = bus.new_method_call(service, objPath, interface, method);
216         reqMsg.append(message, severity, additionalData, ffdcTuples);
217         auto respMsg = bus.call(reqMsg);
218 
219         // Remove FFDC files.  If an exception occurs before this, the files
220         // will be deleted by FFDCFile desctructor but errors will be ignored.
221         removeFFDCFiles(files, journal);
222     }
223     catch (const std::exception& e)
224     {
225         journal.logError(exception_utils::getMessages(e));
226         journal.logError("Unable to log error " + message);
227     }
228 }
229 
230 void DBusErrorLogging::removeFFDCFiles(std::vector<FFDCFile>& files,
231                                        Journal& journal)
232 {
233     // Explicitly remove FFDC files rather than relying on FFDCFile destructor.
234     // This allows any resulting errors to be written to the journal.
235     for (FFDCFile& file : files)
236     {
237         try
238         {
239             file.remove();
240         }
241         catch (const std::exception& e)
242         {
243             journal.logError(exception_utils::getMessages(e));
244         }
245     }
246 
247     // Clear vector since the FFDCFile objects can no longer be used
248     files.clear();
249 }
250 
251 } // namespace phosphor::power::regulators
252