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