/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include "ovms_log.h" static const char *TAG = "config"; #include #include #include #include #include #include #include "crypt_base64.h" #include "ovms_config.h" #include "ovms_command.h" #include "ovms_script.h" #include "ovms_events.h" #include "ovms_utils.h" #include "ovms_boot.h" #include "ovms_semaphore.h" #ifdef CONFIG_OVMS_SC_ZIP #include "zip_archive.h" #endif // CONFIG_OVMS_SC_ZIP #define OVMS_CONFIGPATH "/store/ovms_config" #define OVMS_MAXVALSIZE 2500 //#define OVMS_PERSIST_METADATA OvmsConfig MyConfig __attribute__ ((init_priority (1400))); void store_mount(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyConfig.mount(); writer->puts("Mounted STORE"); } void store_unmount(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyConfig.unmount(); writer->puts("Unmounted STORE"); } int config_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv, bool complete) { if (!MyConfig.ismounted()) return -1; // argv[0] is the if (argc == 1) return MyConfig.m_map.Validate(writer, argc, argv[0], complete); // argv[1] is the if (argc == 2) { OvmsConfigParam* const* p = MyConfig.m_map.FindUniquePrefix(argv[0]); if (!p) // was not valid, so can't check return -1; return (*p)->m_map.Validate(writer, argc, argv[1], complete); } // argv[2] is the value, which we can't validate return -1; } void config_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (!MyConfig.ismounted()) return; if (argc == 0) { // Show all parameters for (ConfigMap::iterator it=MyConfig.m_map.begin(); it!=MyConfig.m_map.end(); ++it) { writer->printf("%-20s %s\n", it->first.c_str(), it->second->GetTitle()); } } else { // Show all instances for a particular parameter OvmsConfigParam *p = MyConfig.CachedParam(argv[0]); if (p) { writer->printf("%s (%s %s)\n",p->GetName().c_str(), (p->Readable()?"readable":"protected"), (p->Writable()?"writeable":"read-only")); for (ConfigParamMap::iterator it=p->m_map.begin(); it!=p->m_map.end(); ++it) { if (p->Readable()) { writer->printf(" %s: %s\n",it->first.c_str(), it->second.c_str()); } else { writer->printf(" %s\n",it->first.c_str()); } } } else { writer->printf("%s not found\n", argv[0]); } } } void config_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (!MyConfig.ismounted()) return; OvmsConfigParam *p = MyConfig.CachedParam(argv[0]); if (p==NULL) { writer->puts("Error: parameter not found"); return; } if (!p->Writable()) { writer->puts("Error: parameter is not writeable"); return; } p->SetValue(argv[1],argv[2]); writer->puts("Parameter has been set."); } void config_rm(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (!MyConfig.ismounted()) return; OvmsConfigParam *p = MyConfig.CachedParam(argv[0]); if (p==NULL) { writer->puts("Error: parameter not found"); return; } if (!p->Writable()) { writer->puts("Error: parameter is not writeable"); return; } if (p->DeleteInstance(argv[1])) { writer->printf("Instance %s has been removed.\n", argv[1]); return; } if (strcmp(argv[1], "*") != 0) return; MyConfig.DeregisterParam(argv[0]); writer->printf("Parameter %s has been removed.\n", argv[0]); } #ifdef CONFIG_OVMS_SC_ZIP void config_backup(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (!MyConfig.ismounted()) { writer->puts("Error: config store not mounted"); return; } // check path: if (MyConfig.ProtectedPath(argv[0])) { writer->printf("Error: path '%s' is protected\n", argv[0]); return; } // get password: std::string password; if (argc >= 2) password = argv[1]; else password = MyConfig.GetParamValue("password", "module"); MyConfig.Backup(argv[0], password, writer, verbosity); } void config_restore(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (!MyConfig.ismounted()) { writer->puts("Error: config store not mounted"); return; } // check path: if (MyConfig.ProtectedPath(argv[0])) { writer->printf("Error: path '%s' is protected\n", argv[0]); return; } // get password: std::string password; if (argc >= 2) password = argv[1]; else password = MyConfig.GetParamValue("password", "module"); MyConfig.Restore(argv[0], password, writer, verbosity); } #endif // CONFIG_OVMS_SC_ZIP #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE static duk_ret_t DukOvmsConfigParams(duk_context *ctx) { if (!MyConfig.ismounted()) return 0; duk_idx_t arr_idx = duk_push_array(ctx); int count = 0; for (ConfigMap::iterator it=MyConfig.m_map.begin(); it!=MyConfig.m_map.end(); ++it) { duk_push_string(ctx, it->first.c_str()); duk_put_prop_index(ctx, arr_idx, count++); } return 1; } static duk_ret_t DukOvmsConfigInstances(duk_context *ctx) { const char *param = duk_to_string(ctx,0); if (!MyConfig.ismounted()) return 0; OvmsConfigParam *p = MyConfig.CachedParam(param); if (p) { if (! p->Readable()) return 0; // Parameter is protected, and not readable duk_idx_t arr_idx = duk_push_array(ctx); int count = 0; for (ConfigParamMap::iterator it=p->m_map.begin(); it!=p->m_map.end(); ++it) { duk_push_string(ctx, it->first.c_str()); duk_put_prop_index(ctx, arr_idx, count++); } return 1; } return 0; } static duk_ret_t DukOvmsConfigGetValues(duk_context *ctx) { const char *param = duk_to_string(ctx,0); std::string prefix = duk_opt_string(ctx,1,""); if (!MyConfig.ismounted()) return 0; OvmsConfigParam *p = MyConfig.CachedParam(param); if (p) { if (! p->Readable()) return 0; // Parameter is protected, and not readable duk_idx_t obj_idx = duk_push_object(ctx); for (ConfigParamMap::iterator it=p->m_map.lower_bound(prefix); it!=p->m_map.end(); ++it) { if (!startsWith(it->first, prefix)) break; duk_push_string(ctx, it->second.c_str()); duk_put_prop_string(ctx, obj_idx, it->first.substr(prefix.length()).c_str()); } return 1; } return 0; } static duk_ret_t DukOvmsConfigSetValues(duk_context *ctx) { const char *param = duk_to_string(ctx,0); std::string prefix = duk_opt_string(ctx,1,""); duk_require_object(ctx,2); if (!duk_is_object(ctx,2)) return 0; if (!MyConfig.ismounted()) return 0; OvmsConfigParam *p = MyConfig.CachedParam(param); if (p) { if (! p->Writable()) return 0; // Parameter is not writeable ConfigParamMap pmap = p->m_map; std::string key, val; duk_enum(ctx, 2, 0); while (duk_next(ctx, -1, true)) { key = duk_to_string(ctx, -2); val = duk_to_string(ctx, -1); duk_pop_2(ctx); pmap[prefix+key] = val; } duk_pop(ctx); p->m_map.clear(); p->m_map = std::move(pmap); p->Save(); } return 0; } static duk_ret_t DukOvmsConfigGet(duk_context *ctx) { const char *param = duk_to_string(ctx,0); const char *instance = duk_to_string(ctx,1); const char *defvalue = duk_to_string(ctx,2); if (!MyConfig.ismounted()) return 0; OvmsConfigParam *p = MyConfig.CachedParam(param); if (p) { if (! p->Readable()) return 0; // Parameter is protected, and not readable if (p->IsDefined(instance)) { std::string v = p->GetValue(instance); duk_push_string(ctx, v.c_str()); } else { duk_push_string(ctx, defvalue); } return 1; } else { return 0; } return 0; } static duk_ret_t DukOvmsConfigSet(duk_context *ctx) { const char *param = duk_to_string(ctx,0); const char *instance = duk_to_string(ctx,1); const char *value = duk_to_string(ctx,2); if (!MyConfig.ismounted()) return 0; OvmsConfigParam *p = MyConfig.CachedParam(param); if (p) { if (! p->Writable()) return 0; // Parameter is not writeable p->SetValue(instance, value); } return 0; } static duk_ret_t DukOvmsConfigDelete(duk_context *ctx) { const char *param = duk_to_string(ctx,0); const char *instance = duk_to_string(ctx,1); if (!MyConfig.ismounted()) return 0; OvmsConfigParam *p = MyConfig.CachedParam(param); if (p) { if (! p->Writable()) return 0; // Parameter is not writeable p->DeleteInstance(instance); } return 0; } #endif // #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE OvmsConfig::OvmsConfig() { ESP_LOGI(TAG, "Initialising CONFIG (1400)"); m_mounted = false; OvmsCommand* cmd_store = MyCommandApp.RegisterCommand("store","STORE framework"); cmd_store->RegisterCommand("mount","Mount STORE",store_mount); cmd_store->RegisterCommand("unmount","Unmount STORE",store_unmount); OvmsCommand* cmd_config = MyCommandApp.RegisterCommand("config","CONFIG framework"); cmd_config->RegisterCommand("list","Show configuration parameters/instances",config_list,"[]",0,1, true, config_validate); cmd_config->RegisterCommand("set","Set parameter:instance=value",config_set," ",3,3, true, config_validate); cmd_config->RegisterCommand("rm","Remove parameter:instance",config_rm," { | *}",2,2, true, config_validate); #ifdef CONFIG_OVMS_SC_ZIP cmd_config->RegisterCommand("backup", "Backup to file", config_backup, " [password=module password]\n" "Backup system configuration & scripts into password protected ZIP file.\n" "Note: user files or directories in /store will not be included.\n" " defaults to the current module password, set to \"\" to disable encryption.\n" "Hint: use 7z to unzip/create backup ZIPs on a PC.", 1, 2); cmd_config->RegisterCommand("restore", "Restore from file", config_restore, " [password=module password]\n" "Restore system configuration & scripts from password protected ZIP file.\n" "Note: user files or directories in /store will not be touched.\n" "The module will perform a reboot after successful restore.\n" " defaults to the current module password.\n" "Note: You need to supply the password used for the backup creation.\n" "The default password is not available after flash is erased.", 1, 2); #endif // CONFIG_OVMS_SC_ZIP RegisterParam("password", "Password store", true, false); RegisterParam("module", "Module configuration", true, true); RegisterParam("usr", "Custom plugin configuration", true, true); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsConfig"); dto->RegisterDuktapeFunction(DukOvmsConfigParams, 0, "Params"); dto->RegisterDuktapeFunction(DukOvmsConfigInstances, 1, "Instances"); dto->RegisterDuktapeFunction(DukOvmsConfigGet, 3, "Get"); dto->RegisterDuktapeFunction(DukOvmsConfigSet, 3, "Set"); dto->RegisterDuktapeFunction(DukOvmsConfigDelete, 2, "Delete"); dto->RegisterDuktapeFunction(DukOvmsConfigGetValues, 2, "GetValues"); dto->RegisterDuktapeFunction(DukOvmsConfigSetValues, 3, "SetValues"); MyDuktape.RegisterDuktapeObject(dto); #endif // #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE } OvmsConfig::~OvmsConfig() { } esp_err_t OvmsConfig::mount() { // if (!spiffs_is_registered) // vfs_spiffs_register(); // if (!spiffs_is_mounted) // spiffs_mount(); if (m_mounted) return ESP_OK; memset(&m_store_fat,0,sizeof(esp_vfs_fat_sdmmc_mount_config_t)); m_store_fat.format_if_mount_failed = true; m_store_fat.max_files = 5; esp_vfs_fat_spiflash_mount("/store", "store", &m_store_fat, &m_store_wlh); m_mounted = true; struct stat ds; if (stat(OVMS_CONFIGPATH, &ds) != 0) { ESP_LOGI(TAG, "Initialising OVMS CONFIG within STORE"); mkdir(OVMS_CONFIGPATH,0); } DIR *dir; struct dirent *dp; if ((dir = opendir(OVMS_CONFIGPATH)) == NULL) { ESP_LOGE(TAG, "Error: Cannot open config store directory"); return ESP_ERR_NOT_FOUND; } while ((dp = readdir(dir)) != NULL) { // Register the param in case this was not already done if (CachedParam(dp->d_name) == NULL) RegisterParam(dp->d_name, "", true, false); } closedir(dir); // load & upgrade params: for (ConfigMap::iterator it=MyConfig.m_map.begin(); it!=MyConfig.m_map.end(); ++it) { it->second->Load(); } upgrade(); MyEvents.SignalEvent("config.mounted", NULL); return ESP_OK; } esp_err_t OvmsConfig::unmount() { // if (spiffs_is_mounted) // spiffs_unmount(0); if (m_mounted) { esp_vfs_fat_spiflash_unmount("/store", m_store_wlh); m_mounted = false; MyEvents.SignalEvent("config.unmounted", NULL); } return ESP_OK; } bool OvmsConfig::ismounted() { return m_mounted; } void OvmsConfig::upgrade() { // Migrate password/changed → module/init: if (GetParamValueBool("password", "changed") == true) { SetParamValue("module", "init", "done"); DeleteInstance("password", "changed"); } // Migrate vehicle.require.* signals to config: if (GetParamValueInt("module", "cfgversion") < 2018112200) { std::string vt = GetParamValue("auto", "vehicle.type"); if (vt=="FT5E" || vt=="KS" || vt=="MI" || vt=="RT" || vt=="TGTC" || vt=="VA" || vt=="ZEVA") { SetParamValueBool("modem", "enable.gps", true); SetParamValueBool("modem", "enable.gpstime", true); } } // Migrate server.v2 password (from server.v2 to password) if (GetParamValueInt("module", "cfgversion") < 2020053100) { if (IsDefined("server.v2","password")) { std::string p = GetParamValue("server.v2", "password"); SetParamValue("password","server.v2",p); DeleteInstance("server.v2","password"); } } // Migrate vehicle IDs VWUP.T26/.OBD back to VWUP std::string vt = GetParamValue("auto", "vehicle.type"); if (vt == "VWUP.T26" || vt == "VWUP.OBD") { SetParamValue("auto", "vehicle.type", "VWUP"); } // Move obsolete VWUP "xut" instances to "xvu": if (CachedParam("xut")) { RegisterParam("xvu", "VW e-Up", true, true); for (const auto& instance : { "canwrite", "modelyear", "cc_temp" }) { if (!IsDefined("xvu", instance) && IsDefined("xut", instance)) SetParamValue("xvu", instance, GetParamValue("xut", instance)); } DeregisterParam("xut"); } // Remove obsolete VWUP "vwup" param: if (CachedParam("vwup")) { DeregisterParam("vwup"); } // Done, set config version: SetParamValueInt("module", "cfgversion", 2020053100); } void OvmsConfig::RegisterParam(std::string name, std::string title, bool writable, bool readable) { auto k = m_map.find(name); if (k == m_map.end()) { OvmsConfigParam* p = new OvmsConfigParam(name, title, writable, readable); m_map[name] = p; } else { k->second->SetTitle(title); k->second->SetAccess(writable, readable); } } void OvmsConfig::DeregisterParam(std::string name) { auto k = m_map.find(name); if (k != m_map.end()) { k->second->DeleteParam(); delete k->second; m_map.erase(k); } } void OvmsConfig::SetParamValue(std::string param, std::string instance, std::string value) { OvmsConfigParam *p = CachedParam(param); if (p) { p->SetValue(instance,value); } } void OvmsConfig::SetParamValueBinary(std::string param, std::string instance, std::string value, BinaryEncoding_t encoding /*=Encoding_HEX*/) { std::string encval; if (encoding == Encoding_BASE64) { encval = base64encode(value); } else // Encoding_HEX { encval = hexencode(value); } SetParamValue(param, instance, encval); } void OvmsConfig::SetParamValueInt(std::string param, std::string instance, int value) { std::ostringstream ss; ss << value; SetParamValue(param, instance, std::string(ss.str())); } void OvmsConfig::SetParamValueFloat(std::string param, std::string instance, float value) { std::ostringstream ss; ss << value; SetParamValue(param, instance, std::string(ss.str())); } void OvmsConfig::SetParamValueBool(std::string param, std::string instance, bool value) { SetParamValue(param, instance, std::string(value ? "yes" : "no")); } void OvmsConfig::DeleteInstance(std::string param, std::string instance) { OvmsConfigParam *p = CachedParam(param); if (p) { p->DeleteInstance(instance); } } std::string OvmsConfig::GetParamValue(std::string param, std::string instance, std::string defvalue) { OvmsConfigParam *p = CachedParam(param); if (p && p->IsDefined(instance)) { return p->GetValue(instance); } else { return defvalue; } } std::string OvmsConfig::GetParamValueBinary(std::string param, std::string instance, std::string defvalue, BinaryEncoding_t encoding /*=Encoding_HEX*/) { std::string encval = GetParamValue(param,instance); size_t len = encval.length(); if (len == 0) return defvalue; if (encoding == Encoding_BASE64) { return base64decode(encval); } else // Encoding_HEX { std::string value = hexdecode(encval); if (value.empty()) { ESP_LOGE(TAG, "Invalid non-hex value for config param %s instance %s", param.c_str(), instance.c_str()); return defvalue; } return value; } } int OvmsConfig::GetParamValueInt(std::string param, std::string instance, int defvalue) { std::string value = GetParamValue(param,instance); if (value.length() == 0) return defvalue; return atoi(value.c_str()); } float OvmsConfig::GetParamValueFloat(std::string param, std::string instance, float defvalue) { std::string value = GetParamValue(param,instance); if (value.length() == 0) return defvalue; return atof(value.c_str()); } bool OvmsConfig::GetParamValueBool(std::string param, std::string instance, bool defvalue) { std::string value = GetParamValue(param,instance); if (value.length() == 0) return defvalue; return strtobool(value); } bool OvmsConfig::IsDefined(std::string param, std::string instance) { OvmsConfigParam *p = CachedParam(param); if (p == NULL) return false; return p->IsDefined(instance); } OvmsConfigParam* OvmsConfig::CachedParam(std::string param) { if (!m_mounted) return NULL; OvmsConfigParam* const* p = m_map.FindUniquePrefix(param.c_str()); if (!p) return NULL; return *p; } bool OvmsConfig::ProtectedPath(std::string path) { #ifdef CONFIG_OVMS_DEV_CONFIGVFS return false; #else return (path.find(OVMS_CONFIGPATH) != std::string::npos); #endif // #ifdef CONFIG_OVMS_DEV_CONFIGVFS } /** * GetParamMap: get map (copy) of param instances */ ConfigParamMap OvmsConfig::GetParamMap(std::string param) { ConfigParamMap pmap; if (!CachedParam(param)) RegisterParam(param, "", true, false); OvmsConfigParam* p = CachedParam(param); if (p) pmap = p->GetMap(); return pmap; } /** * SetParamMap: replace all param instances * - Note: items will be removed from source map, map is empty afterwards */ void OvmsConfig::SetParamMap(std::string param, ConfigParamMap& map) { if (!CachedParam(param)) RegisterParam(param, "", true, false); OvmsConfigParam* p = CachedParam(param); if (p) p->SetMap(map); } #ifdef CONFIG_OVMS_SC_ZIP /** * Backup: */ static struct { const char* name; bool optional; } backup_dir[] = { { "ovms_config", false }, { "events", true }, { "scripts", true }, { "obd2ecu", true }, { "dbc", true }, { "plugin", true }, { "tls", true }, { "trustedca", true }, { "usr", true }, { NULL, false } }; bool OvmsConfig::Backup(std::string path, std::string password, OvmsWriter* writer /*=NULL*/, int verbosity /*=1024*/) { if (writer) writer->printf("Creating config backup '%s'...\n", path.c_str()); else ESP_LOGD(TAG, "Backup: creating '%s'...", path.c_str()); OvmsMutexLock store_lock(&m_store_lock); bool ok = true; ZipArchive zip(path, password, ZIP_CREATE|ZIP_TRUNCATE); if (ok) ok = zip.chdir("/store"); for (int i = 0; ok && backup_dir[i].name; i++) { if (writer && verbosity >= COMMAND_RESULT_NORMAL) writer->printf("..add '%s'\n", backup_dir[i].name); else if (!writer) ESP_LOGD(TAG, "Backup '%s': add '%s'", path.c_str(), backup_dir[i].name); ok = zip.add(backup_dir[i].name, backup_dir[i].optional); } if (ok) ok = zip.close(); if (!ok) { if (writer) writer->printf("Error: zip failed: %s\n", zip.strerror()); else ESP_LOGE(TAG, "Backup '%s': zip failed: %s", path.c_str(), zip.strerror()); } else { if (writer) writer->puts("Done."); else ESP_LOGI(TAG, "Backup '%s' done", path.c_str()); } return ok; } /** * Restore: */ static bool install_dir(std::string src, std::string dst) { if (!path_exists(src)) { rmtree(dst); return true; } std::string dstbak = dst + ".old"; rmtree(dstbak); if (mkpath(dst) != 0) // ensure dst exists return false; if (rename(dst.c_str(), dstbak.c_str()) == 0) { if (rename(src.c_str(), dst.c_str()) == 0) { rmtree(dstbak); return true; } rename(dstbak.c_str(), dst.c_str()); } return false; } bool OvmsConfig::Restore(std::string path, std::string password, OvmsWriter* writer /*=NULL*/, int verbosity /*=1024*/) { if (writer) writer->printf("Restoring config from '%s'...\n", path.c_str()); else ESP_LOGD(TAG, "Restore: reading '%s'...", path.c_str()); // Lock config store: if (!m_store_lock.Lock(pdMS_TO_TICKS(5000))) { if (writer) writer->puts("Error: config store currently in use by another process"); else ESP_LOGE(TAG, "Restore: timeout waiting for config lock"); return false; } // Signal & wait for components to shutdown: { OvmsSemaphore eventdone; auto callback = [](const char* event, void* data) { ((OvmsSemaphore*)data)->Give(); }; MyEvents.SignalEvent("config.restore", &eventdone, callback); eventdone.Take(); } bool ok = true; // unzip into restore directory: // (Note: all paths beginning with "/store/ovms_config" are protected) std::string tempdir = "/store/ovms_config_restore"; if (rmtree(tempdir) != 0 || mkpath(tempdir) != 0) { if (writer) writer->printf("Error: prepare failed: %s\n", strerror(errno)); else ESP_LOGE(TAG, "Restore '%s': prepare failed: %s", path.c_str(), strerror(errno)); m_store_lock.Unlock(); return false; } ZipArchive zip(path, password, ZIP_RDONLY); if (ok) ok = zip.chdir(tempdir); for (int i = 0; ok && backup_dir[i].name; i++) { if (writer && verbosity >= COMMAND_RESULT_NORMAL) writer->printf("..extract '%s'\n", backup_dir[i].name); else if (!writer) ESP_LOGD(TAG, "Restore '%s': extract '%s'", path.c_str(), backup_dir[i].name); ok = zip.extract(backup_dir[i].name, backup_dir[i].optional); } if (ok) ok = zip.close(); if (!ok) { if (writer) writer->printf("Error: unzip failed: %s%s\n", zip.strerror(), password.empty() ? " (password required?)" : ""); else ESP_LOGE(TAG, "Restore '%s': unzip failed: %s%s", path.c_str(), zip.strerror(), password.empty() ? " (password required?)" : ""); rmtree(tempdir); m_store_lock.Unlock(); return false; } // replace config by restored version: if (writer) writer->puts("Installing..."); else ESP_LOGD(TAG, "Restore '%s': installing...", path.c_str()); std::string dstbase = "/store/"; for (int i = 0; backup_dir[i].name; i++) { if (writer && verbosity >= COMMAND_RESULT_NORMAL) writer->printf("..install '%s'\n", backup_dir[i].name); else if (!writer) ESP_LOGD(TAG, "Restore '%s': install '%s'", path.c_str(), backup_dir[i].name); if (!install_dir(tempdir + "/" + backup_dir[i].name, dstbase + backup_dir[i].name)) { ok = false; if (!backup_dir[i].optional) { if (writer) writer->printf("Error: install '%s' failed: %s\n", backup_dir[i].name, strerror(errno)); else ESP_LOGE(TAG, "Restore '%s': install '%s' failed: %s", path.c_str(), backup_dir[i].name, strerror(errno)); break; } else { if (writer) writer->printf("Warning: install '%s' failed: %s\n", backup_dir[i].name, strerror(errno)); else ESP_LOGW(TAG, "Restore '%s': install '%s' failed: %s", path.c_str(), backup_dir[i].name, strerror(errno)); } } } // cleanup & reboot: rmtree(tempdir); if (!ok) { m_store_lock.Unlock(); return false; } if (writer) writer->puts("Done, rebooting now..."); else ESP_LOGI(TAG, "Restore '%s': done, rebooting...", path.c_str()); vTaskDelay(1000/portTICK_PERIOD_MS); MyBoot.Restart(); return true; } #endif // CONFIG_OVMS_SC_ZIP void OvmsConfig::SupportSummary(OvmsWriter* writer) { writer->puts("\nConfiguration"); for (ConfigMap::iterator mi=m_map.begin(); mi!=m_map.end(); ++mi) { writer->printf(" [%s]\n",mi->first.c_str()); OvmsConfigParam* p = mi->second; for (ConfigParamMap::iterator it=p->m_map.begin(); it!=p->m_map.end(); ++it) { if (p->Readable()) { writer->printf(" %s: %s\n",it->first.c_str(), it->second.c_str()); } else { writer->printf(" %s: **redacted**\n",it->first.c_str()); } } } } OvmsConfigParam::OvmsConfigParam(std::string name, std::string title, bool writable, bool readable) { m_name = name; m_title = title; m_writable = writable; m_readable = readable; m_loaded = false; if (MyConfig.ismounted()) { LoadConfig(); } } OvmsConfigParam::~OvmsConfigParam() { } void OvmsConfigParam::LoadConfig() { if (m_loaded) return; // Protected against loading more than once OvmsMutexLock store_lock(&MyConfig.m_store_lock); std::string path(OVMS_CONFIGPATH); path.append("/"); path.append(m_name); // ESP_LOGI(TAG, "Trying %s",path.c_str()); FILE* f = fopen(path.c_str(), "r"); if (f) { char* buf = new char[OVMS_MAXVALSIZE]; while (fgets(buf, OVMS_MAXVALSIZE, f)) { buf[strlen(buf)-1] = 0; // Remove trailing newline #ifdef OVMS_PERSIST_METADATA // check for meta data: if (buf[0] == '#') { if (strncmp(buf, "#access=", 8) == 0) { m_readable = (strchr(buf+8, 'r') != NULL); m_writable = (strchr(buf+8, 'w') != NULL); continue; } else if (strncmp(buf, "#title=", 7) == 0) { m_title = buf+7; continue; } } #endif // OVMS_PERSIST_METADATA // read instance: char *p = index(buf,char(9)); if (p == NULL) p = index(buf,' '); if (p) { *p = 0; // Null terminate the key p++; // and point to the value m_map[std::string(buf)] = std::string(p); // ESP_LOGI(TAG, "Loaded %s/%s=%s", m_name.c_str(), buf, p); } } delete[] buf; fclose(f); } m_loaded = true; } void OvmsConfigParam::SetValue(std::string instance, std::string value) { if (m_map.find(instance) == m_map.end() || m_map[instance] != value) { m_map[instance] = value; RewriteConfig(); MyEvents.SignalEvent("config.changed", this); } } void OvmsConfigParam::DeleteParam() { OvmsMutexLock store_lock(&MyConfig.m_store_lock); std::string path(OVMS_CONFIGPATH); path.append("/"); path.append(m_name); unlink(path.c_str()); MyEvents.SignalEvent("config.changed", this); } bool OvmsConfigParam::DeleteInstance(std::string instance) { bool ret = false; auto k = m_map.find(instance); if (k != m_map.end()) { m_map.erase(k); RewriteConfig(); ret = true; } MyEvents.SignalEvent("config.changed", this); return ret; } std::string OvmsConfigParam::GetValue(std::string instance) { auto k = m_map.find(instance); if (k == m_map.end()) return std::string(""); else return k->second; } bool OvmsConfigParam::IsDefined(std::string instance) { if (instance.empty()) return !m_map.empty(); auto k = m_map.find(instance); if (k == m_map.end()) return false; else return true; } bool OvmsConfigParam::Writable() { return m_writable; } bool OvmsConfigParam::Readable() { return m_readable; } void OvmsConfigParam::SetAccess(bool writable, bool readable) { m_writable = writable; m_readable = readable; } std::string OvmsConfigParam::GetName() { return m_name; } void OvmsConfigParam::RewriteConfig() { OvmsMutexLock store_lock(&MyConfig.m_store_lock); std::string path(OVMS_CONFIGPATH); path.append("/"); path.append(m_name); FILE* f = fopen(path.c_str(), "w"); if (!f) ESP_LOGE(TAG, "RewriteConfig: can't open '%s': %s", path.c_str(), strerror(errno)); else { #ifdef OVMS_PERSIST_METADATA // write meta data: fprintf(f, "#access=%s%s\n", m_readable ? "r" : "", m_writable ? "w" : ""); fprintf(f, "#title=%s\n", m_title.c_str()); #endif // write instances: for (ConfigParamMap::iterator it=m_map.begin(); it!=m_map.end(); ++it) { fprintf(f,"%s\t%s\n",it->first.c_str(),it->second.c_str()); } if (fclose(f)) ESP_LOGE(TAG, "RewriteConfig: error writing '%s': %s", path.c_str(), strerror(errno)); } } void OvmsConfigParam::Load() { if (!m_loaded) LoadConfig(); } void OvmsConfigParam::Save() { if (m_name != "") { RewriteConfig(); MyEvents.SignalEvent("config.changed", this); } } /** * SetMap: replace all param instances * - Note: items will be removed from source map, map is empty afterwards */ void OvmsConfigParam::SetMap(ConfigParamMap& map) { m_map.clear(); m_map = std::move(map); Save(); }