1 /*
2  * Copyright (c) 2023 Nordic Semiconductor
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "main.h"
8 
9 #include <stdio.h>
10 #include <stddef.h>
11 
12 #include <zephyr/kernel.h>
13 #include <zephyr/settings/settings.h>
14 #include <zephyr/types.h>
15 #include "errno.h"
16 #include "argparse.h"
17 
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(settings_backend, 3);
20 
21 #define SETTINGS_FILE setting_file
22 #define SETTINGS_FILE_TMP setting_file_tmp
23 
24 #define ENTRY_LEN_SIZE (4)
25 #define ENTRY_NAME_MAX_LEN (SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN)
26 #define ENTRY_VAL_MAX_LEN (SETTINGS_MAX_VAL_LEN * 2)
27 #define READ_LEN_MAX (ENTRY_VAL_MAX_LEN + ENTRY_NAME_MAX_LEN + 2)
28 
29 struct line_read_ctx {
30 	int len;
31 	const uint8_t *val;
32 };
33 
34 static char setting_file[50];
35 static char setting_file_tmp[sizeof(setting_file) + 1];
36 
entry_check_and_copy(FILE * fin,FILE * fout,const char * name)37 static int entry_check_and_copy(FILE *fin, FILE *fout, const char *name)
38 {
39 	char line[READ_LEN_MAX + 1];
40 	char name_tmp[strlen(name) + 2];
41 
42 	snprintk(name_tmp, sizeof(name_tmp), "%s=", name);
43 
44 	while (fgets(line, sizeof(line), fin) == line) {
45 		if (strstr(line, name_tmp) != NULL) {
46 			return 0;
47 		}
48 
49 		if (fputs(line, fout) < 0) {
50 			return -1;
51 		}
52 	};
53 
54 	return 0;
55 }
56 
settings_line_read_cb(void * cb_arg,void * data,size_t len)57 static ssize_t settings_line_read_cb(void *cb_arg, void *data, size_t len)
58 {
59 	struct line_read_ctx *valctx = (struct line_read_ctx *)cb_arg;
60 
61 	if ((valctx->len / 2) > len) {
62 		return -ENOBUFS;
63 	}
64 
65 	uint8_t *n = (uint8_t *) data;
66 
67 	len = valctx->len / 2;
68 
69 	for (int i = 0; i < len; i++, n++) {
70 		if (sscanf(&valctx->val[i * 2], "%2hhx", n) != 1) {
71 			return 0;
72 		};
73 	}
74 
75 	return len;
76 }
77 
settings_custom_load(struct settings_store * cs,const struct settings_load_arg * arg)78 static int settings_custom_load(struct settings_store *cs, const struct settings_load_arg *arg)
79 {
80 	FILE *fp = fopen(SETTINGS_FILE, "r+");
81 
82 	if (fp == NULL) {
83 		LOG_INF("Settings file doesn't exist, will create it");
84 		return -1;
85 	}
86 
87 	if (fseek(fp, 0, SEEK_SET) < 0) {
88 		return -1;
89 	}
90 
91 	int vallen;
92 	char line[READ_LEN_MAX + 1];
93 
94 	while (fgets(line, sizeof(line), fp) == line) {
95 		/* check for matching subtree */
96 		if (arg->subtree != NULL && !strstr(line, arg->subtree)) {
97 			continue;
98 		}
99 
100 		char *pos = strchr(line, '=');
101 
102 		if (pos <= line) {
103 			return -1;
104 		}
105 
106 		vallen = strlen(line) - (pos - line) - 2;
107 		LOG_DBG("loading entry: %s", line);
108 
109 		struct line_read_ctx valctx;
110 
111 		valctx.len = vallen;
112 		valctx.val = pos + 1;
113 		int err = settings_call_set_handler(line, vallen / 2, settings_line_read_cb,
114 						    &valctx, arg);
115 
116 		if (err < 0) {
117 			return err;
118 		}
119 	};
120 
121 	return fclose(fp);
122 }
123 
124 /* Entries are saved to optimize readability of the settings file for test development and
125  * debugging purposes. Format:
126  * <entry-key>=<entry-value-hex-str>\n
127  */
settings_custom_save(struct settings_store * cs,const char * name,const char * value,size_t val_len)128 static int settings_custom_save(struct settings_store *cs, const char *name,
129 				const char *value, size_t val_len)
130 {
131 	FILE *fcur = fopen(SETTINGS_FILE, "r+");
132 	FILE *fnew = NULL;
133 
134 	if (fcur == NULL) {
135 		fcur = fopen(SETTINGS_FILE, "w");
136 	} else {
137 		fnew = fopen(SETTINGS_FILE_TMP, "w");
138 		if (fnew == NULL) {
139 			LOG_ERR("Failed to create temporary file %s", SETTINGS_FILE_TMP);
140 			return -1;
141 		}
142 	}
143 
144 	if (fcur == NULL) {
145 		LOG_ERR("Failed to create settings file: %s", SETTINGS_FILE);
146 		return -1;
147 	}
148 
149 	if (strlen(name) > ENTRY_NAME_MAX_LEN || val_len > SETTINGS_MAX_VAL_LEN) {
150 		return -1;
151 	}
152 
153 	if (fnew != NULL) {
154 		if (entry_check_and_copy(fcur, fnew, name) < 0) {
155 			return -1;
156 		}
157 	}
158 
159 	if (val_len) {
160 		char bufvname[ENTRY_NAME_MAX_LEN + ENTRY_LEN_SIZE + 3];
161 
162 		snprintk(bufvname, sizeof(bufvname), "%s=", name);
163 		if (fputs(bufvname, fnew != NULL ? fnew : fcur) < 0) {
164 			return -1;
165 		}
166 
167 		char bufval[ENTRY_VAL_MAX_LEN + 2] = {};
168 		size_t valcnt = 0;
169 
170 		while (valcnt < (val_len * 2)) {
171 			valcnt += snprintk(&bufval[valcnt], 3, "%02x",
172 					   (uint8_t)value[valcnt / 2]);
173 		};
174 
175 		/* helps in making settings file readable */
176 		bufval[valcnt++] = '\n';
177 		bufval[valcnt] = 0;
178 
179 		LOG_DBG("writing to disk");
180 
181 		if (fputs(bufval, fnew != NULL ? fnew : fcur) < 0) {
182 			return -1;
183 		}
184 	}
185 
186 	if (fnew != NULL) {
187 		entry_check_and_copy(fcur, fnew, name);
188 	}
189 
190 	fclose(fcur);
191 
192 	if (fnew != NULL) {
193 		fclose(fnew);
194 
195 		remove(SETTINGS_FILE);
196 		rename(SETTINGS_FILE_TMP, SETTINGS_FILE);
197 	}
198 
199 	return 0;
200 }
201 
202 /* custom backend interface */
203 static struct settings_store_itf settings_custom_itf = {
204 	.csi_load = settings_custom_load,
205 	.csi_save = settings_custom_save,
206 };
207 
208 /* custom backend node */
209 static struct settings_store settings_custom_store = {
210 	.cs_itf = &settings_custom_itf
211 };
212 
settings_backend_init(void)213 int settings_backend_init(void)
214 {
215 	snprintf(setting_file, sizeof(setting_file), "%s_%s.log", get_simid(), get_settings_file());
216 	snprintf(setting_file_tmp, sizeof(setting_file_tmp), "~%s", setting_file);
217 
218 	LOG_INF("file path: %s", SETTINGS_FILE);
219 
220 	/* register custom backend */
221 	settings_dst_register(&settings_custom_store);
222 	settings_src_register(&settings_custom_store);
223 	return 0;
224 }
225 
settings_test_backend_clear(void)226 void settings_test_backend_clear(void)
227 {
228 	snprintf(setting_file, sizeof(setting_file), "%s_%s.log", get_simid(), get_settings_file());
229 
230 	if (remove(setting_file)) {
231 		LOG_INF("error deleting file: %s", setting_file);
232 	}
233 }
234