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