blob: fcaa4cf4823bcfe458b77958d20624aea42e58e0 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <deque>
#include <fstream>
#include <sstream>
#include <string>
#include <utility>
#include <fcntl.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "bootid-logger/bootid_logger.h"
#include "bootid-logger/constants.h"
#include "bootid-logger/timestamp_util.h"
#include <base/check_op.h>
namespace {
constexpr char kBootIdProcPath[] = "/proc/sys/kernel/random/boot_id";
// Generate an entry in the boot entry format.
std::string GenerateBootEntryString(const std::string current_boot_id,
const base::Time boot_time) {
// Boot id must be 32 hexadecimal digits.
CHECK_EQ(32u, current_boot_id.length());
// TODO(crbug.com): Change the timezone from local to UTC.
base::Time::Exploded exploded;
boot_time.UTCExplode(&exploded);
const std::string boot_time_str(base::StringPrintf(
"%04d-%02d-%02dT%02d:%02d:%02d.%03d000Z", exploded.year, exploded.month,
exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
exploded.millisecond));
CHECK_EQ(kTimestampLength, boot_time_str.size());
const std::string boot_id_entry = boot_time_str + " " + kBootEntrySeverity +
" " + kBootEntryPrefix +
base::ToLowerASCII(current_boot_id);
CHECK_EQ(kBootEntryLength, boot_id_entry.length());
return boot_id_entry;
}
// Validate the given boot entry is valid (as an entry with UTC timestap).
bool ValidateBootEntryWithUTC(const std::string& boot_id_entry) {
if (boot_id_entry.length() != kBootEntryLength)
return false;
if (boot_id_entry[kBootEntrySeverityOffset - 1] != ' ' ||
boot_id_entry[kBootEntryPrefixOffset - 1] != ' ' ||
boot_id_entry[kBootEntryBootIdOffset - 1] != ' ')
return false;
return true;
}
// Validate the given boot entry is valid (as an entry with local timestap).
bool ValidateBootEntryWithTimezone(const std::string& boot_id_entry) {
if (boot_id_entry.length() != kBootEntryLocalTimeLength)
return false;
if (boot_id_entry[kBootEntryLocalTimeSeverityOffset - 1] != ' ' ||
boot_id_entry[kBootEntryLocalTimeMessageOffset - 1] != ' ' ||
boot_id_entry[kBootEntryLocalTimeBootIdOffset - 1] != ' ')
return false;
return true;
}
// Read previous entries from the log file (FD).
base::Optional<std::deque<std::string>> ReadPreviousBootEntries(
const int fd,
const base::Time first_timestamp_to_keep,
int boot_log_max_entries) {
std::deque<std::string> previous_boot_entries;
struct stat st;
fstat(fd, &st);
const off_t length = st.st_size;
if (length > 0) {
// Here, we do mmap and stringstream to read lines.
// We can't use ifstream here because we want to use fd for keeping locking
// on the file.
char* buffer =
static_cast<char*>(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0));
if (buffer == NULL) {
PLOG(FATAL) << "mmap failed";
return base::nullopt;
}
// Set the buffer to the stream.
std::istringstream ss(std::string(buffer, length));
std::string s;
while (std::getline(ss, s)) {
// Skip an empty log.
if (s.empty())
continue;
// Skip a duplicated entry.
if (!previous_boot_entries.empty() && previous_boot_entries.back() == s)
continue;
// Skip an invalid entry.
if (!ValidateBootEntry(s))
continue;
if (!first_timestamp_to_keep.is_null()) {
base::Time time = ExtractTimestampString(s);
// Skips the entry with older timestamp than |first_timestamp_to_keep|.
if (!time.is_null() && time < first_timestamp_to_keep)
continue;
}
previous_boot_entries.push_back(s);
}
munmap(buffer, length);
// Truncate if the logs are overflown.
while (previous_boot_entries.size() > (boot_log_max_entries - 1)) {
previous_boot_entries.pop_front();
}
}
return previous_boot_entries;
}
base::Time GetCurrentBootTime() {
struct timespec boot_timespec;
if (clock_gettime(CLOCK_BOOTTIME, &boot_timespec) == -1) {
PLOG(FATAL) << "clock_gettime failed";
exit(EXIT_FAILURE);
}
return base::Time::Now() - base::TimeDelta::FromTimeSpec(boot_timespec);
}
} // anonymous namespace
// Validate the given boot entry is valid.
bool ValidateBootEntry(const std::string& boot_id_entry) {
return ValidateBootEntryWithUTC(boot_id_entry) ||
ValidateBootEntryWithTimezone(boot_id_entry);
}
// Extracts the boot ID from the givin boot ID entry.
std::string ExtractBootId(const std::string& boot_id_entry) {
if (boot_id_entry.length() == kBootEntryLength)
return boot_id_entry.substr(kBootEntryBootIdOffset, kBootIdLength);
if (boot_id_entry.length() == kBootEntryLocalTimeLength)
return boot_id_entry.substr(kBootEntryLocalTimeBootIdOffset, kBootIdLength);
return "";
}
std::string GetCurrentBootId() {
std::string boot_id;
if (!base::ReadFileToString(base::FilePath(kBootIdProcPath), &boot_id)) {
LOG(FATAL) << "Reading the log file failed";
exit(EXIT_FAILURE);
}
base::RemoveChars(boot_id, "-\r\n", &boot_id);
CHECK_EQ(kBootIdLength, boot_id.length());
return boot_id;
}
bool WriteCurrentBootEntry(const base::FilePath& bootid_log_path,
const base::Time first_timestamp_to_keep,
const int max_entries) {
std::string boot_id = GetCurrentBootId();
base::Time boot_time = GetCurrentBootTime();
return WriteBootEntry(bootid_log_path, boot_id, boot_time,
first_timestamp_to_keep, max_entries);
}
bool WriteBootEntry(const base::FilePath& bootid_log_path,
const std::string& current_boot_id,
const base::Time boot_time,
const base::Time first_timestamp_to_keep,
const int max_entries) {
// Open the log file.
base::ScopedFD fd(HANDLE_EINTR(
open(bootid_log_path.value().c_str(), O_RDWR | O_CREAT | O_CLOEXEC,
S_IRUSR | S_IWUSR | S_IROTH | S_IRGRP /* 0644 */)));
if (fd.get() == -1) {
PLOG(FATAL) << "open failed";
return false;
}
if (HANDLE_EINTR(flock(fd.get(), LOCK_EX)) == -1) {
PLOG(FATAL) << "flock failed";
return false;
}
auto ret =
ReadPreviousBootEntries(fd.get(), first_timestamp_to_keep, max_entries);
if (!ret.has_value()) {
LOG(FATAL) << "Reading the log file failed";
return false;
}
std::deque<std::string> previous_boot_entries = std::move(*ret);
if (!previous_boot_entries.empty() &&
ExtractBootId(previous_boot_entries.back()) == current_boot_id) {
LOG(INFO) << "The current Boot ID does already exists in the log. New "
"entry is not added to prevent duplication.";
// Returning true, since it is not an issue.
return true;
}
const std::string boot_entry_str =
GenerateBootEntryString(current_boot_id, boot_time);
previous_boot_entries.push_back(boot_entry_str);
// Update the current pos to the beginning of the file.
if (lseek(fd.get(), 0, SEEK_SET) != 0) {
PLOG(FATAL) << "lseek failed";
return false;
}
// Shrink the file to zero.
if (HANDLE_EINTR(ftruncate(fd.get(), 0)) != 0) {
PLOG(FATAL) << "ftruncate failed";
return false;
}
// Rewrite the existing entries.
for (std::string boot_entry : previous_boot_entries) {
boot_entry.append(1, '\n');
if (!base::WriteFileDescriptor(fd.get(), boot_entry.c_str(),
boot_entry.size())) {
PLOG(FATAL) << "Writing to the file failed";
return false;
}
}
// Automatically the file is closed and unlocked at the end of process.
return true;
}