blob: db104bfaa5f19f11aeff3ede1567b29a8e7b7cf2 [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);
Patrick Williams34c0c252025-11-04 22:30:46 -0500109 try
110 {
111 bus.call(method);
112 }
113 catch (sdbusplus::exception_t&)
Matt Spinler271d1432023-01-18 13:58:05 -0600114 {
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
Patrick Williamsa06b4c62024-11-21 11:43:39 -0500192namespace additional_data
193{
194auto 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
214auto 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
Matt Spinlerf61f2922020-06-23 11:32:49 -0500230} // namespace phosphor::logging::util