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