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