| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <string.h> |
| #include <misc/printk.h> |
| |
| #include <zephyr.h> |
| #include <init.h> |
| #include <fs.h> |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLUETOOTH_DEBUG_HCI_CORE) |
| #include <bluetooth/log.h> |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/storage.h> |
| |
| #define STORAGE_ROOT "/bt" |
| |
| /* Required file name length for full storage support. If the maximum |
| * file name length supported by the chosen file system is less than |
| * this value, then only local keys are supported (/bt/abcd). |
| */ |
| #define STORAGE_FILE_NAME_LEN 13 |
| |
| #if MAX_FILE_NAME >= STORAGE_FILE_NAME_LEN |
| /* /bt/aabbccddeeff0/abcd */ |
| #define STORAGE_PATH_MAX 23 |
| #else |
| /* /bt/abcd */ |
| #define STORAGE_PATH_MAX 9 |
| #endif |
| |
| enum storage_access { |
| STORAGE_READ, |
| STORAGE_WRITE |
| }; |
| |
| static int storage_open(const bt_addr_le_t *addr, uint16_t key, |
| enum storage_access access, fs_file_t *file) |
| { |
| char path[STORAGE_PATH_MAX]; |
| |
| if (addr) { |
| #if MAX_FILE_NAME >= STORAGE_FILE_NAME_LEN |
| int len; |
| |
| len = snprintk(path, sizeof(path), |
| STORAGE_ROOT "/%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%u", |
| addr->a.val[5], addr->a.val[4], addr->a.val[3], |
| addr->a.val[2], addr->a.val[1], addr->a.val[0], |
| addr->type); |
| |
| /* Create the subdirectory if necessary */ |
| if (access == STORAGE_WRITE) { |
| struct fs_dirent entry; |
| int err; |
| |
| err = fs_stat(path, &entry); |
| if (err) { |
| err = fs_mkdir(path); |
| if (err) { |
| return err; |
| } |
| } |
| } |
| |
| snprintk(path + len, sizeof(path) - len, "/%04x", key); |
| #else |
| return -ENAMETOOLONG; |
| #endif |
| } else { |
| snprintk(path, sizeof(path), STORAGE_ROOT "/%04x", key); |
| } |
| |
| return fs_open(file, path); |
| } |
| |
| static ssize_t storage_read(const bt_addr_le_t *addr, uint16_t key, void *data, |
| size_t length) |
| { |
| fs_file_t file; |
| ssize_t ret; |
| |
| ret = storage_open(addr, key, STORAGE_READ, &file); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = fs_read(&file, data, length); |
| fs_close(&file); |
| |
| return ret; |
| } |
| |
| static ssize_t storage_write(const bt_addr_le_t *addr, uint16_t key, |
| const void *data, size_t length) |
| { |
| fs_file_t file; |
| ssize_t ret; |
| |
| ret = storage_open(addr, key, STORAGE_WRITE, &file); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = fs_write(&file, data, length); |
| fs_close(&file); |
| |
| return ret; |
| } |
| |
| static int unlink_recursive(char path[STORAGE_PATH_MAX]) |
| { |
| size_t path_len; |
| fs_dir_t dir; |
| int err; |
| |
| err = fs_opendir(&dir, path); |
| if (err) { |
| return err; |
| } |
| |
| /* We calculate this up-front so we can keep reusing the same |
| * buffer for the path when recursing. |
| */ |
| path_len = strlen(path); |
| |
| while (1) { |
| struct fs_dirent entry; |
| |
| err = fs_readdir(&dir, &entry); |
| if (err) { |
| break; |
| } |
| |
| if (entry.name[0] == '\0') { |
| break; |
| } |
| |
| snprintk(path + path_len, STORAGE_PATH_MAX - path_len, "/%s", |
| entry.name); |
| |
| if (entry.type == FS_DIR_ENTRY_DIR) { |
| err = unlink_recursive(path); |
| } else { |
| err = fs_unlink(path); |
| } |
| |
| if (err) { |
| break; |
| } |
| } |
| |
| fs_closedir(&dir); |
| |
| /* Return to the original value */ |
| path[path_len] = '\0'; |
| |
| fs_unlink(path); |
| |
| return err; |
| } |
| |
| static int storage_clear(const bt_addr_le_t *addr) |
| { |
| char path[STORAGE_PATH_MAX]; |
| int err; |
| |
| if (addr) { |
| #if MAX_FILE_NAME >= STORAGE_FILE_NAME_LEN |
| snprintk(path, STORAGE_PATH_MAX, |
| STORAGE_ROOT "/%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%u", |
| addr->a.val[5], addr->a.val[4], addr->a.val[3], |
| addr->a.val[2], addr->a.val[1], addr->a.val[0], |
| addr->type); |
| |
| return unlink_recursive(path); |
| #else |
| return -ENAMETOOLONG; |
| #endif |
| } |
| |
| /* unlink_recursive() uses the given path as a buffer for |
| * constructing sub-paths, so we can't give it a string literal |
| * such as STORAGE_ROOT directly. |
| */ |
| strcpy(path, STORAGE_ROOT); |
| |
| err = unlink_recursive(path); |
| if (err) { |
| return err; |
| } |
| |
| return fs_mkdir(STORAGE_ROOT); |
| } |
| |
| static int storage_init(struct device *unused) |
| { |
| static const struct bt_storage storage = { |
| .read = storage_read, |
| .write = storage_write, |
| .clear = storage_clear |
| }; |
| struct fs_dirent entry; |
| int err; |
| |
| err = fs_stat(STORAGE_ROOT, &entry); |
| if (err) { |
| BT_WARN("%s doesn't seem to exist (err %d). Creating it.", |
| STORAGE_ROOT, err); |
| |
| err = fs_mkdir(STORAGE_ROOT); |
| if (err) { |
| BT_ERR("Unable to create %s (err %d)", |
| STORAGE_ROOT, err); |
| return err; |
| } |
| } |
| |
| bt_storage_register(&storage); |
| |
| return 0; |
| } |
| |
| SYS_INIT(storage_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |