| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * Copyright (c) 2015 Runtime Inc |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <fcb.h> |
| #include <string.h> |
| |
| #include "settings/settings.h" |
| #include "settings/settings_fcb.h" |
| #include "settings_priv.h" |
| |
| #include <logging/log.h> |
| LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL); |
| |
| #define SETTINGS_FCB_VERS 1 |
| |
| struct settings_fcb_load_cb_arg { |
| line_load_cb cb; |
| void *cb_arg; |
| }; |
| |
| static int settings_fcb_load(struct settings_store *cs); |
| static int settings_fcb_save(struct settings_store *cs, const char *name, |
| const char *value, size_t val_len); |
| |
| static struct settings_store_itf settings_fcb_itf = { |
| .csi_load = settings_fcb_load, |
| .csi_save = settings_fcb_save, |
| }; |
| |
| int settings_fcb_src(struct settings_fcb *cf) |
| { |
| int rc; |
| |
| cf->cf_fcb.f_version = SETTINGS_FCB_VERS; |
| cf->cf_fcb.f_scratch_cnt = 1; |
| |
| while (1) { |
| rc = fcb_init(DT_FLASH_AREA_STORAGE_ID, &cf->cf_fcb); |
| if (rc) { |
| return -EINVAL; |
| } |
| |
| /* |
| * Check if system was reset in middle of emptying a sector. |
| * This situation is recognized by checking if the scratch block |
| * is missing. |
| */ |
| if (fcb_free_sector_cnt(&cf->cf_fcb) < 1) { |
| |
| rc = flash_area_erase(cf->cf_fcb.fap, |
| cf->cf_fcb.f_active.fe_sector->fs_off, |
| cf->cf_fcb.f_active.fe_sector->fs_size); |
| |
| if (rc) { |
| return -EIO; |
| } |
| } else { |
| break; |
| } |
| } |
| |
| cf->cf_store.cs_itf = &settings_fcb_itf; |
| settings_src_register(&cf->cf_store); |
| |
| return 0; |
| } |
| |
| int settings_fcb_dst(struct settings_fcb *cf) |
| { |
| cf->cf_store.cs_itf = &settings_fcb_itf; |
| settings_dst_register(&cf->cf_store); |
| |
| return 0; |
| } |
| |
| static int settings_fcb_load_cb(struct fcb_entry_ctx *entry_ctx, void *arg) |
| { |
| struct settings_fcb_load_cb_arg *argp; |
| char buf[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; |
| int rc; |
| |
| argp = (struct settings_fcb_load_cb_arg *)arg; |
| |
| size_t len_read; |
| |
| rc = settings_line_name_read(buf, sizeof(buf), &len_read, |
| (void *)&entry_ctx->loc); |
| if (rc) { |
| return 0; |
| } |
| buf[len_read] = '\0'; |
| |
| /*name, val-read_cb-ctx, val-off*/ |
| /* take into account '=' separator after the name */ |
| argp->cb(buf, (void *)&entry_ctx->loc, len_read + 1, argp->cb_arg); |
| return 0; |
| } |
| |
| static int settings_fcb_load_priv(struct settings_store *cs, line_load_cb cb, |
| void *cb_arg) |
| { |
| struct settings_fcb *cf = (struct settings_fcb *)cs; |
| struct settings_fcb_load_cb_arg arg; |
| int rc; |
| |
| arg.cb = cb; |
| arg.cb_arg = cb_arg; |
| rc = fcb_walk(&cf->cf_fcb, 0, settings_fcb_load_cb, &arg); |
| if (rc) { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int settings_fcb_load(struct settings_store *cs) |
| { |
| return settings_fcb_load_priv(cs, settings_line_load_cb, NULL); |
| } |
| |
| |
| static int read_handler(void *ctx, off_t off, char *buf, size_t *len) |
| { |
| struct fcb_entry_ctx *entry_ctx = ctx; |
| |
| if (off >= entry_ctx->loc.fe_data_len) { |
| *len = 0; |
| return 0; |
| } |
| |
| if ((off + *len) > entry_ctx->loc.fe_data_len) { |
| *len = entry_ctx->loc.fe_data_len - off; |
| } |
| |
| return flash_area_read(entry_ctx->fap, |
| FCB_ENTRY_FA_DATA_OFF(entry_ctx->loc) + off, buf, |
| *len); |
| } |
| |
| static void settings_fcb_compress(struct settings_fcb *cf) |
| { |
| int rc; |
| struct fcb_entry_ctx loc1; |
| struct fcb_entry_ctx loc2; |
| char name1[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN]; |
| char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN]; |
| int copy; |
| u8_t rbs; |
| |
| rc = fcb_append_to_scratch(&cf->cf_fcb); |
| if (rc) { |
| return; /* XXX */ |
| } |
| |
| rbs = flash_area_align(cf->cf_fcb.fap); |
| |
| loc1.fap = cf->cf_fcb.fap; |
| |
| loc1.loc.fe_sector = NULL; |
| loc1.loc.fe_elem_off = 0U; |
| |
| while (fcb_getnext(&cf->cf_fcb, &loc1.loc) == 0) { |
| if (loc1.loc.fe_sector != cf->cf_fcb.f_oldest) { |
| break; |
| } |
| |
| size_t val1_off; |
| |
| rc = settings_line_name_read(name1, sizeof(name1), &val1_off, |
| &loc1); |
| if (rc) { |
| continue; |
| } |
| |
| if (val1_off + 1 == loc1.loc.fe_data_len) { |
| /* Lack of a value so the record is a deletion-record */ |
| /* No sense to copy empty entry from */ |
| /* the oldest sector */ |
| continue; |
| } |
| |
| loc2 = loc1; |
| copy = 1; |
| |
| while (fcb_getnext(&cf->cf_fcb, &loc2.loc) == 0) { |
| size_t val2_off; |
| |
| rc = settings_line_name_read(name2, sizeof(name2), |
| &val2_off, &loc2); |
| if (rc) { |
| continue; |
| } |
| |
| if ((val1_off == val2_off) && |
| !memcmp(name1, name2, val1_off)) { |
| copy = 0; |
| break; |
| } |
| } |
| if (!copy) { |
| continue; |
| } |
| |
| /* |
| * Can't find one. Must copy. |
| */ |
| rc = fcb_append(&cf->cf_fcb, loc1.loc.fe_data_len, &loc2.loc); |
| if (rc) { |
| continue; |
| } |
| |
| rc = settings_line_entry_copy(&loc2, 0, &loc1, 0, |
| loc1.loc.fe_data_len); |
| if (rc) { |
| continue; |
| } |
| rc = fcb_append_finish(&cf->cf_fcb, &loc2.loc); |
| |
| if (rc != 0) { |
| LOG_ERR("Failed to finish fcb_append (%d)", rc); |
| } |
| } |
| rc = fcb_rotate(&cf->cf_fcb); |
| |
| if (rc != 0) { |
| LOG_ERR("Failed to fcb rotate (%d)", rc); |
| } |
| } |
| |
| static size_t get_len_cb(void *ctx) |
| { |
| struct fcb_entry_ctx *entry_ctx = ctx; |
| |
| return entry_ctx->loc.fe_data_len; |
| } |
| |
| static int write_handler(void *ctx, off_t off, char const *buf, size_t len) |
| { |
| struct fcb_entry_ctx *entry_ctx = ctx; |
| |
| return flash_area_write(entry_ctx->fap, |
| FCB_ENTRY_FA_DATA_OFF(entry_ctx->loc) + off, |
| buf, len); |
| } |
| |
| /* ::csi_save implementation */ |
| static int settings_fcb_save_priv(struct settings_store *cs, const char *name, |
| const char *value, size_t val_len) |
| { |
| struct settings_fcb *cf = (struct settings_fcb *)cs; |
| struct fcb_entry_ctx loc; |
| int len; |
| int rc; |
| int i; |
| u8_t wbs; |
| |
| if (!name) { |
| return -EINVAL; |
| } |
| |
| wbs = cf->cf_fcb.f_align; |
| len = settings_line_len_calc(name, val_len); |
| |
| for (i = 0; i < cf->cf_fcb.f_sector_cnt - 1; i++) { |
| rc = fcb_append(&cf->cf_fcb, len, &loc.loc); |
| if (rc != FCB_ERR_NOSPACE) { |
| break; |
| } |
| settings_fcb_compress(cf); |
| } |
| if (rc) { |
| return -EINVAL; |
| } |
| |
| loc.fap = cf->cf_fcb.fap; |
| |
| rc = settings_line_write(name, value, val_len, 0, (void *)&loc); |
| |
| if (rc != -EIO) { |
| i = fcb_append_finish(&cf->cf_fcb, &loc.loc); |
| if (!rc) { |
| rc = i; |
| } |
| } |
| return rc; |
| } |
| |
| static int settings_fcb_save(struct settings_store *cs, const char *name, |
| const char *value, size_t val_len) |
| { |
| struct settings_line_dup_check_arg cdca; |
| |
| if (val_len > 0 && value == NULL) { |
| return -EINVAL; |
| } |
| |
| /* |
| * Check if we're writing the same value again. |
| */ |
| cdca.name = name; |
| cdca.val = (char *)value; |
| cdca.is_dup = 0; |
| cdca.val_len = val_len; |
| settings_fcb_load_priv(cs, settings_line_dup_check_cb, &cdca); |
| if (cdca.is_dup == 1) { |
| return 0; |
| } |
| return settings_fcb_save_priv(cs, name, (char *)value, val_len); |
| } |
| |
| void settings_mount_fcb_backend(struct settings_fcb *cf) |
| { |
| u8_t rbs; |
| |
| rbs = cf->cf_fcb.f_align; |
| |
| settings_line_io_init(read_handler, write_handler, get_len_cb, rbs); |
| } |