| /** | 
 |  * 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 |