/* * Copyright 2024 Google LLC * * 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. */ #include "app_db.h" #include "util/uuid.h" #include "kernel/pbl_malloc.h" #include "process_management/app_install_manager_private.h" #include "services/normal/filesystem/pfs.h" #include "services/normal/settings/settings_file.h" #include "services/normal/app_fetch_endpoint.h" #include "os/mutex.h" #include "system/logging.h" #include "system/passert.h" #include "system/status_codes.h" #include "util/math.h" #include "util/units.h" #define SETTINGS_FILE_NAME "appdb" // Holds about ~150 app metadata blobs #define SETTINGS_FILE_SIZE KiBYTES(20) #define FIRST_VALID_INSTALL_ID (INSTALL_ID_INVALID + 1) static AppInstallId s_next_unique_flash_app_id; static struct { SettingsFile settings_file; PebbleMutex *mutex; } s_app_db; ////////////////////// // Settings helpers ////////////////////// struct AppDBInitData { AppInstallId max_id; uint32_t num_apps; }; static status_t prv_lock_mutex_and_open_file(void) { mutex_lock(s_app_db.mutex); status_t rv = settings_file_open(&s_app_db.settings_file, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE); if (rv != S_SUCCESS) { mutex_unlock(s_app_db.mutex); } return rv; } static void prv_close_file_and_unlock_mutex(void) { settings_file_close(&s_app_db.settings_file); mutex_unlock(s_app_db.mutex); } static status_t prv_cancel_app_fetch(AppInstallId app_id) { if (pebble_task_get_current() == PebbleTask_KernelBackground) { // if we are on kernel_bg, we can go ahead and cancel the app fetch instantly app_fetch_cancel_from_system_task(app_id); return S_SUCCESS; } else { // ignore the deletion and send back a failure message. The phone will retry later. return E_BUSY; } } //! SettingsFileEachCallback function is used to iterate over all keys and find the largest //! AppInstallId currently being using. static bool prv_each_inspect_ids(SettingsFile *file, SettingsRecordInfo *info, void *context) { // check entry is valid if ((info->val_len == 0) || (info->key_len != sizeof(AppInstallId))) { return true; // continue iterating } struct AppDBInitData *data = context; AppInstallId app_id; info->get_key(file, (uint8_t *)&app_id, sizeof(AppInstallId)); data->max_id = MAX(data->max_id, app_id); data->num_apps++; return true; // continue iterating } struct UuidFilterData { Uuid uuid; AppInstallId found_id; }; //! SettingsFileEachCallback function is used to iterate over all entries and search for //! the particular entry with the given UUID. If one is found, it will set the uuid_data->found_id //! to a value other than INSTALL_ID_INVALID static bool prv_db_filter_app_id(SettingsFile *file, SettingsRecordInfo *info, void *context) { // check entry is valid if ((info->val_len == 0) || (info->key_len != sizeof(AppInstallId))) { return true; // continue iterating } struct UuidFilterData *uuid_data = (struct UuidFilterData *)context; AppInstallId app_id; AppDBEntry entry; info->get_key(file, (uint8_t *)&app_id, info->key_len); info->get_val(file, (uint8_t *)&entry, info->val_len); if (uuid_equal(&uuid_data->uuid, &entry.uuid)) { uuid_data->found_id = app_id; return false; // stop iterating } return true; // continue iterating } //! Retrieves the AppInstallId for a given UUID using the SettingsFile that is already open. //! @note Requires holding the lock already static AppInstallId prv_find_install_id_for_uuid(SettingsFile *file, const Uuid *uuid) { // used when iterating through all entries in our database. struct UuidFilterData filter_data = { .found_id = INSTALL_ID_INVALID, .uuid = *uuid, }; settings_file_each(file, prv_db_filter_app_id, (void *)&filter_data); return filter_data.found_id; } ///////////////////////// // App DB Specific API ///////////////////////// AppInstallId app_db_get_install_id_for_uuid(const Uuid *uuid) { status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } AppInstallId app_id = prv_find_install_id_for_uuid(&s_app_db.settings_file, uuid); prv_close_file_and_unlock_mutex(); return app_id; } /////////////////////////// // App DB API /////////////////////////// //! Fills an AppDBEntry for a given UUID. This is a wrapper around app_db_read to keep it uniform //! with `app_db_get_app_entry_for_install_id` status_t app_db_get_app_entry_for_uuid(const Uuid *uuid, AppDBEntry *entry) { return app_db_read((uint8_t *)uuid, sizeof(Uuid), (uint8_t *)entry, sizeof(AppDBEntry)); } status_t app_db_get_app_entry_for_install_id(AppInstallId app_id, AppDBEntry *entry) { status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } rv = settings_file_get(&s_app_db.settings_file, (uint8_t *)&app_id, sizeof(AppInstallId), (uint8_t *)entry, sizeof(AppDBEntry)); prv_close_file_and_unlock_mutex(); return rv; } bool app_db_exists_install_id(AppInstallId app_id) { status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } bool exists = settings_file_exists(&s_app_db.settings_file, (uint8_t *)&app_id, sizeof(AppInstallId)); prv_close_file_and_unlock_mutex(); return exists; } typedef struct { AppDBEnumerateCb cb; void *data; AppDBEntry *entry_buf; } EnumerateData; static bool prv_enumerate_entries(SettingsFile *file, SettingsRecordInfo *info, void *context) { // check entry is valid if ((info->val_len == 0) || (info->key_len != sizeof(AppInstallId))) { return true; // continue iteration } EnumerateData *cb_data = (EnumerateData *)context; AppInstallId id; info->get_key(file, (uint8_t *)&id, info->key_len); info->get_val(file, (uint8_t *)cb_data->entry_buf, info->val_len); // check return value cb_data->cb(id, cb_data->entry_buf, cb_data->data); return true; // continue iteration } void app_db_enumerate_entries(AppDBEnumerateCb cb, void *data) { status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return; } AppDBEntry *db_entry = kernel_malloc_check(sizeof(AppDBEntry)); EnumerateData cb_data = { .cb = cb, .data = data, .entry_buf = db_entry, }; settings_file_each(&s_app_db.settings_file, prv_enumerate_entries, &cb_data); prv_close_file_and_unlock_mutex(); kernel_free(db_entry); return; } ///////////////////////// // Blob DB API ///////////////////////// void app_db_init(void) { memset(&s_app_db, 0, sizeof(s_app_db)); s_app_db.mutex = mutex_create(); // set to zero to reset unit test static variable. s_next_unique_flash_app_id = INSTALL_ID_INVALID; // Iterate through all entires and find the one with the highest AppInstallId. The next unique // is then one greater than the largest found. status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { WTF; } struct AppDBInitData data = { 0 }; settings_file_each(&s_app_db.settings_file, prv_each_inspect_ids, &data); if (data.max_id == INSTALL_ID_INVALID) { s_next_unique_flash_app_id = (INSTALL_ID_INVALID + 1); } else { s_next_unique_flash_app_id = (data.max_id + 1); } PBL_LOG(LOG_LEVEL_INFO, "Found %"PRIu32" apps. Next ID: %"PRIu32" ", data.num_apps, s_next_unique_flash_app_id); prv_close_file_and_unlock_mutex(); } status_t app_db_insert(const uint8_t *key, int key_len, const uint8_t *val, int val_len) { if (key_len != UUID_SIZE || val_len != sizeof(AppDBEntry)) { return E_INVALID_ARGUMENT; } status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } PBL_ASSERTN(key_len == 16); PBL_ASSERTN(val_len > 0); bool new_install = false; AppInstallId app_id = prv_find_install_id_for_uuid(&s_app_db.settings_file, (const Uuid *)key); if (app_id == INSTALL_ID_INVALID) { new_install = true; app_id = s_next_unique_flash_app_id++; } else if (app_fetch_in_progress()) { PBL_LOG(LOG_LEVEL_WARNING, "Got an insert for an app that is currently being fetched, %"PRId32, app_id); rv = prv_cancel_app_fetch(app_id); } if (rv == S_SUCCESS) { rv = settings_file_set(&s_app_db.settings_file, (uint8_t *)&app_id, sizeof(AppInstallId), val, val_len); } prv_close_file_and_unlock_mutex(); if (rv == S_SUCCESS) { // app install something app_install_do_callbacks(new_install ? APP_AVAILABLE : APP_UPGRADED, app_id, NULL, NULL, NULL); } return rv; } int app_db_get_len(const uint8_t *key, int key_len) { status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } PBL_ASSERTN(key_len == 16); // should not increment !!!! AppInstallId app_id = prv_find_install_id_for_uuid(&s_app_db.settings_file, (Uuid *)key); if (app_id == INSTALL_ID_INVALID) { rv = 0; } else { rv = settings_file_get_len(&s_app_db.settings_file, (uint8_t *)&app_id, sizeof(AppInstallId)); } prv_close_file_and_unlock_mutex(); return rv; } status_t app_db_read(const uint8_t *key, int key_len, uint8_t *val_out, int val_len) { status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } PBL_ASSERTN(key_len == 16); AppInstallId app_id = prv_find_install_id_for_uuid(&s_app_db.settings_file, (Uuid *)key); if (app_id == INSTALL_ID_INVALID) { rv = E_DOES_NOT_EXIST; } else { rv = settings_file_get(&s_app_db.settings_file, (uint8_t *)&app_id, sizeof(AppInstallId), val_out, val_len); } prv_close_file_and_unlock_mutex(); return rv; } status_t app_db_delete(const uint8_t *key, int key_len) { if (key_len != UUID_SIZE) { return E_INVALID_ARGUMENT; } status_t rv = prv_lock_mutex_and_open_file(); if (rv != S_SUCCESS) { return rv; } PBL_ASSERTN(key_len == 16); AppInstallId app_id = prv_find_install_id_for_uuid(&s_app_db.settings_file, (Uuid *)key); if (app_id == INSTALL_ID_INVALID) { rv = E_DOES_NOT_EXIST; } else if (app_fetch_in_progress()) { PBL_LOG(LOG_LEVEL_WARNING, "Tried to delete an app that is currently being fetched, %"PRId32, app_id); rv = prv_cancel_app_fetch(app_id); } if (rv == S_SUCCESS) { rv = settings_file_delete(&s_app_db.settings_file, (uint8_t *)&app_id, sizeof(AppInstallId)); } prv_close_file_and_unlock_mutex(); if (rv == S_SUCCESS) { // uuid will be free'd by app_install_manager Uuid *uuid_copy = kernel_malloc_check(sizeof(Uuid)); memcpy(uuid_copy, key, sizeof(Uuid)); app_install_do_callbacks(APP_REMOVED, app_id, uuid_copy, NULL, NULL); } return rv; } status_t app_db_flush(void) { PBL_LOG(LOG_LEVEL_WARNING, "AppDB Flush initiated"); if (app_fetch_in_progress()) { // cancels any app fetch status_t rv = prv_cancel_app_fetch(INSTALL_ID_INVALID); if (rv != S_SUCCESS) { return rv; } } app_install_do_callbacks(APP_DB_CLEARED, INSTALL_ID_INVALID, NULL, NULL, NULL); // let app install manager deal with deleting the cache and removing related timeline pins app_install_clear_app_db(); // remove the settings file mutex_lock(s_app_db.mutex); pfs_remove(SETTINGS_FILE_NAME); mutex_unlock(s_app_db.mutex); PBL_LOG(LOG_LEVEL_WARNING, "AppDB Flush finished"); return S_SUCCESS; } ////////////////////// // Test functions ////////////////////// // automated testing and app_install_manager prompt commands int32_t app_db_check_next_unique_id(void) { return s_next_unique_flash_app_id; }