1 /**
2  * Copyright © 2023 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 "journal.hpp"
17 
18 #include "util.hpp"
19 
20 #include <phosphor-logging/lg2.hpp>
21 
22 #include <format>
23 
24 namespace openpower::pels
25 {
26 
27 using namespace phosphor::logging;
28 
29 /**
30  * @class JournalCloser
31  *
32  * Closes the journal on destruction
33  */
34 class JournalCloser
35 {
36   public:
37     JournalCloser() = delete;
38     JournalCloser(const JournalCloser&) = delete;
39     JournalCloser(JournalCloser&&) = delete;
40     JournalCloser& operator=(const JournalCloser&) = delete;
41     JournalCloser& operator=(JournalCloser&&) = delete;
42 
43     explicit JournalCloser(sd_journal* journal) : journal{journal} {}
44 
45     ~JournalCloser()
46     {
47         sd_journal_close(journal);
48     }
49 
50   private:
51     sd_journal* journal{nullptr};
52 };
53 
54 void Journal::sync() const
55 {
56     auto start = std::chrono::steady_clock::now();
57 
58     util::journalSync();
59 
60     auto end = std::chrono::steady_clock::now();
61     auto duration =
62         std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
63 
64     if (duration.count() > 100)
65     {
66         lg2::info("Journal sync took {DURATION}ms", "DURATION",
67                   duration.count());
68     }
69 }
70 
71 std::vector<std::string>
72     Journal::getMessages(const std::string& syslogID, size_t maxMessages) const
73 {
74     // The message registry JSON schema will also fail if a zero is in the JSON
75     if (0 == maxMessages)
76     {
77         lg2::error(
78             "maxMessages value of zero passed into Journal::getMessages");
79         return std::vector<std::string>{};
80     }
81 
82     sd_journal* journal;
83     int rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY);
84     if (rc < 0)
85     {
86         throw std::runtime_error{
87             std::string{"Failed to open journal: "} + strerror(-rc)};
88     }
89 
90     JournalCloser closer{journal};
91 
92     if (!syslogID.empty())
93     {
94         std::string match{"SYSLOG_IDENTIFIER=" + syslogID};
95 
96         rc = sd_journal_add_match(journal, match.c_str(), 0);
97         if (rc < 0)
98         {
99             throw std::runtime_error{
100                 std::string{"Failed to add journal match: "} + strerror(-rc)};
101         }
102     }
103 
104     // Loop through matching entries from newest to oldest
105     std::vector<std::string> messages;
106     messages.reserve(maxMessages);
107     std::string sID, pid, message, timeStamp, line;
108 
109     SD_JOURNAL_FOREACH_BACKWARDS(journal)
110     {
111         timeStamp = getTimeStamp(journal);
112         sID = getFieldValue(journal, "SYSLOG_IDENTIFIER");
113         pid = getFieldValue(journal, "_PID");
114         message = getFieldValue(journal, "MESSAGE");
115 
116         line = timeStamp + " " + sID + "[" + pid + "]: " + message;
117         messages.emplace(messages.begin(), line);
118 
119         if (messages.size() >= maxMessages)
120         {
121             break;
122         }
123     }
124 
125     return messages;
126 }
127 
128 std::string Journal::getFieldValue(sd_journal* journal,
129                                    const std::string& field) const
130 {
131     std::string value{};
132 
133     const void* data{nullptr};
134     size_t length{0};
135     int rc = sd_journal_get_data(journal, field.c_str(), &data, &length);
136     if (rc < 0)
137     {
138         if (-rc == ENOENT)
139         {
140             // Current entry does not include this field; return empty value
141             return value;
142         }
143         else
144         {
145             throw std::runtime_error{
146                 std::string{"Failed to read journal entry field: "} +
147                 strerror(-rc)};
148         }
149     }
150 
151     // Get value from field data.  Field data in format "FIELD=value".
152     std::string dataString{static_cast<const char*>(data), length};
153     std::string::size_type pos = dataString.find('=');
154     if ((pos != std::string::npos) && ((pos + 1) < dataString.size()))
155     {
156         // Value is substring after the '='
157         value = dataString.substr(pos + 1);
158     }
159 
160     return value;
161 }
162 
163 std::string Journal::getTimeStamp(sd_journal* journal) const
164 {
165     // Get realtime (wallclock) timestamp of current journal entry.  The
166     // timestamp is in microseconds since the epoch.
167     uint64_t usec{0};
168     int rc = sd_journal_get_realtime_usec(journal, &usec);
169     if (rc < 0)
170     {
171         throw std::runtime_error{
172             std::string{"Failed to get journal entry timestamp: "} +
173             strerror(-rc)};
174     }
175 
176     // Convert to number of seconds since the epoch
177     time_t secs = usec / 1000000;
178 
179     // Convert seconds to tm struct required by strftime()
180     struct tm* timeStruct = gmtime(&secs);
181     if (timeStruct == nullptr)
182     {
183         throw std::runtime_error{
184             std::string{"Invalid journal entry timestamp: "} + strerror(errno)};
185     }
186 
187     // Convert tm struct into a date/time string
188     char timeStamp[80];
189     strftime(timeStamp, sizeof(timeStamp), "%b %d %H:%M:%S", timeStruct);
190 
191     return timeStamp;
192 }
193 
194 } // namespace openpower::pels
195