blob: 7ef2d447afce78dd406a6a2fe6148deaed56d279 [file] [log] [blame]
Matt Spinlerf61f2922020-06-23 11:32:49 -05001/**
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
Matt Spinler271d1432023-01-18 13:58:05 -060020#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>
Matt Spinlerdfe021c2025-05-09 14:06:50 -050030#include <fstream>
Matt Spinler271d1432023-01-18 13:58:05 -060031
Matt Spinlerf61f2922020-06-23 11:32:49 -050032namespace phosphor::logging::util
33{
34
35std::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
Matt Spinler271d1432023-01-18 13:58:05 -060058void 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
Patrick Williamsa06b4c62024-11-21 11:43:39 -0500201namespace additional_data
202{
203auto 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
223auto 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
Matt Spinlerf61f2922020-06-23 11:32:49 -0500239} // namespace phosphor::logging::util