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