zephyr/subsys/settings/src/settings_file.c
Marcin Niestroj 2d3365200c settings: file: drop CONFIG_SETTINGS_FILE_DIR
There is already CONFIG_SETTINGS_FILE_PATH, which is set to full file path,
while CONFIG_SETTINGS_FILE_DIR is required to be set to its parent
directory. This is redundant, as parent directory path can be easily found
out either during runtime or optionally during buildtime by CMake.

CONFIG_SETTINGS_FILE_DIR was actually introduced recently after Zephyr 3.2
release as a replacement of deprecated CONFIG_SETTINGS_FS_DIR. This means,
that there is no need to deprecate it for 3.3 release and dropping it
should be fine. Adjust 3.3 release notes accordingly, so that
CONFIG_SETTINGS_FILE_PATH will be used directly.

This patch stops using deprecated CONFIG_SETTINGS_FS_DIR. There is actually
no value in respecting it, as setting anything other than parent directory
of CONFIG_SETTINGS_FS_FILE makes no sense.

There is actually one use of CONFIG_SETTINGS_FILE_DIR in file backend
tests, to derive directory for files containing tested settings.
CONFIG_SETTINGS_FILE_PATH is not used there, so it makes little sense to
derive directory name from it. Instead, just use hardcoded "/settings"
subdirectory, as this was the default value of CONFIG_SETTINGS_FILE_DIR.

Deriving parent directory can be done either in runtime or in
buildtime (e.g. using some helper CMake function). Doing it in runtime
however allows to create directory recursively (which this patch actually
implements), e.g. for some more nested FS tree structure. Additionally it
will simplify migration of settings configuration from Kconfig to
device-tree (yet to be developed feature).

Signed-off-by: Marcin Niestroj <m.niestroj@emb.dev>
2022-12-14 14:11:03 +01:00

592 lines
12 KiB
C

/*
* Copyright (c) 2018 Nordic Semiconductor ASA
* Copyright (c) 2015 Runtime Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdbool.h>
#include <zephyr/kernel.h>
#include <zephyr/fs/fs.h>
#include <zephyr/settings/settings.h>
#include "settings/settings_file.h"
#include "settings_priv.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL);
#ifdef CONFIG_SETTINGS_FS
#define SETTINGS_FILE_MAX_LINES CONFIG_SETTINGS_FS_MAX_LINES
#define SETTINGS_FILE_PATH CONFIG_SETTINGS_FS_FILE
#else
#define SETTINGS_FILE_MAX_LINES CONFIG_SETTINGS_FILE_MAX_LINES
#define SETTINGS_FILE_PATH CONFIG_SETTINGS_FILE_PATH
#endif
int settings_backend_init(void);
void settings_mount_file_backend(struct settings_file *cf);
static int settings_file_load(struct settings_store *cs,
const struct settings_load_arg *arg);
static int settings_file_save(struct settings_store *cs, const char *name,
const char *value, size_t val_len);
static void *settings_file_storage_get(struct settings_store *cs);
static const struct settings_store_itf settings_file_itf = {
.csi_load = settings_file_load,
.csi_save = settings_file_save,
.csi_storage_get = settings_file_storage_get
};
/*
* Register a file to be a source of configuration.
*/
int settings_file_src(struct settings_file *cf)
{
if (!cf->cf_name) {
return -EINVAL;
}
cf->cf_store.cs_itf = &settings_file_itf;
settings_src_register(&cf->cf_store);
return 0;
}
/*
* Register a file to be a destination of configuration.
*/
int settings_file_dst(struct settings_file *cf)
{
if (!cf->cf_name) {
return -EINVAL;
}
cf->cf_store.cs_itf = &settings_file_itf;
settings_dst_register(&cf->cf_store);
return 0;
}
/**
* @brief Check if there is any duplicate of the current setting
*
* This function checks if there is any duplicated data further in the buffer.
*
* @param entry_ctx Current entry context
* @param name The name of the current entry
*
* @retval false No duplicates found
* @retval true Duplicate found
*/
static bool settings_file_check_duplicate(
const struct line_entry_ctx *entry_ctx,
const char * const name)
{
struct line_entry_ctx entry2_ctx = *entry_ctx;
/* Searching the duplicates */
while (settings_next_line_ctx(&entry2_ctx) == 0) {
char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
size_t name2_len;
if (entry2_ctx.len == 0) {
break;
}
if (settings_line_name_read(name2, sizeof(name2), &name2_len,
&entry2_ctx)) {
continue;
}
name2[name2_len] = '\0';
if (!strcmp(name, name2)) {
return true;
}
}
return false;
}
static int read_entry_len(const struct line_entry_ctx *entry_ctx, off_t off)
{
if (off >= entry_ctx->len) {
return 0;
}
return entry_ctx->len - off;
}
static int settings_file_load_priv(struct settings_store *cs, line_load_cb cb,
void *cb_arg, bool filter_duplicates)
{
struct settings_file *cf = CONTAINER_OF(cs, struct settings_file, cf_store);
struct fs_file_t file;
int lines;
int rc;
struct line_entry_ctx entry_ctx = {
.stor_ctx = (void *)&file,
.seek = 0,
.len = 0 /* unknown length */
};
lines = 0;
fs_file_t_init(&file);
rc = fs_open(&file, cf->cf_name, FS_O_READ);
if (rc != 0) {
if (rc == -ENOENT) {
return -ENOENT;
}
return -EINVAL;
}
while (1) {
char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
size_t name_len;
bool pass_entry = true;
rc = settings_next_line_ctx(&entry_ctx);
if (rc || entry_ctx.len == 0) {
break;
}
rc = settings_line_name_read(name, sizeof(name), &name_len,
&entry_ctx);
if (rc || name_len == 0) {
break;
}
name[name_len] = '\0';
if (filter_duplicates &&
(!read_entry_len(&entry_ctx, name_len+1) ||
settings_file_check_duplicate(&entry_ctx, name))) {
pass_entry = false;
}
/*name, val-read_cb-ctx, val-off*/
/* take into account '=' separator after the name */
if (pass_entry) {
cb(name, (void *)&entry_ctx, name_len + 1, cb_arg);
}
lines++;
}
rc = fs_close(&file);
cf->cf_lines = lines;
return rc;
}
/*
* Called to load configuration items.
*/
static int settings_file_load(struct settings_store *cs,
const struct settings_load_arg *arg)
{
return settings_file_load_priv(cs,
settings_line_load_cb,
(void *)arg,
true);
}
static void settings_tmpfile(char *dst, const char *src, char *pfx)
{
int len;
int pfx_len;
len = strlen(src);
pfx_len = strlen(pfx);
if (len + pfx_len >= SETTINGS_FILE_NAME_MAX) {
len = SETTINGS_FILE_NAME_MAX - pfx_len - 1;
}
memcpy(dst, src, len);
memcpy(dst + len, pfx, pfx_len);
dst[len + pfx_len] = '\0';
}
static int settings_file_create_or_replace(struct fs_file_t *zfp,
const char *file_name)
{
struct fs_dirent entry;
if (fs_stat(file_name, &entry) == 0) {
if (entry.type == FS_DIR_ENTRY_FILE) {
if (fs_unlink(file_name)) {
return -EIO;
}
} else {
return -EISDIR;
}
}
return fs_open(zfp, file_name, FS_O_CREATE | FS_O_RDWR);
}
/*
* Try to compress configuration file by keeping unique names only.
*/
static int settings_file_save_and_compress(struct settings_file *cf,
const char *name, const char *value,
size_t val_len)
{
int rc, rc2;
struct fs_file_t rf;
struct fs_file_t wf;
char tmp_file[SETTINGS_FILE_NAME_MAX];
char name1[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN];
char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN];
struct line_entry_ctx loc1 = {
.stor_ctx = &rf,
.seek = 0,
.len = 0 /* unknown length */
};
struct line_entry_ctx loc2;
struct line_entry_ctx loc3 = {
.stor_ctx = &wf
};
int copy;
int lines;
size_t new_name_len;
size_t val1_off;
fs_file_t_init(&rf);
fs_file_t_init(&wf);
if (fs_open(&rf, cf->cf_name, FS_O_CREATE | FS_O_RDWR) != 0) {
return -ENOEXEC;
}
settings_tmpfile(tmp_file, cf->cf_name, ".cmp");
if (settings_file_create_or_replace(&wf, tmp_file)) {
fs_close(&rf);
return -ENOEXEC;
}
lines = 0;
new_name_len = strlen(name);
while (1) {
rc = settings_next_line_ctx(&loc1);
if (rc || loc1.len == 0) {
/* try to amend new value to the compressed file */
break;
}
rc = settings_line_name_read(name1, sizeof(name1), &val1_off,
&loc1);
if (rc) {
/* try to process next line */
continue;
}
if (val1_off + 1 == loc1.len) {
/* Lack of a value so the record is a deletion-record */
/* No sense to copy empty entry from */
/* the oldest sector */
continue;
}
/* avoid copping value which will be overwritten by new value*/
if ((val1_off == new_name_len) &&
!memcmp(name1, name, val1_off)) {
continue;
}
loc2 = loc1;
copy = 1;
while (1) {
size_t val2_off;
rc = settings_next_line_ctx(&loc2);
if (rc || loc2.len == 0) {
/* try to amend new value to */
/* the compressed file */
break;
}
rc = settings_line_name_read(name2, sizeof(name2),
&val2_off, &loc2);
if (rc) {
/* try to process next line */
continue;
}
if ((val1_off == val2_off) &&
!memcmp(name1, name2, val1_off)) {
copy = 0; /* newer version doesn't exist */
break;
}
}
if (!copy) {
continue;
}
loc2 = loc1;
loc2.len += 2;
loc2.seek -= 2;
rc = settings_line_entry_copy(&loc3, 0, &loc2, 0, loc2.len);
if (rc) {
/* compressed file might be corrupted */
goto end_rolback;
}
lines++;
}
/* at last store the new value */
rc = settings_line_write(name, value, val_len, 0, &loc3);
if (rc) {
/* compressed file might be corrupted */
goto end_rolback;
}
rc = fs_close(&wf);
rc2 = fs_close(&rf);
if (rc == 0 && rc2 == 0 && fs_unlink(cf->cf_name) == 0) {
if (fs_rename(tmp_file, cf->cf_name)) {
return -ENOENT;
}
cf->cf_lines = lines + 1;
} else {
rc = -EIO;
}
/*
* XXX at settings_file_load(), look for .cmp if actual file does not
* exist.
*/
return 0;
end_rolback:
(void)fs_close(&wf);
if (fs_close(&rf) == 0) {
(void)fs_unlink(tmp_file);
}
return -EIO;
}
static int settings_file_save_priv(struct settings_store *cs, const char *name,
const char *value, size_t val_len)
{
struct settings_file *cf = CONTAINER_OF(cs, struct settings_file, cf_store);
struct line_entry_ctx entry_ctx;
struct fs_file_t file;
int rc2;
int rc;
if (!name) {
return -EINVAL;
}
fs_file_t_init(&file);
if (cf->cf_maxlines && (cf->cf_lines + 1 >= cf->cf_maxlines)) {
/*
* Compress before config file size exceeds
* the max number of lines.
*/
return settings_file_save_and_compress(cf, name, value,
val_len);
}
/*
* Open the file to add this one value.
*/
rc = fs_open(&file, cf->cf_name, FS_O_CREATE | FS_O_RDWR);
if (rc == 0) {
rc = fs_seek(&file, 0, FS_SEEK_END);
if (rc == 0) {
entry_ctx.stor_ctx = &file;
rc = settings_line_write(name, value, val_len, 0,
(void *)&entry_ctx);
if (rc == 0) {
cf->cf_lines++;
}
}
rc2 = fs_close(&file);
if (rc == 0) {
rc = rc2;
}
}
return rc;
}
/*
* Called to save configuration.
*/
static int settings_file_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_file_load_priv(cs, settings_line_dup_check_cb, &cdca, false);
if (cdca.is_dup == 1) {
return 0;
}
return settings_file_save_priv(cs, name, value, val_len);
}
static int read_handler(void *ctx, off_t off, char *buf, size_t *len)
{
struct line_entry_ctx *entry_ctx = ctx;
struct fs_file_t *file = entry_ctx->stor_ctx;
ssize_t r_len;
int rc;
/* 0 is reserved for reading the length-field only */
if (entry_ctx->len != 0) {
if (off >= entry_ctx->len) {
*len = 0;
return 0;
}
if ((off + *len) > entry_ctx->len) {
*len = entry_ctx->len - off;
}
}
rc = fs_seek(file, entry_ctx->seek + off, FS_SEEK_SET);
if (rc) {
goto end;
}
r_len = fs_read(file, buf, *len);
if (r_len >= 0) {
*len = r_len;
rc = 0;
} else {
rc = r_len;
}
end:
return rc;
}
static size_t get_len_cb(void *ctx)
{
struct line_entry_ctx *entry_ctx = ctx;
return entry_ctx->len;
}
static int write_handler(void *ctx, off_t off, char const *buf, size_t len)
{
struct line_entry_ctx *entry_ctx = ctx;
struct fs_file_t *file = entry_ctx->stor_ctx;
int rc;
/* append to file only */
rc = fs_seek(file, 0, FS_SEEK_END);
if (rc == 0) {
rc = fs_write(file, buf, len);
if (rc > 0) {
rc = 0;
}
}
return rc;
}
void settings_mount_file_backend(struct settings_file *cf)
{
settings_line_io_init(read_handler, write_handler, get_len_cb, 1);
}
static int mkdir_if_not_exists(const char *path)
{
struct fs_dirent entry;
int err;
err = fs_stat(path, &entry);
if (err == -ENOENT) {
return fs_mkdir(path);
} else if (err) {
return err;
}
if (entry.type != FS_DIR_ENTRY_DIR) {
return -EEXIST;
}
return 0;
}
static int mkdir_for_file(const char *file_path)
{
char dir_path[SETTINGS_FILE_NAME_MAX];
int err;
for (size_t i = 0; file_path[i] != '\0'; i++) {
if (i > 0 && file_path[i] == '/') {
dir_path[i] = '\0';
err = mkdir_if_not_exists(dir_path);
if (err) {
return err;
}
}
dir_path[i] = file_path[i];
}
return 0;
}
int settings_backend_init(void)
{
static struct settings_file config_init_settings_file = {
.cf_name = SETTINGS_FILE_PATH,
.cf_maxlines = SETTINGS_FILE_MAX_LINES
};
int rc;
rc = settings_file_src(&config_init_settings_file);
if (rc) {
return rc;
}
rc = settings_file_dst(&config_init_settings_file);
if (rc) {
return rc;
}
settings_mount_file_backend(&config_init_settings_file);
/*
* Must be called after root FS has been initialized.
*/
return mkdir_for_file(config_init_settings_file.cf_name);
}
static void *settings_file_storage_get(struct settings_store *cs)
{
struct settings_file *cf = CONTAINER_OF(cs, struct settings_file, cf_store);
return (void *)cf->cf_name;
}