| /* | |
| * 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 <android/api-level.h> | |
| #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<std::string> getExpectedTableSchema(const LogEvent& logEvent) { | |
| vector<std::string> 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<int32_t> columnTypes; | |
| std::vector<string> columnNames; | |
| std::vector<std::vector<std::string>> 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<string> 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<LogEvent>& 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<LogEvent>& 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<LogEvent>& 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<vector<string>>& rows, | |
| vector<int32_t>& columnTypes, vector<string>& 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<string> 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<const char*>(sqlite3_column_name(stmt, i))); | |
| } | |
| const unsigned char* textResult = sqlite3_column_text(stmt, i); | |
| string colData = | |
| textResult != nullptr ? string(reinterpret_cast<const char*>(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 |