config.c 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. #include "common.h"
  2. #include "config.h"
  3. #include <esp_spiffs.h>
  4. #include <ctype.h>
  5. #define CONFIG_FILE "/spiffs/config.txt"
  6. struct env_var {
  7. const char *var, *val;
  8. };
  9. static const struct env_var default_config[] = {
  10. {"LANG","sv"},
  11. {"TZ", "CET-1CEST,M3.5.0,M10.5.0/3"}, /* Sweden */
  12. {"tzname", "Europe/Stockholm"},
  13. {"hostname", "max80"},
  14. {"mdns.enabled", "1"},
  15. {"sntp.enabled", "1"},
  16. {"sntp.server","time.max80.abc80.org"},
  17. {"abc.hosttype","auto"}
  18. };
  19. static int save_config(void);
  20. static bool config_changed;
  21. int setenv_config(const char *name, const char *value)
  22. {
  23. config_changed = true;
  24. if (name[0] == '-') {
  25. name++;
  26. value = NULL;
  27. }
  28. return setenv_cond(name, value);
  29. }
  30. int setenv_cond(const char *name, const char *value)
  31. {
  32. const char *pfx;
  33. size_t skip;
  34. if (!strncmp("status.", name, 7)) {
  35. pfx = "STATUS";
  36. skip = 7;
  37. } else {
  38. pfx = "CONFIG";
  39. skip = 0;
  40. }
  41. logmsg(pfx, "%s <- %s\n", name+skip, value ? value : "(deleted)");
  42. if (value)
  43. return setenv(name, value, 1);
  44. else
  45. return unsetenv(name);
  46. }
  47. static void reset_config(void)
  48. {
  49. while (1) {
  50. char **envp;
  51. for (envp = environ; *envp; envp++) {
  52. if (!strncmp("status.", *envp, 7))
  53. continue;
  54. else
  55. break;
  56. }
  57. if (!*envp)
  58. break;
  59. const char *eq = strchr(*envp, '=');
  60. if (!eq)
  61. continue; /* This should never happen... */
  62. char ename[eq - *envp + 1];
  63. memcpy(ename, *envp, eq - *envp);
  64. ename[eq - *envp] = '\0';
  65. setenv_cond(ename, NULL);
  66. }
  67. size_t i;
  68. for (i = 0; i < ARRAY_SIZE(default_config); i++)
  69. setenv_cond(default_config[i].var, default_config[i].val);
  70. config_changed = true;
  71. }
  72. static bool is_end_of_string(int c)
  73. {
  74. return c <= 0 || c == '\n' || c == '\r';
  75. }
  76. /*
  77. * Note: the string must be mutable; use strdup() if necessary.
  78. * The "separator" allows the string to be separated into multiple
  79. * arguments; no other decoding is done.
  80. */
  81. static int set_config_string(char *str, unsigned int separator)
  82. {
  83. char *p, *q;
  84. unsigned char c;
  85. p = str;
  86. do {
  87. const char *var = p;
  88. do {
  89. c = *p++;
  90. } while (isalnum(c) || c == '.' || c == '-');
  91. if (c != '=')
  92. return -EINVAL; /* Invalid config line (blank, comment...) */
  93. p[-1] = '\0';
  94. q = p;
  95. do {
  96. c = *q++;
  97. } while (!is_end_of_string(c) && c != separator);
  98. /* Overlong line */
  99. if (q >= str + MAX_CONFIG_LINE)
  100. return -EOVERFLOW;
  101. q[-1] = '\0';
  102. setenv_config(var, p);
  103. p = q;
  104. } while (c == separator);
  105. return 0;
  106. }
  107. static void finish_config_update(bool save)
  108. {
  109. if (config_changed) {
  110. if (save)
  111. save_config();
  112. tzset();
  113. config_changed = false;
  114. }
  115. }
  116. /*
  117. * At the moment "url" just allows values to be separated by semicolons;
  118. * no other decoding is done, and '&' is not supported.
  119. */
  120. int set_config_url_string(const char *str)
  121. {
  122. char *wstr = strdup(str);
  123. if (!wstr)
  124. return -ENOMEM;
  125. int err = set_config_string(wstr, ';');
  126. free(wstr);
  127. finish_config_update(true);
  128. return err;
  129. }
  130. static void skip_rest_of_line(FILE *f)
  131. {
  132. int c;
  133. do {
  134. c = getc(f);
  135. } while (!is_end_of_string(c));
  136. }
  137. /* Calling with with f == NULL just finalizes the update */
  138. int read_config(FILE *f, bool save)
  139. {
  140. char *linebuf = NULL;
  141. int err = -1;
  142. if (f) {
  143. linebuf = malloc(MAX_CONFIG_LINE);
  144. if (!linebuf) {
  145. err = -ENOMEM;
  146. goto exit;
  147. }
  148. while (fgets(linebuf, MAX_CONFIG_LINE, f)) {
  149. if (set_config_string(linebuf, -1) == -EOVERFLOW)
  150. skip_rest_of_line(f);
  151. }
  152. }
  153. err = 0;
  154. if (linebuf)
  155. free(linebuf);
  156. exit:
  157. finish_config_update(save);
  158. return err;
  159. };
  160. int write_env(FILE *f, bool status)
  161. {
  162. size_t skip = status ? 7 : 0;
  163. for (char **var = environ; *var; var++) {
  164. if (!strncmp(*var, "status.", 7) == status) {
  165. fputs(*var + skip, f);
  166. putc('\n', f);
  167. }
  168. }
  169. return ferror(f) ? -1 : 0;
  170. }
  171. static int save_config(void)
  172. {
  173. int err = -ENOENT;
  174. FILE *f = fopen(CONFIG_FILE, "w");
  175. if (f) {
  176. err = write_env(f, false);
  177. fclose(f);
  178. }
  179. if (err)
  180. printf("[CONF] Failed to save configuration (error %d)\n", err);
  181. return err;
  182. }
  183. static const esp_vfs_spiffs_conf_t spiffs_conf = {
  184. .base_path = "/spiffs",
  185. .partition_label = NULL,
  186. .max_files = 4,
  187. .format_if_mount_failed = true
  188. };
  189. void init_config(void)
  190. {
  191. if (!esp_spiffs_mounted(spiffs_conf.partition_label)) {
  192. esp_err_t err;
  193. err = esp_vfs_spiffs_register(&spiffs_conf);
  194. if (err)
  195. printf("[CONF] Failed to mount %s (error 0x%x)\n",
  196. spiffs_conf.base_path, err);
  197. }
  198. reset_config();
  199. FILE *f = fopen(CONFIG_FILE, "r");
  200. if (!f)
  201. printf("[CONF] No configuration file found, using defaults\n");
  202. read_config(f, false);
  203. if (f)
  204. fclose(f);
  205. }
  206. const char *getenv_def(const char *var, const char *def)
  207. {
  208. const char *val = getenv(var);
  209. return val ? val : def;
  210. }
  211. long getenv_l(const char *var, long def)
  212. {
  213. const char *ep;
  214. var = getenv(var);
  215. if (!var || !*var)
  216. return def;
  217. long val = strtol(var, (char **)&ep, 0);
  218. return *ep ? def : val;
  219. }
  220. void setenv_l(const char *var, long val)
  221. {
  222. char vbuf[2+3*sizeof val];
  223. snprintf(vbuf, sizeof vbuf, "%ld", val);
  224. setenv_cond(var, vbuf);
  225. }
  226. unsigned long getenv_ul(const char *var, unsigned long def)
  227. {
  228. const char *ep;
  229. var = getenv(var);
  230. if (!var || !*var)
  231. return def;
  232. unsigned long val = strtol(var, (char **)&ep, 0);
  233. return *ep ? def : val;
  234. }
  235. void setenv_ul(const char *var, unsigned long val)
  236. {
  237. char vbuf[2+3*sizeof val];
  238. snprintf(vbuf, sizeof vbuf, "%lu", val);
  239. setenv_cond(var, vbuf);
  240. }
  241. bool getenv_bool(const char *var)
  242. {
  243. var = getenv(var);
  244. if (!var)
  245. return false;
  246. unsigned char c = *var;
  247. unsigned char cl = c | 0x20;
  248. return !(!c || c == '0' || cl == 'f' || cl == 'n' || cl == 'd' ||
  249. (cl == 'o' && (var[1] | 0x20) == 'f'));
  250. }
  251. void setenv_bool(const char *var, bool val)
  252. {
  253. return setenv_ul(var, val);
  254. }
  255. const char *getenv_notempty(const char *env)
  256. {
  257. const char *str = getenv(env);
  258. if (str && !*str)
  259. str = NULL;
  260. return str;
  261. }
  262. void log_config_status(void)
  263. {
  264. const char *pfx;
  265. size_t skip;
  266. for (char **var = environ; *var; var++) {
  267. if (!strncmp(*var, "status.", 7)) {
  268. pfx = "STATUS";
  269. skip = 7;
  270. } else {
  271. pfx = "CONFIG";
  272. skip = 0;
  273. }
  274. logmsg(pfx, "%s\n", *var+skip);
  275. }
  276. }