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