blob: 356cee023c9ffcf0d7a5e1019fa54d042aab5b2f [file] [log] [blame]
/******************************************************************************
*
* Copyright (C) 2009-2012 Broadcom Corporation
*
* 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.
*
******************************************************************************/
/************************************************************************************
*
* Filename: btif_config.c
*
* Description: Stores the local BT adapter and remote device properties in
* NVRAM storage, typically as xml file in the
* mobile's filesystem
*
*
***********************************************************************************/
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <private/android_filesystem_config.h>
#define LOG_TAG "btif_config"
#include <hardware/bluetooth.h>
#include "btif_api.h"
#include "btif_config.h"
#include "btif_config_util.h"
#include "btif_sock_thread.h"
#include "btif_sock_util.h"
#define asrt(s) if(!(s)) BTIF_TRACE_ERROR3 ("## %s assert %s failed at line:%d ##",__FUNCTION__, #s, __LINE__)
//#define UNIT_TEST
#define CFG_PATH "/data/misc/bluedroid/"
#define CFG_FILE_NAME "bt_config"
#define CFG_FILE_EXT ".xml"
#define CFG_FILE_EXT_OLD ".old"
#define CFG_FILE_EXT_NEW ".new"
#define CFG_GROW_SIZE (10*sizeof(cfg_node))
#define GET_CHILD_MAX_COUNT(node) (short)((int)(node)->bytes / sizeof(cfg_node))
#define IS_EMPTY(node) ((node)->name == NULL)
#define GET_NODE_COUNT(bytes) (bytes / sizeof(cfg_node))
#define MAX_NODE_BYTES 32000
#define MAX_CACHED_COUNT 150
#define CFG_CMD_SAVE 1
#ifndef FALSE
#define TRUE 1
#define FALSE 0
#endif
typedef struct cfg_node_s
{
const char* name;
union
{
struct cfg_node_s* child;
char* value;
};
short bytes;
short type;
short used;
short flag;
} cfg_node;
static pthread_mutex_t slot_lock;
static int pth = -1; //poll thread handle
static cfg_node root;
static int cached_change;
static void cfg_cmd_callback(int cmd_fd, int type, int flags, uint32_t user_id);
static inline short alloc_node(cfg_node* p, short grow);
static inline void free_node(cfg_node* p);
static inline void free_inode(cfg_node* p, int child);
static inline short find_inode(const cfg_node* p, const char* name);
static cfg_node* find_node(const char* section, const char* key, const char* name);
static int remove_node(const char* section, const char* key, const char* name);
static inline cfg_node* find_free_node(cfg_node* p);
static int set_node(const char* section, const char* key, const char* name,
const char* value, short bytes, short type);
static int save_cfg();
static void load_cfg();
static short find_next_node(const cfg_node* p, short start, char* name, int* bytes);
static int create_dir(const char* path);
#ifdef UNIT_TEST
static void cfg_test_load();
static void cfg_test_write();
static void cfg_test_read();
#endif
static inline void dump_node(const char* title, const cfg_node* p)
{
if(p) {
BTIF_TRACE_DEBUG4("%s, p->name:%s, child/value:%p, bytes:%d",
title, p->name, p->child, p->bytes);
BTIF_TRACE_DEBUG3("p->used:%d, type:%x, p->flag:%d",
p->used, p->type, p->flag);
} else BTIF_TRACE_DEBUG1("%s is NULL", title);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
int btif_config_init()
{
static int initialized;
BTIF_TRACE_DEBUG1("in initialized:%d", initialized);
if(!initialized)
{
initialized = 1;
struct stat st;
if(stat(CFG_PATH, &st) != 0)
BTIF_TRACE_ERROR1("%s does not exist, need provision", CFG_PATH);
btsock_thread_init();
init_slot_lock(&slot_lock);
lock_slot(&slot_lock);
root.name = "Bluedroid";
alloc_node(&root, CFG_GROW_SIZE);
dump_node("root", &root);
pth = btsock_thread_create(NULL, cfg_cmd_callback);
load_cfg();
unlock_slot(&slot_lock);
#ifdef UNIT_TEST
//cfg_test_load();
cfg_test_write();
cfg_test_read();
#endif
}
return pth >= 0;
}
int btif_config_get_int(const char* section, const char* key, const char* name, int* value)
{
int size = sizeof(*value);
int type = BTIF_CFG_TYPE_INT;
return btif_config_get(section, key, name, (char*)value, &size, &type);
}
int btif_config_set_int(const char* section, const char* key, const char* name, int value)
{
return btif_config_set(section, key, name, (char*)&value, sizeof(value), BTIF_CFG_TYPE_INT);
}
int btif_config_get_str(const char* section, const char* key, const char* name, char* value, int* size)
{
int type = BTIF_CFG_TYPE_STR;
if(value)
*value = 0;
return btif_config_get(section, key, name, value, size, &type);
}
int btif_config_set_str(const char* section, const char* key, const char* name, const char* value)
{
value = value ? value : "";
return btif_config_set(section, key, name, value, strlen(value) + 1, BTIF_CFG_TYPE_STR);
}
int btif_config_exist(const char* section, const char* key, const char* name)
{
int ret = FALSE;
if(section && *section && key && *key)
{
lock_slot(&slot_lock);
ret = find_node(section, key, name) != NULL;
unlock_slot(&slot_lock);
}
return ret;
}
int btif_config_get(const char* section, const char* key, const char* name, char* value, int* bytes, int* type)
{
int ret = FALSE;
asrt(section && *section && key && *key && name && *name && bytes && type);
//debug("section:%s, key:%s, name:%s, value:%p, bytes:%d, type:%d",
// section, key, name, value, *bytes, *type);
if(section && *section && key && *key && name && *name && bytes && type)
{
lock_slot(&slot_lock);
const cfg_node* node = find_node(section, key, name);
dump_node("found node", node);
if(node)
{
if(*type == node->type && value && *bytes >= node->used)
{
if(node->used > 0)
memcpy(value, node->value, node->used);
ret = TRUE;
}
*type = node->type;
*bytes = node->used;
if(ret != TRUE)
{
if(*type != node->type)
BTIF_TRACE_ERROR3("value:%s, wrong type:%d, need to be type: %d",
name, *type, node->type);
if(value && *bytes < node->used)
BTIF_TRACE_ERROR3("value:%s, not enough size: %d bytes, need %d bytes",
name, node->used, *bytes);
}
}
unlock_slot(&slot_lock);
}
//debug("out");
return ret;
}
int btif_config_set(const char* section, const char* key, const char* name, const char* value, int bytes, int type)
{
int ret = FALSE;
asrt(section && *section && key && *key && name && *name);
asrt(bytes < MAX_NODE_BYTES);
if(section && *section && key && *key && name && *name && bytes < MAX_NODE_BYTES)
{
lock_slot(&slot_lock);
ret = set_node(section, key, name, value, (short)bytes, (short)type);
if(ret && !(type & BTIF_CFG_TYPE_VOLATILE) && ++cached_change > MAX_CACHED_COUNT)
{
cached_change = 0;
btsock_thread_post_cmd(pth, CFG_CMD_SAVE, NULL, 0, 0);
}
unlock_slot(&slot_lock);
}
return ret;
}
int btif_config_remove(const char* section, const char* key, const char* name)
{
asrt(section && *section && key && *key);
int ret = FALSE;
if(section && *section && key && *key)
{
lock_slot(&slot_lock);
ret = remove_node(section, key, name);
if(ret)
cached_change++;
unlock_slot(&slot_lock);
}
return ret;
}
typedef struct {
short si;
short ki;
short vi;
short reserved;
} cfg_node_pos;
short btif_config_next_key(short pos, const char* section, char * name, int* bytes)
{
int next = -1;
lock_slot(&slot_lock);
short si = find_inode(&root, section);
if(si >= 0)
{
const cfg_node* section_node = &root.child[si];
next = find_next_node(section_node, pos, name, bytes);
}
unlock_slot(&slot_lock);
return next;
}
short btif_config_next_value(short pos, const char* section, const char* key, char* name, int* bytes)
{
int next = -1;
lock_slot(&slot_lock);
short si = find_inode(&root, section);
if(si >= 0)
{
const cfg_node* section_node = &root.child[si];
short ki = find_inode(section_node, key);
if(ki >= 0)
{
const cfg_node* key_node = &section_node->child[ki];
next = find_next_node(key_node, pos, name, bytes);
}
}
unlock_slot(&slot_lock);
return next;
}
int btif_config_enum(btif_config_enum_callback cb, void* user_data)
{
asrt(cb);
if(!cb)
return FALSE;
lock_slot(&slot_lock);
int si, ki, vi;
cfg_node *section_node, *key_node, *value_node;
for(si = 0; si < GET_CHILD_MAX_COUNT(&root); si++)
{
section_node = &root.child[si];
if(section_node->name && *section_node->name)
{
for(ki = 0; ki < GET_CHILD_MAX_COUNT(section_node); ki++)
{
key_node = &section_node->child[ki];
if(key_node->name && *key_node->name)
{
for(vi = 0; vi < GET_CHILD_MAX_COUNT(key_node); vi++)
{
value_node = &key_node->child[vi];
if(value_node->name && *value_node->name)
{
cb(user_data, section_node->name, key_node->name, value_node->name,
value_node->value, value_node->used, value_node->type);
}
}
}
}
}
}
unlock_slot(&slot_lock);
return TRUE;
}
int btif_config_save()
{
lock_slot(&slot_lock);
if(cached_change > 0)
{
cached_change = 0;
btsock_thread_post_cmd(pth, CFG_CMD_SAVE, NULL, 0, 0);
}
unlock_slot(&slot_lock);
return TRUE;
}
void btif_config_flush()
{
lock_slot(&slot_lock);
if(cached_change > 0)
save_cfg();
unlock_slot(&slot_lock);
}
/////////////////////////////////////////////////////////////////////////////////////////////
static inline short alloc_node(cfg_node* p, short grow)
{
int new_bytes = p->bytes + grow;
//debug("in, bytes:%d, new bytes:%d, grow:%d", p->bytes, new_bytes, grow);
if(grow > 0 && new_bytes < MAX_NODE_BYTES)
{
char* value = (char*)realloc(p->value, new_bytes);
if(value)
{
short old_bytes = p->bytes;
//clear to zero
memset(value + old_bytes, 0, grow);
p->bytes = old_bytes + grow;
p->value = value;
//debug("out");
return old_bytes;//return the previous size
}
else BTIF_TRACE_ERROR3("realloc failed, old_bytes:%d, grow:%d, total:%d", p->bytes, grow, p->bytes + grow);
}
//debug("out, alloc failed");
return -1;
}
static inline void free_node(cfg_node* p)
{
if(p)
{
if(p->child)
{
free(p->child);
p->child = NULL;
}
if(p->name)
{
free((void*)p->name);
p->name = 0;
}
p->used = p->bytes = p->flag = p->type = 0;
}
}
static inline short find_inode(const cfg_node* p, const char* name)
{
//debug("in");
if(p && p->child && name && *name)
{
int i;
int count = GET_CHILD_MAX_COUNT(p);
//debug("child name:%s, child max count:%d", name, count);
for(i = 0; i < count; i++)
{
if(p->child[i].name && *p->child[i].name &&
strcmp(p->child[i].name, name) == 0)
{
//debug("out found child index:%d", i);
return (short)i;
}
}
}
//debug("out, child name: %s not found", name);
return -1;
}
static inline cfg_node* find_free_node(cfg_node* p)
{
if(p && p->child)
{
int i;
int count = GET_CHILD_MAX_COUNT(p);
//debug("p->name:%s, max child count:%d", p->name, count);
for(i = 0; i < count; i++)
{
if(IS_EMPTY(p->child + i))
return p->child + i;
}
}
return NULL;
}
static cfg_node* find_add_node(cfg_node* p, const char* name)
{
int i = -1;
cfg_node* node = NULL;
//debug("in, p->name:%s, p->bytes:%d, adding child:%s", p->name, p->bytes, name);
if((i = find_inode(p, name)) < 0)
{
if(!(node = find_free_node(p)))
{
int old_size = alloc_node(p, CFG_GROW_SIZE);
if(old_size >= 0)
{
i = GET_NODE_COUNT(old_size);
node = &p->child[i];
}
}
}
else node = &p->child[i];
if(!node->name)
node->name = strdup(name);
//debug("out");
return node;
}
static int set_node(const char* section, const char* key, const char* name,
const char* value, short bytes, short type)
{
int si = -1, ki = -1, vi = -1;
cfg_node* section_node = NULL;
//debug("in");
//dump_node("root", &root);
if((section_node = find_add_node(&root, section)))
{
//dump_node("section node", section_node);
cfg_node* key_node;
if((key_node = find_add_node(section_node, key)))
{
//dump_node("key node", key_node);
cfg_node* value_node;
if((value_node = find_add_node(key_node, name)))
{
//dump_node("value node", value_node);
if(value_node->bytes < bytes)
{
if(value_node->value)
free(value_node->value);
value_node->value = (char*)malloc(bytes);
if(value_node->value)
value_node->bytes = bytes;
else
{
BTIF_TRACE_ERROR0("not enough memory!");
value_node->bytes = 0;
return FALSE;
}
}
if(value_node->value && value != NULL && bytes > 0)
memcpy(value_node->value, value, bytes);
value_node->type = type;
value_node->used = bytes;
//dump_node("changed value node", value_node);
return TRUE;
}
}
}
return FALSE;
}
static cfg_node* find_node(const char* section, const char* key, const char* name)
{
int si = -1, ki = -1, vi = -1;
if((si = find_inode(&root, section)) >= 0)
{
cfg_node* section_node = &root.child[si];
if(key)
{
//dump_node("found section node", section_node);
if((ki = find_inode(section_node, key)) >= 0)
{
cfg_node* key_node = &section_node->child[ki];
//dump_node("found key node", key_node);
if(name)
{
if((vi = find_inode(key_node, name)) >= 0)
{
//dump_node("found value node", &key_node->child[vi]);
return &key_node->child[vi];
}
//debug("value node:%s not found", name);
return NULL;
}
return key_node;
}
//debug("key node:%s not found", key);
return NULL;
}
return section_node;
}
//debug("section node:%s not found", section);
return NULL;
}
static short find_next_node(const cfg_node* p, short start, char* name, int* bytes)
{
asrt(0 <= start && start < GET_CHILD_MAX_COUNT(p));
//debug("in, start:%d, max child count:%d", start, GET_CHILD_MAX_COUNT(p));
//dump_node("find_next_node, parent", p);
short next = -1;
if(name) *name = 0;
if(0 <= start && start < GET_CHILD_MAX_COUNT(p))
{
int i;
for(i = start; i < GET_CHILD_MAX_COUNT(p); i++)
{
cfg_node* child = &p->child[i];
if(child->name)
{
int name_bytes = strlen(child->name) + 1;
if(name && bytes && *bytes >= name_bytes)
{
memcpy(name, child->name, name_bytes);
if(i + 1 < GET_CHILD_MAX_COUNT(p))
next = (short)(i + 1);
*bytes = name_bytes;
}
else if(bytes)
{
//debug("not enough room to copy the name, size in:%d, size needed:%d", *bytes, name_bytes);
*bytes = name_bytes;
}
break;
}
}
}
return next;
}
static int remove_node(const char* section, const char* key, const char* name)
{
short si = -1, ki = -1, vi = -1;
if((si = find_inode(&root, section)) >= 0)
{
cfg_node* section_node = &root.child[si];
if((ki = find_inode(section_node, key)) >= 0)
{
cfg_node* key_node = &section_node->child[ki];
if(name == NULL)
{
int count = GET_CHILD_MAX_COUNT(key_node);
int i;
for(i = 0; i < count; i++)
free_node(&key_node->child[i]);
free_node(key_node);
return TRUE;
}
else if((vi = find_inode(key_node, name)) >= 0)
{
//debug("remove value:%s", key_node->child[vi].name);
free_node(&key_node->child[vi]);
return TRUE;
}
}
}
return FALSE;
}
static int save_cfg()
{
const char* file_name = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT;
const char* file_name_new = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT_NEW;
const char* file_name_old = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT_OLD;
int ret = FALSE;
if(access(file_name_old, F_OK) == 0)
unlink(file_name_old);
if(access(file_name_new, F_OK) == 0)
unlink(file_name_new);
if(btif_config_save_file(file_name_new))
{
cached_change = 0;
chown(file_name_new, -1, AID_NET_BT_STACK);
chmod(file_name_new, 0660);
rename(file_name, file_name_old);
rename(file_name_new, file_name);
ret = TRUE;
}
else BTIF_TRACE_ERROR0("btif_config_save_file failed");
return ret;
}
static int load_bluez_cfg()
{
char adapter_path[256];
if(load_bluez_adapter_info(adapter_path, sizeof(adapter_path)))
{
if(load_bluez_linkkeys(adapter_path))
return TRUE;
}
return FALSE;
}
static void remove_bluez_cfg()
{
rename(BLUEZ_PATH, BLUEZ_PATH_BAK);
}
static void clean_newline_char()
{
char kname[128], vname[128];
short kpos = 0;
int kname_size, vname_size;
vname[0] = 0;
vname_size = sizeof(vname);
//BTIF_TRACE_DEBUG0("removing newline at the end of the adapter and device name");
if(btif_config_get_str("Local", "Adapter", "Name", vname, &vname_size) &&
vname_size > 2)
{
if(vname[vname_size - 2] == '\n')
{
BTIF_TRACE_DEBUG1("remove newline at the end of the adapter name:%s", vname);
vname[vname_size - 2] = 0;
btif_config_set_str("Local", "Adapter", "Name", vname);
}
}
do
{
kname_size = sizeof(kname);
kname[0] = 0;
kpos = btif_config_next_key(kpos, "Remote", kname, &kname_size);
//BTIF_TRACE_DEBUG2("Remote device:%s, size:%d", kname, kname_size);
vname_size = sizeof(vname);
vname[0] = 0;
if(btif_config_get_str("Remote", kname, "Name", vname, &vname_size) &&
vname_size > 2)
{
BTIF_TRACE_DEBUG1("remote device name:%s", vname);
if(vname[vname_size - 2] == '\n')
{
BTIF_TRACE_DEBUG1("remove newline at the end of the device name:%s", vname);
vname[vname_size - 2] = 0;
btif_config_set_str("Remote", kname, "Name", vname);
}
}
} while(kpos != -1);
}
static void load_cfg()
{
const char* file_name = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT;
const char* file_name_new = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT_NEW;
const char* file_name_old = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT_OLD;
if(!btif_config_load_file(file_name))
{
unlink(file_name);
if(!btif_config_load_file(file_name_old))
{
unlink(file_name_old);
if(load_bluez_cfg() && save_cfg())
remove_bluez_cfg();
}
}
int bluez_migration_done = 0;
btif_config_get_int("Local", "Adapter", "BluezMigrationDone", &bluez_migration_done);
if(!bluez_migration_done)
{
//clean the new line char at the end of the device name. Caused by bluez config import bug
clean_newline_char();
btif_config_set_int("Local", "Adapter", "BluezMigrationDone", 1);
btif_config_save();
}
}
static void cfg_cmd_callback(int cmd_fd, int type, int size, uint32_t user_id)
{
//BTIF_TRACE_DEBUG2("cmd type:%d, size:%d", type, size);
switch(type)
{
case CFG_CMD_SAVE:
lock_slot(&slot_lock);
save_cfg();
unlock_slot(&slot_lock);
break;
}
}
#ifdef UNIT_TEST
static void cfg_test_load()
{
load_cfg();
char kname[128], vname[128];
short kpos, vpos;
int kname_size, vname_size;
BTIF_TRACE_DEBUG0("list all remote devices values:");
kname_size = sizeof(kname);
kname[0] = 0;
kpos = 0;
do
{
kpos = btif_config_next_key(kpos, "Remote Devices", kname, &kname_size);
BTIF_TRACE_DEBUG2("Remote devices:%s, size:%d", kname, kname_size);
vpos = 0;
vname[0] = 0;
vname_size = sizeof(vname);
while((vpos = btif_config_next_value(vpos, "Remote Devices", kname, vname, &vname_size)) != -1)
{
char v[128] = {0};
int vtype = BTIF_CFG_TYPE_STR;
int vsize = sizeof(v);
int ret = btif_config_get("Remote Devices", kname, vname, v, &vsize, &vtype);
BTIF_TRACE_DEBUG6("btif_config_get return:%d, Remote devices:%s, value name:%s, value:%s, value size:%d, type:0x%x",
ret, kname, vname, v, vsize, vtype);
vname[0] = 0;
vname_size = sizeof(vname);
}
kname[0] = 0;
kname_size = sizeof(kname);
} while(kpos != -1);
}
static void cfg_test_write()
{
int i;
char key[128];
const char* section;
char link_key[64];
for(i = 0; i < (int)sizeof(link_key); i++)
link_key[i] = i;
for(i = 0; i < 100; i++)
{
sprintf(key, "00:22:5F:97:56:%02d", i);
link_key[0] = i;
section = "Remote Devices";
btif_config_set_str(section, key, "class", "smart phone");
btif_config_set(section, key, "link keys", link_key, sizeof(link_key), BTIF_CFG_TYPE_BIN);
btif_config_set_int(section, key, "connect time out", i);
}
btif_config_save();
}
static void cfg_test_read()
{
//debug("in");
char class[128] = {0};
char link_key[128] = {0};
int size, type;
char key[128];
const char* section;
int ret, i;
for(i = 0; i < 100; i++)
{
sprintf(key, "00:22:5F:97:56:%02d", i);
section = "Remote Devices";
size = sizeof(class);
ret = btif_config_get_str(section, key, "class", class, &size);
BTIF_TRACE_DEBUG3("btif_config_get_str return:%d, Remote devices:%s, class:%s", ret, key, class);
size = sizeof(link_key);
type = BTIF_CFG_TYPE_BIN;
ret = btif_config_get(section, key, "link keys", link_key, &size, &type);
//debug("btif_config_get return:%d, Remote devices:%s, link key:%x, %x",
// ret, key, *(int *)link_key, *((int *)link_key + 1));
int timeout;
ret = btif_config_get_int(section, key, "connect time out", &timeout);
//debug("btif_config_get_int return:%d, Remote devices:%s, connect time out:%d", ret, key, timeout);
}
// debug("testing btif_config_remove");
size = sizeof(class);
type = BTIF_CFG_TYPE_STR;
btif_config_set("Remote Devices", "00:22:5F:97:56:04", "Class Delete", class, strlen(class) + 1, BTIF_CFG_TYPE_STR);
btif_config_get("Remote Devices", "00:22:5F:97:56:04", "Class Delete", class, &size, &type);
// debug("Remote devices, 00:22:5F:97:56:04 Class Delete:%s", class);
btif_config_remove("Remote Devices", "00:22:5F:97:56:04", "Class Delete");
size = sizeof(class);
type = BTIF_CFG_TYPE_STR;
ret = btif_config_get("Remote Devices", "00:22:5F:97:56:04", "Class Delete", class, &size, &type);
// debug("after removed, btif_config_get ret:%d, Remote devices, 00:22:5F:97:56:04 Class Delete:%s", ret, class);
// debug("out");
}
#endif