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