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 #include "fan_error.hpp"
17 
18 #include "logging.hpp"
19 #include "sdbusplus.hpp"
20 
21 #include <nlohmann/json.hpp>
22 #include <xyz/openbmc_project/Logging/Create/server.hpp>
23 
24 #include <filesystem>
25 
26 namespace phosphor::fan::monitor
27 {
28 
29 using FFDCFormat =
30     sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat;
31 using FFDCFiles = std::vector<
32     std::tuple<FFDCFormat, uint8_t, uint8_t, sdbusplus::message::unix_fd>>;
33 using json = nlohmann::json;
34 
35 const auto loggingService = "xyz.openbmc_project.Logging";
36 const auto loggingPath = "/xyz/openbmc_project/logging";
37 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
38 
39 namespace fs = std::filesystem;
40 using namespace phosphor::fan::util;
41 
42 FFDCFile::FFDCFile(const fs::path& name) :
43     _fd(open(name.c_str(), O_RDONLY)), _name(name)
44 {
45     if (_fd() == -1)
46     {
47         auto e = errno;
48         getLogger().log(fmt::format("Could not open FFDC file {}. errno {}",
49                                     _name.string(), e));
50     }
51 }
52 
53 void FanError::commit(const json& jsonFFDC, bool isPowerOffError)
54 {
55     FFDCFiles ffdc;
56     auto ad = getAdditionalData(isPowerOffError);
57 
58     // Add the Logger contents as FFDC
59     auto logFile = makeLogFFDCFile();
60     if (logFile && (logFile->fd() != -1))
61     {
62         ffdc.emplace_back(FFDCFormat::Text, 0x01, 0x01, logFile->fd());
63     }
64 
65     // Add the passed in JSON as FFDC
66     auto ffdcFile = makeJsonFFDCFile(jsonFFDC);
67     if (ffdcFile && (ffdcFile->fd() != -1))
68     {
69         ffdc.emplace_back(FFDCFormat::JSON, 0x01, 0x01, ffdcFile->fd());
70     }
71 
72     try
73     {
74         auto sev = _severity;
75 
76         // If this is a power off, change severity to Critical
77         if (isPowerOffError)
78         {
79             using namespace sdbusplus::xyz::openbmc_project::Logging::server;
80             sev = convertForMessage(Entry::Level::Critical);
81         }
82         SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
83                               "CreateWithFFDCFiles", _errorName, sev, ad, ffdc);
84     }
85     catch (const DBusError& e)
86     {
87         getLogger().log(
88             fmt::format("Call to create a {} error for fan {} failed: {}",
89                         _errorName, _fanName, e.what()),
90             Logger::error);
91     }
92 }
93 
94 std::map<std::string, std::string>
95     FanError::getAdditionalData(bool isPowerOffError)
96 {
97     std::map<std::string, std::string> ad;
98 
99     ad.emplace("_PID", std::to_string(getpid()));
100 
101     if (!_fanName.empty())
102     {
103         ad.emplace("CALLOUT_INVENTORY_PATH", _fanName);
104     }
105 
106     if (!_sensorName.empty())
107     {
108         ad.emplace("FAN_SENSOR", _sensorName);
109     }
110 
111     // If this is a power off, specify that it's a power
112     // fault and a system termination.  This is used by some
113     // implementations for service reasons.
114     if (isPowerOffError)
115     {
116         ad.emplace("POWER_THERMAL_CRITICAL_FAULT", "TRUE");
117         ad.emplace("SEVERITY_DETAIL", "SYSTEM_TERM");
118     }
119 
120     return ad;
121 }
122 
123 std::unique_ptr<FFDCFile> FanError::makeLogFFDCFile()
124 {
125     try
126     {
127         auto logFile = getLogger().saveToTempFile();
128         return std::make_unique<FFDCFile>(logFile);
129     }
130     catch (const std::exception& e)
131     {
132         log<level::ERR>(
133             fmt::format("Could not save log contents in FFDC. Error msg: {}",
134                         e.what())
135                 .c_str());
136     }
137     return nullptr;
138 }
139 
140 std::unique_ptr<FFDCFile> FanError::makeJsonFFDCFile(const json& ffdcData)
141 {
142     char tmpFile[] = "/tmp/fanffdc.XXXXXX";
143     auto fd = mkstemp(tmpFile);
144     if (fd != -1)
145     {
146         auto jsonString = ffdcData.dump();
147 
148         auto rc = write(fd, jsonString.data(), jsonString.size());
149         close(fd);
150         if (rc != -1)
151         {
152             fs::path jsonFile{tmpFile};
153             return std::make_unique<FFDCFile>(jsonFile);
154         }
155         else
156         {
157             getLogger().log("Failed call to write JSON FFDC file");
158         }
159     }
160     else
161     {
162         auto e = errno;
163         getLogger().log(fmt::format("Failed called to mkstemp, errno = {}", e),
164                         Logger::error);
165     }
166     return nullptr;
167 }
168 
169 } // namespace phosphor::fan::monitor
170