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