1 /*
2 * Copyright (c) 2023 Nordic Semiconductor
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "main.h"
8
9 #include <stdio.h>
10 #include <stddef.h>
11
12 #include <zephyr/kernel.h>
13 #include <zephyr/settings/settings.h>
14 #include <zephyr/types.h>
15 #include "errno.h"
16 #include "argparse.h"
17
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(settings_backend, 3);
20
21 #define SETTINGS_FILE setting_file
22 #define SETTINGS_FILE_TMP setting_file_tmp
23
24 #define ENTRY_LEN_SIZE (4)
25 #define ENTRY_NAME_MAX_LEN (SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN)
26 #define ENTRY_VAL_MAX_LEN (SETTINGS_MAX_VAL_LEN * 2)
27 #define READ_LEN_MAX (ENTRY_VAL_MAX_LEN + ENTRY_NAME_MAX_LEN + 2)
28
29 struct line_read_ctx {
30 int len;
31 const uint8_t *val;
32 };
33
34 static char setting_file[50];
35 static char setting_file_tmp[sizeof(setting_file) + 1];
36
entry_check_and_copy(FILE * fin,FILE * fout,const char * name)37 static int entry_check_and_copy(FILE *fin, FILE *fout, const char *name)
38 {
39 char line[READ_LEN_MAX + 1];
40 char name_tmp[strlen(name) + 2];
41
42 snprintk(name_tmp, sizeof(name_tmp), "%s=", name);
43
44 while (fgets(line, sizeof(line), fin) == line) {
45 if (strstr(line, name_tmp) != NULL) {
46 return 0;
47 }
48
49 if (fputs(line, fout) < 0) {
50 return -1;
51 }
52 };
53
54 return 0;
55 }
56
settings_line_read_cb(void * cb_arg,void * data,size_t len)57 static ssize_t settings_line_read_cb(void *cb_arg, void *data, size_t len)
58 {
59 struct line_read_ctx *valctx = (struct line_read_ctx *)cb_arg;
60
61 if ((valctx->len / 2) > len) {
62 return -ENOBUFS;
63 }
64
65 uint8_t *n = (uint8_t *) data;
66
67 len = valctx->len / 2;
68
69 for (int i = 0; i < len; i++, n++) {
70 if (sscanf(&valctx->val[i * 2], "%2hhx", n) != 1) {
71 return 0;
72 };
73 }
74
75 return len;
76 }
77
settings_custom_load(struct settings_store * cs,const struct settings_load_arg * arg)78 static int settings_custom_load(struct settings_store *cs, const struct settings_load_arg *arg)
79 {
80 FILE *fp = fopen(SETTINGS_FILE, "r+");
81
82 if (fp == NULL) {
83 LOG_INF("Settings file doesn't exist, will create it");
84 return -1;
85 }
86
87 if (fseek(fp, 0, SEEK_SET) < 0) {
88 return -1;
89 }
90
91 int vallen;
92 char line[READ_LEN_MAX + 1];
93
94 while (fgets(line, sizeof(line), fp) == line) {
95 /* check for matching subtree */
96 if (arg->subtree != NULL && !strstr(line, arg->subtree)) {
97 continue;
98 }
99
100 char *pos = strchr(line, '=');
101
102 if (pos <= line) {
103 return -1;
104 }
105
106 vallen = strlen(line) - (pos - line) - 2;
107 LOG_DBG("loading entry: %s", line);
108
109 struct line_read_ctx valctx;
110
111 valctx.len = vallen;
112 valctx.val = pos + 1;
113 int err = settings_call_set_handler(line, vallen / 2, settings_line_read_cb,
114 &valctx, arg);
115
116 if (err < 0) {
117 return err;
118 }
119 };
120
121 return fclose(fp);
122 }
123
124 /* Entries are saved to optimize readability of the settings file for test development and
125 * debugging purposes. Format:
126 * <entry-key>=<entry-value-hex-str>\n
127 */
settings_custom_save(struct settings_store * cs,const char * name,const char * value,size_t val_len)128 static int settings_custom_save(struct settings_store *cs, const char *name,
129 const char *value, size_t val_len)
130 {
131 FILE *fcur = fopen(SETTINGS_FILE, "r+");
132 FILE *fnew = NULL;
133
134 if (fcur == NULL) {
135 fcur = fopen(SETTINGS_FILE, "w");
136 } else {
137 fnew = fopen(SETTINGS_FILE_TMP, "w");
138 if (fnew == NULL) {
139 LOG_ERR("Failed to create temporary file %s", SETTINGS_FILE_TMP);
140 return -1;
141 }
142 }
143
144 if (fcur == NULL) {
145 LOG_ERR("Failed to create settings file: %s", SETTINGS_FILE);
146 return -1;
147 }
148
149 if (strlen(name) > ENTRY_NAME_MAX_LEN || val_len > SETTINGS_MAX_VAL_LEN) {
150 return -1;
151 }
152
153 if (fnew != NULL) {
154 if (entry_check_and_copy(fcur, fnew, name) < 0) {
155 return -1;
156 }
157 }
158
159 if (val_len) {
160 char bufvname[ENTRY_NAME_MAX_LEN + ENTRY_LEN_SIZE + 3];
161
162 snprintk(bufvname, sizeof(bufvname), "%s=", name);
163 if (fputs(bufvname, fnew != NULL ? fnew : fcur) < 0) {
164 return -1;
165 }
166
167 char bufval[ENTRY_VAL_MAX_LEN + 2] = {};
168 size_t valcnt = 0;
169
170 while (valcnt < (val_len * 2)) {
171 valcnt += snprintk(&bufval[valcnt], 3, "%02x",
172 (uint8_t)value[valcnt / 2]);
173 };
174
175 /* helps in making settings file readable */
176 bufval[valcnt++] = '\n';
177 bufval[valcnt] = 0;
178
179 LOG_DBG("writing to disk");
180
181 if (fputs(bufval, fnew != NULL ? fnew : fcur) < 0) {
182 return -1;
183 }
184 }
185
186 if (fnew != NULL) {
187 entry_check_and_copy(fcur, fnew, name);
188 }
189
190 fclose(fcur);
191
192 if (fnew != NULL) {
193 fclose(fnew);
194
195 remove(SETTINGS_FILE);
196 rename(SETTINGS_FILE_TMP, SETTINGS_FILE);
197 }
198
199 return 0;
200 }
201
202 /* custom backend interface */
203 static struct settings_store_itf settings_custom_itf = {
204 .csi_load = settings_custom_load,
205 .csi_save = settings_custom_save,
206 };
207
208 /* custom backend node */
209 static struct settings_store settings_custom_store = {
210 .cs_itf = &settings_custom_itf
211 };
212
settings_backend_init(void)213 int settings_backend_init(void)
214 {
215 snprintf(setting_file, sizeof(setting_file), "%s_%s.log", get_simid(), get_settings_file());
216 snprintf(setting_file_tmp, sizeof(setting_file_tmp), "~%s", setting_file);
217
218 LOG_INF("file path: %s", SETTINGS_FILE);
219
220 /* register custom backend */
221 settings_dst_register(&settings_custom_store);
222 settings_src_register(&settings_custom_store);
223 return 0;
224 }
225
settings_test_backend_clear(void)226 void settings_test_backend_clear(void)
227 {
228 snprintf(setting_file, sizeof(setting_file), "%s_%s.log", get_simid(), get_settings_file());
229
230 if (remove(setting_file)) {
231 LOG_INF("error deleting file: %s", setting_file);
232 }
233 }
234