1 /**
2  * Copyright © 2019 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 "repository.hpp"
17 
18 #include <fstream>
19 #include <phosphor-logging/log.hpp>
20 #include <xyz/openbmc_project/Common/File/error.hpp>
21 
22 namespace openpower
23 {
24 namespace pels
25 {
26 
27 namespace fs = std::filesystem;
28 using namespace phosphor::logging;
29 namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error;
30 
31 Repository::Repository(const std::filesystem::path& basePath) :
32     _logPath(basePath / "logs")
33 {
34     if (!fs::exists(_logPath))
35     {
36         fs::create_directories(_logPath);
37     }
38 
39     restore();
40 }
41 
42 void Repository::restore()
43 {
44     for (auto& dirEntry : fs::directory_iterator(_logPath))
45     {
46         try
47         {
48             if (!fs::is_regular_file(dirEntry.path()))
49             {
50                 continue;
51             }
52 
53             std::ifstream file{dirEntry.path()};
54             std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
55                                       std::istreambuf_iterator<char>()};
56             file.close();
57 
58             PEL pel{data};
59             if (pel.valid())
60             {
61                 // If the host hasn't acked it, reset the host state so
62                 // it will get sent up again.
63                 if (pel.hostTransmissionState() == TransmissionState::sent)
64                 {
65                     pel.setHostTransmissionState(TransmissionState::newPEL);
66                     try
67                     {
68                         write(pel, dirEntry.path());
69                     }
70                     catch (std::exception& e)
71                     {
72                         log<level::ERR>(
73                             "Failed to save PEL after updating host state",
74                             entry("PELID=0x%X", pel.id()));
75                     }
76                 }
77 
78                 PELAttributes attributes{
79                     dirEntry.path(), pel.userHeader().actionFlags(),
80                     pel.hostTransmissionState(), pel.hmcTransmissionState()};
81 
82                 using pelID = LogID::Pel;
83                 using obmcID = LogID::Obmc;
84                 _pelAttributes.emplace(
85                     LogID(pelID(pel.id()), obmcID(pel.obmcLogID())),
86                     attributes);
87             }
88             else
89             {
90                 log<level::ERR>(
91                     "Found invalid PEL file while restoring.  Removing.",
92                     entry("FILENAME=%s", dirEntry.path().c_str()));
93                 fs::remove(dirEntry.path());
94             }
95         }
96         catch (std::exception& e)
97         {
98             log<level::ERR>("Hit exception while restoring PEL File",
99                             entry("FILENAME=%s", dirEntry.path().c_str()),
100                             entry("ERROR=%s", e.what()));
101         }
102     }
103 }
104 
105 std::string Repository::getPELFilename(uint32_t pelID, const BCDTime& time)
106 {
107     char name[50];
108     sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", time.yearMSB,
109             time.yearLSB, time.month, time.day, time.hour, time.minutes,
110             time.seconds, time.hundredths, pelID);
111     return std::string{name};
112 }
113 
114 void Repository::add(std::unique_ptr<PEL>& pel)
115 {
116     pel->setHostTransmissionState(TransmissionState::newPEL);
117     pel->setHMCTransmissionState(TransmissionState::newPEL);
118 
119     auto path = _logPath / getPELFilename(pel->id(), pel->commitTime());
120 
121     write(*(pel.get()), path);
122 
123     PELAttributes attributes{path, pel->userHeader().actionFlags(),
124                              pel->hostTransmissionState(),
125                              pel->hmcTransmissionState()};
126 
127     using pelID = LogID::Pel;
128     using obmcID = LogID::Obmc;
129     _pelAttributes.emplace(LogID(pelID(pel->id()), obmcID(pel->obmcLogID())),
130                            attributes);
131 
132     processAddCallbacks(*pel);
133 }
134 
135 void Repository::write(const PEL& pel, const fs::path& path)
136 {
137     std::ofstream file{path, std::ios::binary};
138 
139     if (!file.good())
140     {
141         // If this fails, the filesystem is probably full so it isn't like
142         // we could successfully create yet another error log here.
143         auto e = errno;
144         fs::remove(path);
145         log<level::ERR>("Unable to open PEL file for writing",
146                         entry("ERRNO=%d", e), entry("PATH=%s", path.c_str()));
147         throw file_error::Open();
148     }
149 
150     auto data = pel.data();
151     file.write(reinterpret_cast<const char*>(data.data()), data.size());
152 
153     if (file.fail())
154     {
155         // Same note as above about not being able to create an error log
156         // for this case even if we wanted.
157         auto e = errno;
158         file.close();
159         fs::remove(path);
160         log<level::ERR>("Unable to write PEL file", entry("ERRNO=%d", e),
161                         entry("PATH=%s", path.c_str()));
162         throw file_error::Write();
163     }
164 }
165 
166 void Repository::remove(const LogID& id)
167 {
168     auto pel = findPEL(id);
169     if (pel != _pelAttributes.end())
170     {
171         log<level::DEBUG>("Removing PEL from repository",
172                           entry("PEL_ID=0x%X", pel->first.pelID.id),
173                           entry("OBMC_LOG_ID=%d", pel->first.obmcID.id));
174         fs::remove(pel->second.path);
175         _pelAttributes.erase(pel);
176 
177         processDeleteCallbacks(pel->first.pelID.id);
178     }
179     else
180     {
181         log<level::DEBUG>("Could not find PEL to remove",
182                           entry("PEL_ID=0x%X", id.pelID.id),
183                           entry("OBMC_LOG_ID=%d", id.obmcID.id));
184     }
185 }
186 
187 std::optional<std::vector<uint8_t>> Repository::getPELData(const LogID& id)
188 {
189     auto pel = findPEL(id);
190     if (pel != _pelAttributes.end())
191     {
192         std::ifstream file{pel->second.path.c_str()};
193         if (!file.good())
194         {
195             auto e = errno;
196             log<level::ERR>("Unable to open PEL file", entry("ERRNO=%d", e),
197                             entry("PATH=%s", pel->second.path.c_str()));
198             throw file_error::Open();
199         }
200 
201         std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
202                                   std::istreambuf_iterator<char>()};
203         return data;
204     }
205 
206     return std::nullopt;
207 }
208 
209 std::optional<sdbusplus::message::unix_fd> Repository::getPELFD(const LogID& id)
210 {
211     auto pel = findPEL(id);
212     if (pel != _pelAttributes.end())
213     {
214         FILE* fp = fopen(pel->second.path.c_str(), "rb");
215 
216         if (fp == nullptr)
217         {
218             auto e = errno;
219             log<level::ERR>("Unable to open PEL File", entry("ERRNO=%d", e),
220                             entry("PATH=%s", pel->second.path.c_str()));
221             throw file_error::Open();
222         }
223 
224         // Must leave the file open here.  It will be closed by sdbusplus
225         // when it sends it back over D-Bus.
226 
227         return fileno(fp);
228     }
229     return std::nullopt;
230 }
231 
232 void Repository::for_each(ForEachFunc func) const
233 {
234     for (const auto& [id, attributes] : _pelAttributes)
235     {
236         std::ifstream file{attributes.path};
237 
238         if (!file.good())
239         {
240             auto e = errno;
241             log<level::ERR>("Repository::for_each: Unable to open PEL file",
242                             entry("ERRNO=%d", e),
243                             entry("PATH=%s", attributes.path.c_str()));
244             continue;
245         }
246 
247         std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
248                                   std::istreambuf_iterator<char>()};
249         file.close();
250 
251         PEL pel{data};
252 
253         try
254         {
255             if (func(pel))
256             {
257                 break;
258             }
259         }
260         catch (std::exception& e)
261         {
262             log<level::ERR>("Repository::for_each function exception",
263                             entry("ERROR=%s", e.what()));
264         }
265     }
266 }
267 
268 void Repository::processAddCallbacks(const PEL& pel) const
269 {
270     for (auto& [name, func] : _addSubscriptions)
271     {
272         try
273         {
274             func(pel);
275         }
276         catch (std::exception& e)
277         {
278             log<level::ERR>("PEL Repository add callback exception",
279                             entry("NAME=%s", name.c_str()),
280                             entry("ERROR=%s", e.what()));
281         }
282     }
283 }
284 
285 void Repository::processDeleteCallbacks(uint32_t id) const
286 {
287     for (auto& [name, func] : _deleteSubscriptions)
288     {
289         try
290         {
291             func(id);
292         }
293         catch (std::exception& e)
294         {
295             log<level::ERR>("PEL Repository delete callback exception",
296                             entry("NAME=%s", name.c_str()),
297                             entry("ERROR=%s", e.what()));
298         }
299     }
300 }
301 
302 std::optional<std::reference_wrapper<const Repository::PELAttributes>>
303     Repository::getPELAttributes(const LogID& id) const
304 {
305     auto pel = findPEL(id);
306     if (pel != _pelAttributes.end())
307     {
308         return pel->second;
309     }
310 
311     return std::nullopt;
312 }
313 
314 void Repository::setPELHostTransState(uint32_t pelID, TransmissionState state)
315 {
316     LogID id{LogID::Pel{pelID}};
317     auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
318                              [&id](const auto& a) { return a.first == id; });
319 
320     if ((attr != _pelAttributes.end()) && (attr->second.hostState != state))
321     {
322         PELUpdateFunc func = [state](PEL& pel) {
323             pel.setHostTransmissionState(state);
324         };
325 
326         try
327         {
328             updatePEL(attr->second.path, func);
329 
330             attr->second.hostState = state;
331         }
332         catch (std::exception& e)
333         {
334             log<level::ERR>("Unable to update PEL host transmission state",
335                             entry("PATH=%s", attr->second.path.c_str()),
336                             entry("ERROR=%s", e.what()));
337         }
338     }
339 }
340 
341 void Repository::setPELHMCTransState(uint32_t pelID, TransmissionState state)
342 {
343     LogID id{LogID::Pel{pelID}};
344     auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
345                              [&id](const auto& a) { return a.first == id; });
346 
347     if ((attr != _pelAttributes.end()) && (attr->second.hmcState != state))
348     {
349         PELUpdateFunc func = [state](PEL& pel) {
350             pel.setHMCTransmissionState(state);
351         };
352 
353         try
354         {
355             updatePEL(attr->second.path, func);
356 
357             attr->second.hmcState = state;
358         }
359         catch (std::exception& e)
360         {
361             log<level::ERR>("Unable to update PEL HMC transmission state",
362                             entry("PATH=%s", attr->second.path.c_str()),
363                             entry("ERROR=%s", e.what()));
364         }
365     }
366 }
367 
368 void Repository::updatePEL(const fs::path& path, PELUpdateFunc updateFunc)
369 {
370     std::ifstream file{path};
371     std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
372                               std::istreambuf_iterator<char>()};
373     file.close();
374 
375     PEL pel{data};
376 
377     if (pel.valid())
378     {
379         updateFunc(pel);
380 
381         write(pel, path);
382     }
383     else
384     {
385         throw std::runtime_error(
386             "Unable to read a valid PEL when trying to update it");
387     }
388 }
389 
390 } // namespace pels
391 } // namespace openpower
392