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