blob: 4df3d1786df559f5a8da794e8a5065921a1748e2 [file] [log] [blame] [edit]
// Copyright 2015 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <fcntl.h>
#include <sstream>
#include <string>
#include <vector>
#include <sysexits.h>
#include <sys/types.h>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/functional/callback_helpers.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/time/time.h>
#include <brillo/daemons/daemon.h>
#include <libec/ec_command.h>
#include <libec/get_features_command.h>
#include "timberslide/log_listener_factory.h"
#include "timberslide/timberslide.h"
namespace timberslide {
namespace {
const char kCurrentLogExt[] = ".log";
const char kPreviousLogExt[] = ".previous";
const int kMaxCurrentLogSize = 10 * 1024 * 1024;
// This line fails to compile with a static_assert if the database is invalid.
constexpr pw::tokenizer::TokenDatabase kDefaultDatabase =
pw::tokenizer::TokenDatabase();
// String proxy class for dealing with lines.
class LineExtractor {
public:
// Extract one line from beginning of string stream.
friend std::istream& operator>>(std::istream& is, LineExtractor& l) {
std::getline(is, l.str_);
return is;
}
// Transparently convert to std::string.
operator std::string() const { return str_; }
private:
std::string str_;
};
} // namespace
TimberSlide::TimberSlide(const std::string& ec_type,
base::File device_file,
base::File uptime_file,
const base::FilePath& log_dir,
const base::FilePath& token_db)
: device_file_(std::move(device_file)),
tokens_db_(token_db),
detokenizer_(kDefaultDatabase),
total_size_(0),
uptime_file_(std::move(uptime_file)),
uptime_file_valid_(uptime_file_.IsValid()) {
current_log_ = log_dir.Append(ec_type + kCurrentLogExt);
previous_log_ = log_dir.Append(ec_type + kPreviousLogExt);
log_listener_ = LogListenerFactory::Create(ec_type);
xfrm_ = std::make_unique<StringTransformer>();
}
TimberSlide::TimberSlide(std::unique_ptr<LogListener> log_listener,
std::unique_ptr<StringTransformer> xfrm)
: detokenizer_(kDefaultDatabase),
log_listener_(std::move(log_listener)),
xfrm_(std::move(xfrm)) {}
int TimberSlide::OnInit() {
LOG(INFO) << "Starting timberslide daemon";
int ret = brillo::Daemon::OnInit();
if (ret != EX_OK)
return ret;
int64_t ec_uptime_ms;
if (uptime_file_valid_) {
LOG(INFO) << "EC uptime file is valid";
if (GetEcUptime(&ec_uptime_ms))
xfrm_->UpdateTimestamps(ec_uptime_ms, base::Time::Now());
} else {
LOG(WARNING) << "EC uptime file is not valid; ignoring";
}
RotateLogs(previous_log_, current_log_);
auto cros_fd = base::ScopedFD(open(ec::kCrosEcPath, O_RDWR | O_CLOEXEC));
ec::GetFeaturesCommand getFeaturesCmd;
if (getFeaturesCmd.Run(cros_fd.get())) {
tokenized_logging_ = getFeaturesCmd.IsFeatureSupported(
ec_feature_code::EC_FEATURE_TOKENIZED_LOGGING);
}
if (tokenized_logging_) {
LOG(INFO) << "EC logging: tokenized";
if (base::PathExists(tokens_db_)) {
token_watcher_ = std::make_unique<base::FilePathWatcher>();
token_watcher_->Watch(
tokens_db_, base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(&TimberSlide::OnEventTokenChange,
base::Unretained(this)));
detokenizer_ = OpenDatabase(tokens_db_);
} else {
LOG(ERROR) << "EC token database not found";
return EX_OSERR;
}
} else {
LOG(INFO) << "EC logging: raw text";
}
watcher_ = base::FileDescriptorWatcher::WatchReadable(
device_file_.GetPlatformFile(),
base::BindRepeating(&TimberSlide::OnEventReadable,
base::Unretained(this)));
return watcher_ ? EX_OK : EX_OSERR;
}
//
// From kernel's Documentation/filesystems/sysfs.txt: If userspace seeks back
// to zero or does a pread(2) with an offset of '0' the show() method will
// be called again, rearmed, to fill the buffer.
//
// Therefore, the 'uptime' file will be kept open and just seeked back to
// 0 when new uptime is needed.
//
bool TimberSlide::GetEcUptime(int64_t* ec_uptime_ms) {
char uptime_buf[64] = {0};
if (!uptime_file_valid_ || uptime_file_.Seek(base::File::FROM_BEGIN, 0) != 0)
return false;
// Read single line from file and parse as a number.
int count = uptime_file_.ReadAtCurrentPos(uptime_buf, sizeof(uptime_buf) - 1);
if (count <= 0)
return false;
uptime_buf[count] = '\0';
base::StringToInt64(uptime_buf, ec_uptime_ms);
// If the 'uptime' file contains zero, that means the kernel patch is
// available, but the EC doesn't support EC_CMD_GET_UPTIME_INFO. In
// that case, this returns false so that incorrect times aren't reported
// in the EC log file.
return (*ec_uptime_ms > 0);
}
pw::tokenizer::Detokenizer TimberSlide::OpenDatabase(
const base::FilePath& token_db) {
LOG(INFO) << "Loading tokens: " << token_db;
base::File tokens(token_db, base::File::FLAG_OPEN | base::File::FLAG_READ);
std::vector<uint8_t> data(tokens.GetLength());
tokens.ReadAndCheck(0, base::make_span(data));
pw::tokenizer::TokenDatabase database =
pw::tokenizer::TokenDatabase::Create(data);
// This checks if the file contained a valid database. It is safe to use a
// TokenDatabase that failed to load (it will be empty), but it may be
// desirable to provide a default database or otherwise handle the error.
return pw::tokenizer::Detokenizer(database.ok() ? database
: kDefaultDatabase);
}
std::string TimberSlide::ProcessLogBuffer(const std::string& buffer,
const base::Time& now) {
int64_t ec_current_uptime_ms = 0;
std::string log =
(tokenized_logging_ ? detokenizer_.DetokenizeBase64(buffer) : buffer);
std::istringstream iss(log);
if (GetEcUptime(&ec_current_uptime_ms))
xfrm_->UpdateTimestamps(ec_current_uptime_ms, now);
auto fn_xfrm = [this](const std::string& line) {
if (log_listener_) {
log_listener_->OnLogLine(line);
}
return xfrm_->AddHostTs(line);
};
// Iterate over each line and prepend the corresponding host timestamp if we
// have it
std::ostringstream oss;
std::transform(std::istream_iterator<LineExtractor>(iss),
std::istream_iterator<LineExtractor>(),
std::ostream_iterator<std::string>(oss, "\n"), fn_xfrm);
return oss.str();
}
void TimberSlide::OnEventReadable() {
char buffer[4096];
int ret;
ret = TEMP_FAILURE_RETRY(
device_file_.ReadAtCurrentPosNoBestEffort(buffer, sizeof(buffer)));
if (ret == 0)
return;
if (ret < 0) {
PLOG(ERROR) << "Read error";
Quit();
return;
}
std::string str =
ProcessLogBuffer(std::string(buffer, ret), base::Time::Now());
ret = str.size();
if (!base::AppendToFile(current_log_, str)) {
PLOG(ERROR) << "Could not append to log file";
Quit();
return;
}
total_size_ += ret;
if (total_size_ >= kMaxCurrentLogSize) {
RotateLogs(previous_log_, current_log_);
total_size_ = 0;
}
}
void TimberSlide::OnEventTokenChange(const base::FilePath& file_path,
bool error) {
// Refresh tokens.
LOG(INFO) << "Token DB changed: " << file_path;
detokenizer_ = OpenDatabase(file_path);
}
void TimberSlide::RotateLogs(const base::FilePath& previous_log,
const base::FilePath& current_log) {
CHECK(base::DeleteFile(previous_log));
if (base::PathExists(current_log))
CHECK(base::Move(current_log, previous_log));
base::WriteFile(current_log, "", 0);
}
} // namespace timberslide