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