1 /**
2  * Copyright © 2021 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 #pragma once
17 #include "config.h"
18 
19 #include "types.hpp"
20 
21 #include <cereal/archives/json.hpp>
22 #include <cereal/types/string.hpp>
23 #include <cereal/types/tuple.hpp>
24 #include <cereal/types/vector.hpp>
25 #include <phosphor-logging/log.hpp>
26 #include <sdeventplus/clock.hpp>
27 #include <sdeventplus/utility/timer.hpp>
28 
29 #include <filesystem>
30 #include <format>
31 #include <fstream>
32 #include <map>
33 #include <tuple>
34 
35 namespace sensor::monitor
36 {
37 
38 /**
39  * @class AlarmTimestamps
40  *
41  * This class keeps track of the timestamps at which the shutdown
42  * timers are started in case the process or whole BMC restarts
43  * while a timer is running.  In the case where the process starts
44  * when a timer was previously running and an alarm is still active,
45  * a new timer can be started with just the remaining time.
46  */
47 class AlarmTimestamps
48 {
49   public:
50     ~AlarmTimestamps() = default;
51     AlarmTimestamps(const AlarmTimestamps&) = delete;
52     AlarmTimestamps& operator=(const AlarmTimestamps&) = delete;
53     AlarmTimestamps(AlarmTimestamps&&) = delete;
54     AlarmTimestamps& operator=(AlarmTimestamps&&) = delete;
55 
56     /**
57      * @brief Constructor
58      *
59      * Loads any saved timestamps
60      */
AlarmTimestamps()61     AlarmTimestamps()
62     {
63         load();
64     }
65 
66     /**
67      * @brief Adds an entry to the timestamps map and persists it.
68      *
69      * @param[in] key - The AlarmKey value
70      * @param[in] timestamp - The start timestamp to save
71      */
add(const AlarmKey & key,uint64_t timestamp)72     void add(const AlarmKey& key, uint64_t timestamp)
73     {
74         // Emplace won't do anything if an entry with that
75         // key was already present, so only save if an actual
76         // entry was added.
77         auto result = timestamps.emplace(key, timestamp);
78         if (result.second)
79         {
80             save();
81         }
82     }
83 
84     /**
85      * @brief Erase an entry using the passed in alarm key.
86      *
87      * @param[in] key - The AlarmKey value
88      */
erase(const AlarmKey & key)89     void erase(const AlarmKey& key)
90     {
91         size_t removed = timestamps.erase(key);
92         if (removed)
93         {
94             save();
95         }
96     }
97 
98     /**
99      * @brief Erase an entry using an iterator.
100      */
erase(std::map<AlarmKey,uint64_t>::const_iterator & entry)101     void erase(std::map<AlarmKey, uint64_t>::const_iterator& entry)
102     {
103         timestamps.erase(entry);
104         save();
105     }
106 
107     /**
108      * @brief Clear all entries.
109      */
clear()110     void clear()
111     {
112         if (!timestamps.empty())
113         {
114             timestamps.clear();
115             save();
116         }
117     }
118 
119     /**
120      * @brief Remove any entries for which there is not a running timer
121      *        for.  This is used on startup when an alarm could have cleared
122      *        during a restart to get rid of the old entries.
123      *
124      * @param[in] alarms - The current alarms map.
125      */
prune(const std::map<AlarmKey,std::unique_ptr<sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>> & alarms)126     void prune(
127         const std::map<AlarmKey, std::unique_ptr<sdeventplus::utility::Timer<
128                                      sdeventplus::ClockId::Monotonic>>>& alarms)
129     {
130         auto size = timestamps.size();
131 
132         auto isTimerStopped = [&alarms](const AlarmKey& key) {
133             auto alarm = alarms.find(key);
134             if (alarm != alarms.end())
135             {
136                 auto& timer = alarm->second;
137                 if (timer && timer->isEnabled())
138                 {
139                     return false;
140                 }
141             }
142             return true;
143         };
144 
145         auto it = timestamps.begin();
146 
147         while (it != timestamps.end())
148         {
149             if (isTimerStopped(it->first))
150             {
151                 it = timestamps.erase(it);
152             }
153             else
154             {
155                 ++it;
156             }
157         }
158 
159         if (size != timestamps.size())
160         {
161             save();
162         }
163     }
164 
165     /**
166      * @brief Returns the timestamps map
167      */
get() const168     const std::map<AlarmKey, uint64_t>& get() const
169     {
170         return timestamps;
171     }
172 
173     /**
174      * @brief Saves the timestamps map in the filesystem using cereal.
175      *
176      * Since cereal doesn't understand the AlarmType or ShutdownType
177      * enums, they are converted to ints before being written.
178      */
save()179     void save()
180     {
181         std::filesystem::path path =
182             std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
183             timestampsFilename;
184 
185         if (!std::filesystem::exists(path.parent_path()))
186         {
187             std::filesystem::create_directory(path.parent_path());
188         }
189 
190         std::vector<std::tuple<std::string, int, int, uint64_t>> times;
191 
192         for (const auto& [key, time] : timestamps)
193         {
194             times.emplace_back(std::get<std::string>(key),
195                                static_cast<int>(std::get<ShutdownType>(key)),
196                                static_cast<int>(std::get<AlarmType>(key)),
197                                time);
198         }
199 
200         std::ofstream stream{path.c_str()};
201         cereal::JSONOutputArchive oarchive{stream};
202 
203         oarchive(times);
204     }
205 
206   private:
207     static constexpr auto timestampsFilename = "shutdownAlarmStartTimes";
208 
209     /**
210      * @brief Loads the saved timestamps from the filesystem.
211      *
212      * As with save(), cereal doesn't understand the ShutdownType or AlarmType
213      * enums so they have to have been saved as ints and converted.
214      */
load()215     void load()
216     {
217         std::vector<std::tuple<std::string, int, int, uint64_t>> times;
218 
219         std::filesystem::path path =
220             std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
221             timestampsFilename;
222 
223         if (!std::filesystem::exists(path))
224         {
225             return;
226         }
227 
228         try
229         {
230             std::ifstream stream{path.c_str()};
231             cereal::JSONInputArchive iarchive{stream};
232             iarchive(times);
233 
234             for (const auto& [path, shutdownType, alarmType, timestamp] : times)
235             {
236                 timestamps.emplace(
237                     AlarmKey{path, static_cast<ShutdownType>(shutdownType),
238                              static_cast<AlarmType>(alarmType)},
239                     timestamp);
240             }
241         }
242         catch (const std::exception& e)
243         {
244             // Include possible exception when removing file, otherwise ec = 0
245             using namespace phosphor::logging;
246             std::error_code ec;
247             std::filesystem::remove(path, ec);
248             log<level::ERR>(
249                 std::format("Unable to restore persisted times ({}, ec: {})",
250                             e.what(), ec.value())
251                     .c_str());
252         }
253     }
254 
255     /**
256      * @brief The map of AlarmKeys and time start times.
257      */
258     std::map<AlarmKey, uint64_t> timestamps;
259 };
260 
261 } // namespace sensor::monitor
262