1 #include <fstream>
2 #include <iostream>
3 #include <chrono>
4 #include <cstdio>
5 #include <set>
6 #include <string>
7 #include <vector>
8 #include <sdbusplus/vtable.hpp>
9 #include <systemd/sd-bus.h>
10 #include <systemd/sd-journal.h>
11 #include "config.h"
12 #include "elog_entry.hpp"
13 #include <phosphor-logging/log.hpp>
14 #include "log_manager.hpp"
15 #include "elog_meta.hpp"
16 #include "elog_serialize.hpp"
17 
18 using namespace phosphor::logging;
19 extern const std::map<metadata::Metadata,
20                       std::function<metadata::associations::Type>> meta;
21 
22 namespace phosphor
23 {
24 namespace logging
25 {
26 namespace internal
27 {
28 void Manager::commit(uint64_t transactionId, std::string errMsg)
29 {
30     auto reqLevel = level::ERR; // Default to ERR
31     size_t realErrCnt = entries.size() - infoErrors.size();
32     auto levelmap = g_errLevelMap.find(errMsg);
33 
34     if (levelmap != g_errLevelMap.end())
35     {
36         reqLevel = levelmap->second;
37     }
38 
39     if (static_cast<Entry::Level>(reqLevel) < Entry::sevLowerLimit)
40     {
41         if (capped)
42         {
43             return;
44         }
45         if (realErrCnt >= ERROR_CAP)
46         {
47             log<level::ERR>("Reached error cap, Ignoring error",
48                             entry("SIZE=%d", realErrCnt),
49                             entry("ERROR_CAP=%d", ERROR_CAP));
50             capped = true;
51             return;
52         }
53     }
54     else
55     {
56         if (infoErrors.size() >= ERROR_INFO_CAP)
57         {
58             erase(infoErrors.front());
59         }
60     }
61     constexpr const auto transactionIdVar = "TRANSACTION_ID";
62     // Length of 'TRANSACTION_ID' string.
63     constexpr const auto transactionIdVarSize = strlen(transactionIdVar);
64     // Length of 'TRANSACTION_ID=' string.
65     constexpr const auto transactionIdVarOffset = transactionIdVarSize + 1;
66 
67     // Flush all the pending log messages into the journal via Synchronize
68     constexpr auto JOURNAL_BUSNAME = "org.freedesktop.journal1";
69     constexpr auto JOURNAL_PATH = "/org/freedesktop/journal1";
70     constexpr auto JOURNAL_INTERFACE = "org.freedesktop.journal1";
71     auto bus = sdbusplus::bus::new_default();
72     auto method = bus.new_method_call(JOURNAL_BUSNAME, JOURNAL_PATH,
73                                       JOURNAL_INTERFACE, "Synchronize");
74     bus.call_noreply(method);
75 
76     sd_journal *j = nullptr;
77     int rc = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
78     if (rc < 0)
79     {
80         logging::log<logging::level::ERR>("Failed to open journal",
81                            logging::entry("DESCRIPTION=%s", strerror(-rc)));
82         return;
83     }
84 
85     std::string transactionIdStr = std::to_string(transactionId);
86     std::set<std::string> metalist;
87     auto metamap = g_errMetaMap.find(errMsg);
88     if (metamap != g_errMetaMap.end())
89     {
90         metalist.insert(metamap->second.begin(), metamap->second.end());
91     }
92 
93     //Add _PID field information in AdditionalData.
94     metalist.insert("_PID");
95 
96     std::vector<std::string> additionalData;
97 
98     // Read the journal from the end to get the most recent entry first.
99     // The result from the sd_journal_get_data() is of the form VARIABLE=value.
100     SD_JOURNAL_FOREACH_BACKWARDS(j)
101     {
102         const char *data = nullptr;
103         size_t length = 0;
104 
105         // Look for the transaction id metadata variable
106         rc = sd_journal_get_data(j, transactionIdVar, (const void **)&data,
107                                 &length);
108         if (rc < 0)
109         {
110             // This journal entry does not have the TRANSACTION_ID
111             // metadata variable.
112             continue;
113         }
114 
115         // journald does not guarantee that sd_journal_get_data() returns NULL
116         // terminated strings, so need to specify the size to use to compare,
117         // use the returned length instead of anything that relies on NULL
118         // terminators like strlen().
119         // The data variable is in the form of 'TRANSACTION_ID=1234'. Remove
120         // the TRANSACTION_ID characters plus the (=) sign to do the comparison.
121         // 'data + transactionIdVarOffset' will be in the form of '1234'.
122         // 'length - transactionIdVarOffset' will be the length of '1234'.
123         if ((length <= (transactionIdVarOffset)) ||
124             (transactionIdStr.compare(0,
125                                       transactionIdStr.size(),
126                                       data + transactionIdVarOffset,
127                                       length - transactionIdVarOffset) != 0))
128         {
129             // The value of the TRANSACTION_ID metadata is not the requested
130             // transaction id number.
131             continue;
132         }
133 
134         // Search for all metadata variables in the current journal entry.
135         for (auto i = metalist.cbegin(); i != metalist.cend();)
136         {
137             rc = sd_journal_get_data(j, (*i).c_str(),
138                                     (const void **)&data, &length);
139             if (rc < 0)
140             {
141                 // Metadata variable not found, check next metadata variable.
142                 i++;
143                 continue;
144             }
145 
146             // Metadata variable found, save it and remove it from the set.
147             additionalData.emplace_back(data, length);
148             i = metalist.erase(i);
149         }
150         if (metalist.empty())
151         {
152             // All metadata variables found, break out of journal loop.
153             break;
154         }
155     }
156     if (!metalist.empty())
157     {
158         // Not all the metadata variables were found in the journal.
159         for (auto& metaVarStr : metalist)
160         {
161             logging::log<logging::level::INFO>("Failed to find metadata",
162                     logging::entry("META_FIELD=%s", metaVarStr.c_str()));
163         }
164     }
165 
166     sd_journal_close(j);
167 
168     // Create error Entry dbus object
169     entryId++;
170     if (static_cast<Entry::Level>(reqLevel) >= Entry::sevLowerLimit)
171     {
172         infoErrors.push_back(entryId);
173     }
174     auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
175                 std::chrono::system_clock::now().time_since_epoch()).count();
176     auto objPath =  std::string(OBJ_ENTRY) + '/' +
177             std::to_string(entryId);
178 
179     AssociationList objects {};
180     processMetadata(errMsg, additionalData, objects);
181 
182     auto e = std::make_unique<Entry>(
183                  busLog,
184                  objPath,
185                  entryId,
186                  ms, // Milliseconds since 1970
187                  static_cast<Entry::Level>(reqLevel),
188                  std::move(errMsg),
189                  std::move(additionalData),
190                  std::move(objects),
191                  *this);
192     serialize(*e);
193     entries.insert(std::make_pair(entryId, std::move(e)));
194 }
195 
196 void Manager::processMetadata(const std::string& errorName,
197                               const std::vector<std::string>& additionalData,
198                               AssociationList& objects) const
199 {
200     // additionalData is a list of "metadata=value"
201     constexpr auto separator = '=';
202     for(const auto& entry: additionalData)
203     {
204         auto found = entry.find(separator);
205         if(std::string::npos != found)
206         {
207             auto metadata = entry.substr(0, found);
208             auto iter = meta.find(metadata);
209             if(meta.end() != iter)
210             {
211                 (iter->second)(metadata, additionalData, objects);
212             }
213         }
214     }
215 }
216 
217 void Manager::erase(uint32_t entryId)
218 {
219     auto entry = entries.find(entryId);
220     auto id = entry->second->id();
221     if(entries.end() != entry)
222     {
223         // Delete the persistent representation of this error.
224         fs::path errorPath(ERRLOG_PERSIST_PATH);
225         errorPath /= std::to_string(id);
226         fs::remove(errorPath);
227         if (entry->second->severity() >= Entry::sevLowerLimit)
228         {
229             auto it = std::find(infoErrors.begin(), infoErrors.end(), entryId);
230             if (it != infoErrors.end())
231             {
232                 infoErrors.erase(it);
233             }
234         }
235         entries.erase(entry);
236     }
237 
238     size_t realErrCnt = entries.size() - infoErrors.size();
239 
240     if (realErrCnt <  ERROR_CAP)
241     {
242         capped = false;
243     }
244 }
245 
246 void Manager::restore()
247 {
248     std::vector<uint32_t> errorIds;
249 
250     fs::path dir(ERRLOG_PERSIST_PATH);
251     if (!fs::exists(dir) || fs::is_empty(dir))
252     {
253         return;
254     }
255 
256     for(auto& file: fs::directory_iterator(dir))
257     {
258         auto id = file.path().filename().c_str();
259         auto idNum = std::stol(id);
260         auto e = std::make_unique<Entry>(
261                      busLog,
262                      std::string(OBJ_ENTRY) + '/' + id,
263                      idNum,
264                      *this);
265         if (deserialize(file.path(), *e))
266         {
267             e->emit_object_added();
268             if (e->severity() >= Entry::sevLowerLimit)
269             {
270                 infoErrors.push_back(idNum);
271             }
272             entries.insert(std::make_pair(idNum, std::move(e)));
273             errorIds.push_back(idNum);
274         }
275     }
276 
277     if (!errorIds.empty())
278     {
279         entryId = *(std::max_element(errorIds.begin(), errorIds.end()));
280     }
281 }
282 
283 } // namespace internal
284 } // namespace logging
285 } // namepsace phosphor
286