1 /**
2  * Copyright © 2020 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 
17 #include "journal.hpp"
18 
19 #include <errno.h>
20 #include <stdint.h>
21 #include <string.h>
22 #include <time.h>
23 
24 #include <chrono>
25 #include <stdexcept>
26 #include <thread>
27 
28 namespace phosphor::power::regulators
29 {
30 
31 /**
32  * @class JournalCloser
33  *
34  * Automatically closes the journal when the object goes out of scope.
35  */
36 class JournalCloser
37 {
38   public:
39     // Specify which compiler-generated methods we want
40     JournalCloser() = delete;
41     JournalCloser(const JournalCloser&) = delete;
42     JournalCloser(JournalCloser&&) = delete;
43     JournalCloser& operator=(const JournalCloser&) = delete;
44     JournalCloser& operator=(JournalCloser&&) = delete;
45 
46     JournalCloser(sd_journal* journal) : journal{journal}
47     {
48     }
49 
50     ~JournalCloser()
51     {
52         sd_journal_close(journal);
53     }
54 
55   private:
56     sd_journal* journal{nullptr};
57 };
58 
59 std::vector<std::string>
60     SystemdJournal::getMessages(const std::string& field,
61                                 const std::string& fieldValue, unsigned int max)
62 {
63     // Sleep 100ms; otherwise recent journal entries sometimes not available
64     using namespace std::chrono_literals;
65     std::this_thread::sleep_for(100ms);
66 
67     // Open the journal
68     sd_journal* journal;
69     int rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY);
70     if (rc < 0)
71     {
72         throw std::runtime_error{std::string{"Failed to open journal: "} +
73                                  strerror(-rc)};
74     }
75 
76     // Create object to automatically close journal
77     JournalCloser closer{journal};
78 
79     // Add match so we only loop over entries with specified field value
80     std::string match{field + '=' + fieldValue};
81     rc = sd_journal_add_match(journal, match.c_str(), 0);
82     if (rc < 0)
83     {
84         throw std::runtime_error{std::string{"Failed to add journal match: "} +
85                                  strerror(-rc)};
86     }
87 
88     // Loop through matching entries from newest to oldest
89     std::vector<std::string> messages;
90     messages.reserve((max != 0) ? max : 10);
91     std::string syslogID, pid, message, timeStamp, line;
92     SD_JOURNAL_FOREACH_BACKWARDS(journal)
93     {
94         // Get relevant journal entry fields
95         timeStamp = getTimeStamp(journal);
96         syslogID = getFieldValue(journal, "SYSLOG_IDENTIFIER");
97         pid = getFieldValue(journal, "_PID");
98         message = getFieldValue(journal, "MESSAGE");
99 
100         // Build one line string containing field values
101         line = timeStamp + " " + syslogID + "[" + pid + "]: " + message;
102         messages.emplace(messages.begin(), line);
103 
104         // Stop looping if a max was specified and we have reached it
105         if ((max != 0) && (messages.size() >= max))
106         {
107             break;
108         }
109     }
110 
111     return messages;
112 }
113 
114 std::string SystemdJournal::getFieldValue(sd_journal* journal,
115                                           const std::string& field)
116 {
117     std::string value{};
118 
119     // Get field data from current journal entry
120     const void* data{nullptr};
121     size_t length{0};
122     int rc = sd_journal_get_data(journal, field.c_str(), &data, &length);
123     if (rc < 0)
124     {
125         if (-rc == ENOENT)
126         {
127             // Current entry does not include this field; return empty value
128             return value;
129         }
130         else
131         {
132             throw std::runtime_error{
133                 std::string{"Failed to read journal entry field: "} +
134                 strerror(-rc)};
135         }
136     }
137 
138     // Get value from field data.  Field data in format "FIELD=value".
139     std::string dataString{static_cast<const char*>(data), length};
140     std::string::size_type pos = dataString.find('=');
141     if ((pos != std::string::npos) && ((pos + 1) < dataString.size()))
142     {
143         // Value is substring after the '='
144         value = dataString.substr(pos + 1);
145     }
146 
147     return value;
148 }
149 
150 std::string SystemdJournal::getTimeStamp(sd_journal* journal)
151 {
152     // Get realtime (wallclock) timestamp of current journal entry.  The
153     // timestamp is in microseconds since the epoch.
154     uint64_t usec{0};
155     int rc = sd_journal_get_realtime_usec(journal, &usec);
156     if (rc < 0)
157     {
158         throw std::runtime_error{
159             std::string{"Failed to get journal entry timestamp: "} +
160             strerror(-rc)};
161     }
162 
163     // Convert to number of seconds since the epoch
164     time_t secs = usec / 1000000;
165 
166     // Convert seconds to tm struct required by strftime()
167     struct tm* timeStruct = localtime(&secs);
168     if (timeStruct == nullptr)
169     {
170         throw std::runtime_error{
171             std::string{"Invalid journal entry timestamp: "} + strerror(errno)};
172     }
173 
174     // Convert tm struct into a date/time string
175     char timeStamp[80];
176     strftime(timeStamp, sizeof(timeStamp), "%b %d %H:%M:%S", timeStruct);
177 
178     return timeStamp;
179 }
180 
181 } // namespace phosphor::power::regulators
182