blob: 2b08925a4aaca47ba09b98d03a47326fa0e13c4a [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "heartd/daemon/database.h"
#include <string>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <sqlite3.h>
#include "heartd/daemon/boot_record.h"
namespace heartd {
namespace {
int TableExistsCallback(void* const /* int* const */ data,
const int col_count,
char** const cols,
char** const /* names */) {
DCHECK(data != nullptr);
DCHECK(cols != nullptr);
auto* const table_count = static_cast<int*>(data);
if (col_count != 1 || cols[0] == nullptr ||
!base::StringToInt(cols[0], table_count)) {
LOG(ERROR) << "Table existence check failed";
return SQLITE_ERROR;
}
return SQLITE_OK;
}
int GetBootRecordCallback(void* const /* std::vector<BootRecord>* const */ data,
const int col_count,
char** const cols,
char** const /* names */) {
DCHECK(data != nullptr);
DCHECK(cols != nullptr);
if (col_count != 2) {
LOG(ERROR) << "GetBootRecord failed";
return SQLITE_ERROR;
}
auto* const boot_records = static_cast<std::vector<BootRecord>*>(data);
BootRecord boot_record;
if (!cols[0]) {
LOG(ERROR) << "BootRecord.id is null";
return SQLITE_ERROR;
}
boot_record.id = std::string(cols[0]);
if (!cols[1]) {
LOG(ERROR) << "BootRecord.time is null";
return SQLITE_ERROR;
}
int64_t time;
if (!base::StringToInt64(cols[1], &time)) {
LOG(ERROR) << "BootRecord.time is not a number";
return SQLITE_ERROR;
}
boot_record.time = base::Time::FromMillisecondsSinceUnixEpoch(time);
boot_records->push_back(boot_record);
return SQLITE_OK;
}
} // namespace
Database::Database(const std::string& db_path) {
sqlite3* db_ptr;
int result = sqlite3_open(db_path.c_str(), &db_ptr);
db_ = std::unique_ptr<sqlite3, decltype(&sqlite3_close)>(db_ptr,
&sqlite3_close);
if (result != SQLITE_OK) {
LOG(ERROR) << "Failed to connect to database: " << result;
db_ = nullptr;
}
}
Database::~Database() = default;
void Database::Init() const {
if (!db_) {
LOG(ERROR) << "No database connection, skip the initialization.";
return;
}
CreateBootRecordTableIfNotExist();
}
bool Database::IsOpen() const {
return db_ != nullptr;
}
bool Database::TableExists(const std::string& table_name) const {
if (!IsOpen()) {
LOG(ERROR) << "Trying to query table of a closed database";
return false;
}
int table_count = 0;
const std::string sql = base::StringPrintf(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = "
"'%s';",
table_name.c_str());
ExecResult result = ExecSQL(sql, TableExistsCallback, &table_count);
if (result.code != SQLITE_OK) {
LOG(ERROR) << "Failed to query table existence: " << result.msg;
return false;
}
if (table_count <= 0)
return false;
DCHECK(table_count == 1) << "There should be only one table with name '"
<< table_name << "'";
return true;
}
void Database::RemoveOutdatedData(const std::string& table_name) const {
if (!IsOpen()) {
LOG(ERROR) << "Trying to modify table of a closed database";
return;
}
auto time_before_30_days = base::Time().Now() - base::Days(30);
const std::string sql = base::StringPrintf(
"DELETE FROM %s WHERE time < %" PRId64, table_name.c_str(),
time_before_30_days.InMillisecondsSinceUnixEpoch());
ExecResult result = ExecSQL(sql);
if (result.code != SQLITE_OK) {
LOG(ERROR) << "Failed to delete outdated data: " << result.msg;
}
}
void Database::InsertBootRecord(const BootRecord& boot_record) const {
if (!IsOpen()) {
LOG(ERROR) << "Trying to modify table of a closed database";
return;
}
const std::string sql = base::StringPrintf(
"INSERT INTO %s (id, time) VALUES (\"%s\", %" PRId64 ")",
kBootRecordTable, boot_record.id.c_str(),
boot_record.time.InMillisecondsSinceUnixEpoch());
ExecResult result = ExecSQL(sql);
if (result.code != SQLITE_OK) {
LOG(ERROR) << "Failed to insert boot record data: " << result.msg;
}
}
std::vector<BootRecord> Database::GetBootRecordFromTime(
const base::Time& time) const {
if (!IsOpen()) {
LOG(ERROR) << "Trying to query table of a closed database";
return {};
}
std::vector<BootRecord> boot_records;
const std::string sql =
base::StringPrintf("SELECT id,time FROM %s WHERE time >= %" PRId64,
kBootRecordTable, time.InMillisecondsSinceUnixEpoch());
ExecResult result = ExecSQL(sql, GetBootRecordCallback, &boot_records);
if (result.code != SQLITE_OK) {
LOG(ERROR) << "Failed to query boot record data: " << result.msg;
return {};
}
return boot_records;
}
Database::ExecResult Database::ExecSQL(const std::string& sql) const {
return ExecSQL(sql, nullptr, nullptr);
}
Database::ExecResult Database::ExecSQL(const std::string& sql,
SqliteCallback callback,
void* data) const {
char* error_msg = nullptr;
int result = sqlite3_exec(db_.get(), sql.c_str(), callback, data, &error_msg);
std::string msg;
if (error_msg) {
msg.assign(error_msg);
sqlite3_free(error_msg);
}
return {result, msg};
}
bool Database::CreateBootRecordTableIfNotExist() const {
if (!IsOpen()) {
LOG(ERROR) << "Trying to create table of a closed database";
return false;
}
if (TableExists(kBootRecordTable)) {
return true;
}
const std::string sql = base::StringPrintf(
"CREATE TABLE %s ("
"id TEXT PRIMARY KEY NOT NULL,"
"time INTEGER NOT NULL)",
kBootRecordTable);
const auto result = ExecSQL(sql);
if (result.code != SQLITE_OK) {
LOG(ERROR) << "Failed to create " << kBootRecordTable
<< " table: " << result.msg;
return false;
}
return true;
}
} // namespace heartd