xref: /openbmc/phosphor-logging/util.cpp (revision dfe021c0cb08630116f4778a01a59da05d8bf4ec)
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 #include "config.h"
17 
18 #include "util.hpp"
19 
20 #include <poll.h>
21 #include <sys/inotify.h>
22 #include <systemd/sd-bus.h>
23 #include <systemd/sd-journal.h>
24 #include <unistd.h>
25 
26 #include <phosphor-logging/lg2.hpp>
27 #include <sdbusplus/bus.hpp>
28 
29 #include <chrono>
30 #include <fstream>
31 
32 namespace phosphor::logging::util
33 {
34 
getOSReleaseValue(const std::string & key)35 std::optional<std::string> getOSReleaseValue(const std::string& key)
36 {
37     std::ifstream versionFile{BMC_VERSION_FILE};
38     std::string line;
39     std::string keyPattern{key + '='};
40 
41     while (std::getline(versionFile, line))
42     {
43         if (line.substr(0, keyPattern.size()).find(keyPattern) !=
44             std::string::npos)
45         {
46             // If the value isn't surrounded by quotes, then pos will be
47             // npos + 1 = 0, and the 2nd arg to substr() will be npos
48             // which means get the rest of the string.
49             auto value = line.substr(keyPattern.size());
50             std::size_t pos = value.find_first_of('"') + 1;
51             return value.substr(pos, value.find_last_of('"') - pos);
52         }
53     }
54 
55     return std::nullopt;
56 }
57 
journalSync()58 void journalSync()
59 {
60     bool syncRequested = false;
61     auto fd = -1;
62     auto rc = -1;
63     auto wd = -1;
64     auto bus = sdbusplus::bus::new_default();
65 
66     auto start = std::chrono::duration_cast<std::chrono::microseconds>(
67                      std::chrono::steady_clock::now().time_since_epoch())
68                      .count();
69 
70     // Make a request to sync the journal with the SIGRTMIN+1 signal and
71     // block until it finishes, waiting at most 5 seconds.
72     //
73     // Number of loop iterations = 3 for the following reasons:
74     // Iteration #1: Requests a journal sync by killing the journald service.
75     // Iteration #2: Setup an inotify watch to monitor the synced file that
76     //               journald updates with the timestamp the last time the
77     //               journal was flushed.
78     // Iteration #3: Poll to wait until inotify reports an event which blocks
79     //               the error log from being commited until the sync completes.
80     constexpr auto maxRetry = 3;
81     for (int i = 0; i < maxRetry; i++)
82     {
83         // Read timestamp from synced file
84         constexpr auto syncedPath = "/run/systemd/journal/synced";
85         std::ifstream syncedFile(syncedPath);
86         if (syncedFile.fail())
87         {
88             // If the synced file doesn't exist, a sync request will create it.
89             if (errno != ENOENT)
90             {
91                 lg2::error(
92                     "Failed to open journal synced file {FILENAME}: {ERROR}",
93                     "FILENAME", syncedPath, "ERROR", strerror(errno));
94                 return;
95             }
96         }
97         else
98         {
99             // Only read the synced file if it exists.
100             // See if a sync happened by now
101             std::string timestampStr;
102             std::getline(syncedFile, timestampStr);
103             auto timestamp = std::stoll(timestampStr);
104             if (timestamp >= start)
105             {
106                 break;
107             }
108         }
109 
110         // Let's ask for a sync, but only once
111         if (!syncRequested)
112         {
113             syncRequested = true;
114 
115             constexpr auto JOURNAL_UNIT = "systemd-journald.service";
116             auto signal = SIGRTMIN + 1;
117 
118             auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
119                                               SYSTEMD_INTERFACE, "KillUnit");
120             method.append(JOURNAL_UNIT, "main", signal);
121             bus.call(method);
122             if (method.is_method_error())
123             {
124                 lg2::error("Failed to kill journal service");
125                 break;
126             }
127 
128             continue;
129         }
130 
131         // Let's install the inotify watch, if we didn't do that yet. This watch
132         // monitors the syncedFile for when journald updates it with a newer
133         // timestamp. This means the journal has been flushed.
134         if (fd < 0)
135         {
136             fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
137             if (fd < 0)
138             {
139                 lg2::error("Failed to create inotify watch: {ERROR}", "ERROR",
140                            strerror(errno));
141                 return;
142             }
143 
144             constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal";
145             wd = inotify_add_watch(fd, JOURNAL_RUN_PATH,
146                                    IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR);
147             if (wd < 0)
148             {
149                 lg2::error("Failed to watch journal directory: {PATH}: {ERROR}",
150                            "PATH", JOURNAL_RUN_PATH, "ERROR", strerror(errno));
151                 close(fd);
152                 return;
153             }
154             continue;
155         }
156 
157         // Let's wait until inotify reports an event
158         struct pollfd fds = {
159             fd,
160             POLLIN,
161             0,
162         };
163         constexpr auto pollTimeout = 5; // 5 seconds
164         rc = poll(&fds, 1, pollTimeout * 1000);
165         if (rc < 0)
166         {
167             lg2::error("Failed to add event: {ERROR}", "ERROR",
168                        strerror(errno));
169             inotify_rm_watch(fd, wd);
170             close(fd);
171             return;
172         }
173         else if (rc == 0)
174         {
175             lg2::info("Poll timeout ({TIMEOUT}), no new journal synced data",
176                       "TIMEOUT", pollTimeout);
177             break;
178         }
179 
180         // Read from the specified file descriptor until there is no new data,
181         // throwing away everything read since the timestamp will be read at the
182         // beginning of the loop.
183         constexpr auto maxBytes = 64;
184         uint8_t buffer[maxBytes];
185         while (read(fd, buffer, maxBytes) > 0)
186             ;
187     }
188 
189     if (fd != -1)
190     {
191         if (wd != -1)
192         {
193             inotify_rm_watch(fd, wd);
194         }
195         close(fd);
196     }
197 
198     return;
199 }
200 
201 namespace additional_data
202 {
parse(const std::vector<std::string> & data)203 auto parse(const std::vector<std::string>& data)
204     -> std::map<std::string, std::string>
205 {
206     std::map<std::string, std::string> metadata{};
207 
208     constexpr auto separator = '=';
209     for (const auto& entryItem : data)
210     {
211         auto pos = entryItem.find(separator);
212         if (std::string::npos != pos)
213         {
214             auto key = entryItem.substr(0, entryItem.find(separator));
215             auto value = entryItem.substr(entryItem.find(separator) + 1);
216             metadata.emplace(std::move(key), std::move(value));
217         }
218     }
219 
220     return metadata;
221 }
222 
combine(const std::map<std::string,std::string> & data)223 auto combine(const std::map<std::string, std::string>& data)
224     -> std::vector<std::string>
225 {
226     std::vector<std::string> metadata{};
227 
228     for (const auto& [key, value] : data)
229     {
230         std::string line{key};
231         line += "=" + value;
232         metadata.emplace_back(std::move(line));
233     }
234 
235     return metadata;
236 }
237 } // namespace additional_data
238 
239 } // namespace phosphor::logging::util
240