blob: 687773b6e5565705d92db748cd41fd2377bf8dd8 [file] [log] [blame]
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
* Copyright (c) 2015 Runtime Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <assert.h>
#include <zephyr.h>
#include <fs.h>
#include "settings/settings.h"
#include "settings/settings_file.h"
#include "settings_priv.h"
static int settings_file_load(struct settings_store *cs, load_cb cb,
void *cb_arg);
static int settings_file_save(struct settings_store *cs, const char *name,
const char *value);
static struct settings_store_itf settings_file_itf = {
.csi_load = settings_file_load,
.csi_save = settings_file_save,
};
/*
* 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;
}
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;
}
int settings_getnext_line(struct fs_file_t *file, char *buf, int blen,
off_t *loc)
{
int rc;
char *end;
rc = fs_seek(file, *loc, FS_SEEK_SET);
if (rc < 0) {
*loc = 0;
return -1;
}
rc = fs_read(file, buf, blen);
if (rc <= 0) {
*loc = 0;
return -1;
}
if (rc == blen) {
rc--;
}
buf[rc] = '\0';
end = strchr(buf, '\n');
if (end) {
*end = '\0';
} else {
end = strchr(buf, '\0');
}
blen = end - buf;
*loc += (blen + 1);
return blen;
}
/*
* Called to load configuration items. cb must be called for every configuration
* item found.
*/
static int settings_file_load(struct settings_store *cs, load_cb cb,
void *cb_arg)
{
struct settings_file *cf = (struct settings_file *)cs;
struct fs_file_t file;
off_t loc;
char tmpbuf[SETTINGS_MAX_NAME_LEN + SETTINGS_MAX_VAL_LEN +
SETTINGS_EXTRA_LEN];
char *name_str;
char *val_str;
int rc;
int lines;
rc = fs_open(&file, cf->cf_name);
if (rc != 0) {
return -EINVAL;
}
loc = 0;
lines = 0;
while (1) {
rc = settings_getnext_line(&file, tmpbuf, sizeof(tmpbuf), &loc);
if (loc == 0) {
break;
}
if (rc < 0) {
continue;
}
rc = settings_line_parse(tmpbuf, &name_str, &val_str);
if (rc != 0) {
continue;
}
lines++;
cb(name_str, val_str, cb_arg);
}
rc = fs_close(&file);
cf->cf_lines = lines;
return rc;
}
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);
}
/*
* Try to compress configuration file by keeping unique names only.
*/
void settings_file_compress(struct settings_file *cf)
{
int rc;
struct fs_file_t rf;
struct fs_file_t wf;
char tmp_file[SETTINGS_FILE_NAME_MAX];
char buf1[SETTINGS_MAX_NAME_LEN + SETTINGS_MAX_VAL_LEN +
SETTINGS_EXTRA_LEN];
char buf2[SETTINGS_MAX_NAME_LEN + SETTINGS_MAX_VAL_LEN +
SETTINGS_EXTRA_LEN];
off_t loc1, loc2;
char *name1, *val1;
char *name2, *val2;
int copy;
int len, len2;
int lines;
if (fs_open(&rf, cf->cf_name) != 0) {
return;
}
settings_tmpfile(tmp_file, cf->cf_name, ".cmp");
if (settings_file_create_or_replace(&wf, tmp_file)) {
fs_close(&rf);
return;
}
loc1 = 0;
lines = 0;
while (1) {
len = settings_getnext_line(&rf, buf1, sizeof(buf1), &loc1);
if (loc1 == 0 || len < 0) {
break;
}
rc = settings_line_parse(buf1, &name1, &val1);
if (rc) {
continue;
}
loc2 = loc1;
copy = 1;
while ((len2 = settings_getnext_line(&rf, buf2, sizeof(buf2),
&loc2)) > 0) {
rc = settings_line_parse(buf2, &name2, &val2);
if (rc) {
continue;
}
if (!strcmp(name1, name2)) {
copy = 0;
break;
}
}
if (!copy) {
continue;
}
/*
* Can't find one. Must copy.
*/
len = settings_line_make(buf2, sizeof(buf2), name1, val1);
if (len < 0 || len + 2 > sizeof(buf2)) {
continue;
}
buf2[len++] = '\n';
if (fs_write(&wf, buf2, len) != len) {
ARG_UNUSED(fs_close(&rf));
ARG_UNUSED(fs_close(&wf));
return;
}
lines++;
}
len = fs_close(&wf);
len2 = fs_close(&rf);
if (len == 0 && len2 == 0 && fs_unlink(cf->cf_name) == 0) {
ARG_UNUSED(fs_rename(tmp_file, cf->cf_name));
cf->cf_lines = lines;
/*
* XXX at settings_file_load(), look for .cmp if actual file does not
* exist.
*/
}
}
/*
* Called to save configuration.
*/
static int settings_file_save(struct settings_store *cs, const char *name,
const char *value)
{
struct settings_file *cf = (struct settings_file *)cs;
struct fs_file_t file;
char buf[SETTINGS_MAX_NAME_LEN + SETTINGS_MAX_VAL_LEN +
SETTINGS_EXTRA_LEN];
int len;
int rc2;
int rc;
if (!name) {
return -EINVAL;
}
if (cf->cf_maxlines && (cf->cf_lines + 1 >= cf->cf_maxlines)) {
/*
* Compress before config file size exceeds
* the max number of lines.
*/
settings_file_compress(cf);
}
len = settings_line_make(buf, sizeof(buf), name, value);
if (len < 0 || len + 2 > sizeof(buf)) {
return -EINVAL;
}
buf[len++] = '\n';
/*
* Open the file to add this one value.
*/
rc = fs_open(&file, cf->cf_name);
if (rc == 0) {
rc = fs_seek(&file, 0, FS_SEEK_END);
if (rc == 0) {
rc2 = fs_write(&file, buf, len);
if (rc2 == len) {
cf->cf_lines++;
}
}
rc2 = fs_close(&file);
if (rc == 0) {
rc = rc2;
}
}
return rc;
}