diff --git a/examples/settings/CMakeLists.txt b/examples/settings/CMakeLists.txt new file mode 100644 index 00000000000..314ac5adcd6 --- /dev/null +++ b/examples/settings/CMakeLists.txt @@ -0,0 +1,33 @@ +# ############################################################################## +# apps/examples/settings/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_EXAMPLES_SETTINGS) + nuttx_add_application( + NAME + ${CONFIG_EXAMPLES_SETTINGS_PROGNAME} + PRIORITY + ${CONFIG_EXAMPLES_SETTINGS_PRIORITY} + STACKSIZE + ${CONFIG_EXAMPLES_SETTINGS_STACKSIZE} + MODULE + ${CONFIG_EXAMPLES_SETTINGS} + SRCS + settings_main.c) +endif() diff --git a/examples/settings/Kconfig b/examples/settings/Kconfig new file mode 100644 index 00000000000..b6d33f78ac8 --- /dev/null +++ b/examples/settings/Kconfig @@ -0,0 +1,53 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_SETTINGS + tristate "Settings example" + default n + ---help--- + Enable the "settings" example. This uses the functions provided in + utils/settings to test the storage and retrieval od various + configurable parameters + +if EXAMPLES_SETTINGS + +config EXAMPLES_SETTINGS_PROGNAME + string "Program name" + default "settings" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config EXAMPLES_SETTINGS_PRIORITY + int "settings task priority" + default 100 + +config EXAMPLES_SETTINGS_STACKSIZE + int "settings stack size" + default DEFAULT_TASK_STACKSIZE + +choice + prompt "Select settings storage location" + default EXAMPLES_SETTINGS_USE_TMPFS + +config EXAMPLES_SETTINGS_USE_TMPFS + bool "Use TMPFS" + select FS_TMPFS + ---help--- + TMPFS will be enabled and used + +config EXAMPLES_SETTINGS_USE_OTHER + bool "Use existing/other storage media" + +endchoice # Select settings storage location + +if EXAMPLES_SETTINGS_USE_OTHER + +config EXAMPLES_SETTINGS_EXISTING_STORAGE + string "Path to existing storage media" + default "/mnt/sdcard0" + +endif # EXAMPLES_SETTINGS_USE_OTHER +endif # EXAMPLES_SETTINGS diff --git a/examples/settings/Make.defs b/examples/settings/Make.defs new file mode 100644 index 00000000000..8198e28a302 --- /dev/null +++ b/examples/settings/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/examples/settings/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_SETTINGS),) +CONFIGURED_APPS += $(APPDIR)/examples/settings +endif diff --git a/examples/settings/Makefile b/examples/settings/Makefile new file mode 100644 index 00000000000..53bf1f22d92 --- /dev/null +++ b/examples/settings/Makefile @@ -0,0 +1,34 @@ +############################################################################ +# apps/examples/settings/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# settings built-in application info + +PROGNAME = $(CONFIG_EXAMPLES_SETTINGS_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_SETTINGS_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_SETTINGS_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_SETTINGS) + +# Settings Example + +MAINSRC = settings_main.c + +include $(APPDIR)/Application.mk diff --git a/examples/settings/settings_main.c b/examples/settings/settings_main.c new file mode 100644 index 00000000000..7f70c936954 --- /dev/null +++ b/examples/settings/settings_main.c @@ -0,0 +1,275 @@ +/**************************************************************************** + * apps/examples/settings/settings_main.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "system/settings.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * settings_main + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + int ret; + int fd; + enum settings_type_e stype; + char path[50]; + char *teststr = ""; + int testval = 0x5aa5; + char readstr[CONFIG_SETTINGS_VALUE_SIZE]; + enum storage_type_e storage_type = STORAGE_BINARY; + bool flag_present = false; + struct stat sbuf; + + if ((argc < 1) || *argv[1] == 0 || *(argv[1] + 1) == 0) + { + goto print_help; + } + + if (*argv[1] == '-') + { + flag_present = true; + if (*(argv[1] + 1) == 'b') + { + storage_type = STORAGE_BINARY; + } + else if (*(argv[1] + 1) == 't') + { + storage_type = STORAGE_TEXT; + } + else + { + goto print_help; + } + } + + if (flag_present && argc < 2) + { + goto print_help; + } + +#ifdef CONFIG_EXAMPLES_SETTINGS_USE_TMPFS +# ifndef CONFIG_FS_TMPFS +# error TMPFS must be enabled +# endif +# ifndef CONFIG_LIBC_TMPDIR +# error LIBC_TMPDIR must be defined +# endif + if (stat(CONFIG_LIBC_TMPDIR, &sbuf)) + { + ret = nx_mount(NULL, CONFIG_LIBC_TMPDIR, "tmpfs", 0, NULL); + if (ret < 0) + { + printf("ERROR: Failed to mount tmpfs at %s: %d\n", + CONFIG_LIBC_TMPDIR, ret); + goto end; + } + } + + strcpy(path, CONFIG_LIBC_TMPDIR); + strcat(path, "/settings"); +#else + strcpy(path, CONFIG_EXAMPLES_SETTINGS_EXISTING_STORAGE); + if (path == NULL) + { + printf("Settings filepath is empty!"); + goto end; + } + + strcat(path, "/settings"); +#endif + + printf("Example of settings usage: %s. Path: %s\n", + ((storage_type == STORAGE_TEXT) ? "text" : "binary"), path); + printf("-------------------------------------------------------\n"); + + ret = settings_init(); + if (ret < 0) + { + printf("settings init failed: %d", ret); + goto end; + } + + ret = settings_setstorage(path, storage_type); + if (ret == -ENOENT) + { + printf("INFO: no existing binary settings file found. Creating it.\n"); + fd = open(path, O_CREAT); + if (fd < 0) + { + printf("failed to create settings file\n"); + goto end; + } + + close(fd); + } + else if (ret < 0) + { + printf("settings setstorage failed: %d\n", ret); + goto end; + } + else + { + printf("INFO: a settings file was found and loaded\n"); + ret = settings_get("v1", SETTING_STRING, readstr, + CONFIG_SETTINGS_VALUE_SIZE); + if (ret < 0) + { + printf("settings retrieve failed: %d\n", ret); + goto end; + } + + printf("Retrieved settings value (v1) with value:%s\n", + readstr); + } + + printf("Creating an integer settings value (v1) with value:0x%x\n", + testval); + ret = settings_create("v1", SETTING_INT, testval); + if (ret < 0) + { + printf("settings create failed: %d\n", ret); + goto end; + } + + ret = settings_type("v1", &stype); + if (ret < 0) + { + printf("failed to read settings type: %d\n", ret); + goto end; + } + + printf("Retrieved setting type is:%d\n", stype); + + ret = settings_get("v1", SETTING_INT, &testval); + if (ret < 0) + { + printf("settings retrieve failed: %d\n", ret); + goto end; + } + + printf("Retrieved integer settings value (v1) with value:0x%x\n", + testval); + + ret = settings_get("v1", SETTING_BOOL, &testval); + if (ret < 0) + { + printf("Deliberate fail: BOOL type requested not INT. Error:%d\n", + ret); + } + + ret = settings_get("v2", SETTING_INT, &testval); + if (ret < 0) + { + printf("Deliberate fail: non-existent setting requested. Error:%d\n", + ret); + } + + teststr = "I'm a string!"; + printf("Changing an integer settings value (v1) to string:%s\n", + teststr); + ret = settings_create("v1", SETTING_STRING, teststr); + if (ret < 0) + { + printf("settings changed failed: %d\n", ret); + goto end; + } + + ret = settings_get("v1", SETTING_STRING, &readstr, + CONFIG_SETTINGS_VALUE_SIZE); + if (ret < 0) + { + printf("settings retrieve failed: %d\n", ret); + goto end; + } + + printf("Retrieved string settings value (v1) with value:%s\n", + readstr); + + FAR struct in_addr save_ip; + FAR struct in_addr load_ip; + save_ip.s_addr = HTONL(0xc0a86401); + ret = settings_create("IP0", SETTING_IP_ADDR, &save_ip); + if (ret < 0) + { + printf("IP address settings create failed: %d\n", ret); + goto end; + } + + printf("Creating an IP settings value (IP0) with value:0x%08lx\n", + save_ip.s_addr); + ret = settings_get("IP0", SETTING_IP_ADDR, &load_ip); + if (ret < 0) + { + printf("IP address settings retrieve failed: %d\n", ret); + goto end; + } + + printf("Retrieved IP address settings value (IP0) with value:0x%08lx\n", + NTOHL(load_ip.s_addr)); +end: + +#ifdef CONFIG_SETTINGS_CACHED_SAVES + /* saves may not have been written out before we exit */ + + sleep(1); +#endif + return OK; + +print_help: + printf("Usage...\n"); + printf("settings [-b | -t] \n"); + printf(" -i = use a binary storage file (default)\n"); + printf(" -t = use a text storage file\n"); + printf(" Example:\n"); + printf(" settings -b\n"); + return -EINVAL; +} diff --git a/include/system/settings.h b/include/system/settings.h new file mode 100644 index 00000000000..73520a73559 --- /dev/null +++ b/include/system/settings.h @@ -0,0 +1,355 @@ +/**************************************************************************** + * apps/include/system/settings.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * The settings storage can be used to store and retrieve various + * configurable parameters. This storage is a RAM-based map (for fast + * access), but one or more files can be used too (so values can persist + * across system reboots). + * + * Each setting is a key / value pair. + * The key is always a string, while there are many value types supported. + * + * All strings, whether they represent a key or a value, must use a specific + * format. They must be ASCII, NULL-terminated strings, and they are always + * case-sensitive. They must obey to the following rules: + * - they cannot start with a number. + * - they cannot contain the characters '=', ';'. + * - they cannot contain the characters '\n', '\r'. + * + * Since each setting has its own type, it is the user's responsibility to + * access the setting using the correct type. Some "casts" are possible + * (e.g. between bool and int), but generally reading with the wrong type + * will lead to failure. + * + * There are also various types of files that can be used. Each file type + * is using a different data format to parse the stored data. Every type is + * expected to be best suited for a specific file system or medium type, or + * be better performing while used with external systems. In any case, all + * configured files are automatically synchronized whenever a value changes. + * + * This is a thread-safe implementation. Different threads may access the + * settings simultaneously. + * + ****************************************************************************/ + +#ifndef SYSTEM_SETTINGS_H_ +#define SYSTEM_SETTINGS_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum settings_type_e +{ + SETTING_EMPTY = 0, + SETTING_INT = 1, + SETTING_BOOL = 2, + SETTING_FLOAT = 3, + SETTING_STRING = 4, + SETTING_IP_ADDR = 5, +}; + +typedef struct +{ + char key[CONFIG_SETTINGS_KEY_SIZE]; + enum settings_type_e type; + union + { + int i; + double f; + char s[CONFIG_SETTINGS_VALUE_SIZE]; + struct in_addr ip; + }; +} setting_t; + +typedef struct +{ + char file[CONFIG_SETTINGS_MAX_FILENAME]; + + int (*load_fn)(FAR char *file); + int (*save_fn)(FAR char *file); +} storage_t; + +struct notify_s +{ + pid_t pid; + uint8_t signo; +}; + +typedef struct +{ + pthread_mutex_t mtx; + uint32_t hash; + int wrpend; + setting_t map[CONFIG_SETTINGS_MAP_SIZE]; + storage_t store[CONFIG_SETTINGS_MAX_STORAGES]; + struct notify_s notify[CONFIG_SETTINGS_MAX_SIGNALS]; + + #ifdef CONFIG_SETTINGS_CACHED_SAVES + struct sigevent sev; + struct itimerspec trigger; + timer_t timerid; + #endif +} state_t; + +enum storage_type_e +{ + STORAGE_BINARY = 0, + STORAGE_TEXT = 1, +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: settings_init + * + * Description: + * Initializes the settings storage. + * + * Input Parameters: none + * + * Returned Value: Success + * + ****************************************************************************/ + +int settings_init(void); + +/**************************************************************************** + * Name: settings_setstorage + * + * Description: + * Sets a file to be used as a settings storage. + * Except from the first file, if loading the file causes any changes + * to the settings, then the new map will be dumped to all files + * (effectively it syncs all storages). + * + * Input Parameters: + * file - the filename of the storage to use + * type - the type of the storage (BINARY or TEXT) + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_setstorage(FAR char * file, enum storage_type_e type); + +/**************************************************************************** + * Name: settings_sync + * + * Description: + * Sets a file to be used as a settings storage. + * Except from the first file, if loading the file causes any changes + * to the settings, then the new map will be dumped to all files + * (effectively it syncs all storages). + * + * Input Parameters: + * file - the filename of the storage to use + * type - the type of the storage + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_sync(void); + +/**************************************************************************** + * Name: settings_notify + * + * Description: + * Registers a task to be notified on any change of the settings. + * Whenever any value is changed, a signal will be sent to all + * registered threads. Signals are NOT sent when new settings are + * created or when the whole storage is cleared. + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_notify(void); + +/**************************************************************************** + * Name: settings_hash + * + * Description: + * Gets the hash of the settings storage. + * + * This hash represents the internal state of the settings map. A + * unique number is calculated based on the contents of the whole map. + * + * This hash can be used to check the settings for any alterations, i.e. + * any setting that may had its value changed since last check. + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_hash(FAR uint32_t *h); + +/**************************************************************************** + * Name: settings_clear + * + * Description: + * Clears all settings. + * Data in all storages are purged. + * + * Note that if the settings are cleared during the application run-time + * (i.e. not during initialization), every access to the settings storage + * will fail. + * + * All settings must be created again. This can be done either by calling + * Settings_create() again, or by restarting the application. + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_clear(void); + +/**************************************************************************** + * Name: settings_create + * + * Description: + * Creates a new setting. + * + * If the setting is found to exist in any of the storages, it will + * be loaded. Else, it will be created and the default value will be + * assigned. + * + * Input Parameters: + * key - the key of the setting. + * type - the type of the setting. + * ... - the default value of the setting. + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_create(FAR char * key, enum settings_type_e type, ...); + +/**************************************************************************** + * Name: settings_type + * + * Description: + * Gets the type of a setting. + * + * Input Parameters: + * key - the key of the setting. + * type = pointer to int for the returned setting type + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_type(FAR char * key, FAR enum settings_type_e * stype); + +/**************************************************************************** + * Name: settings_get + * + * Description: + * Gets the value of a setting. + * + * Input Parameters: + * key - the key of the setting. + * type - the type of the setting + * ... - pointer to store the setting value. + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_get(FAR char * key, FAR enum settings_type_e type, ...); + +/**************************************************************************** + * Name: settings_set + * + * Description: + * Sets the value of a setting. + * + * Input Parameters: + * key - the key of the setting. + * type - the type of the setting + * ... - the new value of the setting. + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_set(FAR char * key, FAR enum settings_type_e type, ...); + +/**************************************************************************** + * Name: settings_iterate + * + * Description: + * Gets a copy of a setting at the specified position. It can be used to + * iterate over the settings map, by using successive values of idx. + * + * Input Parameters: + * idx - the iteration index for the setting. + * setting - pointer to return the setting value + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_iterate(int idx, FAR setting_t * setting); + +#endif /* SYSTEM_SETTINGS_H_ */ + diff --git a/include/system/storage.h b/include/system/storage.h new file mode 100644 index 00000000000..fbf9f82951a --- /dev/null +++ b/include/system/storage.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * apps/include/system/storage.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef SETTINGS_STORAGE_H_ +#define SETTINGS_STORAGE_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/* Text storage. */ + +int load_text(FAR char * file); +int save_text(FAR char * file); + +/* Binary storage. */ + +int load_bin(FAR char * file); +int save_bin(FAR char * file); + +#endif /* SETTINGS_STORAGE_H_*/ + diff --git a/system/settings/Kconfig b/system/settings/Kconfig new file mode 100644 index 00000000000..88dadfd2d26 --- /dev/null +++ b/system/settings/Kconfig @@ -0,0 +1,76 @@ +# +# Settings Storage Configuration +# + +config SYSTEM_SETTINGS + bool "Settings Functions" + default n + ---help--- + The settings storage can be used to store and retrieve various + configurable parameters. This storage is a RAM-based map (for fast access) + but one or more files can be used too (so values can persist across system + reboots). + +if SYSTEM_SETTINGS + +config SETTINGS_MAP_SIZE + int "Settings storage size" + default 20 + ---help--- + The size of the settings storage, i.e. the + maximum number of settings that can be created. + +config SETTINGS_KEY_SIZE + int "Settings key size" + default 20 + ---help--- + Maximum size of a setting key. + +config SETTINGS_VALUE_SIZE + int "Settings value size" + default 20 + ---help--- + Maximum size of a string setting value. + +config SETTINGS_MAX_STORAGES + int "Settings files" + default 2 + ---help--- + Maximum number of supported settings files. + +config SETTINGS_MAX_FILENAME + int "Settings filename size" + default 50 + ---help--- + Maximum size of settings filename. + +config SETTINGS_CACHED_SAVES + bool "Cache save operations" + default y + ---help--- + If enabled saving to the storages will be cached. + + Whenever a save is triggered, the settings will only + be saved in the RAM map (and will be immediately + effective). Writes to the storages though will + happen after a small delay. + + This improves performance greatly when multiple + successive saves are to be performed, as the actual + write will happen only once, asynchronously. + +config SETTINGS_MAX_SIGNALS + int "Max. settings signals" + default 2 + ---help--- + Maximum number of signals that can be registered + on the settings storage. + +config SETTINGS_SIGNO + int "Signal Number to use" + default 32 + range 32 63 + ---help--- + The Signal number to use for notifies + +endif # SYSTEM_SETTINGS diff --git a/system/settings/Make.defs b/system/settings/Make.defs new file mode 100644 index 00000000000..9092af73f9e --- /dev/null +++ b/system/settings/Make.defs @@ -0,0 +1,24 @@ +############################################################################ +# apps/system/settings/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_SETTINGS),) +CONFIGURED_APPS += $(APPDIR)/system/settings +endif + diff --git a/system/settings/Makefile b/system/settings/Makefile new file mode 100644 index 00000000000..3681722aa44 --- /dev/null +++ b/system/settings/Makefile @@ -0,0 +1,28 @@ +############################################################################ +# apps/system/settings/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +ifneq ($CONFIG_SYSTEM_SETTINGS,) +CSRCS += settings.c storage_bin.c storage_text.c +endif + +include $(APPDIR)/Application.mk + diff --git a/system/settings/settings.c b/system/settings/settings.c new file mode 100644 index 00000000000..fec13d329ee --- /dev/null +++ b/system/settings/settings.c @@ -0,0 +1,1432 @@ +/**************************************************************************** + * apps/system/settings/settings.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "system/storage.h" +#include "system/settings.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_SETTINGS_CACHED_SAVES +# define TIMER_SIGNAL SIGRTMIN +#endif + +#ifndef CONFIG_SETTINGS_SIGNO +# define CONFIG_SETTINGS_SIGNO 32 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int sanity_check(FAR char *str); +static uint32_t hash_calc(void); +static int get_setting(FAR char *key, FAR setting_t *setting); +static size_t get_string(FAR setting_t *setting, FAR char *buffer, + size_t size); +static int set_string(FAR setting_t *setting, FAR char *str); +static int get_int(FAR setting_t *setting, FAR int *i); +static int set_int(FAR setting_t *setting, int i); +static int get_bool(FAR setting_t *setting, FAR int *i); +static int set_bool(FAR setting_t *setting, int i); +static int get_float(FAR setting_t *setting, FAR double *f); +static int set_float(FAR setting_t *setting, double f); +static int get_ip(FAR setting_t *setting, struct in_addr *ip); +static int set_ip(FAR setting_t *setting, struct in_addr *ip); +static int load(void); +static int save(void); +static void signotify(void); +static int dump_cache(union sigval value); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +state_t g_settings; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: hash_calc + * + * Description: + * Gets a setting for a string value + * + * Input Parameters: + * none + * Returned Value: + * crc32 hash of all the settings + * + ****************************************************************************/ + +static uint32_t hash_calc(void) +{ + return crc32((FAR uint8_t *)g_settings.map, sizeof(g_settings.map)); +} + +/**************************************************************************** + * Name: get_setting + * + * Description: + * Gets a setting for a string value + * + * Input Parameters: + * key - key of the required setting + * setting - pointer to return the setting + * + * Returned Value: + * The value of the setting for the given key + * + ****************************************************************************/ + +static int get_setting(FAR char *key, FAR setting_t *setting) +{ + int i; + int ret = OK; + + for (i = 0; i < CONFIG_SETTINGS_MAP_SIZE; i++) + { + if (g_settings.map[i].type == SETTING_EMPTY) + { + ret = -ENOENT; + goto exit; + } + + if (strcmp(g_settings.map[i].key, key) == 0) + { + *setting = g_settings.map[i]; + goto exit; + } + } + + ret = -ENOENT; + +exit: + return ret; +} + +/**************************************************************************** + * Name: get_string + * + * Description: + * Gets a setting for a string value + * + * Input Parameters: + * setting - pointer to the setting type + * str - pointer to return the string value + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static size_t get_string(FAR setting_t *setting, FAR char *buffer, + size_t size) +{ + DEBUGASSERT(setting == NULL); + DEBUGASSERT((setting->type == SETTING_STRING) || + (setting->type == SETTING_IP_ADDR)); + + if (setting->type == SETTING_STRING) + { + FAR const char *s = setting->s; + size_t len = strlen(s); + + DEBUGASSERT(len < size); + if (len >= size) + { + return 0; + } + + strncpy(buffer, s, size); + buffer[size - 1] = '\0'; + + return len; + } + else if (setting->type == SETTING_IP_ADDR) + { + DEBUGASSERT(INET_ADDRSTRLEN < size); + + inet_ntop(AF_INET, &setting->ip, buffer, size); + buffer[size - 1] = '\0'; + + return strlen(buffer); + } + + return 0; +} + +/**************************************************************************** + * Name: set_string + * + * Description: + * Creates a setting for a string value + * + * Input Parameters: + * setting - pointer to the setting type + * str - the string value + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int set_string(FAR setting_t *setting, FAR char *str) +{ + DEBUGASSERT(setting); + DEBUGASSERT((setting->type == SETTING_STRING) || + (setting->type == SETTING_IP_ADDR)); + + ASSERT(strlen(str) < CONFIG_SETTINGS_VALUE_SIZE); + if (strlen(str) >= CONFIG_SETTINGS_VALUE_SIZE) + { + return -EINVAL; + } + + if (strlen(str) && (sanity_check(str) < 0)) + { + return -EINVAL; + } + + setting->type = SETTING_STRING; + strncpy(setting->s, str, CONFIG_SETTINGS_VALUE_SIZE); + setting->s[CONFIG_SETTINGS_VALUE_SIZE - 1] = '\0'; + + return OK; +} + +/**************************************************************************** + * Name: get_int + * + * Description: + * Gets a setting for an integer value + * + * Input Parameters: + * setting - pointer to the setting type + * i - pointer to return the integer value + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int get_int(FAR setting_t *setting, FAR int *i) +{ + DEBUGASSERT(setting == NULL); + DEBUGASSERT((setting->type == SETTING_INT) || + (setting->type == SETTING_BOOL) || + (setting->type == SETTING_FLOAT)); + + if (setting->type == SETTING_INT) + { + *i = setting->i; + } + else if (setting->type == SETTING_BOOL) + { + *i = !!setting->i; + } + else if (setting->type == SETTING_FLOAT) + { + *i = (int)setting->f; + } + else + { + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: set_int + * + * Description: + * Creates a setting for an integer value + * + * Input Parameters: + * setting - pointer to the setting type + * i - the integer value + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int set_int(FAR setting_t *setting, int i) +{ + DEBUGASSERT(setting); + DEBUGASSERT((setting->type == SETTING_INT) || + (setting->type == SETTING_BOOL) || + (setting->type == SETTING_FLOAT)); + + setting->type = SETTING_INT; + setting->i = i; + + return OK; +} + +/**************************************************************************** + * Name: get_bool + * + * Description: + * Gets a setting for a boolean value + * + * Input Parameters: + * setting - pointer to the setting type + * f - pointer to return the boolean value + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int get_bool(FAR setting_t *setting, FAR int *i) +{ + DEBUGASSERT(setting == NULL); + DEBUGASSERT((setting->type == SETTING_BOOL) || + (setting->type == SETTING_INT)); + + if ((setting->type == SETTING_INT) || (setting->type == SETTING_BOOL)) + { + *i = !!setting->i; + } + else + { + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: set_bool + * + * Description: + * Creates a setting for a boolean value + * + * Input Parameters: + * setting - pointer to the setting type + * i - the boolean value + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int set_bool(FAR setting_t *setting, int i) +{ + DEBUGASSERT(setting); + DEBUGASSERT((setting->type == SETTING_BOOL) || + (setting->type == SETTING_INT)); + + setting->type = SETTING_BOOL; + setting->i = !!i; + + return OK; +} + +/**************************************************************************** + * Name: get_float + * + * Description: + * Gets a setting for a float value + * + * Input Parameters: + * setting - pointer to the setting type + * f - pointer to return the floating point value + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int get_float(FAR setting_t *setting, FAR double *f) +{ + DEBUGASSERT(setting == NULL); + DEBUGASSERT((setting->type == SETTING_FLOAT) || + (setting->type == SETTING_INT)); + + if (setting->type == SETTING_FLOAT) + { + *f = setting->f; + } + else if (setting->type == SETTING_INT) + { + *f = (double)setting->i; + } + else + { + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: set_float + * + * Description: + * Creates a setting for a float value + * + * Input Parameters: + * setting - pointer to the setting type + * f - the floating point value + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int set_float(FAR setting_t *setting, double f) +{ + DEBUGASSERT(setting); + DEBUGASSERT((setting->type == SETTING_FLOAT) || + (setting->type == SETTING_INT)); + + setting->type = SETTING_FLOAT; + setting->f = f; + + return OK; +} + +/**************************************************************************** + * Name: get_ip + * + * Description: + * Creates a setting for an IP address + * + * Input Parameters: + * setting - pointer to the setting type + * ip - pointer to return the IP address + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int get_ip(FAR setting_t *setting, FAR struct in_addr *ip) +{ + DEBUGASSERT(setting == NULL); + DEBUGASSERT((setting->type == SETTING_IP_ADDR) || + (setting->type == SETTING_STRING)); + + if (setting->type == SETTING_IP_ADDR) + { + memcpy(ip, &setting->ip, sizeof(struct in_addr)); + return OK; + } + else if (setting->type == SETTING_STRING) + { + if (inet_pton(AF_INET, setting->s, ip) != 1) + { + return -EAFNOSUPPORT; + } + } + + return OK; +} + +/**************************************************************************** + * Name: set_ip + * + * Description: + * Creates a setting for an IP address + * + * Input Parameters: + * setting - pointer to the setting type + * ip - IP address (in network byte order) + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int set_ip(FAR setting_t *setting, FAR struct in_addr *ip) +{ + DEBUGASSERT(setting); + DEBUGASSERT((setting->type == SETTING_IP_ADDR) || + (setting->type == SETTING_STRING)); + + setting->type = SETTING_IP_ADDR; + memcpy(&setting->ip, ip, sizeof(struct in_addr)); + + return OK; +} + +/**************************************************************************** + * Name: load + * + * Description: + * Loads all values + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int load(void) +{ + int i; + int ret = OK; + + for (i = 0; i < CONFIG_SETTINGS_MAX_STORAGES; i++) + { + if ((g_settings.store[i].file[0] != '\0') && + g_settings.store[i].load_fn) + { + ret = g_settings.store[i].load_fn(g_settings.store[i].file); + if (ret < 0) + { + break; + } + } + } + + return ret; +} + +/**************************************************************************** + * Name: save + * + * Description: + * Saves cached values, either immediately or on a timer + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int save(void) +{ + int ret = OK; + g_settings.wrpend = 1; + +#ifdef CONFIG_SETTINGS_CACHED_SAVES + timer_settime(g_settings.timerid, 0, &g_settings.trigger, NULL); +#else + union sigval value = + { + .sival_int = 0, + }; + + ret = dump_cache(value); +#endif + + return ret; +} + +/**************************************************************************** + * Name: signotify + * + * Description: + * Notify anything waiting for a signal + * + * Input Parameters: + * none + * + * Returned Value: + * none + * + ****************************************************************************/ + +static void signotify(void) +{ + int i; + + for (i = 0; i < CONFIG_SETTINGS_MAX_SIGNALS; i++) + { + if (g_settings.notify[i].pid == 0) + { + break; + } + + kill(g_settings.notify[i].pid, g_settings.notify[i].signo); + } +} + +/**************************************************************************** + * Name: dump_cache + * + * Description: + * Writes out the cached data to the appropriate storage + * + * Input Parameters: + * value - parameter passed if called from a timer signal + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int dump_cache(union sigval value) +{ + int ret = OK; + (void)value; + int i; + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + for (i = 0; i < CONFIG_SETTINGS_MAX_STORAGES; i++) + { + if ((g_settings.store[i].file[0] != '\0') && + g_settings.store[i].save_fn) + { + ret = g_settings.store[i].save_fn(g_settings.store[i].file); + if (ret < 0) + { + break; + } + } + } + + g_settings.wrpend = 0; + + pthread_mutex_unlock(&g_settings.mtx); + + return ret; +} + +/**************************************************************************** + * Name: sanity_check + * + * Description: + * Checks that the string does not contain "illegal" characters + * + * Input Parameters: + * str - the string to check + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +static int sanity_check(FAR char *str) +{ +#ifdef CONFIG_DEBUG_ASSERTIONS + DEBUGASSERT(strchr(str, '=') == NULL); + DEBUGASSERT(strchr(str, ';') == NULL); + DEBUGASSERT(strchr(str, '\n') == NULL); + DEBUGASSERT(strchr(str, '\r') == NULL); +#else + if ((strchr(str, '=') != NULL) || (strchr(str, ';') != NULL) || + (strchr(str, '\n') != NULL) || (strchr(str, '\r') != NULL)) + { + return -EINVAL; + } + +#endif + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: settings_init + * + * Description: + * Initializes the settings storage. + * + * Input Parameters: none + * + * Returned Value: none + * + ****************************************************************************/ + +int settings_init(void) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); + pthread_mutex_init(&g_settings.mtx, &attr); + + g_settings.hash = 0; + g_settings.wrpend = 0; + memset(g_settings.map, 0, sizeof(g_settings.map)); + memset(g_settings.store, 0, sizeof(g_settings.store)); + + memset(g_settings.notify, 0, sizeof(g_settings.notify)); + +#ifdef CONFIG_SETTINGS_CACHED_SAVES + memset(&g_settings.sev, 0, sizeof(struct sigevent)); + g_settings.sev.sigev_notify = SIGEV_THREAD; + g_settings.sev.sigev_signo = TIMER_SIGNAL; + g_settings.sev.sigev_value.sival_int = 0; + g_settings.sev.sigev_notify_function = dump_cache; + + memset(&g_settings.trigger, 0, sizeof(struct itimerspec)); + g_settings.trigger.it_value.tv_sec = 0; + g_settings.trigger.it_value.tv_nsec = (50 * 1000000); + + timer_create(CLOCK_REALTIME, &g_settings.sev, &g_settings.timerid); +#endif + + return OK; +} + +/**************************************************************************** + * Name: settings_setstorage + * + * Description: + * Sets a file to be used as a settings storage. + * Except from the first file, if loading the file causes any changes + * to the settings, then the new map will be dumped to all files + * (effectively it syncs all storages). + * + * Input Parameters: + * file - the filename of the storage to use + * type - the type of the storage (BINARY or TEXT) + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_setstorage(FAR char *file, enum storage_type_e type) +{ + FAR storage_t *storage = NULL; + int ret = OK; + int idx = 0; + uint32_t h; + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + while ((idx < CONFIG_SETTINGS_MAX_STORAGES) && + (g_settings.store[idx].file[0] != '\0')) + { + idx++; + } + + DEBUGASSERT(idx < CONFIG_SETTINGS_MAX_STORAGES); + if (idx >= CONFIG_SETTINGS_MAX_STORAGES) + { + ret = -ENOSPC; + goto end; + } + + DEBUGASSERT(strlen(file) < CONFIG_SETTINGS_MAX_FILENAME); + if (strlen(file) >= CONFIG_SETTINGS_MAX_FILENAME) + { + ret = -EINVAL; + goto end; + } + + storage = &g_settings.store[idx]; + strncpy(storage->file, file, sizeof(storage->file)); + storage->file[sizeof(storage->file) - 1] = '\0'; + + switch (type) + { + case STORAGE_BINARY: + { + storage->load_fn = load_bin; + storage->save_fn = save_bin; + } + break; + + case STORAGE_TEXT: + { + storage->load_fn = load_text; + storage->save_fn = save_text; + } + break; + + default: + { + DEBUGASSERT(0); + } + break; + } + + ret = storage->load_fn(storage->file); + + h = hash_calc(); + + /* Only save if there are more than 1 storages. */ + + if ((storage != &g_settings.store[0]) && ((h != g_settings.hash) || + (access(file, F_OK) != 0))) + { + signotify(); + save(); + } + + g_settings.hash = h; + +end: + pthread_mutex_unlock(&g_settings.mtx); + + return ret; +} + +/**************************************************************************** + * Name: settings_sync + * + * Description: + * Sets a file to be used as a settings storage. + * Except from the first file, if loading the file causes any changes + * to the settings, then the new map will be dumped to all files + * (effectively it syncs all storages). + * + * Input Parameters: + * file - the filename of the storage to use + * type - the type of the storage + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_sync(void) +{ + int ret; + uint32_t h; + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + load(); + + h = hash_calc(); + if (h != g_settings.hash) + { + g_settings.hash = h; + + signotify(); + save(); + } + + pthread_mutex_unlock(&g_settings.mtx); + + return OK; +} + +/**************************************************************************** + * Name: settings_notify + * + * Description: + * Registers a task to be notified on any change of the settings. + * Whenever any value is changed, a signal will be sent to all + * registered threads. Signals are NOT sent when new settings are + * created or when the whole storage is cleared. + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_notify(void) +{ + int ret; + int idx = 0; + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + while ((idx < CONFIG_SETTINGS_MAX_SIGNALS) && g_settings.notify[idx].pid) + idx++; + + DEBUGASSERT(idx < CONFIG_SETTINGS_MAX_SIGNALS); + if (idx >= CONFIG_SETTINGS_MAX_SIGNALS) + { + return -EINVAL; + } + + g_settings.notify[idx].pid = getpid(); + g_settings.notify[idx].signo = CONFIG_SETTINGS_SIGNO; + + pthread_mutex_unlock(&g_settings.mtx); + + return OK; +} + +/**************************************************************************** + * Name: settings_hash + * + * Description: + * Gets the hash of the settings storage. + * + * This hash represents the internal state of the settings map. A + * unique number is calculated based on the contents of the whole map. + * + * This hash can be used to check the settings for any alterations, i.e. + * any setting that may had its value changed since last check. + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_hash(FAR uint32_t *h) +{ + *h = g_settings.hash; + + return OK; +} + +/**************************************************************************** + * Name: settings_clear + * + * Description: + * Clears all settings. + * Data in all storages are purged. + * + * Note that if the settings are cleared during the application run-time + * (i.e. not during initialization), every access to the settings storage + * will fail. + * + * All settings must be created again. This can be done either by calling + * Settings_create() again, or by restarting the application. + * + * Input Parameters: + * none + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_clear(void) +{ + int ret; + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + memset(g_settings.map, 0, sizeof(g_settings.map)); + g_settings.hash = 0; + + save(); + + pthread_mutex_unlock(&g_settings.mtx); + + while (g_settings.wrpend) + { + usleep(10 * 1000); /* Sleep for 10ms */ + } + + return OK; +} + +/**************************************************************************** + * Name: settings_create + * + * Description: + * Creates a new setting. + * + * If the setting is found to exist in any of the storages, it will + * be loaded. Else, it will be created and the default value will be + * assigned. + * + * Input Parameters: + * key - the key of the setting. + * type - the type of the setting. + * ... - the default value of the setting. + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_create(FAR char *key, enum settings_type_e type, ...) +{ + int ret = OK; + FAR setting_t *setting = NULL; + + DEBUGASSERT(type != SETTING_EMPTY); + DEBUGASSERT(strlen(key)); + if (strlen(key) == 0) + { + return -EINVAL; + } + + DEBUGASSERT(strlen(key) < CONFIG_SETTINGS_KEY_SIZE); + if (strlen(key) >= CONFIG_SETTINGS_KEY_SIZE) + { + return -EINVAL; + } + + DEBUGASSERT(isalpha(key[0]) && (sanity_check(key) == OK)); + if (!isalpha(key[0]) || (sanity_check(key) < 0)) + { + return -EINVAL; + } + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + int i; + for (i = 0; i < CONFIG_SETTINGS_MAP_SIZE; i++) + { + if (strcmp(key, g_settings.map[i].key) == 0) + { + setting = &g_settings.map[i]; + break; + } + + if (g_settings.map[i].type == SETTING_EMPTY) + { + setting = &g_settings.map[i]; + break; + } + } + + DEBUGASSERT(setting); + + if (setting && (setting->type != type)) + { + strncpy(setting->key, key, CONFIG_SETTINGS_KEY_SIZE); + setting->key[CONFIG_SETTINGS_KEY_SIZE - 1] = '\0'; + setting->type = type; + + va_list ap; + va_start(ap, type); + + switch (type) + { + case SETTING_STRING: + { + FAR char *str = va_arg(ap, FAR char *); + ret = set_string(setting, str); + } + break; + + case SETTING_INT: + { + i = va_arg(ap, int); + ret = set_int(setting, i); + } + break; + + case SETTING_BOOL: + { + i = va_arg(ap, int); + ret = set_bool(setting, i); + } + break; + + case SETTING_FLOAT: + { + double f = va_arg(ap, double); + ret = set_float(setting, f); + } + break; + + case SETTING_IP_ADDR: + { + FAR struct in_addr *ip = va_arg(ap, FAR struct in_addr *); + ret = set_ip(setting, ip); + } + break; + + default: + case SETTING_EMPTY: + { + DEBUGASSERT(0); + } + break; + } + + va_end(ap); + + if (ret < 0) + { + memset(setting, 0, sizeof(setting_t)); + } + else + { + g_settings.hash = hash_calc(); + save(); + } + } + + pthread_mutex_unlock(&g_settings.mtx); + + return ret; +} + +/**************************************************************************** + * Name: settings_type + * + * Description: + * Gets the type of a setting. + * + * Input Parameters: + * key - the key of the setting. + * type - pointer to int for the returned setting type + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_type(FAR char *key, FAR enum settings_type_e *stype) +{ + int ret; + FAR setting_t *setting = NULL; + + DEBUGASSERT(stype != NULL); + DEBUGASSERT(key != NULL); + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + ret = get_setting(key, setting); + + *stype = setting->type; + + pthread_mutex_unlock(&g_settings.mtx); + + return ret; +} + +/**************************************************************************** + * Name: settings_get + * + * Description: + * Gets the value of a setting. + * + * Input Parameters: + * key - the key of the setting. + * type - the type of the setting + * ... - pointer to store the setting value plus, if a string + * setting, the length of the string to get + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_get(FAR char *key, enum settings_type_e type, ...) +{ + int ret; + FAR setting_t *setting = NULL; + enum settings_type_e stype; + + DEBUGASSERT(type != SETTING_EMPTY); + DEBUGASSERT(key[0] != '\0'); + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + ret = settings_type(key, &stype); + if (ret < 0) + { + goto end; + } + + if (type != stype) + { + if (stype == SETTING_EMPTY) + { + ret = -ENOENT; + } + else + { + ret = -EINVAL; + } + + goto end; + } + + ret = get_setting(key, setting); + if (ret < 0) + { + goto end; + } + + va_list ap; + va_start(ap, type); + + switch (type) + { + case SETTING_STRING: + { + FAR char *buf = va_arg(ap, FAR char *); + size_t len = va_arg(ap, size_t); + ret = (int)get_string(setting, buf, len); + } + break; + + case SETTING_INT: + { + FAR int *i = va_arg(ap, FAR int *); + ret = get_int(setting, i); + } + break; + + case SETTING_BOOL: + { + FAR int *i = va_arg(ap, FAR int *); + ret = get_bool(setting, i); + } + break; + + case SETTING_FLOAT: + { + FAR double *f = va_arg(ap, FAR double *); + ret = get_float(setting, f); + } + break; + + case SETTING_IP_ADDR: + { + FAR struct in_addr *ip = va_arg(ap, FAR struct in_addr *); + ret = get_ip(setting, ip); + } + break; + + default: + { + DEBUGASSERT(0); + } + break; + } + + va_end(ap); + +end: + pthread_mutex_unlock(&g_settings.mtx); + + return ret; +} + +/**************************************************************************** + * Name: settings_set + * + * Description: + * Sets the value of a setting. + * + * Input Parameters: + * key - the key of the setting. + * type - the type of the setting + * ... - the new value of the setting. + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_set(FAR char *key, enum settings_type_e type, ...) +{ + int ret; + int i; + FAR setting_t *setting = NULL; + enum settings_type_e stype; + uint32_t h; + + DEBUGASSERT(type != SETTING_EMPTY); + DEBUGASSERT(key[0] != '\0'); + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + stype = settings_type(key, &stype); + if (type != stype) + { + ret = -EINVAL; + goto end; + } + + ret = get_setting(key, setting); + if (ret < 0) + { + goto end; + } + + va_list ap; + va_start(ap, type); + + switch (type) + { + case SETTING_STRING: + { + FAR char *str = va_arg(ap, FAR char *); + ret = set_string(setting, str); + } + break; + + case SETTING_INT: + { + i = va_arg(ap, int); + ret = set_int(setting, i); + } + break; + + case SETTING_BOOL: + { + i = va_arg(ap, int); + ret = set_bool(setting, i); + } + break; + + case SETTING_FLOAT: + { + double f = va_arg(ap, double); + ret = set_float(setting, f); + } + break; + + case SETTING_IP_ADDR: + { + FAR struct in_addr *ip = va_arg(ap, FAR struct in_addr *); + ret = set_ip(setting, ip); + } + break; + + default: + { + DEBUGASSERT(0); + } + break; + } + + va_end(ap); + + h = hash_calc(); + if (h != g_settings.hash) + { + g_settings.hash = h; + + signotify(); + save(); + } + +end: + pthread_mutex_unlock(&g_settings.mtx); + + return ret; +} + +/**************************************************************************** + * Name: settings_iterate + * + * Description: + * Gets a copy of a setting at the specified position. It can be used to + * iterate over the settings map, by using successive values of idx. + * + * Input Parameters: + * idx - the iteration index for the setting. + * setting - pointer to return the setting value + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int settings_iterate(int idx, FAR setting_t *setting) +{ + int ret; + + DEBUGASSERT(setting != NULL); + + if ((idx < 0) || (idx >= CONFIG_SETTINGS_MAP_SIZE)) + { + memset(setting, 0, sizeof(setting_t)); + return -EINVAL; + } + + ret = pthread_mutex_lock(&g_settings.mtx); + if (ret < 0) + { + return ret; + } + + memcpy(setting, &g_settings.map[idx], sizeof(setting_t)); + + pthread_mutex_unlock(&g_settings.mtx); + + if (g_settings.map[idx].type == SETTING_EMPTY) + { + return -ENOENT; + } + + return OK; +} diff --git a/system/settings/storage_bin.c b/system/settings/storage_bin.c new file mode 100644 index 00000000000..0fd2a3ba962 --- /dev/null +++ b/system/settings/storage_bin.c @@ -0,0 +1,246 @@ +/**************************************************************************** + * apps/system/settings/storage_bin.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "system/settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define VALID 0x600d +#define BUFFER_SIZE 256 /* Note alignment for Flash writes! */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +FAR static setting_t *getsetting(char *key); +extern state_t g_settings; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: load_bin + * + * Description: + * Loads binary data from a storage file. + * + * Input Parameters: + * file - the filename of the storage to use + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int load_bin(FAR char *file) +{ + static int fd; + int i; + int ret = OK; + uint16_t valid; + uint16_t count; + + fd = open(file, O_RDONLY); + if (fd < 0) + { + return -ENOENT; + } + + valid = 0; + read(fd, &valid, sizeof(uint16_t)); + + if (valid != VALID) + { + goto abort; /* Just exit - the settings aren't valid. What to do? */ + } + + count = 0; + read(fd, &count, sizeof(uint16_t)); + + uint32_t calc_crc = 0; + uint32_t exp_crc = 0; + + for (i = 0; i < count; i++) + { + setting_t setting; + read(fd, &setting, sizeof(setting_t)); + calc_crc = crc32part((FAR uint8_t *)&setting, sizeof(setting_t), + calc_crc); + } + + read(fd, &exp_crc, sizeof(uint32_t)); + + if (calc_crc != exp_crc) + { + ret = -EIO; + goto abort; + } + + lseek(fd, (sizeof(uint16_t) *2), SEEK_SET); /* Get after valid & size */ + + for (i = 0; i < count; i++) + { + setting_t setting; + read(fd, &setting, sizeof(setting_t)); + + FAR setting_t *slot = getsetting(setting.key); + if (slot == NULL) + { + continue; + } + + memcpy(slot, &setting, sizeof(setting_t)); + } + +abort: + close(fd); + return ret; +} + +/**************************************************************************** + * Name: save_bin + * + * Description: + * Saves binary data to a storage file. + * + * Input Parameters: + * file - the filename of the storage to use + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int save_bin(FAR char *file) +{ + FAR uint8_t *buffer = malloc(BUFFER_SIZE); + int count; + int fd; + int ret = OK; + + if (buffer == NULL) + { + return -ENOMEM; + } + + count = 0; + int i; + for (i = 0; i < CONFIG_SETTINGS_MAP_SIZE; i++) + { + if (g_settings.map[i].type == SETTING_EMPTY) + { + break; + } + + count++; + } + + fd = open(file, (O_RDWR | O_TRUNC), 0666); + if (fd < 0) + { + ret = -ENODEV; + goto abort; + } + + memset(buffer, 0xff, BUFFER_SIZE); + *((FAR uint16_t *)buffer) = VALID; + *(((FAR uint16_t *)buffer) + 1) = count; + + off_t offset = sizeof(uint16_t) *2; /* Valid & count */ + size_t data_size = count *sizeof(setting_t); + size_t rem_data = data_size; + uint32_t crc = crc32((FAR uint8_t *)g_settings.map, data_size); + size_t rem_crc = sizeof(crc); + + while ((offset + rem_data + rem_crc) > 0) + { + size_t to_write = ((offset + rem_data) > BUFFER_SIZE) ? + (size_t)(BUFFER_SIZE - offset) : rem_data; + memcpy((buffer + offset), (((FAR uint8_t *)g_settings.map) + + (data_size - rem_data)), to_write); + + size_t j; + for (j = (to_write + offset); + (j < BUFFER_SIZE) && (rem_crc > 0); j++, rem_crc--) + { + off_t crc_byte = (off_t)(sizeof(crc) - rem_crc); + buffer[j] = (crc >> (8 *crc_byte)) & 0xff; + } + + write(fd, buffer, BUFFER_SIZE); + + rem_data -= to_write; + offset = 0; + } + + close(fd); + +abort: + free(buffer); + + return ret; +} + +FAR setting_t *getsetting(FAR char *key) +{ + int i; + + for (i = 0; i < CONFIG_SETTINGS_MAP_SIZE; i++) + { + FAR setting_t *setting = &g_settings.map[i]; + + if (strcmp(key, setting->key) == 0) + { + return setting; + } + + if (setting->type == SETTING_EMPTY) + { + return setting; + } + } + + return NULL; +} + diff --git a/system/settings/storage_text.c b/system/settings/storage_text.c new file mode 100644 index 00000000000..6a003713748 --- /dev/null +++ b/system/settings/storage_text.c @@ -0,0 +1,379 @@ +/**************************************************************************** + * apps/system/settings/storage_text.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "system/settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define BUFFER_SIZE 256 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +FAR static setting_t *getsetting(char *key); +extern state_t g_settings; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: load_text + * + * Description: + * Loads text from a storage file. + * + * Input Parameters: + * file - the filename of the storage to use + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int load_text(FAR char *file) +{ + int ret = OK; + FAR FILE *f; + + /* Check that the file exists */ + + if (access(file, F_OK) != 0) + { + /* If not, try the backup file */ + + FAR char *backup_file = malloc(strlen(file) + 2); + if (backup_file == NULL) + { + return -ENODEV; + } + + strcpy(backup_file, file); + strcat(backup_file, "~"); + + if (access(backup_file, F_OK) == 0) + { + /* There is a backup file + * Restore this as the new settings file + */ + + rename(backup_file, file); + } + + free(backup_file); + } + + f = fopen(file, "r"); + if (f == NULL) + { + return -ENOENT; + } + + FAR char *buffer = malloc(BUFFER_SIZE); + if (buffer == NULL) + { + ret = -ENOMEM; + goto abort; + } + + while (fgets(buffer, BUFFER_SIZE, f)) + { + /* Remove any line terminators */ + + for (int i = ((int)strlen(buffer) - 1); i > 0; i--) + { + if (buffer[i] == ';' || buffer[i] == '\n' || buffer[i] == '\r') + { + buffer[i] = '\0'; + } + else + { + break; + } + } + + /* Separate the key / value pair */ + + FAR char *eq = strchr(buffer, '='); + if (eq == NULL) + { + continue; + } + + FAR char *key = buffer; + FAR char *val = eq + 1; + *eq = '\0'; + + /* Check if the key is valid */ + + if (!isalpha(key[0])) + { + continue; + } + + /* Get the setting slot */ + + FAR setting_t *setting = getsetting(key); + if (setting == NULL) + { + continue; + } + + /* Parse the value */ + + if (isalpha(val[0])) + { + if (strcasecmp(val, "true") == 0) + { + /* It's a boolean */ + + setting->type = SETTING_BOOL; + setting->i = 1; + } + else if (strcasecmp(val, "false") == 0) + { + /* It's a boolean */ + + setting->type = SETTING_BOOL; + setting->i = 0; + } + else + { + /* It's a string */ + + if (strlen(val) >= CONFIG_SETTINGS_VALUE_SIZE) + { + continue; + } + + setting->type = SETTING_STRING; + strncpy(setting->s, val, CONFIG_SETTINGS_VALUE_SIZE); + setting->s[CONFIG_SETTINGS_VALUE_SIZE - 1] = '\0'; + } + } + else + { + if (strchr(val, '.') != NULL) + { + FAR char *s = val; + int i = 0; + while (s[i]) s[i] == '.' ? i++ : *s++; + if (i == 1) + { + /* It's a float */ + + double d = 0; + if (sscanf(val, "%lf", &d) == 1) + { + setting->type = SETTING_FLOAT; + setting->f = d; + } + } + else if (i == 3) + { + /* It's an IP address */ + + setting->type = SETTING_IP_ADDR; + inet_pton(AF_INET, val, &setting->ip); + } + } + else + { + /* It's an integer */ + + int i = 0; + if (sscanf(val, "%d", &i) == 1) + { + setting->type = SETTING_INT; + setting->i = i; + } + } + } + + /* Handle parse errors */ + + if (setting->type == SETTING_EMPTY) + { + memset(setting->key, 0, CONFIG_SETTINGS_KEY_SIZE); + } + } + + free(buffer); + +abort: + fclose(f); + + return ret; +} + +/**************************************************************************** + * Name: save_text + * + * Description: + * Saves text to a storage file. + * + * Input Parameters: + * file - the filename of the storage to use + * + * Returned Value: + * Success or negated failure code + * + ****************************************************************************/ + +int save_text(FAR char *file) +{ + int ret = OK; + FAR char *backup_file = malloc(strlen(file) + 2); + int i; + + if (backup_file == NULL) + { + return -ENODEV; + } + + strcpy(backup_file, file); + strcat(backup_file, "~"); + + FAR FILE *f; + f = fopen(backup_file, "w"); + if (f == NULL) + { + ret = -ENODEV; + goto abort; + } + + for (i = 0; i < CONFIG_SETTINGS_MAP_SIZE; i++) + { + if (g_settings.map[i].type == SETTING_EMPTY) + { + break; + } + + switch (g_settings.map[i].type) + { + case SETTING_STRING: + { + fprintf(f, "%s=%s\n", g_settings.map[i].key, + g_settings.map[i].s); + } + break; + + case SETTING_INT: + { + fprintf(f, "%s=%d\n", g_settings.map[i].key, + g_settings.map[i].i); + } + break; + + case SETTING_BOOL: + { + fprintf(f, "%s=%s\n", g_settings.map[i].key, + g_settings.map[i].i ? + "true" : "false"); + } + break; + + case SETTING_FLOAT: + { + fprintf(f, "%s=%.06f\n", g_settings.map[i].key, + g_settings.map[i].f); + } + break; + + case SETTING_IP_ADDR: + { + char buffer[20]; + inet_ntop(AF_INET, &g_settings.map[i].ip, buffer, 20); + fprintf(f, "%s=%s\n", g_settings.map[i].key, buffer); + } + break; + + default: + { + return -EINVAL; + } + break; + } + } + + fclose(f); + + remove(file); + rename(backup_file, file); + +abort: + free(backup_file); + + return ret; +} + +FAR setting_t *getsetting(char *key) +{ + int i; + if (strlen(key) >= CONFIG_SETTINGS_KEY_SIZE) + { + return NULL; + } + + for (i = 0; i < CONFIG_SETTINGS_MAP_SIZE; i++) + { + FAR setting_t *setting = &g_settings.map[i]; + + if (strcmp(key, setting->key) == 0) + { + return setting; + } + + if (setting->type == SETTING_EMPTY) + { + strncpy(setting->key, key, CONFIG_SETTINGS_KEY_SIZE); + setting->key[CONFIG_SETTINGS_KEY_SIZE - 1] = '\0'; + return setting; + } + } + + return NULL; +} +