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