/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define STATSD_DEBUG false // STOPSHIP if true #include "Log.h" #include "utils/DbUtils.h" #include #include "FieldValue.h" #include "android-base/properties.h" #include "android-base/stringprintf.h" #include "stats_log_util.h" #include "storage/StorageManager.h" namespace android { namespace os { namespace statsd { namespace dbutils { using ::android::os::statsd::FLOAT; using ::android::os::statsd::INT; using ::android::os::statsd::LONG; using ::android::os::statsd::StorageManager; using ::android::os::statsd::STRING; using base::GetProperty; using base::StringPrintf; const string TABLE_NAME_PREFIX = "metric_"; const string COLUMN_NAME_ATOM_TAG = "atomId"; const string COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS = "elapsedTimestampNs"; const string COLUMN_NAME_EVENT_WALL_CLOCK_NS = "wallTimestampNs"; const string COLUMN_NAME_SDK_VERSION = "sdkVersion"; const string COLUMN_NAME_MODEL = "model"; const string COLUMN_NAME_PRODUCT = "product"; const string COLUMN_NAME_HARDWARE = "hardware"; const string COLUMN_NAME_DEVICE = "device"; const string COLUMN_NAME_BUILD = "osBuild"; const string COLUMN_NAME_FINGERPRINT = "fingerprint"; const string COLUMN_NAME_BRAND = "brand"; const string COLUMN_NAME_MANUFACTURER = "manufacturer"; const string COLUMN_NAME_BOARD = "board"; static std::vector getExpectedTableSchema(const LogEvent& logEvent) { vector result; for (const FieldValue& fieldValue : logEvent.getValues()) { if (fieldValue.mField.getDepth() > 0) { // Repeated fields are not supported. continue; } switch (fieldValue.mValue.getType()) { case INT: case LONG: result.push_back("INTEGER"); break; case STRING: result.push_back("TEXT"); break; case FLOAT: result.push_back("REAL"); break; default: // Byte array fields are not supported. break; } } return result; } static int integrityCheckCallback(void*, int colCount, char** queryResults, char**) { if (colCount == 0 || strcmp(queryResults[0], "ok") != 0) { // Returning 1 is an error code that causes exec to stop and error. return 1; } return 0; } string getDbName(const ConfigKey& key) { return StringPrintf("%s/%d_%lld.db", STATS_RESTRICTED_DATA_DIR, key.GetUid(), (long long)key.GetId()); } static string getCreateSqlString(const int64_t metricId, const LogEvent& event) { string result = StringPrintf("CREATE TABLE IF NOT EXISTS %s%s", TABLE_NAME_PREFIX.c_str(), reformatMetricId(metricId).c_str()); result += StringPrintf("(%s INTEGER,%s INTEGER,%s INTEGER,", COLUMN_NAME_ATOM_TAG.c_str(), COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS.c_str(), COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str()); for (size_t fieldId = 1; fieldId <= event.getValues().size(); ++fieldId) { const FieldValue& fieldValue = event.getValues()[fieldId - 1]; if (fieldValue.mField.getDepth() > 0) { // Repeated fields are not supported. continue; } switch (fieldValue.mValue.getType()) { case INT: case LONG: result += StringPrintf("field_%d INTEGER,", fieldValue.mField.getPosAtDepth(0)); break; case STRING: result += StringPrintf("field_%d TEXT,", fieldValue.mField.getPosAtDepth(0)); break; case FLOAT: result += StringPrintf("field_%d REAL,", fieldValue.mField.getPosAtDepth(0)); break; default: // Byte array fields are not supported. break; } } result.pop_back(); result += ") STRICT;"; return result; } string reformatMetricId(const int64_t metricId) { return metricId < 0 ? StringPrintf("n%lld", (long long)metricId * -1) : StringPrintf("%lld", (long long)metricId); } bool createTableIfNeeded(const ConfigKey& key, const int64_t metricId, const LogEvent& event) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) { sqlite3_close(db); return false; } char* error = nullptr; string zSql = getCreateSqlString(metricId, event); sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error); sqlite3_close(db); if (error) { ALOGW("Failed to create table to db: %s", error); return false; } return true; } bool isEventCompatible(const ConfigKey& key, const int64_t metricId, const LogEvent& event) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) { sqlite3_close(db); return false; } string zSql = StringPrintf("PRAGMA table_info(metric_%s);", reformatMetricId(metricId).c_str()); string err; std::vector columnTypes; std::vector columnNames; std::vector> rows; if (!query(key, zSql, rows, columnTypes, columnNames, err)) { ALOGE("Failed to check table schema for metric %lld: %s", (long long)metricId, err.c_str()); sqlite3_close(db); return false; } // Sample query result // cid name type notnull dflt_value pk // --- ----------------- ------- ------- ---------- -- // 0 atomId INTEGER 0 (null) 0 // 1 elapsedTimestampNs INTEGER 0 (null) 0 // 2 wallTimestampNs INTEGER 0 (null) 0 // 3 field_1 INTEGER 0 (null) 0 // 4 field_2 TEXT 0 (null) 0 std::vector tableSchema; for (size_t i = 3; i < rows.size(); ++i) { // Atom fields start at the third row tableSchema.push_back(rows[i][2]); // The third column stores the data type for the column } sqlite3_close(db); // An empty rows vector implies the table has not yet been created. return rows.size() == 0 || getExpectedTableSchema(event) == tableSchema; } bool deleteTable(const ConfigKey& key, const int64_t metricId) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) { sqlite3_close(db); return false; } string zSql = StringPrintf("DROP TABLE metric_%s", reformatMetricId(metricId).c_str()); char* error = nullptr; sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error); sqlite3_close(db); if (error) { ALOGW("Failed to drop table from db: %s", error); return false; } return true; } void deleteDb(const ConfigKey& key) { const string dbName = getDbName(key); StorageManager::deleteFile(dbName.c_str()); } sqlite3* getDb(const ConfigKey& key) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) == SQLITE_OK) { return db; } return nullptr; } void closeDb(sqlite3* db) { sqlite3_close(db); } static bool getInsertSqlStmt(sqlite3* db, sqlite3_stmt** stmt, const int64_t metricId, const vector& events, string& err) { string result = StringPrintf("INSERT INTO metric_%s VALUES", reformatMetricId(metricId).c_str()); for (auto& logEvent : events) { result += StringPrintf("(%d, %lld, %lld,", logEvent.GetTagId(), (long long)logEvent.GetElapsedTimestampNs(), (long long)logEvent.GetLogdTimestampNs()); for (auto& fieldValue : logEvent.getValues()) { if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) { // Repeated fields and byte fields are not supported. continue; } result += "?,"; } result.pop_back(); result += "),"; } result.pop_back(); result += ";"; if (sqlite3_prepare_v2(db, result.c_str(), -1, stmt, nullptr) != SQLITE_OK) { err = sqlite3_errmsg(db); return false; } // ? parameters start with an index of 1 from start of query string to the // end. int32_t index = 1; for (auto& logEvent : events) { for (auto& fieldValue : logEvent.getValues()) { if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) { // Repeated fields and byte fields are not supported. continue; } switch (fieldValue.mValue.getType()) { case INT: sqlite3_bind_int(*stmt, index, fieldValue.mValue.int_value); break; case LONG: sqlite3_bind_int64(*stmt, index, fieldValue.mValue.long_value); break; case STRING: sqlite3_bind_text(*stmt, index, fieldValue.mValue.str_value.c_str(), -1, SQLITE_STATIC); break; case FLOAT: sqlite3_bind_double(*stmt, index, fieldValue.mValue.float_value); break; default: // Byte array fields are not supported. break; } ++index; } } return true; } bool insert(const ConfigKey& key, const int64_t metricId, const vector& events, string& error) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) { error = sqlite3_errmsg(db); sqlite3_close(db); return false; } bool success = insert(db, metricId, events, error); sqlite3_close(db); return success; } bool insert(sqlite3* db, const int64_t metricId, const vector& events, string& error) { sqlite3_stmt* stmt = nullptr; if (!getInsertSqlStmt(db, &stmt, metricId, events, error)) { ALOGW("Failed to generate prepared sql insert query %s", error.c_str()); sqlite3_finalize(stmt); return false; } if (sqlite3_step(stmt) != SQLITE_DONE) { error = sqlite3_errmsg(db); ALOGW("Failed to insert data to db: %s", error.c_str()); sqlite3_finalize(stmt); return false; } sqlite3_finalize(stmt); return true; } bool query(const ConfigKey& key, const string& zSql, vector>& rows, vector& columnTypes, vector& columnNames, string& err) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open_v2(dbName.c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) { err = sqlite3_errmsg(db); sqlite3_close(db); return false; } sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db, zSql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { err = sqlite3_errmsg(db); sqlite3_finalize(stmt); sqlite3_close(db); return false; } int result = sqlite3_step(stmt); bool firstIter = true; while (result == SQLITE_ROW) { int colCount = sqlite3_column_count(stmt); vector rowData(colCount); for (int i = 0; i < colCount; ++i) { if (firstIter) { int32_t columnType = sqlite3_column_type(stmt, i); // Needed to convert to java compatible cursor types. See AbstractCursor#getType() if (columnType == 5) { columnType = 0; // Remap 5 (null type) to 0 for java cursor } columnTypes.push_back(columnType); columnNames.push_back(reinterpret_cast(sqlite3_column_name(stmt, i))); } const unsigned char* textResult = sqlite3_column_text(stmt, i); string colData = textResult != nullptr ? string(reinterpret_cast(textResult)) : ""; rowData[i] = std::move(colData); } rows.push_back(std::move(rowData)); firstIter = false; result = sqlite3_step(stmt); } sqlite3_finalize(stmt); if (result != SQLITE_DONE) { err = sqlite3_errmsg(db); sqlite3_close(db); return false; } sqlite3_close(db); return true; } bool flushTtl(sqlite3* db, const int64_t metricId, const int64_t ttlWallClockNs) { string zSql = StringPrintf("DELETE FROM %s%s WHERE %s <= %lld", TABLE_NAME_PREFIX.c_str(), reformatMetricId(metricId).c_str(), COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str(), (long long)ttlWallClockNs); char* error = nullptr; sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error); if (error) { ALOGW("Failed to enforce ttl: %s", error); return false; } return true; } void verifyIntegrityAndDeleteIfNecessary(const ConfigKey& configKey) { const string dbName = getDbName(configKey); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) { sqlite3_close(db); return; } string zSql = "PRAGMA integrity_check"; char* error = nullptr; sqlite3_exec(db, zSql.c_str(), integrityCheckCallback, nullptr, &error); if (error) { StatsdStats::getInstance().noteDbCorrupted(configKey); ALOGW("Integrity Check failed %s", error); sqlite3_close(db); deleteDb(configKey); return; } sqlite3_close(db); } static bool getDeviceInfoInsertStmt(sqlite3* db, sqlite3_stmt** stmt, string error) { string insertSql = StringPrintf("INSERT INTO device_info VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); if (sqlite3_prepare_v2(db, insertSql.c_str(), -1, stmt, nullptr) != SQLITE_OK) { error = sqlite3_errmsg(db); return false; } // ? parameters start with an index of 1 from start of query string to the end. int32_t index = 1; int32_t sdkVersion = android_get_device_api_level(); sqlite3_bind_int(*stmt, index, sdkVersion); ++index; string model = GetProperty("ro.product.model", "(unknown)"); sqlite3_bind_text(*stmt, index, model.c_str(), -1, SQLITE_TRANSIENT); ++index; string product = GetProperty("ro.product.name", "(unknown)"); sqlite3_bind_text(*stmt, index, product.c_str(), -1, SQLITE_TRANSIENT); ++index; string hardware = GetProperty("ro.hardware", "(unknown)"); sqlite3_bind_text(*stmt, index, hardware.c_str(), -1, SQLITE_TRANSIENT); ++index; string device = GetProperty("ro.product.device", "(unknown)"); sqlite3_bind_text(*stmt, index, device.c_str(), -1, SQLITE_TRANSIENT); ++index; string osBuild = GetProperty("ro.build.id", "(unknown)"); sqlite3_bind_text(*stmt, index, osBuild.c_str(), -1, SQLITE_TRANSIENT); ++index; string fingerprint = GetProperty("ro.build.fingerprint", "(unknown)"); sqlite3_bind_text(*stmt, index, fingerprint.c_str(), -1, SQLITE_TRANSIENT); ++index; string brand = GetProperty("ro.product.brand", "(unknown)"); sqlite3_bind_text(*stmt, index, brand.c_str(), -1, SQLITE_TRANSIENT); ++index; string manufacturer = GetProperty("ro.product.manufacturer", "(unknown)"); sqlite3_bind_text(*stmt, index, manufacturer.c_str(), -1, SQLITE_TRANSIENT); ++index; string board = GetProperty("ro.product.board", "(unknown)"); sqlite3_bind_text(*stmt, index, board.c_str(), -1, SQLITE_TRANSIENT); ++index; return true; } bool updateDeviceInfoTable(const ConfigKey& key, string& error) { const string dbName = getDbName(key); sqlite3* db; if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) { error = sqlite3_errmsg(db); sqlite3_close(db); return false; } string dropTableSql = "DROP TABLE device_info"; // Ignore possible error result code if table has not yet been created. sqlite3_exec(db, dropTableSql.c_str(), nullptr, nullptr, nullptr); string createTableSql = StringPrintf( "CREATE TABLE device_info(%s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s " "TEXT, %s TEXT, %s TEXT, %s TEXT) " "STRICT", COLUMN_NAME_SDK_VERSION.c_str(), COLUMN_NAME_MODEL.c_str(), COLUMN_NAME_PRODUCT.c_str(), COLUMN_NAME_HARDWARE.c_str(), COLUMN_NAME_DEVICE.c_str(), COLUMN_NAME_BUILD.c_str(), COLUMN_NAME_FINGERPRINT.c_str(), COLUMN_NAME_BRAND.c_str(), COLUMN_NAME_MANUFACTURER.c_str(), COLUMN_NAME_BOARD.c_str()); if (sqlite3_exec(db, createTableSql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) { error = sqlite3_errmsg(db); ALOGW("Failed to create device info table %s", error.c_str()); sqlite3_close(db); return false; } sqlite3_stmt* stmt = nullptr; if (!getDeviceInfoInsertStmt(db, &stmt, error)) { ALOGW("Failed to generate device info prepared sql insert query %s", error.c_str()); sqlite3_finalize(stmt); sqlite3_close(db); return false; } if (sqlite3_step(stmt) != SQLITE_DONE) { error = sqlite3_errmsg(db); ALOGW("Failed to insert data to device info table: %s", error.c_str()); sqlite3_finalize(stmt); sqlite3_close(db); return false; } sqlite3_finalize(stmt); sqlite3_close(db); return true; } } // namespace dbutils } // namespace statsd } // namespace os } // namespace android