Skip to content

Commit 7098094

Browse files
committed
Add loading of configs from VFS devices
This change adds loading of config overrides from raw devices. If defined at build time, each of MGOS_CONFIG_DEV_n macros will be parsed to extract device name and optional offset within the device, and then attempt will be made to load and parse JSON config at the corresponding level n (from 1 to 8). Note that this does not replace loading overrides from files but augments it: if both DEV_n is defined and confn.json exists, device will be loaded first, then file. JSON data on the device must be terminated by either NUL or 0xff.
1 parent fd7f9ab commit 7098094

File tree

3 files changed

+166
-61
lines changed

3 files changed

+166
-61
lines changed

include/mgos_config_util.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ struct mgos_config;
8484
* checking keys against `acl`.
8585
*/
8686
bool mgos_conf_parse(const struct mg_str json, const char *acl,
87-
const struct mgos_conf_entry *schema,
88-
struct mgos_config *cfg);
87+
const struct mgos_conf_entry *schema, void *cfg);
8988

9089
/*
9190
* Identical to `mgos_conf_parse()` but allows the caller to get an error
@@ -95,8 +94,8 @@ bool mgos_conf_parse(const struct mg_str json, const char *acl,
9594
* is owned by the caller and has to be free()d.
9695
*/
9796
bool mgos_conf_parse_msg(const struct mg_str json, const char *acl,
98-
const struct mgos_conf_entry *schema,
99-
struct mgos_config *cfg, char **msg);
97+
const struct mgos_conf_entry *schema, void *cfg,
98+
char **msg);
10099

101100
/*
102101
* Parse a sub-section of the config.

src/mgos_config_util.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,13 @@ static bool mgos_conf_parse_off(const struct mg_str json, const char *acl,
234234
}
235235

236236
bool mgos_conf_parse(const struct mg_str json, const char *acl,
237-
const struct mgos_conf_entry *schema,
238-
struct mgos_config *cfg) {
237+
const struct mgos_conf_entry *schema, void *cfg) {
239238
return mgos_conf_parse_off(json, acl, schema, 0, cfg, NULL);
240239
}
241240

242241
bool mgos_conf_parse_msg(const struct mg_str json, const char *acl,
243-
const struct mgos_conf_entry *schema,
244-
struct mgos_config *cfg, char **msg) {
242+
const struct mgos_conf_entry *schema, void *cfg,
243+
char **msg) {
245244
return mgos_conf_parse_off(json, acl, schema, 0, cfg, msg);
246245
}
247246

src/mgos_sys_config.c

Lines changed: 160 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ bool mgos_sys_config_is_initialized(void) {
6464
static mgos_config_validator_fn *s_validators;
6565
static int s_num_validators;
6666

67-
static int load_config_file(const char *filename, const char *acl,
68-
bool check_try, bool delete_try,
69-
struct mgos_config *cfg);
7067
static bool mgos_sys_config_load_level_internal(struct mgos_config *cfg,
7168
enum mgos_config_level level,
7269
bool check_try,
@@ -94,6 +91,138 @@ void mgos_expand_placeholders(const struct mg_str src, struct mg_str *str) {
9491
}
9592
}
9693

94+
static bool load_config(const char *name, struct mg_str cfg_data,
95+
const char *acl, const struct mgos_conf_entry *schema,
96+
void *cfg) {
97+
bool result = true;
98+
/* Make a temporary copy, in case it gets overridden while loading. */
99+
char *acl_copy = (acl != NULL ? strdup(acl) : NULL);
100+
if (!mgos_conf_parse(cfg_data, acl_copy, schema, cfg)) {
101+
LOG(LL_ERROR, ("Failed to parse %s", name));
102+
result = false;
103+
}
104+
free(acl_copy);
105+
return result;
106+
}
107+
108+
static bool load_config_file(const char *filename, const char *acl,
109+
bool check_try, bool delete_try,
110+
const struct mgos_conf_entry *schema, void *cfg) {
111+
char *data = NULL;
112+
size_t size;
113+
bool result = false;
114+
struct stat st;
115+
char tfn_buf[32], *try_filename = tfn_buf;
116+
// See if we have "${filename}.try" and prefer that.
117+
// Delete the file immediately after loading.
118+
if (check_try &&
119+
mg_asprintf(&try_filename, sizeof(tfn_buf), "%s%s", filename,
120+
CONF_FILE_TRY_SUFFIX) > 0 &&
121+
stat(try_filename, &st) == 0) {
122+
filename = try_filename;
123+
} else {
124+
if (try_filename != tfn_buf) free(try_filename);
125+
try_filename = NULL;
126+
}
127+
if (stat(filename, &st) != 0) {
128+
goto out;
129+
}
130+
LOG(LL_DEBUG, ("Loading %s", filename));
131+
data = cs_read_file(filename, &size);
132+
if (data == NULL) {
133+
goto out;
134+
}
135+
result = load_config(filename, mg_mk_str_n(data, size), acl, schema, cfg);
136+
137+
out:
138+
free(data);
139+
if (try_filename != NULL) {
140+
if (delete_try) remove(try_filename);
141+
if (try_filename != tfn_buf) free(try_filename);
142+
}
143+
return result;
144+
}
145+
146+
/*
147+
* Parse config JSON from a VFS device.
148+
* `spec` should specify device name and offset (`name,offset`).
149+
* `offset` is optional and defaults to 0.
150+
* JSON data must be terminated with NUL or 0xff.
151+
*/
152+
void mgos_conf_parse_dev(const char *spec, const char *acl,
153+
const struct mgos_conf_entry *schema, void *cfg) {
154+
char *data = NULL, *data2 = NULL;
155+
struct mg_str entry, dev_name = MG_NULL_STR, s;
156+
struct mgos_vfs_dev *dev = NULL;
157+
size_t i = 0, offset = 0, dev_size = 0, data_size = 0, data_len = 0;
158+
while (spec != NULL && i < 2) {
159+
spec = mg_next_comma_list_entry(spec, &entry, NULL);
160+
if (entry.len == 0) break;
161+
switch (i) {
162+
case 0: {
163+
dev_name = mg_strdup_nul(entry);
164+
break;
165+
}
166+
case 1: {
167+
struct mg_str tmp = mg_strdup_nul(entry);
168+
offset = (size_t) atoi(tmp.p);
169+
mg_strfree(&tmp);
170+
break;
171+
}
172+
}
173+
i++;
174+
}
175+
if (dev_name.len == 0) goto out;
176+
dev = mgos_vfs_dev_open(dev_name.p);
177+
if (dev == NULL) goto out;
178+
dev_size = mgos_vfs_dev_get_size(dev);
179+
if (dev_size == 0) goto out;
180+
while (data_size < dev_size) {
181+
const char *end;
182+
data_size += 128;
183+
if (data_size > dev_size) data_size = dev_size;
184+
data2 = realloc(data, data_size);
185+
if (data2 == NULL) goto out;
186+
data = data2;
187+
if (mgos_vfs_dev_read(dev, offset + data_len, data_size - data_len,
188+
data + data_len) != MGOS_VFS_DEV_ERR_NONE) {
189+
goto out;
190+
}
191+
// If it doesn't look like JSON, abort early.
192+
if (data[0] != '{') {
193+
LOG(LL_INFO, ("Not a valid config"));
194+
goto out;
195+
}
196+
s = mg_mk_str_n(data + data_len, data_size - data_len);
197+
end = mg_strchr(s, '\0');
198+
if (end == NULL) {
199+
// We allow 0xff termination to make it even more friendlt to the user.
200+
// 0xff is the zero value for NOR flash and is not valid JSON, so it's ok.
201+
end = mg_strchr(s, '\xff');
202+
}
203+
if (end != NULL) {
204+
data_len += (end - s.p);
205+
break;
206+
}
207+
data_len += 128;
208+
}
209+
if (data_len == dev_size) goto out;
210+
LOG(LL_DEBUG, ("Applying dev %s off %d len %d", dev_name.p, (int) offset,
211+
(int) data_len));
212+
load_config(spec, mg_mk_str_n(data, data_len), acl, schema, cfg);
213+
214+
out:
215+
mg_strfree(&dev_name);
216+
mgos_vfs_dev_close(dev);
217+
free(data);
218+
return;
219+
}
220+
221+
#define PARSE_CONFIG_DEV_LEVEL(l) \
222+
if (i == l) { \
223+
mgos_conf_parse_dev(CS_STRINGIFY_MACRO(MGOS_CONFIG_DEV_##l), acl, sch, \
224+
cfg); \
225+
}
97226
static bool mgos_sys_config_load_level_internal(struct mgos_config *cfg,
98227
enum mgos_config_level level,
99228
bool check_try,
@@ -106,14 +235,39 @@ static bool mgos_sys_config_load_level_internal(struct mgos_config *cfg,
106235
// Start with compiled-in defaults.
107236
mgos_config_set_defaults(cfg);
108237
const char *acl = "*";
238+
const struct mgos_conf_entry *sch = mgos_config_schema();
109239
for (i = 1; i <= (int) level; i++) {
240+
#ifdef MGOS_CONFIG_DEV_1
241+
PARSE_CONFIG_DEV_LEVEL(1);
242+
#endif
243+
#ifdef MGOS_CONFIG_DEV_2
244+
PARSE_CONFIG_DEV_LEVEL(2);
245+
#endif
246+
#ifdef MGOS_CONFIG_DEV_3
247+
PARSE_CONFIG_DEV_LEVEL(3);
248+
#endif
249+
#ifdef MGOS_CONFIG_DEV_4
250+
PARSE_CONFIG_DEV_LEVEL(4);
251+
#endif
252+
#ifdef MGOS_CONFIG_DEV_5
253+
PARSE_CONFIG_DEV_LEVEL(5);
254+
#endif
255+
#ifdef MGOS_CONFIG_DEV_6
256+
PARSE_CONFIG_DEV_LEVEL(6);
257+
#endif
258+
#ifdef MGOS_CONFIG_DEV_7
259+
PARSE_CONFIG_DEV_LEVEL(7);
260+
#endif
261+
#ifdef MGOS_CONFIG_DEV_8
262+
PARSE_CONFIG_DEV_LEVEL(8);
263+
#endif
110264
fname[CONF_USER_FILE_NUM_IDX] = '0' + i;
111265
/* Backward compat: load conf_vendor.json at level 5.5 */
112266
if (i == 6) {
113-
load_config_file(CONF_VENDOR_FILE, cfg->conf_acl, false, false, cfg);
267+
load_config_file(CONF_VENDOR_FILE, cfg->conf_acl, false, false, sch, cfg);
114268
acl = cfg->conf_acl;
115269
}
116-
if (!load_config_file(fname, acl, check_try, delete_try, cfg)) {
270+
if (!load_config_file(fname, acl, check_try, delete_try, sch, cfg)) {
117271
// Nothing to do, all the overlays are optional.
118272
}
119273
acl = cfg->conf_acl;
@@ -206,53 +360,6 @@ void mgos_config_reset(int level) {
206360
}
207361
}
208362

209-
static int load_config_file(const char *filename, const char *acl,
210-
bool check_try, bool delete_try,
211-
struct mgos_config *cfg) {
212-
char *data = NULL, *acl_copy = NULL;
213-
size_t size;
214-
int result = 1;
215-
struct stat st;
216-
char tfn_buf[32], *try_filename = tfn_buf;
217-
// See if we have "${filename}.try" and prefer that.
218-
// Delete the file immediately after loading.
219-
if (check_try &&
220-
mg_asprintf(&try_filename, sizeof(tfn_buf), "%s%s", filename,
221-
CONF_FILE_TRY_SUFFIX) > 0 &&
222-
stat(try_filename, &st) == 0) {
223-
filename = try_filename;
224-
} else {
225-
if (try_filename != tfn_buf) free(try_filename);
226-
try_filename = NULL;
227-
}
228-
if (stat(filename, &st) != 0) {
229-
result = 0;
230-
goto clean;
231-
}
232-
LOG(LL_INFO, ("Loading %s", filename));
233-
data = cs_read_file(filename, &size);
234-
if (data == NULL) {
235-
result = 0;
236-
goto clean;
237-
}
238-
/* Make a temporary copy, in case it gets overridden while loading. */
239-
acl_copy = (acl != NULL ? strdup(acl) : NULL);
240-
if (!mgos_conf_parse(mg_mk_str_n(data, size), acl_copy, mgos_config_schema(),
241-
cfg)) {
242-
LOG(LL_ERROR, ("Failed to parse %s", filename));
243-
result = 0;
244-
goto clean;
245-
}
246-
clean:
247-
free(data);
248-
free(acl_copy);
249-
if (try_filename != NULL) {
250-
if (delete_try) remove(try_filename);
251-
if (try_filename != tfn_buf) free(try_filename);
252-
}
253-
return result;
254-
}
255-
256363
void mbedtls_debug_set_threshold(int threshold);
257364

258365
enum mgos_init_result mgos_sys_config_init(void) {
@@ -289,7 +396,7 @@ enum mgos_init_result mgos_sys_config_init(void) {
289396
/* Successfully loaded system config. Try overrides - they are optional. */
290397
load_config_file(CONF_USER_FILE, mgos_sys_config_get_conf_acl(),
291398
true /* check_try */, true /* delete_try */,
292-
&mgos_sys_config);
399+
mgos_config_schema(), &mgos_sys_config);
293400

294401
s_initialized = true;
295402

0 commit comments

Comments
 (0)