mirror of
https://github.com/google/pebble.git
synced 2025-07-05 06:10:27 -04:00
424 lines
12 KiB
C
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;
|
|
}
|