blob: 492e9547a8a04c12824919289ef55cbb0178c493 [file] [log] [blame]
Alexander Hansen40fb5492025-10-28 17:56:12 +01001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2020 IBM Corporation
3
Matt Spinlerf61f2922020-06-23 11:32:49 -05004#include "config.h"
5
6#include "util.hpp"
7
Matt Spinler271d1432023-01-18 13:58:05 -06008#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>
Matt Spinlerdfe021c2025-05-09 14:06:50 -050018#include <fstream>
Matt Spinler271d1432023-01-18 13:58:05 -060019
Matt Spinlerf61f2922020-06-23 11:32:49 -050020namespace phosphor::logging::util
21{
22
23std::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
Matt Spinler271d1432023-01-18 13:58:05 -060046void 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 bus.call(method);
110 if (method.is_method_error())
111 {
112 lg2::error("Failed to kill journal service");
113 break;
114 }
115
116 continue;
117 }
118
119 // Let's install the inotify watch, if we didn't do that yet. This watch
120 // monitors the syncedFile for when journald updates it with a newer
121 // timestamp. This means the journal has been flushed.
122 if (fd < 0)
123 {
124 fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
125 if (fd < 0)
126 {
127 lg2::error("Failed to create inotify watch: {ERROR}", "ERROR",
128 strerror(errno));
129 return;
130 }
131
132 constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal";
133 wd = inotify_add_watch(fd, JOURNAL_RUN_PATH,
134 IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR);
135 if (wd < 0)
136 {
137 lg2::error("Failed to watch journal directory: {PATH}: {ERROR}",
138 "PATH", JOURNAL_RUN_PATH, "ERROR", strerror(errno));
139 close(fd);
140 return;
141 }
142 continue;
143 }
144
145 // Let's wait until inotify reports an event
146 struct pollfd fds = {
147 fd,
148 POLLIN,
149 0,
150 };
151 constexpr auto pollTimeout = 5; // 5 seconds
152 rc = poll(&fds, 1, pollTimeout * 1000);
153 if (rc < 0)
154 {
155 lg2::error("Failed to add event: {ERROR}", "ERROR",
156 strerror(errno));
157 inotify_rm_watch(fd, wd);
158 close(fd);
159 return;
160 }
161 else if (rc == 0)
162 {
163 lg2::info("Poll timeout ({TIMEOUT}), no new journal synced data",
164 "TIMEOUT", pollTimeout);
165 break;
166 }
167
168 // Read from the specified file descriptor until there is no new data,
169 // throwing away everything read since the timestamp will be read at the
170 // beginning of the loop.
171 constexpr auto maxBytes = 64;
172 uint8_t buffer[maxBytes];
173 while (read(fd, buffer, maxBytes) > 0)
174 ;
175 }
176
177 if (fd != -1)
178 {
179 if (wd != -1)
180 {
181 inotify_rm_watch(fd, wd);
182 }
183 close(fd);
184 }
185
186 return;
187}
188
Patrick Williamsa06b4c62024-11-21 11:43:39 -0500189namespace additional_data
190{
191auto parse(const std::vector<std::string>& data)
192 -> std::map<std::string, std::string>
193{
194 std::map<std::string, std::string> metadata{};
195
196 constexpr auto separator = '=';
197 for (const auto& entryItem : data)
198 {
199 auto pos = entryItem.find(separator);
200 if (std::string::npos != pos)
201 {
202 auto key = entryItem.substr(0, entryItem.find(separator));
203 auto value = entryItem.substr(entryItem.find(separator) + 1);
204 metadata.emplace(std::move(key), std::move(value));
205 }
206 }
207
208 return metadata;
209}
210
211auto combine(const std::map<std::string, std::string>& data)
212 -> std::vector<std::string>
213{
214 std::vector<std::string> metadata{};
215
216 for (const auto& [key, value] : data)
217 {
218 std::string line{key};
219 line += "=" + value;
220 metadata.emplace_back(std::move(line));
221 }
222
223 return metadata;
224}
225} // namespace additional_data
226
Matt Spinlerf61f2922020-06-23 11:32:49 -0500227} // namespace phosphor::logging::util