| /** |
| * Copyright © 2020 IBM Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "config.h" |
| |
| #include "util.hpp" |
| |
| #include <poll.h> |
| #include <sys/inotify.h> |
| #include <systemd/sd-bus.h> |
| #include <systemd/sd-journal.h> |
| #include <unistd.h> |
| |
| #include <phosphor-logging/lg2.hpp> |
| #include <sdbusplus/bus.hpp> |
| |
| #include <chrono> |
| |
| namespace phosphor::logging::util |
| { |
| |
| std::optional<std::string> getOSReleaseValue(const std::string& key) |
| { |
| std::ifstream versionFile{BMC_VERSION_FILE}; |
| std::string line; |
| std::string keyPattern{key + '='}; |
| |
| while (std::getline(versionFile, line)) |
| { |
| if (line.substr(0, keyPattern.size()).find(keyPattern) != |
| std::string::npos) |
| { |
| // If the value isn't surrounded by quotes, then pos will be |
| // npos + 1 = 0, and the 2nd arg to substr() will be npos |
| // which means get the rest of the string. |
| auto value = line.substr(keyPattern.size()); |
| std::size_t pos = value.find_first_of('"') + 1; |
| return value.substr(pos, value.find_last_of('"') - pos); |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| void journalSync() |
| { |
| bool syncRequested = false; |
| auto fd = -1; |
| auto rc = -1; |
| auto wd = -1; |
| auto bus = sdbusplus::bus::new_default(); |
| |
| auto start = std::chrono::duration_cast<std::chrono::microseconds>( |
| std::chrono::steady_clock::now().time_since_epoch()) |
| .count(); |
| |
| // Make a request to sync the journal with the SIGRTMIN+1 signal and |
| // block until it finishes, waiting at most 5 seconds. |
| // |
| // Number of loop iterations = 3 for the following reasons: |
| // Iteration #1: Requests a journal sync by killing the journald service. |
| // Iteration #2: Setup an inotify watch to monitor the synced file that |
| // journald updates with the timestamp the last time the |
| // journal was flushed. |
| // Iteration #3: Poll to wait until inotify reports an event which blocks |
| // the error log from being commited until the sync completes. |
| constexpr auto maxRetry = 3; |
| for (int i = 0; i < maxRetry; i++) |
| { |
| // Read timestamp from synced file |
| constexpr auto syncedPath = "/run/systemd/journal/synced"; |
| std::ifstream syncedFile(syncedPath); |
| if (syncedFile.fail()) |
| { |
| // If the synced file doesn't exist, a sync request will create it. |
| if (errno != ENOENT) |
| { |
| lg2::error( |
| "Failed to open journal synced file {FILENAME}: {ERROR}", |
| "FILENAME", syncedPath, "ERROR", strerror(errno)); |
| return; |
| } |
| } |
| else |
| { |
| // Only read the synced file if it exists. |
| // See if a sync happened by now |
| std::string timestampStr; |
| std::getline(syncedFile, timestampStr); |
| auto timestamp = std::stoll(timestampStr); |
| if (timestamp >= start) |
| { |
| break; |
| } |
| } |
| |
| // Let's ask for a sync, but only once |
| if (!syncRequested) |
| { |
| syncRequested = true; |
| |
| constexpr auto JOURNAL_UNIT = "systemd-journald.service"; |
| auto signal = SIGRTMIN + 1; |
| |
| auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, |
| SYSTEMD_INTERFACE, "KillUnit"); |
| method.append(JOURNAL_UNIT, "main", signal); |
| bus.call(method); |
| if (method.is_method_error()) |
| { |
| lg2::error("Failed to kill journal service"); |
| break; |
| } |
| |
| continue; |
| } |
| |
| // Let's install the inotify watch, if we didn't do that yet. This watch |
| // monitors the syncedFile for when journald updates it with a newer |
| // timestamp. This means the journal has been flushed. |
| if (fd < 0) |
| { |
| fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); |
| if (fd < 0) |
| { |
| lg2::error("Failed to create inotify watch: {ERROR}", "ERROR", |
| strerror(errno)); |
| return; |
| } |
| |
| constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal"; |
| wd = inotify_add_watch(fd, JOURNAL_RUN_PATH, |
| IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR); |
| if (wd < 0) |
| { |
| lg2::error("Failed to watch journal directory: {PATH}: {ERROR}", |
| "PATH", JOURNAL_RUN_PATH, "ERROR", strerror(errno)); |
| close(fd); |
| return; |
| } |
| continue; |
| } |
| |
| // Let's wait until inotify reports an event |
| struct pollfd fds = { |
| fd, |
| POLLIN, |
| 0, |
| }; |
| constexpr auto pollTimeout = 5; // 5 seconds |
| rc = poll(&fds, 1, pollTimeout * 1000); |
| if (rc < 0) |
| { |
| lg2::error("Failed to add event: {ERROR}", "ERROR", |
| strerror(errno)); |
| inotify_rm_watch(fd, wd); |
| close(fd); |
| return; |
| } |
| else if (rc == 0) |
| { |
| lg2::info("Poll timeout ({TIMEOUT}), no new journal synced data", |
| "TIMEOUT", pollTimeout); |
| break; |
| } |
| |
| // Read from the specified file descriptor until there is no new data, |
| // throwing away everything read since the timestamp will be read at the |
| // beginning of the loop. |
| constexpr auto maxBytes = 64; |
| uint8_t buffer[maxBytes]; |
| while (read(fd, buffer, maxBytes) > 0) |
| ; |
| } |
| |
| if (fd != -1) |
| { |
| if (wd != -1) |
| { |
| inotify_rm_watch(fd, wd); |
| } |
| close(fd); |
| } |
| |
| return; |
| } |
| |
| } // namespace phosphor::logging::util |