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