1 /*
2 * Copyright (c) 2018 Nordic Semiconductor ASA
3 * Copyright (c) 2015 Runtime Inc
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <string.h>
9 #include <stdbool.h>
10 #include <assert.h>
11 #include <ble_os.h>
12
13 #include <fs/fs.h>
14
15 #include "settings/settings.h"
16 #include "settings/settings_file.h"
17 #include "settings_priv.h"
18
19 #include <logging/log.h>
20
21 LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL);
22
23 int settings_backend_init(void);
24 void settings_mount_fs_backend(struct settings_file *cf);
25
26 static int settings_file_load(struct settings_store *cs,
27 const struct settings_load_arg *arg);
28 static int settings_file_save(struct settings_store *cs, const char *name,
29 const char *value, size_t val_len);
30
31 static const struct settings_store_itf settings_file_itf = {
32 .csi_load = settings_file_load,
33 .csi_save = settings_file_save,
34 };
35
36 /*
37 * Register a file to be a source of configuration.
38 */
settings_file_src(struct settings_file * cf)39 int settings_file_src(struct settings_file *cf)
40 {
41 if (!cf->cf_name) {
42 return -EINVAL;
43 }
44 cf->cf_store.cs_itf = &settings_file_itf;
45 settings_src_register(&cf->cf_store);
46
47 return 0;
48 }
49
50 /*
51 * Register a file to be a destination of configuration.
52 */
settings_file_dst(struct settings_file * cf)53 int settings_file_dst(struct settings_file *cf)
54 {
55 if (!cf->cf_name) {
56 return -EINVAL;
57 }
58 cf->cf_store.cs_itf = &settings_file_itf;
59 settings_dst_register(&cf->cf_store);
60
61 return 0;
62 }
63
64 /**
65 * @brief Check if there is any duplicate of the current setting
66 *
67 * This function checks if there is any duplicated data further in the buffer.
68 *
69 * @param entry_ctx Current entry context
70 * @param name The name of the current entry
71 *
72 * @retval false No duplicates found
73 * @retval true Duplicate found
74 */
settings_file_check_duplicate(const struct line_entry_ctx * entry_ctx,const char * const name)75 static bool settings_file_check_duplicate(
76 const struct line_entry_ctx *entry_ctx,
77 const char * const name)
78 {
79 struct line_entry_ctx entry2_ctx = *entry_ctx;
80
81 /* Searching the duplicates */
82 while (settings_next_line_ctx(&entry2_ctx) == 0) {
83 char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
84 size_t name2_len;
85
86 if (entry2_ctx.len == 0) {
87 break;
88 }
89
90 if (settings_line_name_read(name2, sizeof(name2), &name2_len,
91 &entry2_ctx)) {
92 continue;
93 }
94 name2[name2_len] = '\0';
95
96 if (!strcmp(name, name2)) {
97 return true;
98 }
99 }
100 return false;
101 }
102
read_entry_len(const struct line_entry_ctx * entry_ctx,off_t off)103 static int read_entry_len(const struct line_entry_ctx *entry_ctx, off_t off)
104 {
105 if (off >= entry_ctx->len) {
106 return 0;
107 }
108 return entry_ctx->len - off;
109 }
110
settings_file_load_priv(struct settings_store * cs,line_load_cb cb,void * cb_arg,bool filter_duplicates)111 static int settings_file_load_priv(struct settings_store *cs, line_load_cb cb,
112 void *cb_arg, bool filter_duplicates)
113 {
114 struct settings_file *cf = (struct settings_file *)cs;
115 struct fs_file_t file;
116 int lines;
117 int rc;
118
119 struct line_entry_ctx entry_ctx = {
120 .stor_ctx = (void *)&file,
121 .seek = 0,
122 .len = 0 /* unknown length */
123 };
124
125 lines = 0;
126
127 rc = fs_open(&file, cf->cf_name);
128 if (rc != 0) {
129 return -EINVAL;
130 }
131
132 while (1) {
133 char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
134 size_t name_len;
135 bool pass_entry = true;
136
137 rc = settings_next_line_ctx(&entry_ctx);
138 if (rc || entry_ctx.len == 0) {
139 break;
140 }
141
142 rc = settings_line_name_read(name, sizeof(name), &name_len,
143 &entry_ctx);
144
145 if (rc || name_len == 0) {
146 break;
147 }
148 name[name_len] = '\0';
149
150 if (filter_duplicates &&
151 (!read_entry_len(&entry_ctx, name_len+1) ||
152 settings_file_check_duplicate(&entry_ctx, name))) {
153 pass_entry = false;
154 }
155 /*name, val-read_cb-ctx, val-off*/
156 /* take into account '=' separator after the name */
157 if (pass_entry) {
158 cb(name, (void *)&entry_ctx, name_len + 1, cb_arg);
159 }
160 lines++;
161 }
162
163 rc = fs_close(&file);
164 cf->cf_lines = lines;
165
166 return rc;
167 }
168
169 /*
170 * Called to load configuration items.
171 */
settings_file_load(struct settings_store * cs,const struct settings_load_arg * arg)172 static int settings_file_load(struct settings_store *cs,
173 const struct settings_load_arg *arg)
174 {
175 return settings_file_load_priv(cs,
176 settings_line_load_cb,
177 (void *)arg,
178 true);
179 }
180
settings_tmpfile(char * dst,const char * src,char * pfx)181 static void settings_tmpfile(char *dst, const char *src, char *pfx)
182 {
183 int len;
184 int pfx_len;
185
186 len = strlen(src);
187 pfx_len = strlen(pfx);
188 if (len + pfx_len >= SETTINGS_FILE_NAME_MAX) {
189 len = SETTINGS_FILE_NAME_MAX - pfx_len - 1;
190 }
191 memcpy(dst, src, len);
192 memcpy(dst + len, pfx, pfx_len);
193 dst[len + pfx_len] = '\0';
194 }
195
settings_file_create_or_replace(struct fs_file_t * zfp,const char * file_name)196 static int settings_file_create_or_replace(struct fs_file_t *zfp,
197 const char *file_name)
198 {
199 struct fs_dirent entry;
200
201 if (fs_stat(file_name, &entry) == 0) {
202 if (entry.type == FS_DIR_ENTRY_FILE) {
203 if (fs_unlink(file_name)) {
204 return -EIO;
205 }
206 } else {
207 return -EISDIR;
208 }
209 }
210
211 return fs_open(zfp, file_name);
212 }
213
214 /*
215 * Try to compress configuration file by keeping unique names only.
216 */
settings_file_save_and_compress(struct settings_file * cf,const char * name,const char * value,size_t val_len)217 static int settings_file_save_and_compress(struct settings_file *cf,
218 const char *name, const char *value,
219 size_t val_len)
220 {
221 int rc, rc2;
222 struct fs_file_t rf;
223 struct fs_file_t wf;
224 char tmp_file[SETTINGS_FILE_NAME_MAX];
225 char name1[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN];
226 char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN];
227 struct line_entry_ctx loc1 = {
228 .stor_ctx = &rf,
229 .seek = 0,
230 .len = 0 /* unknown length */
231 };
232
233 struct line_entry_ctx loc2;
234
235 struct line_entry_ctx loc3 = {
236 .stor_ctx = &wf
237 };
238
239 int copy;
240 int lines;
241 size_t new_name_len;
242 size_t val1_off;
243
244 if (fs_open(&rf, cf->cf_name) != 0) {
245 return -ENOEXEC;
246 }
247
248 settings_tmpfile(tmp_file, cf->cf_name, ".cmp");
249
250 if (settings_file_create_or_replace(&wf, tmp_file)) {
251 fs_close(&rf);
252 return -ENOEXEC;
253 }
254
255 lines = 0;
256 new_name_len = strlen(name);
257
258 while (1) {
259 rc = settings_next_line_ctx(&loc1);
260
261 if (rc || loc1.len == 0) {
262 /* try to amend new value to the commpresed file */
263 break;
264 }
265
266 rc = settings_line_name_read(name1, sizeof(name1), &val1_off,
267 &loc1);
268 if (rc) {
269 /* try to process next line */
270 continue;
271 }
272
273 if (val1_off + 1 == loc1.len) {
274 /* Lack of a value so the record is a deletion-record */
275 /* No sense to copy empty entry from */
276 /* the oldest sector */
277 continue;
278 }
279
280 /* avoid copping value which will be overwritten by new value*/
281 if ((val1_off == new_name_len) &&
282 !memcmp(name1, name, val1_off)) {
283 continue;
284 }
285
286 loc2 = loc1;
287
288 copy = 1;
289 while (1) {
290 size_t val2_off;
291
292 rc = settings_next_line_ctx(&loc2);
293
294 if (rc || loc2.len == 0) {
295 /* try to amend new value to */
296 /* the commpresed file */
297 break;
298 }
299
300 rc = settings_line_name_read(name2, sizeof(name2),
301 &val2_off, &loc2);
302 if (rc) {
303 /* try to process next line */
304 continue;
305 }
306 if ((val1_off == val2_off) &&
307 !memcmp(name1, name2, val1_off)) {
308 copy = 0; /* newer version doesn't exist */
309 break;
310 }
311 }
312 if (!copy) {
313 continue;
314 }
315
316 loc2 = loc1;
317 loc2.len += 2;
318 loc2.seek -= 2;
319 rc = settings_line_entry_copy(&loc3, 0, &loc2, 0, loc2.len);
320 if (rc) {
321 /* compressed file might be corrupted */
322 goto end_rolback;
323 }
324
325 lines++;
326 }
327
328 /* at last store the new value */
329 rc = settings_line_write(name, value, val_len, 0, &loc3);
330 if (rc) {
331 /* compressed file might be corrupted */
332 goto end_rolback;
333 }
334
335 rc = fs_close(&wf);
336 rc2 = fs_close(&rf);
337 if (rc == 0 && rc2 == 0 && fs_unlink(cf->cf_name) == 0) {
338 if (fs_rename(tmp_file, cf->cf_name)) {
339 return -ENOENT;
340 }
341 cf->cf_lines = lines + 1;
342 } else {
343 rc = -EIO;
344 }
345 /*
346 * XXX at settings_file_load(), look for .cmp if actual file does not
347 * exist.
348 */
349 return 0;
350 end_rolback:
351 (void)fs_close(&wf);
352 if (fs_close(&rf) == 0) {
353 (void)fs_unlink(tmp_file);
354 }
355 return -EIO;
356
357 }
358
settings_file_save_priv(struct settings_store * cs,const char * name,const char * value,size_t val_len)359 static int settings_file_save_priv(struct settings_store *cs, const char *name,
360 const char *value, size_t val_len)
361 {
362 struct settings_file *cf = (struct settings_file *)cs;
363 struct line_entry_ctx entry_ctx;
364 struct fs_file_t file;
365 int rc2;
366 int rc;
367
368 if (!name) {
369 return -EINVAL;
370 }
371
372 if (cf->cf_maxlines && (cf->cf_lines + 1 >= cf->cf_maxlines)) {
373 /*
374 * Compress before config file size exceeds
375 * the max number of lines.
376 */
377 return settings_file_save_and_compress(cf, name, value,
378 val_len);
379 }
380
381 /*
382 * Open the file to add this one value.
383 */
384 rc = fs_open(&file, cf->cf_name);
385 if (rc == 0) {
386 rc = fs_seek(&file, 0, FS_SEEK_END);
387 if (rc == 0) {
388 entry_ctx.stor_ctx = &file;
389 rc = settings_line_write(name, value, val_len, 0,
390 (void *)&entry_ctx);
391 if (rc == 0) {
392 cf->cf_lines++;
393 }
394 }
395
396 rc2 = fs_close(&file);
397 if (rc == 0) {
398 rc = rc2;
399 }
400 }
401
402 return rc;
403 }
404
405
406 /*
407 * Called to save configuration.
408 */
settings_file_save(struct settings_store * cs,const char * name,const char * value,size_t val_len)409 static int settings_file_save(struct settings_store *cs, const char *name,
410 const char *value, size_t val_len)
411 {
412 struct settings_line_dup_check_arg cdca;
413
414 if (val_len > 0 && value == NULL) {
415 return -EINVAL;
416 }
417
418 /*
419 * Check if we're writing the same value again.
420 */
421 cdca.name = name;
422 cdca.val = (char *)value;
423 cdca.is_dup = 0;
424 cdca.val_len = val_len;
425 settings_file_load_priv(cs, settings_line_dup_check_cb, &cdca, false);
426 if (cdca.is_dup == 1) {
427 return 0;
428 }
429 return settings_file_save_priv(cs, name, (char *)value, val_len);
430 }
431
read_handler(void * ctx,off_t off,char * buf,size_t * len)432 static int read_handler(void *ctx, off_t off, char *buf, size_t *len)
433 {
434 struct line_entry_ctx *entry_ctx = ctx;
435 struct fs_file_t *file = entry_ctx->stor_ctx;
436 ssize_t r_len;
437 int rc;
438
439 /* 0 is reserved for reding the length-field only */
440 if (entry_ctx->len != 0) {
441 if (off >= entry_ctx->len) {
442 *len = 0;
443 return 0;
444 }
445
446 if ((off + *len) > entry_ctx->len) {
447 *len = entry_ctx->len - off;
448 }
449 }
450
451 rc = fs_seek(file, entry_ctx->seek + off, FS_SEEK_SET);
452 if (rc) {
453 goto end;
454 }
455
456 r_len = fs_read(file, buf, *len);
457
458 if (r_len >= 0) {
459 *len = r_len;
460 rc = 0;
461 } else {
462 rc = r_len;
463 }
464 end:
465 return rc;
466 }
467
get_len_cb(void * ctx)468 static size_t get_len_cb(void *ctx)
469 {
470 struct line_entry_ctx *entry_ctx = ctx;
471
472 return entry_ctx->len;
473 }
474
write_handler(void * ctx,off_t off,char const * buf,size_t len)475 static int write_handler(void *ctx, off_t off, char const *buf, size_t len)
476 {
477 struct line_entry_ctx *entry_ctx = ctx;
478 struct fs_file_t *file = entry_ctx->stor_ctx;
479 int rc;
480
481 /* append to file only */
482 rc = fs_seek(file, 0, FS_SEEK_END);
483
484 if (rc == 0) {
485 rc = fs_write(file, buf, len);
486
487 if (rc > 0) {
488 rc = 0;
489 }
490 }
491
492 return rc;
493 }
494
settings_mount_fs_backend(struct settings_file * cf)495 void settings_mount_fs_backend(struct settings_file *cf)
496 {
497 settings_line_io_init(read_handler, write_handler, get_len_cb, 1);
498 }
499
settings_backend_init(void)500 int settings_backend_init(void)
501 {
502 static struct settings_file config_init_settings_file = {
503 .cf_name = CONFIG_SETTINGS_FS_FILE,
504 .cf_maxlines = CONFIG_SETTINGS_FS_MAX_LINES
505 };
506 int rc;
507
508
509 rc = settings_file_src(&config_init_settings_file);
510 if (rc) {
511 k_panic();
512 }
513
514 rc = settings_file_dst(&config_init_settings_file);
515 if (rc) {
516 k_panic();
517 }
518
519 settings_mount_fs_backend(&config_init_settings_file);
520
521 /*
522 * Must be called after root FS has been initialized.
523 */
524 rc = fs_mkdir(CONFIG_SETTINGS_FS_DIR);
525
526 /*
527 * The following lines mask the file exist error.
528 */
529 if (rc == -EEXIST) {
530 rc = 0;
531 }
532
533 return rc;
534 }
535