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