1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright 2020 IBM Corporation 3 4 #include "config.h" 5 6 #include "util.hpp" 7 8 #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> 18 #include <fstream> 19 20 namespace phosphor::logging::util 21 { 22 23 std::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 46 void 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); 109 bus.call(method); 110 if (method.is_method_error()) 111 { 112 lg2::error("Failed to kill journal service"); 113 break; 114 } 115 116 continue; 117 } 118 119 // Let's install the inotify watch, if we didn't do that yet. This watch 120 // monitors the syncedFile for when journald updates it with a newer 121 // timestamp. This means the journal has been flushed. 122 if (fd < 0) 123 { 124 fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 125 if (fd < 0) 126 { 127 lg2::error("Failed to create inotify watch: {ERROR}", "ERROR", 128 strerror(errno)); 129 return; 130 } 131 132 constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal"; 133 wd = inotify_add_watch(fd, JOURNAL_RUN_PATH, 134 IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR); 135 if (wd < 0) 136 { 137 lg2::error("Failed to watch journal directory: {PATH}: {ERROR}", 138 "PATH", JOURNAL_RUN_PATH, "ERROR", strerror(errno)); 139 close(fd); 140 return; 141 } 142 continue; 143 } 144 145 // Let's wait until inotify reports an event 146 struct pollfd fds = { 147 fd, 148 POLLIN, 149 0, 150 }; 151 constexpr auto pollTimeout = 5; // 5 seconds 152 rc = poll(&fds, 1, pollTimeout * 1000); 153 if (rc < 0) 154 { 155 lg2::error("Failed to add event: {ERROR}", "ERROR", 156 strerror(errno)); 157 inotify_rm_watch(fd, wd); 158 close(fd); 159 return; 160 } 161 else if (rc == 0) 162 { 163 lg2::info("Poll timeout ({TIMEOUT}), no new journal synced data", 164 "TIMEOUT", pollTimeout); 165 break; 166 } 167 168 // Read from the specified file descriptor until there is no new data, 169 // throwing away everything read since the timestamp will be read at the 170 // beginning of the loop. 171 constexpr auto maxBytes = 64; 172 uint8_t buffer[maxBytes]; 173 while (read(fd, buffer, maxBytes) > 0) 174 ; 175 } 176 177 if (fd != -1) 178 { 179 if (wd != -1) 180 { 181 inotify_rm_watch(fd, wd); 182 } 183 close(fd); 184 } 185 186 return; 187 } 188 189 namespace additional_data 190 { 191 auto parse(const std::vector<std::string>& data) 192 -> std::map<std::string, std::string> 193 { 194 std::map<std::string, std::string> metadata{}; 195 196 constexpr auto separator = '='; 197 for (const auto& entryItem : data) 198 { 199 auto pos = entryItem.find(separator); 200 if (std::string::npos != pos) 201 { 202 auto key = entryItem.substr(0, entryItem.find(separator)); 203 auto value = entryItem.substr(entryItem.find(separator) + 1); 204 metadata.emplace(std::move(key), std::move(value)); 205 } 206 } 207 208 return metadata; 209 } 210 211 auto combine(const std::map<std::string, std::string>& data) 212 -> std::vector<std::string> 213 { 214 std::vector<std::string> metadata{}; 215 216 for (const auto& [key, value] : data) 217 { 218 std::string line{key}; 219 line += "=" + value; 220 metadata.emplace_back(std::move(line)); 221 } 222 223 return metadata; 224 } 225 } // namespace additional_data 226 227 } // namespace phosphor::logging::util 228