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 try 110 { 111 bus.call(method); 112 } 113 catch (sdbusplus::exception_t&) 114 { 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 192 namespace additional_data 193 { 194 auto 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 214 auto 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 230 } // namespace phosphor::logging::util 231