1 #include "config.h"
2 
3 #include "log_manager.hpp"
4 
5 #include "elog_entry.hpp"
6 #include "elog_meta.hpp"
7 #include "elog_serialize.hpp"
8 #include "extensions.hpp"
9 
10 #include <poll.h>
11 #include <sys/inotify.h>
12 #include <systemd/sd-bus.h>
13 #include <systemd/sd-journal.h>
14 #include <unistd.h>
15 
16 #include <cassert>
17 #include <chrono>
18 #include <cstdio>
19 #include <cstring>
20 #include <fstream>
21 #include <functional>
22 #include <future>
23 #include <iostream>
24 #include <map>
25 #include <phosphor-logging/log.hpp>
26 #include <sdbusplus/vtable.hpp>
27 #include <set>
28 #include <string>
29 #include <vector>
30 
31 using namespace phosphor::logging;
32 using namespace std::chrono;
33 extern const std::map<metadata::Metadata,
34                       std::function<metadata::associations::Type>>
35     meta;
36 
37 namespace phosphor
38 {
39 namespace logging
40 {
41 namespace internal
42 {
43 inline auto getLevel(const std::string& errMsg)
44 {
45     auto reqLevel = Entry::Level::Error; // Default to Error
46 
47     auto levelmap = g_errLevelMap.find(errMsg);
48     if (levelmap != g_errLevelMap.end())
49     {
50         reqLevel = static_cast<Entry::Level>(levelmap->second);
51     }
52 
53     return reqLevel;
54 }
55 
56 int Manager::getRealErrSize()
57 {
58     return realErrors.size();
59 }
60 
61 int Manager::getInfoErrSize()
62 {
63     return infoErrors.size();
64 }
65 
66 void Manager::commit(uint64_t transactionId, std::string errMsg)
67 {
68     auto level = getLevel(errMsg);
69     _commit(transactionId, std::move(errMsg), level);
70 }
71 
72 void Manager::commitWithLvl(uint64_t transactionId, std::string errMsg,
73                             uint32_t errLvl)
74 {
75     _commit(transactionId, std::move(errMsg),
76             static_cast<Entry::Level>(errLvl));
77 }
78 
79 void Manager::_commit(uint64_t transactionId, std::string&& errMsg,
80                       Entry::Level errLvl)
81 {
82     if (!Extensions::disableDefaultLogCaps())
83     {
84         if (errLvl < Entry::sevLowerLimit)
85         {
86             if (realErrors.size() >= ERROR_CAP)
87             {
88                 erase(realErrors.front());
89             }
90         }
91         else
92         {
93             if (infoErrors.size() >= ERROR_INFO_CAP)
94             {
95                 erase(infoErrors.front());
96             }
97         }
98     }
99 
100     constexpr const auto transactionIdVar = "TRANSACTION_ID";
101     // Length of 'TRANSACTION_ID' string.
102     constexpr const auto transactionIdVarSize = std::strlen(transactionIdVar);
103     // Length of 'TRANSACTION_ID=' string.
104     constexpr const auto transactionIdVarOffset = transactionIdVarSize + 1;
105 
106     // Flush all the pending log messages into the journal
107     journalSync();
108 
109     sd_journal* j = nullptr;
110     int rc = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
111     if (rc < 0)
112     {
113         logging::log<logging::level::ERR>(
114             "Failed to open journal",
115             logging::entry("DESCRIPTION=%s", strerror(-rc)));
116         return;
117     }
118 
119     std::string transactionIdStr = std::to_string(transactionId);
120     std::set<std::string> metalist;
121     auto metamap = g_errMetaMap.find(errMsg);
122     if (metamap != g_errMetaMap.end())
123     {
124         metalist.insert(metamap->second.begin(), metamap->second.end());
125     }
126 
127     // Add _PID field information in AdditionalData.
128     metalist.insert("_PID");
129 
130     std::vector<std::string> additionalData;
131 
132     // Read the journal from the end to get the most recent entry first.
133     // The result from the sd_journal_get_data() is of the form VARIABLE=value.
134     SD_JOURNAL_FOREACH_BACKWARDS(j)
135     {
136         const char* data = nullptr;
137         size_t length = 0;
138 
139         // Look for the transaction id metadata variable
140         rc = sd_journal_get_data(j, transactionIdVar, (const void**)&data,
141                                  &length);
142         if (rc < 0)
143         {
144             // This journal entry does not have the TRANSACTION_ID
145             // metadata variable.
146             continue;
147         }
148 
149         // journald does not guarantee that sd_journal_get_data() returns NULL
150         // terminated strings, so need to specify the size to use to compare,
151         // use the returned length instead of anything that relies on NULL
152         // terminators like strlen().
153         // The data variable is in the form of 'TRANSACTION_ID=1234'. Remove
154         // the TRANSACTION_ID characters plus the (=) sign to do the comparison.
155         // 'data + transactionIdVarOffset' will be in the form of '1234'.
156         // 'length - transactionIdVarOffset' will be the length of '1234'.
157         if ((length <= (transactionIdVarOffset)) ||
158             (transactionIdStr.compare(0, transactionIdStr.size(),
159                                       data + transactionIdVarOffset,
160                                       length - transactionIdVarOffset) != 0))
161         {
162             // The value of the TRANSACTION_ID metadata is not the requested
163             // transaction id number.
164             continue;
165         }
166 
167         // Search for all metadata variables in the current journal entry.
168         for (auto i = metalist.cbegin(); i != metalist.cend();)
169         {
170             rc = sd_journal_get_data(j, (*i).c_str(), (const void**)&data,
171                                      &length);
172             if (rc < 0)
173             {
174                 // Metadata variable not found, check next metadata variable.
175                 i++;
176                 continue;
177             }
178 
179             // Metadata variable found, save it and remove it from the set.
180             additionalData.emplace_back(data, length);
181             i = metalist.erase(i);
182         }
183         if (metalist.empty())
184         {
185             // All metadata variables found, break out of journal loop.
186             break;
187         }
188     }
189     if (!metalist.empty())
190     {
191         // Not all the metadata variables were found in the journal.
192         for (auto& metaVarStr : metalist)
193         {
194             logging::log<logging::level::INFO>(
195                 "Failed to find metadata",
196                 logging::entry("META_FIELD=%s", metaVarStr.c_str()));
197         }
198     }
199 
200     sd_journal_close(j);
201 
202     // Create error Entry dbus object
203     entryId++;
204     if (errLvl >= Entry::sevLowerLimit)
205     {
206         infoErrors.push_back(entryId);
207     }
208     else
209     {
210         realErrors.push_back(entryId);
211     }
212     auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
213                   std::chrono::system_clock::now().time_since_epoch())
214                   .count();
215     auto objPath = std::string(OBJ_ENTRY) + '/' + std::to_string(entryId);
216 
217     AssociationList objects{};
218     processMetadata(errMsg, additionalData, objects);
219 
220     auto e = std::make_unique<Entry>(busLog, objPath, entryId,
221                                      ms, // Milliseconds since 1970
222                                      errLvl, std::move(errMsg),
223                                      std::move(additionalData),
224                                      std::move(objects), fwVersion, *this);
225     serialize(*e);
226 
227     doExtensionLogCreate(*e);
228 
229     entries.insert(std::make_pair(entryId, std::move(e)));
230 }
231 
232 void Manager::doExtensionLogCreate(const Entry& entry)
233 {
234     // Make the association <endpointpath>/<endpointtype> paths
235     std::vector<std::string> assocs;
236     for (const auto& [forwardType, reverseType, endpoint] :
237          entry.associations())
238     {
239         std::string e{endpoint};
240         e += '/' + reverseType;
241         assocs.push_back(e);
242     }
243 
244     for (auto& create : Extensions::getCreateFunctions())
245     {
246         try
247         {
248             create(entry.message(), entry.id(), entry.timestamp(),
249                    entry.severity(), entry.additionalData(), assocs);
250         }
251         catch (std::exception& e)
252         {
253             log<level::ERR>("An extension's create function threw an exception",
254                             phosphor::logging::entry("ERROR=%s", e.what()));
255         }
256     }
257 }
258 
259 void Manager::processMetadata(const std::string& errorName,
260                               const std::vector<std::string>& additionalData,
261                               AssociationList& objects) const
262 {
263     // additionalData is a list of "metadata=value"
264     constexpr auto separator = '=';
265     for (const auto& entryItem : additionalData)
266     {
267         auto found = entryItem.find(separator);
268         if (std::string::npos != found)
269         {
270             auto metadata = entryItem.substr(0, found);
271             auto iter = meta.find(metadata);
272             if (meta.end() != iter)
273             {
274                 (iter->second)(metadata, additionalData, objects);
275             }
276         }
277     }
278 }
279 
280 void Manager::erase(uint32_t entryId)
281 {
282     auto entryFound = entries.find(entryId);
283     if (entries.end() != entryFound)
284     {
285         for (auto& func : Extensions::getDeleteProhibitedFunctions())
286         {
287             try
288             {
289                 bool prohibited = false;
290                 func(entryId, prohibited);
291                 if (prohibited)
292                 {
293                     // Future work remains to throw an error here.
294                     return;
295                 }
296             }
297             catch (std::exception& e)
298             {
299                 log<level::ERR>(
300                     "An extension's deleteProhibited function threw "
301                     "an exception",
302                     entry("ERROR=%s", e.what()));
303             }
304         }
305 
306         // Delete the persistent representation of this error.
307         fs::path errorPath(ERRLOG_PERSIST_PATH);
308         errorPath /= std::to_string(entryId);
309         fs::remove(errorPath);
310 
311         auto removeId = [](std::list<uint32_t>& ids, uint32_t id) {
312             auto it = std::find(ids.begin(), ids.end(), id);
313             if (it != ids.end())
314             {
315                 ids.erase(it);
316             }
317         };
318         if (entryFound->second->severity() >= Entry::sevLowerLimit)
319         {
320             removeId(infoErrors, entryId);
321         }
322         else
323         {
324             removeId(realErrors, entryId);
325         }
326         entries.erase(entryFound);
327 
328         for (auto& remove : Extensions::getDeleteFunctions())
329         {
330             try
331             {
332                 remove(entryId);
333             }
334             catch (std::exception& e)
335             {
336                 log<level::ERR>("An extension's delete function threw an "
337                                 "exception",
338                                 entry("ERROR=%s", e.what()));
339             }
340         }
341     }
342     else
343     {
344         logging::log<level::ERR>("Invalid entry ID to delete",
345                                  logging::entry("ID=%d", entryId));
346     }
347 }
348 
349 void Manager::restore()
350 {
351     auto sanity = [](const auto& id, const auto& restoredId) {
352         return id == restoredId;
353     };
354     std::vector<uint32_t> errorIds;
355 
356     fs::path dir(ERRLOG_PERSIST_PATH);
357     if (!fs::exists(dir) || fs::is_empty(dir))
358     {
359         return;
360     }
361 
362     for (auto& file : fs::directory_iterator(dir))
363     {
364         auto id = file.path().filename().c_str();
365         auto idNum = std::stol(id);
366         auto e = std::make_unique<Entry>(
367             busLog, std::string(OBJ_ENTRY) + '/' + id, idNum, *this);
368         if (deserialize(file.path(), *e))
369         {
370             // validate the restored error entry id
371             if (sanity(static_cast<uint32_t>(idNum), e->id()))
372             {
373                 e->emit_object_added();
374                 if (e->severity() >= Entry::sevLowerLimit)
375                 {
376                     infoErrors.push_back(idNum);
377                 }
378                 else
379                 {
380                     realErrors.push_back(idNum);
381                 }
382 
383                 entries.insert(std::make_pair(idNum, std::move(e)));
384                 errorIds.push_back(idNum);
385             }
386             else
387             {
388                 logging::log<logging::level::ERR>(
389                     "Failed in sanity check while restoring error entry. "
390                     "Ignoring error entry",
391                     logging::entry("ID_NUM=%d", idNum),
392                     logging::entry("ENTRY_ID=%d", e->id()));
393             }
394         }
395     }
396 
397     if (!errorIds.empty())
398     {
399         entryId = *(std::max_element(errorIds.begin(), errorIds.end()));
400     }
401 }
402 
403 void Manager::journalSync()
404 {
405     bool syncRequested = false;
406     auto fd = -1;
407     auto rc = -1;
408     auto wd = -1;
409     auto bus = sdbusplus::bus::new_default();
410 
411     auto start =
412         duration_cast<microseconds>(steady_clock::now().time_since_epoch())
413             .count();
414 
415     constexpr auto maxRetry = 2;
416     for (int i = 0; i < maxRetry; i++)
417     {
418         // Read timestamp from synced file
419         constexpr auto syncedPath = "/run/systemd/journal/synced";
420         std::ifstream syncedFile(syncedPath);
421         if (syncedFile.fail())
422         {
423             // If the synced file doesn't exist, a sync request will create it.
424             if (errno != ENOENT)
425             {
426                 log<level::ERR>("Failed to open journal synced file",
427                                 entry("FILENAME=%s", syncedPath),
428                                 entry("ERRNO=%d", errno));
429                 return;
430             }
431         }
432         else
433         {
434             // Only read the synced file if it exists.
435             // See if a sync happened by now
436             std::string timestampStr;
437             std::getline(syncedFile, timestampStr);
438             auto timestamp = std::stoll(timestampStr);
439             if (timestamp >= start)
440             {
441                 return;
442             }
443         }
444 
445         // Let's ask for a sync, but only once
446         if (!syncRequested)
447         {
448             syncRequested = true;
449 
450             constexpr auto JOURNAL_UNIT = "systemd-journald.service";
451             auto signal = SIGRTMIN + 1;
452 
453             auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
454                                               SYSTEMD_INTERFACE, "KillUnit");
455             method.append(JOURNAL_UNIT, "main", signal);
456             bus.call(method);
457             if (method.is_method_error())
458             {
459                 log<level::ERR>("Failed to kill journal service");
460                 return;
461             }
462             continue;
463         }
464 
465         // Let's install the inotify watch, if we didn't do that yet. This watch
466         // monitors the syncedFile for when journald updates it with a newer
467         // timestamp. This means the journal has been flushed.
468         if (fd < 0)
469         {
470             fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
471             if (fd < 0)
472             {
473                 log<level::ERR>("Failed to create inotify watch",
474                                 entry("ERRNO=%d", errno));
475                 return;
476             }
477 
478             constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal";
479             wd = inotify_add_watch(fd, JOURNAL_RUN_PATH,
480                                    IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR);
481             if (wd < 0)
482             {
483                 log<level::ERR>("Failed to watch journal directory",
484                                 entry("PATH=%s", JOURNAL_RUN_PATH),
485                                 entry("ERRNO=%d", errno));
486                 close(fd);
487                 return;
488             }
489             continue;
490         }
491 
492         // Let's wait until inotify reports an event
493         struct pollfd fds = {
494             .fd = fd,
495             .events = POLLIN,
496         };
497         constexpr auto pollTimeout = 5; // 5 seconds
498         rc = poll(&fds, 1, pollTimeout * 1000);
499         if (rc < 0)
500         {
501             log<level::ERR>("Failed to add event", entry("ERRNO=%d", errno),
502                             entry("ERR=%s", strerror(-rc)));
503             inotify_rm_watch(fd, wd);
504             close(fd);
505             return;
506         }
507         else if (rc == 0)
508         {
509             log<level::INFO>("Poll timeout, no new journal synced data",
510                              entry("TIMEOUT=%d", pollTimeout));
511             break;
512         }
513 
514         // Read from the specified file descriptor until there is no new data,
515         // throwing away everything read since the timestamp will be read at the
516         // beginning of the loop.
517         constexpr auto maxBytes = 64;
518         uint8_t buffer[maxBytes];
519         while (read(fd, buffer, maxBytes) > 0)
520             ;
521     }
522 
523     if (fd != -1)
524     {
525         if (wd != -1)
526         {
527             inotify_rm_watch(fd, wd);
528         }
529         close(fd);
530     }
531 
532     return;
533 }
534 
535 std::string Manager::readFWVersion()
536 {
537     std::string version;
538     std::ifstream versionFile{BMC_VERSION_FILE};
539     std::string line;
540     static constexpr auto VERSION_ID = "VERSION_ID=";
541 
542     while (std::getline(versionFile, line))
543     {
544         if (line.find(VERSION_ID) != std::string::npos)
545         {
546             auto pos = line.find_first_of('"') + 1;
547             version = line.substr(pos, line.find_last_of('"') - pos);
548             break;
549         }
550     }
551 
552     if (version.empty())
553     {
554         log<level::ERR>("Unable to read BMC firmware version");
555     }
556 
557     return version;
558 }
559 
560 } // namespace internal
561 } // namespace logging
562 } // namespace phosphor
563