Read up to `max_lines` final lines from a log file by walking backwards a chunk at a time. Returns lines in chronological order (oldest first). Used by `RuntimeManager::Logs` when no live session exists, so the operator can still read crash output after the VM is gone.
| 219 | // by `RuntimeManager::Logs` when no live session exists, so the operator can |
| 220 | // still read crash output after the VM is gone. |
| 221 | std::vector<std::string> TailLogFile(const fs::path& path, size_t max_lines) { |
| 222 | std::ifstream input(path, std::ios::binary); |
| 223 | if (!input) return {}; |
| 224 | input.seekg(0, std::ios::end); |
| 225 | std::streamoff size = input.tellg(); |
| 226 | if (size <= 0) return {}; |
| 227 | |
| 228 | constexpr std::streamoff kChunk = 8192; |
| 229 | std::string buffer; |
| 230 | std::streamoff pos = size; |
| 231 | size_t newlines = 0; |
| 232 | while (pos > 0 && newlines <= max_lines) { |
| 233 | const std::streamoff read_size = std::min(kChunk, pos); |
| 234 | pos -= read_size; |
| 235 | input.seekg(pos); |
| 236 | std::string chunk(static_cast<size_t>(read_size), '\0'); |
| 237 | input.read(chunk.data(), read_size); |
| 238 | buffer.insert(0, chunk); |
| 239 | newlines = std::count(buffer.begin(), buffer.end(), '\n'); |
| 240 | } |
| 241 | |
| 242 | std::vector<std::string> lines; |
| 243 | size_t start = 0; |
| 244 | for (size_t i = 0; i < buffer.size(); ++i) { |
| 245 | if (buffer[i] == '\n') { |
| 246 | lines.emplace_back(buffer.substr(start, i - start)); |
| 247 | start = i + 1; |
| 248 | } |
| 249 | } |
| 250 | if (start < buffer.size()) lines.emplace_back(buffer.substr(start)); |
| 251 | if (lines.size() > max_lines) lines.erase(lines.begin(), lines.end() - max_lines); |
| 252 | return lines; |
| 253 | } |
| 254 | |
| 255 | // Returns true if the host port can currently be bound (i.e. is free). We |
| 256 | // open a TCP socket with SO_REUSEADDR off and try to bind to the requested |