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 "error_reporter.hpp"
17 
18 #include "get_power_state.hpp"
19 #include "logging.hpp"
20 #include "psensor.hpp"
21 #include "utility.hpp"
22 
23 #include <fmt/format.h>
24 #include <unistd.h>
25 
26 #include <phosphor-logging/log.hpp>
27 #include <xyz/openbmc_project/Logging/Create/server.hpp>
28 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
29 
30 namespace phosphor::fan::presence
31 {
32 
33 using json = nlohmann::json;
34 using namespace phosphor::logging;
35 using namespace sdbusplus::bus::match;
36 using namespace std::literals::string_literals;
37 using namespace std::chrono;
38 namespace fs = std::filesystem;
39 
40 const auto itemIface = "xyz.openbmc_project.Inventory.Item"s;
41 const auto invPrefix = "/xyz/openbmc_project/inventory"s;
42 const auto loggingPath = "/xyz/openbmc_project/logging";
43 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
44 
45 ErrorReporter::ErrorReporter(
46     sdbusplus::bus::bus& bus,
47     const std::vector<
48         std::tuple<Fan, std::vector<std::unique_ptr<PresenceSensor>>>>& fans) :
49     _bus(bus),
50     _event(sdeventplus::Event::get_default()),
51     _powerState(getPowerStateObject())
52 {
53     _powerState->addCallback("errorReporter",
54                              std::bind(&ErrorReporter::powerStateChanged, this,
55                                        std::placeholders::_1));
56 
57     for (const auto& fan : fans)
58     {
59         const auto& fanData = std::get<0>(fan);
60 
61         // Only deal with fans that have an error time defined.
62         if (std::get<std::optional<size_t>>(fanData))
63         {
64             auto path = invPrefix + std::get<1>(fanData);
65 
66             // Register for fan presence changes, get their initial states,
67             // and create the fan missing timers.
68 
69             _matches.emplace_back(
70                 _bus, rules::propertiesChanged(path, itemIface),
71                 std::bind(std::mem_fn(&ErrorReporter::presenceChanged), this,
72                           std::placeholders::_1));
73 
74             _fanStates.emplace(path, getPresence(fanData));
75 
76             auto timer = std::make_unique<
77                 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
78                 _event,
79                 std::bind(std::mem_fn(&ErrorReporter::fanMissingTimerExpired),
80                           this, path));
81 
82             seconds errorTime{std::get<std::optional<size_t>>(fanData).value()};
83 
84             _fanMissingTimers.emplace(
85                 path, std::make_tuple(std::move(timer), std::move(errorTime)));
86         }
87     }
88 
89     // If power is already on, check for currently missing fans.
90     if (_powerState->isPowerOn())
91     {
92         powerStateChanged(true);
93     }
94 }
95 
96 void ErrorReporter::presenceChanged(sdbusplus::message::message& msg)
97 {
98     bool present;
99     auto fanPath = msg.get_path();
100     std::string interface;
101     std::map<std::string, std::variant<bool>> properties;
102 
103     msg.read(interface, properties);
104 
105     auto presentProp = properties.find("Present");
106     if (presentProp != properties.end())
107     {
108         present = std::get<bool>(presentProp->second);
109         if (_fanStates[fanPath] != present)
110         {
111             getLogger().log(fmt::format("Fan {} presence state change to {}",
112                                         fanPath, present));
113 
114             _fanStates[fanPath] = present;
115             checkFan(fanPath);
116         }
117     }
118 }
119 
120 void ErrorReporter::checkFan(const std::string& fanPath)
121 {
122     auto& timer = std::get<0>(_fanMissingTimers[fanPath]);
123 
124     if (!_fanStates[fanPath])
125     {
126         // Fan is missing. If power is on, start the timer.
127         // If power is off, stop a running timer.
128         if (_powerState->isPowerOn())
129         {
130             timer->restartOnce(std::get<seconds>(_fanMissingTimers[fanPath]));
131         }
132         else if (timer->isEnabled())
133         {
134             timer->setEnabled(false);
135         }
136     }
137     else
138     {
139         // Fan is present. Stop a running timer.
140         if (timer->isEnabled())
141         {
142             timer->setEnabled(false);
143         }
144     }
145 }
146 
147 void ErrorReporter::fanMissingTimerExpired(const std::string& fanPath)
148 {
149     getLogger().log(
150         fmt::format("Creating event log for missing fan {}", fanPath),
151         Logger::error);
152 
153     std::map<std::string, std::string> additionalData;
154     additionalData.emplace("_PID", std::to_string(getpid()));
155     additionalData.emplace("CALLOUT_INVENTORY_PATH", fanPath);
156 
157     auto severity =
158         sdbusplus::xyz::openbmc_project::Logging::server::convertForMessage(
159             sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level::
160                 Error);
161 
162     // Save our logs to a temp file and get the file descriptor
163     // so it can be passed in as FFDC data.
164     auto logFile = getLogger().saveToTempFile();
165     util::FileDescriptor fd{-1};
166     fd.open(logFile, O_RDONLY);
167 
168     std::vector<std::tuple<
169         sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat,
170         uint8_t, uint8_t, sdbusplus::message::unix_fd>>
171         ffdc;
172 
173     ffdc.emplace_back(sdbusplus::xyz::openbmc_project::Logging::server::Create::
174                           FFDCFormat::Text,
175                       0x01, 0x01, fd());
176 
177     try
178     {
179         util::SDBusPlus::lookupAndCallMethod(
180             loggingPath, loggingCreateIface, "CreateWithFFDCFiles",
181             "xyz.openbmc_project.Fan.Error.Missing", severity, additionalData,
182             ffdc);
183     }
184     catch (const util::DBusError& e)
185     {
186         getLogger().log(
187             fmt::format(
188                 "Call to create an error log for missing fan {} failed: {}",
189                 fanPath, e.what()),
190             Logger::error);
191         fs::remove(logFile);
192         throw;
193     }
194 
195     fs::remove(logFile);
196 }
197 
198 void ErrorReporter::powerStateChanged(bool powerState)
199 {
200     if (powerState)
201     {
202         // If there are fans already missing, log it.
203         auto missing = std::count_if(
204             _fanStates.begin(), _fanStates.end(),
205             [](const auto& fanState) { return fanState.second == false; });
206 
207         if (missing)
208         {
209             getLogger().log(
210                 fmt::format("At power on, there are {} missing fans", missing));
211         }
212     }
213 
214     std::for_each(
215         _fanStates.begin(), _fanStates.end(),
216         [this](const auto& fanState) { this->checkFan(fanState.first); });
217 }
218 
219 } // namespace phosphor::fan::presence
220