pebble/src/fw/services/normal/blob_db/app_db.c
2025-01-27 11:38:16 -08:00

424 lines
12 KiB
C

/*
* 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;
}