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