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